Skip to content

Search Tools Mastery: Combining rg, grepai, Serena, ast-grep, scip-search & lilmd

Last updated:

Master code search and documentation navigation by combining the right tools for maximum efficiency

Author: Florian BRUNIAUX | Contributions from Claude (Anthropic) Reading time: ~25 minutes Last updated: May 2026


  1. Quick Reference Matrix
  2. Tool Comparison
  3. Decision Tree
  4. Combined Workflows
  5. Real-World Scenarios
  6. Performance Optimization
  7. Common Pitfalls
  8. Extended Toolkit: scip-search & lilmd

I need to…Use This ToolCommand Example
Find exact textrg (Grep tool)rg "authenticate" --type ts
Find by meaninggrepaigrepai search "user login flow"
Find function definitionSerenaserena find_symbol --name "login"
Find structural patternast-grepast-grep "async function $F"
See who calls functiongrepaigrepai trace callers "login"
Get file structureSerenaserena get_symbols_overview
Refactor across filesSerena + ast-grepCombined workflow
Explore unknown codebasegrepai β†’ SerenaDiscovery pattern
Find symbol refs without MCP (worktree-safe)scip-searchscip-search refs AuthService.login
Navigate a large Markdown documentlilmdlilmd read docs/arch.md "Authentication"

Featurerg (ripgrep)grepaiSerenaast-grep
Search TypeRegex/textSemantic (meaning)Symbol-awareAST structure
TechnologyPattern matchingEmbeddings (Ollama)Symbol parsingAbstract Syntax Tree
Speed⚑ ~20ms🐒 ~500ms⚑ ~100msπŸ• ~200ms
Setupβœ… None (built-in)⚠️ Ollama + install⚠️ MCP config⚠️ npm install
Integrationβœ… Native (Grep)⚠️ MCP server⚠️ MCP server⚠️ Plugin
Privacyβœ… 100% localβœ… 100% localβœ… 100% localβœ… 100% local
Context neededNoneNoneProject indexationNone
LanguagesAll (text)AllTS/JS/Py/Rust/GoTS/JS/Py/Rust/Go/C++
Call graph❌ Noβœ… Yes❌ No❌ No
Symbol tracking❌ No❌ Noβœ… Yes❌ No
Session memory❌ No❌ Noβœ… Yes❌ No
False positivesMediumLowVery lowVery low
Learning curveLowMediumLowHigh
ToolTypical QueryTokens ConsumedResults Returned
rg”authenticate”~500Exact matches only
grepai”auth flow”~2000Intent-based matches
Serenafind_symbol~1000Symbol + context
ast-grepAST pattern~1500Structural matches

Key insight: rg is 4x more token-efficient but 10x less intelligent than semantic tools.

Two tools that address gaps in the core stack, covered in depth in Β§ Extended Toolkit:

ToolCategoryvs existing stack
scip-searchSymbol index search (SCIP)Like Serena but stateless, no MCP, worktree-safe
lilmdMarkdown section navigationNo equivalent in the stack (targets docs, not code)

Do you know the EXACT text/pattern?
β”‚
β”œβ”€ YES β†’ Use rg (ripgrep)
β”‚ β”œβ”€ Known function name: rg "createSession"
β”‚ β”œβ”€ Known import: rg "import.*React"
β”‚ └─ Known pattern: rg "async function"
β”‚
└─ NO β†’ Go to Level 2
What's your search intent?
β”‚
β”œβ”€ "Find by MEANING/CONCEPT"
β”‚ β†’ Use grepai
β”‚ └─ Example: grepai search "payment validation logic"
β”‚
β”œβ”€ "Find FUNCTION/CLASS definition"
β”‚ β†’ Use Serena
β”‚ └─ Example: serena find_symbol --name "UserController"
β”‚
β”œβ”€ "Find by CODE STRUCTURE"
β”‚ β†’ Use ast-grep
β”‚ └─ Example: async without error handling
β”‚
└─ "Understand DEPENDENCIES"
β†’ Use grepai trace
└─ Example: grepai trace callers "validatePayment"

When running in a CI environment, a git worktree, or any context where MCP servers are unavailable:

Known symbol name, no MCP available?
β”‚
└─ Use scip-search (pre-built SCIP index, millisecond cold start)
└─ scip-search refs "AuthService.login" --format json
Found too many results?
β”‚
β”œβ”€ rg β†’ Add --type filter or narrow path
β”œβ”€ grepai β†’ Add --path filter or use trace
β”œβ”€ Serena β†’ Filter by symbol type (function/class)
└─ ast-grep β†’ Add constraints to pattern

Goal: Understand a new project quickly

Step-by-step:

Terminal window
# 1. SEMANTIC DISCOVERY (grepai)
# Find files related to authentication
grepai search "user authentication and session management"
# β†’ Output: auth.service.ts, session.middleware.ts, user.controller.ts
# 2. STRUCTURAL OVERVIEW (Serena)
# Understand each file's structure
serena get_symbols_overview --file auth.service.ts
# β†’ Output:
# - class AuthService
# - login(email, password)
# - logout(sessionId)
# - validateSession(token)
# 3. DEPENDENCY MAPPING (grepai trace)
# See how login is used
grepai trace callers "login"
# β†’ Output: Called by UserController, ApiGateway, AdminPanel
# 4. EXACT SEARCH (rg)
# Find specific implementation details
rg "validateSession" --type ts -A 5
# β†’ Output: Full function with 5 lines of context

Result: Complete understanding in 4 commands (vs 30+ file reads)


Goal: Rename createSession β†’ initializeUserSession across 50+ files

Step-by-step:

Terminal window
# 1. IMPACT ANALYSIS (grepai trace)
# Understand full scope
grepai trace callers "createSession"
# β†’ Output: 47 callers across 23 files
grepai trace callees "createSession"
# β†’ Output: Calls validateUser, createToken, storeSession
# 2. STRUCTURAL VALIDATION (ast-grep)
# Ensure consistent usage pattern
ast-grep "createSession($$$ARGS)"
# β†’ Output: All invocations with their argument patterns
# 3. SYMBOL-AWARE REFACTORING (Serena)
# Precise renaming
serena find_symbol --name "createSession" --include-body true
# β†’ Get exact definition + all references
serena replace_symbol_body \
--name "createSession" \
--new-name "initializeUserSession"
# β†’ Rename across all files maintaining structure
# 4. VERIFICATION (rg)
# Confirm no old references remain
rg "createSession" --type ts
# β†’ Should return 0 results

Result: Safe refactoring with full dependency awareness


Goal: Find security vulnerabilities

Step-by-step:

Terminal window
# 1. SEMANTIC DISCOVERY (grepai)
# Find security-sensitive code
grepai search "SQL query construction"
grepai search "user input validation"
grepai search "password handling"
# 2. STRUCTURAL PATTERNS (ast-grep)
# Find specific vulnerability patterns
# SQL injection risks
ast-grep 'db.query(`${$VAR}`)'
# XSS risks
ast-grep 'innerHTML = $VAR'
# Missing error handling
ast-grep -p 'async function $F($$$) { $$$BODY }' \
--without 'try { $$$TRY } catch'
# 3. DEPENDENCY TRACING (grepai)
# See where vulnerable code is called
grepai trace callers "executeQuery"
# β†’ Identify all entry points
# 4. EXACT VERIFICATION (rg)
# Confirm findings
rg "innerHTML\s*=" --type ts
rg "password" --type ts | rg -v "hashed"

Result: Comprehensive security audit in minutes


Contexte: Benchmark sur Excalidraw (155k lignes TypeScript) Auteur: YoanDev (mainteneur de grepai - biais potentiel) MΓ©thodologie: 5 questions de dΓ©couverte de code identiques

MΓ©triquegrepgrepaiDiffΓ©rence
Tool calls13962-55%
Input tokens51k1.3k-97%

Γ€ retenir: Recherche sΓ©mantique rΓ©duit drastiquement les tokens en identifiant les fichiers pertinents dΓ¨s la premiΓ¨re tentative, Γ©vitant l’exploration itΓ©rative.

Limitations:

  • Benchmark par le mainteneur de l’outil
  • Single-project validation (TypeScript only)
  • Pas de validation indΓ©pendante Γ  ce jour

Source: yoandev.co/grepai-benchmark

Note: Ce benchmark reflΓ¨te l’état de janvier 2026. Les performances peuvent Γ©voluer avec les mises Γ  jour de Claude Code et grepai.


Goal: Migrate React class components β†’ hooks

Step-by-step:

Terminal window
# 1. INVENTORY (ast-grep)
# Find all class components
ast-grep 'class $C extends React.Component'
# β†’ Output: 34 components to migrate
# 2. DEPENDENCY ANALYSIS (grepai)
# Understand component relationships
for component in $(ast-grep 'class $C extends' --json | jq -r '.[].name'); do
grepai trace callers "$component"
done
# β†’ Build migration order (leaf components first)
# 3. PATTERN DETECTION (ast-grep)
# Identify lifecycle methods used
ast-grep 'componentDidMount() { $$$BODY }'
ast-grep 'componentWillReceiveProps($$$) { $$$BODY }'
# β†’ Map to equivalent hooks
# 4. INCREMENTAL MIGRATION (Serena + ast-grep)
# Migrate one component at a time
serena find_symbol --name "UserProfile" --include-body true
# β†’ Get full component code
# Use ast-grep to transform
ast-grep --rewrite \
--from 'class $C extends React.Component' \
--to 'const $C = () => { }'
# 5. VERIFICATION (rg + grepai)
# Ensure migration successful
rg "React.Component" --type tsx # Should decrease
grepai search "component lifecycle methods" # Find any missed

Result: Systematic migration with minimal breakage


Goal: Identify and fix performance bottlenecks

Step-by-step:

Terminal window
# 1. HOTSPOT DISCOVERY (grepai)
# Find performance-critical code
grepai search "heavy computation or loops"
grepai search "database queries in loops"
# 2. PATTERN DETECTION (ast-grep)
# Find N+1 query patterns
ast-grep 'for ($$$) { await db.query($$$) }'
# Find missing memoization
ast-grep 'useMemo' --invert-match \
--in 'const $VAR = $$$'
# 3. CALL GRAPH ANALYSIS (grepai trace)
# Find hot paths
grepai trace graph "renderUserList" --depth 3
# β†’ Visualize dependency tree
# 4. SYMBOL TRACKING (Serena)
# Track function changes
serena write_memory "perf_baseline" \
"renderUserList: 450ms avg"
# After optimization
serena write_memory "perf_optimized" \
"renderUserList: 45ms avg (10x improvement)"
# 5. VERIFICATION (rg)
# Confirm optimizations applied
rg "useMemo|useCallback" --type tsx

Result: Data-driven performance improvements


Scenario 1: β€œI Don’t Know What I’m Looking For”

Section titled β€œScenario 1: β€œI Don’t Know What I’m Looking For””

Problem: New project, no documentation, need to add feature

Solution: Semantic-first discovery

Terminal window
# Start broad with meaning
grepai search "user profile management"
# β†’ Discover relevant files
# Then narrow with structure
serena get_symbols_overview --file user-profile.service.ts
# β†’ Understand available functions
# Finally, exact search for details
rg "updateProfile" --type ts -C 3

Scenario 2: β€œThis Function is Called from Everywhere”

Section titled β€œScenario 2: β€œThis Function is Called from Everywhere””

Problem: Need to modify a function but worried about breaking things

Solution: Dependency mapping first

Terminal window
# 1. See all callers
grepai trace callers "calculateTotal"
# β†’ 47 callers found
# 2. Analyze caller contexts
for file in $(grepai trace callers "calculateTotal" --json | jq -r '.[].file'); do
serena get_symbols_overview --file "$file"
done
# 3. Identify safe vs risky call sites
ast-grep 'calculateTotal($ARGS)' --json
# β†’ Group by argument patterns
# 4. Make change with confidence
# Now you know all impact points

Problem: Need to apply consistent pattern across codebase

Solution: Combine semantic + structural

Terminal window
# Example: Find all error handling code
# 1. Semantic discovery
grepai search "error handling and exception management"
# 2. Structural patterns
ast-grep 'try { $$$TRY } catch ($ERR) { $$$CATCH }'
ast-grep 'throw new Error($MSG)'
# 3. Verify consistency
rg "catch\s*\(" --type ts | wc -l
# Compare with ast-grep count to find anomalies

Scenario 4: β€œI Need to Understand This Module”

Section titled β€œScenario 4: β€œI Need to Understand This Module””

Problem: Complex module with unclear responsibilities

Solution: Multi-tool analysis

Terminal window
# 1. Get symbol overview (Serena)
serena get_symbols_overview --file payment.module.ts
# β†’ See all exports, classes, functions
# 2. Understand dependencies (grepai)
grepai trace callees "PaymentModule"
# β†’ What does this module use?
grepai trace callers "PaymentModule"
# β†’ Who uses this module?
# 3. Find implementation patterns (ast-grep)
ast-grep 'export class $C' --file payment.module.ts
ast-grep 'async $METHOD($$$)' --file payment.module.ts
# 4. Read specific implementations (rg)
rg "processPayment" --type ts -A 20

General Rules:

  1. Known exact text β†’ Always use rg first
  2. Unknown exact text β†’ Use grepai, then rg for verification
  3. Refactoring β†’ Serena for symbol safety
  4. Large migrations β†’ ast-grep for structural precision

Test: Find authentication code in 500k line codebase

StrategyTimeResults Quality
rg β€œauth” only0.2s5000+ false positives
grepai β€œauth” only2.5s50 relevant results
grepai β†’ rg (combined)2.7s50 relevant, verified
Serena symbols only1.5s12 auth functions
ast-grep patterns3.0s8 auth flows

Winner: Serena symbols (fastest + high quality) for known function names

For large codebases (>100k lines):

Terminal window
# Run searches in parallel
# Terminal 1: Semantic discovery
grepai search "authentication flow" > /tmp/grepai-results.json &
# Terminal 2: Symbol indexing
serena get_symbols_overview --file src/**/*.ts > /tmp/symbols.json &
# Terminal 3: Pattern detection
ast-grep 'async function $F' --json > /tmp/ast-results.json &
# Wait for all, then combine results
wait
jq -s '.[0] + .[1] + .[2]' \
/tmp/grepai-results.json \
/tmp/symbols.json \
/tmp/ast-results.json

❌ Wrong:

Terminal window
grepai search "createSession" # Slow, overkill

βœ… Right:

Terminal window
rg "createSession" --type ts # Fast, precise

Rule: If you know the exact text, never use semantic search.


❌ Wrong:

Terminal window
rg "auth.*login.*session" --type ts # Misses variations

βœ… Right:

Terminal window
grepai search "authentication and session management"

Rule: Regex doesn’t understand meaning, use semantic tools.


❌ Wrong:

Terminal window
# Directly refactor without checking callers
rg "oldFunction" --type ts | sed 's/oldFunction/newFunction/g'

βœ… Right:

Terminal window
# Check impact first
grepai trace callers "oldFunction"
# See 47 callers across 23 files
# Then plan refactoring strategy

Rule: Always trace dependencies before modifying shared code.


❌ Wrong:

Terminal window
# Use only one tool for complex task
ast-grep 'async function $F' --json | jq '.[].file' | xargs -I {} vim {}
# Blindly edit without understanding context

βœ… Right:

Terminal window
# Combine for full understanding
ast-grep 'async function $F' --json > /tmp/async.json
for file in $(jq -r '.[].file' /tmp/async.json); do
serena get_symbols_overview --file "$file" # Context
grepai trace callers "$(jq -r '.[].name' /tmp/async.json)" # Usage
done

Rule: Complex tasks need multiple perspectives.


❌ Wrong:

Terminal window
# Setup grepai + Ollama just to find a TODO comment
grepai search "TODO comments in the code"

βœ… Right:

Terminal window
rg "TODO" --type ts

Rule: Use the simplest tool that works.


Your SituationUse ThisNot This
”Find function login”rg β€œlogin”grepai search β€œlogin"
"Find login-related code”grepai β€œlogin flow”rg β€œlogin.*"
"Rename function safely”Serena find_symbolrg + sed
”Who calls this function?β€œgrepai trace callersrg + grep
”Get file structure”Serena overviewrg β€œclass|function"
"Find async without try/catch”ast-greprg β€œasync.*{"
"Migrate React classes”ast-greprg + manual
”Find TODOs”rg β€œTODO”Any other tool

Two CLI tools that address gaps in the rg/grepai/Serena stack.

scip-search queries pre-built SCIP (Sourcegraph Code Intelligence Protocol) symbol indexes. Where grepai searches by semantic meaning and Serena requires a live MCP connection, scip-search operates against a static binary index with millisecond cold starts.

AttributeDetails
Sourcegithub.com/liza-mas/scip-search
Installcurl -fsSL https://raw.githubusercontent.com/liza-mas/scip-search/main/install.sh | bash
Index formatSCIP (Go, TypeScript, Python, Java, Rust, and others)
OutputOne-line text, JSON, or location-only

Workflow: scip-search replaces the 5-10 rg/read round-trips typical for symbol discovery.

Terminal window
# Step 1: generate SCIP index once per language
scip-typescript index --output index.scip
# Step 2: find a symbol definition
scip-search find AuthService
# Step 3: get all references with line numbers
scip-search refs AuthService.login --format json
# Step 4: read only the returned line ranges

vs Serena: Serena connects to a language server and has session memory. scip-search is stateless and works against a snapshot: no MCP, no persistent process. This makes it reliable in worktrees and ephemeral CI environments where Serena’s LSP backend may not be available.

vs grepai: grepai finds by semantic intent (β€œpayment validation logic”). scip-search finds by exact or near-exact symbol identifier. They work in sequence: grepai discovers the concept, scip-search confirms the symbol.

Worktree compatibility: indexes are per-repository files with no shared state. Running scip-typescript index inside a worktree produces a local index for that worktree.


lilmd treats Markdown files as databases. It returns a table of contents with line ranges and enables targeted section reads. An agent reading a 2,000-line guide fetches one section in a single call instead of reading the full file.

AttributeDetails
Sourcegithub.com/molefrog/lilmd
Installnpm install -g lilmd
RuntimeNode or Bun

Key commands:

Terminal window
# TOC with line ranges (inclusive, 1-indexed)
lilmd docs/architecture.md
# Read a section by name (fuzzy match by default)
lilmd read docs/architecture.md "Authentication"
# Read a nested section
lilmd read docs/architecture.md "Security > JWT"
# Exact match (prefix with =)
lilmd read docs/architecture.md "=Authentication Flow"

Agent-specific value: the TOC output contains line ranges for each heading. An agent parses these and requests only the relevant section rather than loading the full file. The natural pipeline is: rg (find which file) then lilmd (get TOC with ranges) then Read lines:N-M (load the specific section). This also works for CHANGELOG.md, large README files, and knowledge base documents.

Worktree compatibility: stateless, no index, runs per file. Works anywhere.


Recommended Setup Order:

  1. Start: rg (already built-in with Grep tool) βœ…
  2. Next: Serena MCP (symbol awareness, session memory)
  3. Then: grepai (semantic search + call graph)
  4. If worktrees are part of your workflow: scip-search (stateless symbol lookup, no MCP required)
  5. For large documentation: lilmd (targeted Markdown section reads, no setup)
  6. Finally: ast-grep (structural patterns, large refactoring)

Rationale: 90% of searches work with rg + Serena. Add grepai for semantic needs. Add scip-search for worktree or CI environments where MCP is unavailable. Add ast-grep only for large-scale refactoring.


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SEARCH TOOL MASTERY β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ rg (ripgrep) β†’ Fast, exact text matching β”‚
β”‚ β”œβ”€ Use: 90% of searches β”‚
β”‚ └─ Speed: ~20ms β”‚
β”‚ β”‚
β”‚ grepai β†’ Semantic + Call graph β”‚
β”‚ β”œβ”€ Use: Concept discovery, dependency tracing β”‚
β”‚ └─ Speed: ~500ms (finds what rg cannot) β”‚
β”‚ β”‚
β”‚ Serena β†’ Symbol-aware + Session memory β”‚
β”‚ β”œβ”€ Use: Refactoring, structure understanding β”‚
β”‚ └─ Speed: ~100ms β”‚
β”‚ β”‚
β”‚ ast-grep β†’ AST structural patterns β”‚
β”‚ β”œβ”€ Use: Large migrations, complex patterns β”‚
β”‚ └─ Speed: ~200ms β”‚
β”‚ β”‚
β”‚ scip-search β†’ Symbol index (stateless, SCIP) β”‚
β”‚ β”œβ”€ Use: CI / worktrees / no MCP environments β”‚
β”‚ └─ Speed: ~5ms cold start β”‚
β”‚ β”‚
β”‚ lilmd β†’ Markdown section navigation β”‚
β”‚ β”œβ”€ Use: Large docs, TOC + line ranges per section β”‚
β”‚ └─ Speed: instant, no index β”‚
β”‚ β”‚
β”‚ ═══════════════════════════════════════════════════ β”‚
β”‚ β”‚
β”‚ Master the combination, not individual tools. β”‚
β”‚ Each tool has a sweet spot. Use the right one. β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


Last updated: May 2026 Compatible with: Claude Code 2.1.7+