# Using ast-grep for Structural Search and Replace by AST Pattern in code-graph-rag

> Learn to perform structural search and replace with ast-grep in code-graph-rag. Transform code efficiently using AST patterns, not fragile regex, for language-agnostic refactoring.

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

---

**The code-graph-rag repository provides a Python wrapper around ast-grep-py that enables fast, language-agnostic structural code transformation using AST patterns instead of fragile regex.**

This guide explains how to leverage the built-in **ast-grep integration** in vitali87/code-graph-rag to perform precise code searches and automated refactors across multiple programming languages. The implementation wraps the native [ast-grep-py](https://github.com/ast-grep/ast-grep) library with LangChain-compatible tooling for AI-driven code manipulation.

## Core Architecture

The ast-grep integration consists of four primary components working together:

| Component | Implementation | Purpose |
|-----------|----------------|---------|
| **AstGrepService** | [`codebase_rag/tools/ast_grep_service.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tools/ast_grep_service.py) | Loads source files, builds language-specific AST roots, and executes pattern matches |
| **AstGrepTier** | [`codebase_rag/parsers/ast_grep_tier.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/ast_grep_tier.py) | Registers per-extension language IDs and loads pattern configuration files |
| **Dependency Detection** | [`codebase_rag/utils/dependencies.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/utils/dependencies.py) | Runtime check via `has_ast_grep()` for optional `ast_grep_py` availability |
| **LangChain Tools** | [`codebase_rag/main.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/main.py) lines 1648-1653 | `create_structural_search_tool()` and `create_structural_editor_tool()` factories |

The `has_ast_grep()` function in [`codebase_rag/utils/dependencies.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/utils/dependencies.py) ensures graceful degradation—if the optional dependency is missing, structural tools fall back to no-op implementations rather than crashing.

## How AST Pattern Matching Works

Unlike regex that matches raw text, **ast-grep operates on the Abstract Syntax Tree**. This eliminates false positives from string literals, comments, or formatting variations.

The workflow in `codebase_rag` follows this sequence:

1. **Project Discovery**: `AstGrepService._iter_source_files()` walks the repository, filtering files by supported extensions
2. **Language Mapping**: Extensions map to ast-grep language IDs via [`constants/ast_python.py`](https://github.com/vitali87/code-graph-rag/blob/main/constants/ast_python.py), [`constants/ast_php.py`](https://github.com/vitali87/code-graph-rag/blob/main/constants/ast_php.py), and related modules
3. **Pattern Execution**: `AstGrepService._find_all()` traverses `SgRoot` nodes to yield matching `SgNode` objects
4. **Transformation**: `SgNode.replace_with()` preserves formatting while applying replacements

## Setting Up Structural Search

Initialize the service with your project root, then execute patterns using JSON/YAML AST descriptors:

```python
from codebase_rag.tools.ast_grep_service import AstGrepService

service = AstGrepService(project_root="/path/to/your/repo")

# Pattern: find function definitions lacking docstrings

pattern = """
{
  "type": "FunctionDef",
  "children": [
    {
      "type": "body",
      "children": [
        {
          "type": "Expr",
          "constraint": "not Str"
        }
      ]
    }
  ]
}
"""

matches = service.search(language="python", pattern=pattern)

for file_path, line, col in matches:
    print(f"{file_path}:{line}:{col} — function missing docstring")

```

The `search()` method at line 147-152 in [`ast_grep_service.py`](https://github.com/vitali87/code-graph-rag/blob/main/ast_grep_service.py) returns tuples of `(file_path, line_number, column_offset)` for every match.

## Performing Structural Replacement

Replace matched nodes while preserving surrounding formatting and indentation:

```python
from codebase_rag.tools.ast_grep_service import AstGrepService

service = AstGrepService(project_root="/path/to/your/repo")

# Find all print() calls

search_pattern = """
{
  "type": "Call",
  "children": [
    {
      "type": "func",
      "constraint": "Str == 'print'"
    }
  ]
}
"""

# Replacement template with interpolation

replacement = "logger.info({{args}})"

service.replace(
    language="python",
    pattern=search_pattern,
    replacement=replacement,
    dry_run=True  # Set False to apply changes

)

```

The `replace()` method (lines 187-197) uses `SgNode.replace_with()` from the underlying library. The `{{args}}` placeholder injects original call arguments into the replacement.

## Integrating with LangChain Agents

Expose structural capabilities to LLM agents through standardized tool interfaces:

```python
from codebase_rag.main import (
    create_structural_search_tool,
    create_structural_editor_tool,
    AstGrepService
)

service = AstGrepService(project_root="/path/to/repo")
search_tool = create_structural_search_tool(service)
editor_tool = create_structural_editor_tool(service)

# Tool invocation from agent prompts

results = search_tool.run("""{
  "language": "python",
  "pattern": "{ \\"type\\": \\"ImportFrom\\", \\"children\\": [{ \\"type\\": \\"module\\", \\"constraint\\": \\"Str == 'os'\\" }] }"
}""")

edit_result = editor_tool.run("""{
  "language": "python",
  "pattern": "{ \\"type\\": \\"Call\\", \\"children\\": [{ \\"type\\": \\"func\\", \\"constraint\\": \\"Str == 'print'\\" }] }",
  "replacement": "logging.info({{args}})"
}""")

```

These factory functions at lines 1648-1653 in [`codebase_rag/main.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/main.py) return `Tool` objects compatible with LangChain's agent framework.

## Adding Support for New Languages

Extend language coverage by modifying three locations:

1. **Register the language ID** in [`codebase_rag/constants/ast_nodes.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants/ast_nodes.py):

```python

# Example: adding Ruby support

AST_GREP_LANGUAGES = {
    "py": "python",
    "js": "javascript",
    "rb": "ruby",  # new entry

}

```

2. **Create pattern configuration** at [`parsers/ast_grep_patterns/ruby.yaml`](https://github.com/vitali87/code-graph-rag/blob/main/parsers/ast_grep_patterns/ruby.yaml):

```yaml
ast_grep_id: ruby
default_patterns:
  - name: class_definition
    pattern: |
      class $NAME
        $BODY
      end

```

3. The `AstGrepTier` class auto-discovers new configs on startup—no service restart required.

## Pattern Syntax Reference

ast-grep patterns use **structural variables** (prefixed with `$`) and **constraints**:

| Syntax | Meaning | Example |
|--------|---------|---------|
| `$NAME` | Capture any node into variable | `class $NAME:` captures class names |
| `^$NAME` | Anchor to start of pattern | `^$FUNC` matches top-level functions |
| `constraint: "Str == 'value'"` | String literal equality | Match specific identifiers |
| `constraint: "not Type"` | Negation | Exclude nodes of given type |

Complex patterns nest `type` and `children` fields to match hierarchical AST structures precisely.

## Summary

- **AstGrepService** in [`codebase_rag/tools/ast_grep_service.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tools/ast_grep_service.py) provides the core search/replace API
- **Language-agnostic operation** spans Python, PHP, JavaScript, Rust, Go, and extensible to others
- **AST-level matching** eliminates regex false positives from strings and comments
- **LangChain integration** via `create_structural_search_tool()` and `create_structural_editor_tool()` enables LLM-driven refactoring
- **Graceful degradation** through `has_ast_grep()` detection in [`codebase_rag/utils/dependencies.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/utils/dependencies.py)
- **Extensible architecture** via `AstGrepTier` pattern discovery in [`codebase_rag/parsers/ast_grep_tier.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/ast_grep_tier.py)

## Frequently Asked Questions

### What makes ast-grep better than regex for code search?

AST patterns match the **semantic structure** of code rather than text surface. A regex searching for `print(` hits string literals containing that text; ast-grep's `{"type": "Call", "children": [{"type": "func", "constraint": "Str == 'print'"}]}` only matches actual function calls. This precision is essential for safe automated refactoring.

### Does the code-graph-rag implementation require ast-grep CLI installation?

No. The repository uses `ast-grep-py`, a Python-native binding. The `has_ast_grep()` utility checks for this package at runtime. If absent, structural tools return empty results rather than failing—allowing deployments to function without the optional dependency.

### How do I preview changes before applying them?

Pass `dry_run=True` to `AstGrepService.replace()`. The method returns a list of proposed `(file_path, original_snippet, replacement_snippet)` tuples without writing to disk. Set `dry_run=False` to execute the transformation.

### Can I use this with languages not officially listed?

Yes. Add the file extension-to-language ID mapping in [`constants/ast_nodes.py`](https://github.com/vitali87/code-graph-rag/blob/main/constants/ast_nodes.py), create a YAML config in `parsers/ast_grep_patterns/`, and ensure the underlying `ast-grep-py` binary supports the language. The `AstGrepTier` loader auto-detects new configurations on the next service initialization.