How ast-grep Integration Enables Structural Search and Replace in Code-Graph-RAG

Code-Graph-RAG leverages the ast-grep library to perform language-aware structural search and replace by parsing source files into abstract syntax trees and applying pattern matching that captures whole code constructs rather than relying on fragile text regex.

Code-Graph-RAG is an open-source repository that enhances LLM-driven code analysis through deep integration with ast-grep. This integration enables agents to locate and refactor code based on syntactic structures, allowing operations that understand language semantics instead of performing naive text matching.

The Architecture of ast-grep Integration in Code-Graph-RAG

The core integration resides in codebase_rag/tools/ast_grep_service.py, where the AstGrepService class orchestrates the entire structural search and replace pipeline. The service acts as a bridge between the ast-grep Rust engine and the Python-based agent framework.

Language Resolution and File Classification

Before parsing, the service must identify which grammar to apply. The _resolve_language method (lines 41-55) maps the repository-wide SupportedLanguage enum to ast-grep's language identifiers stored in cs.AST_GREP_LANGUAGES, or accepts an ast-grep ID directly.

Once the language is determined, _classify_file (lines 64-82) filters the repository to include only files with recognized extensions while respecting .gitignore and .cgrignore rules used during graph ingestion. This ensures that the structural search operates on relevant source files while excluding build artifacts and dependencies.

AST Parsing and Pattern Matching

For each accepted file, the service reads source text via _read and constructs an AST root node using _root (lines 18-22), producing an SgRoot object. This node serves as the entry point for structural queries.

When searching, service.search walks the project and invokes _find_all to collect SgNode matches (lines 36-59). Each match captures not just the text but the exact structural location, returning StructuralSearchMatch objects containing file paths, line numbers, columns, and the matched source fragments.

Performing Structural Search Across the Repository

The structural search capability allows agents to find code patterns that span multiple lines or nested structures. Unlike regex, which might match partial expressions or broken across lines, ast-grep patterns understand function boundaries, statement blocks, and expression nesting.

You can invoke structural search directly through the service layer:

from codebase_rag.tools.ast_grep_service import AstGrepService

service = AstGrepService(project_root=".")

# Find all print calls with any arguments in Python files

matches = service.search(pattern='print($$ARGS)', language='python')
for m in matches:
    print(f"{m['file']}:{m['line']} – {m['text']}")

For LLM agent integration, the create_structural_search_tool wrapper in codebase_rag/tools/structural_search.py (lines 24-46) exposes this functionality as the STRUCTURAL_SEARCH tool. This async wrapper automatically checks for the ast-grep extra via has_ast_grep, offloads CPU-intensive parsing to a background thread, and formats results with truncation notices when matches exceed display limits.

from codebase_rag.tools.structural_search import create_structural_search_tool

service = AstGrepService()
search_tool = create_structural_search_tool(service)

# Async invocation by the LLM agent

result = await search_tool.function(
    pattern='if ($COND) {$BODY}', 
    language='javascript'
)

Structural Replace with Metavariable Interpolation

Beyond search, the integration enables precise code refactoring through pattern-based replacement. The system supports metavariables—placeholders like $NAME for single captures and $$$NAME for multiple captures—that preserve matched code fragments during rewriting.

The Search-and-Replace Workflow

The _interpolate method (lines 64-77) expands these metavariables within rewrite templates, ensuring that captured variables, expressions, or entire statement blocks are preserved exactly as they appeared in the source.

The service.replace method (lines 80-116) orchestrates the full replacement workflow:

  1. Collects matches using the search infrastructure
  2. Applies the interpolated rewrite template to each match
  3. Builds edited source strings via root.commit_edits
  4. Generates unified diffs for human review
  5. Writes changes to disk only when dry_run=False

Dry-Run Mode and Diff Generation

Safety is built into the replacement workflow through the dry_run flag. When enabled, the service computes and returns diffs without modifying files, allowing review before permanent changes.

from codebase_rag.tools.structural_editor import create_structural_editor_tool

service = AstGrepService()
replace_tool = create_structural_editor_tool(service)

# Preview changes: replace console.log with logger.info

diffs = await replace_tool.function(
    pattern='console.log($$ARGS);',
    rewrite='logger.info($$ARGS);',
    language='javascript',
    dry_run=True
)
print(diffs)  # Shows unified diff; no files modified

To apply changes after approval, simply set dry_run=False:

await replace_tool.function(
    pattern='console.log($$ARGS);',
    rewrite='logger.info($$ARGS);',
    language='javascript',
    dry_run=False
)

Agentic Tool Wrappers for LLM Integration

The low-level service is exposed to LLM agents through two specialized tools defined in codebase_rag/tools/structural_editor.py (lines 25-57) and codebase_rag/tools/structural_search.py. These wrappers handle:

  • Availability checking: Verifying the ast-grep extra is installed before execution
  • Thread management: Offloading CPU-intensive AST parsing to background threads to prevent blocking the async event loop
  • Result formatting: Converting raw matches into LLM-friendly strings with proper truncation and diff headers

The STRUCTURAL_REPLACE tool specifically enforces a two-phase workflow: preview (dry-run) followed by explicit application. This prevents accidental mass modifications while giving the agent visibility into the exact changes proposed.

Summary

  • AST-based parsing: AstGrepService parses files into SgRoot nodes using language-specific grammars, enabling syntax-aware matching instead of text regex.
  • Metavariable support: The _interpolate system handles $NAME and $$$NAME captures, preserving matched constructs during replacement.
  • Safe refactoring: dry_run mode generates unified diffs for review before service.replace commits edits to disk.
  • Agent integration: Async wrappers in structural_search.py and structural_editor.py expose these capabilities as STRUCTURAL_SEARCH and STRUCTURAL_REPLACE tools, with automatic threading and availability checks.

Frequently Asked Questions

What is ast-grep and why does Code-Graph-RAG use it?

ast-grep is a fast, cross-language structural search tool based on tree-sitter grammars. Code-Graph-RAG integrates it to enable pattern matching that understands code syntax—matching entire functions, classes, or statements—rather than relying on line-based or character-based regex that can break on formatting changes or nested structures.

How does metavariable interpolation work in structural replace?

When performing replacements, the _interpolate method scans the rewrite template for $NAME (single capture) and $$$NAME (multiple captures) placeholders. It substitutes these with the exact source text captured during the pattern match, ensuring that variables, arguments, or code blocks are preserved identically in the rewritten output without manual string manipulation.

Can I use structural search without modifying files?

Yes. The service.search method and create_structural_search_tool wrapper provide read-only structural queries. Additionally, the create_structural_editor_tool defaults to dry_run=True, allowing you to preview diffs indefinitely before opting into actual file modifications by setting dry_run=False.

Which programming languages are supported?

Support depends on ast-grep's underlying tree-sitter implementations. The _resolve_language method maps internal SupportedLanguage enums to ast-grep identifiers, while _classify_file filters files by their extensions. According to the source code, the integration checks against cs.AST_GREP_LANGUAGES and respects the same ignore rules used for graph ingestion, covering most mainstream languages including Python, JavaScript, TypeScript, Rust, Go, and Java.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →