How AST-Based Surgical Patching with Diff Preview Works in Code-Graph-RAG

AST-based surgical patching combines Tree-sitter parsing with diff-match-patch algorithms to locate exact code blocks, generate unified diff previews, and atomically apply changes only after validation.

Code-Graph-RAG implements a precision editing workflow that enables LLMs to modify source code safely through AST-based surgical patching with diff preview. This system, located in the vitali87/code-graph-rag repository, leverages Tree-sitter for robust code extraction and the diff-match-patch library for fail-safe text replacement. The architecture ensures that only intended code blocks are modified while providing complete visibility into changes before they are committed.

The Three-Stage Surgical Pipeline

The FileEditor class in codebase_rag/tools/file_editor.py orchestrates the patching process through three distinct phases: AST-driven location, diff generation with preview, and validated application.

Stage 1: Locating Target Blocks via Tree-sitter

Before any text manipulation occurs, the system identifies the precise code block to modify. The get_parser method (lines 43-48) initializes a Tree-sitter parser specific to the file's language, loading grammar definitions via load_parsers().

When the request specifies a function or class name, the system traverses AST nodes using get_function_source_code, matching against function_node_types and class_node_types constants. This AST traversal extracts the exact source snippet while accounting for language-specific syntax, ensuring the target_block string is retrieved reliably regardless of minor whitespace variations or comments.

Stage 2: Generating Diff Previews

Once the target block is identified, the system prepares the modification through the get_diff method (lines 63-86). This method generates a human-readable preview using Python's difflib.unified_diff to create a standard Git-style diff output.

Internally, the replace_code_block method (lines 10-52) constructs the modified content by executing original_content.replace(target_block, replacement_block, 1). It then utilizes the diff_match_patch library to create a patch object via self.dmp.patch_make(original_content, modified_content).

The resulting diff displays line-by-line changes showing removed lines prefixed with - and added lines with +, allowing the LLM or user to verify the exact scope of the modification before committing to disk.

Stage 3: Safe Patch Application

The final phase applies the patch only after rigorous safety checks. The patch_apply method from the diff-match-patch library executes the transformation, returning a tuple of (new_content, results) where results is a boolean array indicating success for each patch chunk.

In replace_code_block (lines 44-46), the system verifies that all results are True before proceeding. If any chunk fails to apply cleanly, the operation aborts and logs a warning via the EDITOR_PATCH_FAILED constant from codebase_rag/constants.py.

Safety Mechanisms and Validation

The surgical patcher implements multiple guardrails to prevent accidental modifications:

  • Path Resolution: The requested file_path is resolved against the project root, with explicit checks for full_path.is_file() (lines 15-21).
  • Existence Verification: The system verifies that the exact target_block string exists in the original content; if absent, the operation fails immediately (lines 25-28).
  • Single Replacement: Even if the target block appears multiple times, only the first occurrence is replaced, emitting a warning for duplicate matches (lines 30-33).
  • Atomic Writes: Upon successful patch validation, the new content is written atomically to the file system, preventing corruption from partial writes (lines 48-50).

Practical Implementation Example

The following example demonstrates the complete workflow using the FileEditor class:

from codebase_rag.tools.file_editor import FileEditor

# Initialize the editor with project root

editor = FileEditor(project_root="my_repo")

# Define the exact block to replace

target = """def greet(name):
    print(f"Hello, {name}")"""

# Define the replacement code

replacement = """def greet(name):
    # Added logging

    logger.info(f"Greeting {name}")
    print(f"Hello, {name}")"""

# Generate diff preview before applying

diff_preview = editor.get_diff(
    file_path="src/utils.py",
    function_name="greet",
    new_code=replacement,
)
print("Proposed changes:\n", diff_preview)

# Apply the surgical patch if preview is acceptable

success = editor.replace_code_block(
    file_path="src/utils.py",
    target_code=target,
    replacement_code=replacement,
)
print(f"Patch applied successfully: {success}")

Summary

  • AST-driven precision: Tree-sitter parsers locate target blocks reliably across Python, Java, Go, and other languages supported by load_parsers().
  • Diff-match-patch safety: The diff_match_patch library ensures text replacements apply cleanly, with patch_apply validation preventing partial modifications.
  • Human-readable previews: The get_diff method generates unified diffs via difflib.unified_diff, showing exact line changes before commitment.
  • Atomic operations: File writes occur only after all validation checks pass, ensuring either complete success or no change.
  • Repository location: All functionality resides in codebase_rag/tools/file_editor.py, with logging constants referenced from codebase_rag/constants.py and codebase_rag/logs.py.

Frequently Asked Questions

How does the AST parser improve patch accuracy in Code-Graph-RAG?

The AST parser improves accuracy by resolving function or class names to exact source text blocks through Tree-sitter's language-specific grammar trees. When a request specifies function_name="greet", the get_function_source_code method walks function_node_types nodes to extract the precise text span, eliminating ambiguity from manual line number calculations or whitespace variations.

What happens if the target code block appears multiple times in the file?

If the target block exists multiple times, replace_code_block replaces only the first occurrence and emits a warning via the logging system. The replace operation uses a count of 1 (lines 30-33 in file_editor.py), ensuring deterministic behavior while alerting the user to potential duplicates that might require separate handling.

Can the diff preview be generated without applying the patch?

Yes, the get_diff method (lines 63-86) operates independently of replace_code_block, allowing LLMs or developers to inspect the unified diff output before deciding to apply changes. This separation enables iterative refinement of the replacement_block until the diff preview shows exactly the intended modifications.

What error handling exists if a patch fails to apply?

If patch_apply returns any False values in its results array, the operation aborts before writing to disk and logs the failure using the EDITOR_PATCH_FAILED message template. The original file remains unmodified because the atomic write only occurs after verifying all patch chunks succeeded, preventing partial or corrupted file states.

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 →