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

> Discover how ast-grep integration empowers Code-Graph-RAG with powerful structural search and replace. Enhance code understanding and manipulation with AST-based pattern matching.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-19

---

**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`](https://github.com/vitali87/code-graph-rag/blob/main/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:

```python
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`](https://github.com/vitali87/code-graph-rag/blob/main/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.

```python
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.

```python
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`:

```python
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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tools/structural_editor.py) (lines 25-57) and [`codebase_rag/tools/structural_search.py`](https://github.com/vitali87/code-graph-rag/blob/main/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`](https://github.com/vitali87/code-graph-rag/blob/main/structural_search.py) and [`structural_editor.py`](https://github.com/vitali87/code-graph-rag/blob/main/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.