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

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 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 Loads source files, builds language-specific AST roots, and executes pattern matches
AstGrepTier codebase_rag/parsers/ast_grep_tier.py Registers per-extension language IDs and loads pattern configuration files
Dependency Detection codebase_rag/utils/dependencies.py Runtime check via has_ast_grep() for optional ast_grep_py availability
LangChain Tools 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 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, 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

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

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 returns tuples of (file_path, line_number, column_offset) for every match.

Performing Structural Replacement

Replace matched nodes while preserving surrounding formatting and indentation:

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:

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

# Example: adding Ruby support

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

}
  1. Create pattern configuration at parsers/ast_grep_patterns/ruby.yaml:
ast_grep_id: ruby
default_patterns:
  - name: class_definition
    pattern: |
      class $NAME
        $BODY
      end
  1. 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 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
  • Extensible architecture via AstGrepTier pattern discovery in codebase_rag/parsers/ast_grep_tier.py

Frequently Asked Questions

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, 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.

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 →