<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aakash Pawar</title>
    <description>The latest articles on DEV Community by Aakash Pawar (@aakashpawar1999).</description>
    <link>https://dev.to/aakashpawar1999</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F478946%2Febab67cf-b635-4315-ab6a-4e4d537200b4.jpg</url>
      <title>DEV Community: Aakash Pawar</title>
      <link>https://dev.to/aakashpawar1999</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aakashpawar1999"/>
    <language>en</language>
    <item>
      <title>I ran ripgrep, Serena, claude-context and my own tool on the same repo</title>
      <dc:creator>Aakash Pawar</dc:creator>
      <pubDate>Sun, 01 Nov 2026 10:49:54 +0000</pubDate>
      <link>https://dev.to/aakashpawar1999/i-ran-ripgrep-serena-claude-context-and-my-own-tool-on-the-same-repo-2m5p</link>
      <guid>https://dev.to/aakashpawar1999/i-ran-ripgrep-serena-claude-context-and-my-own-tool-on-the-same-repo-2m5p</guid>
      <description>&lt;p&gt;There are a lot of ways to let a coding agent find code now. Plain grep, a language server behind MCP, a vector database, a local index. Every README says its way is the right one, so I wanted to see them do the same job on the same code.&lt;/p&gt;

&lt;p&gt;I build one of these tools, Semlith, so treat my bias accordingly. That's also why I'm showing the questions it got wrong.&lt;/p&gt;

&lt;p&gt;The setup is Flask 3.1.3 on an M1 MacBook Air with 8 GB. Versions are ripgrep 15.2.0, Serena 2.1.0, claude-context 0.1.15 (with Milvus 3.0.3 and Ollama running nomic-embed-text) and semlith 0.50.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--branch&lt;/span&gt; 3.1.3 https://github.com/pallets/flask &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;flask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The four approaches in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ripgrep&lt;/th&gt;
&lt;th&gt;Serena&lt;/th&gt;
&lt;th&gt;claude-context&lt;/th&gt;
&lt;th&gt;Semlith&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;Text search, no index&lt;/td&gt;
&lt;td&gt;Language servers behind MCP&lt;/td&gt;
&lt;td&gt;Embeddings in Milvus&lt;/td&gt;
&lt;td&gt;Local index, embeddings plus keyword plus code graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finds code by meaning&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edits code&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, by symbol&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No, read only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup on Flask&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;30.9 s to index&lt;/td&gt;
&lt;td&gt;About 21 min (CPU in Docker)&lt;/td&gt;
&lt;td&gt;84.3 s to index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Moving parts&lt;/td&gt;
&lt;td&gt;One binary&lt;/td&gt;
&lt;td&gt;Python env plus language servers&lt;/td&gt;
&lt;td&gt;Docker, Milvus, Ollama, npx&lt;/td&gt;
&lt;td&gt;One binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reads PDF, Word, Excel&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rest of this post is how I got those numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  ripgrep, when you know the name
&lt;/h2&gt;

&lt;p&gt;When the agent already knows the identifier, nothing beats grep.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; max_content_length src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/flask/wrappers.py:55:    _max_content_length: int | None = None
src/flask/wrappers.py:60:    def max_content_length(self) -&amp;gt; int | None:
src/flask/wrappers.py:80:        if self._max_content_length is not None:
src/flask/wrappers.py:81:            return self._max_content_length
src/flask/wrappers.py:84:            return super().max_content_length
src/flask/wrappers.py:88:    @max_content_length.setter
src/flask/wrappers.py:89:    def max_content_length(self, value: int | None) -&amp;gt; None:
src/flask/wrappers.py:90:        self._max_content_length = value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight lines, all of them right, in milliseconds.&lt;/p&gt;

&lt;p&gt;Now ask the same thing the way a person would, without the name. An agent turns "limit how big an uploaded request body can be" into words and greps for them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"upload|big"&lt;/span&gt; src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/flask/cli.py:297:    a bigger role.  Typically it's created automatically by the
src/flask/helpers.py:542:        @app.route("/uploads/&amp;lt;path:name&amp;gt;")
src/flask/helpers.py:545:                app.config['UPLOAD_FOLDER'], name, as_attachment=True
src/flask/helpers.py:549:    files or uploads. Uses :func:`~werkzeug.security.safe_join` to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these is the answer, because &lt;code&gt;wrappers.py&lt;/code&gt; never says "upload" or "big". This is where an agent starts guessing more words and opening more files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serena, when you want a language server
&lt;/h2&gt;

&lt;p&gt;Serena gives the agent symbol tools backed by real language servers, over 40 languages. Since version 2 it shows up as a REPL, so the agent writes a little Python against Serena's API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv venv &lt;span class="nt"&gt;-p&lt;/span&gt; 3.11 venv-serena
uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; venv-serena &lt;span class="s2"&gt;"serena-agent @ git+https://github.com/oraios/serena@v2.1.0"&lt;/span&gt;
serena project index &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indexing took 30.9 seconds. Given the name, it's precise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_symbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_content_length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# [{"name_path": "Request/max_content_length", "kind": "Method",
#   "relative_path": "src/flask/wrappers.py", "body_location": {"start_line": 87, ...}}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References come back in about 2 seconds and they come from the language server, so they are real references, not text matches. Line numbers count from zero, which caught me out the first time.&lt;/p&gt;

&lt;p&gt;Without the name though, Serena falls back to a regex pattern search, and that returns the same lines grep did.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_for_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload|big&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thing Serena does that none of the others do is edit. Replace a function body, insert after a symbol, rename across files. If your agent spends most of its time changing code it already found, that's a real advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  claude-context, run fully local
&lt;/h2&gt;

&lt;p&gt;claude-context is Zilliz's MCP server. It chunks code, embeds it and stores the vectors in Milvus, with hybrid search on top. By default it wants an OpenAI key for embeddings and a Zilliz Cloud key for the database. It also supports a fully local setup, so that's what I ran.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://github.com/milvus-io/milvus/releases/download/v3.0.3/milvus-standalone-docker-compose.yaml &lt;span class="nt"&gt;-o&lt;/span&gt; docker-compose.yaml
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; cc-ollama &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:11434:11434 ollama/ollama:0.40.13
docker &lt;span class="nb"&gt;exec &lt;/span&gt;cc-ollama ollama pull nomic-embed-text

&lt;span class="nv"&gt;EMBEDDING_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Ollama &lt;span class="nv"&gt;EMBEDDING_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nomic-embed-text &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OLLAMA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:11434 &lt;span class="nv"&gt;MILVUS_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:19530 &lt;span class="se"&gt;\&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; @zilliz/claude-context-mcp@0.1.15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The images came to 6.14 GB and the first npx start pulled 418 MB of packages. While it worked I watched its connections, and it only talked to Milvus and Ollama on the machine.&lt;/p&gt;

&lt;p&gt;Two things bit me. On the first run Milvus exited mid-index while Ollama had every CPU, so I capped Ollama at four. The second one is worth knowing if you use it. &lt;code&gt;get_indexing_status&lt;/code&gt; said this at 92 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Codebase '…/flask' is fully indexed and ready for search.
📊 Statistics: 100 files, 100 chunks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It wasn't done. The server's own log finished at 1,266.9 seconds with 86 files and 1,837 chunks. If your agent trusts that status and searches early, it searches a partial index without knowing.&lt;/p&gt;

&lt;p&gt;That 21 minutes isn't a fair speed test either. Docker on a Mac has no GPU access, so Ollama embedded on the CPU. A native Ollama install would be quicker, I just didn't measure it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semlith, the one I build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;semlith index &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;indexed 211 files (2778 chunks, 5 images) in 84.3s — 0 already indexed, 4 skipped (4 empty), 0 removed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That count includes the &lt;code&gt;.rst&lt;/code&gt; docs, which claude-context skips by default. Serena was quicker to set up here, since it doesn't embed anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzq4vhk4zjfd53m5k6o2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzq4vhk4zjfd53m5k6o2k.png" alt="Horizontal bar chart of the time to index Flask 3.1.3 before the first search. ripgrep needs no index, Serena takes 30.9 seconds, Semlith 84.3 seconds and claude-context 1,266.9 seconds with Ollama embedding on the CPU in Docker." width="800" height="416"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setup time on the same checkout. Serena was the quickest of the indexed tools, and claude-context's time is CPU-only embedding in Docker.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;semlith search &lt;span class="nt"&gt;-k&lt;/span&gt; 5 &lt;span class="s2"&gt;"limit how big an uploaded request body can be"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. 0.087 vf  src/flask/wrappers.py:59-71
2. 0.083 vf  src/flask/wrappers.py:91-106
3. 0.077 v   docs/web-security.rst:26-38
4. 0.076 vf  docs/patterns/fileuploads.rst:135-153
5. 0.072 vf  src/flask/wrappers.py:105-123
5 hits in 360.027667ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first hit is the property. The letters after the score say how it was found, &lt;code&gt;v&lt;/code&gt; for the vector list and &lt;code&gt;f&lt;/code&gt; for the keyword list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question everyone got wrong
&lt;/h2&gt;

&lt;p&gt;I asked all of them where Flask decides which error handler runs for an exception. The answer is &lt;code&gt;_find_error_handler&lt;/code&gt; in &lt;code&gt;src/flask/sansio/app.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Grep did best. &lt;code&gt;rg -n -i "error handler" src&lt;/code&gt; lands on line 826 of that file, the function's docstring, because the question uses the same words the code does.&lt;/p&gt;

&lt;p&gt;Semlith's top five were all in &lt;code&gt;docs/errorhandling.rst&lt;/code&gt;. Those pages explain the order Flask uses, so they answer the question, but they aren't the code. claude-context had the function at rank 8.&lt;/p&gt;

&lt;p&gt;So "semantic search beats grep" isn't true in general. When the question already uses the code's own words, grep is very hard to beat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bigger samples
&lt;/h2&gt;

&lt;p&gt;Three questions on one repo prove nothing, so here's what I could measure at scale.&lt;/p&gt;

&lt;p&gt;On SWE-bench Verified, 500 real GitHub issues, the issue text is the query and the gold is every file the fix edits. Semlith had a right file in its top 10 for 76.8% of them, against 47.0% for one ripgrep pass ranked and read in order. That's retrieval only, not whether an agent then fixes the bug.&lt;/p&gt;

&lt;p&gt;On 20 issues from SWE-bench Lite, every tool got the same checkout and issue text. Serena 1.7.0 finished 16 of 20 (4 timed out) and had the right file in its top 5 for 25.0% of those, against 56.2% for Semlith on the same 16. That run predates Serena's REPL, so 2.1.0 might do differently. claude-context isn't in that comparison yet, so I won't give it a number.&lt;/p&gt;

&lt;p&gt;And the one that keeps me honest. On 50 questions about a single unfamiliar repo, headless Claude Code on Opus scored 92% with grep alone and 92% with Semlith installed beside it. A strong agent with room to explore gets there with grep.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each one costs before you use it
&lt;/h2&gt;

&lt;p&gt;Every MCP server sends its tool definitions at session start, and the agent pays for them on every turn. I counted the &lt;code&gt;tools/list&lt;/code&gt; result at about four bytes a token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Serena   tools: 2   4544 bytes, about 1136 tokens
semlith  tools: 8   2668 bytes, about 667 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are small. Serena keeps the list short with a REPL, then the agent reads 9,292 characters of instructions when it calls &lt;code&gt;initial_instructions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which I'd pick
&lt;/h2&gt;

&lt;p&gt;Honestly they do different jobs, and you can run more than one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One repo, a strong model, names you mostly know. Grep is enough.&lt;/li&gt;
&lt;li&gt;An agent that edits a lot by symbol. Serena.&lt;/li&gt;
&lt;li&gt;You already run Milvus or Zilliz Cloud, or want to pick the embedding provider. claude-context.&lt;/li&gt;
&lt;li&gt;Many repos, documents next to the code, nothing to sign up for. That's the case I built Semlith for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full runs with every command are on &lt;a href="https://semlith.com/docs/compare?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=devto-articles&amp;amp;utm_content=four-ways-code-search" rel="noopener noreferrer"&gt;semlith.com/docs/compare&lt;/a&gt; and the benchmark harness is in the repo at &lt;a href="https://github.com/semlith/semlith" rel="noopener noreferrer"&gt;https://github.com/semlith/semlith&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What are you using for code search with your agents right now, and where does it fall over?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
