How FileEditor Performs AST‑Based Surgical Code Patching in code‑graph‑rag
FileEditor uses tree‑sitter AST parsing and byte‑level node offsets to apply precise, language‑aware code edits without line‑number guessing.
The FileEditor tool in the code‑graph‑rag repository enables retrieval‑augmented generation systems to modify source files with surgical precision. By anchoring edits to AST node boundaries rather than fragile line numbers, it ensures patches survive reflows, comment changes, and surrounding code motion. This article breaks down the four‑stage pipeline implemented across tools/file_editor.py and its supporting modules.
Parse Stage: From Source Bytes to Tree‑Sitter AST
Every edit begins with deterministic parsing. The codebase_rag.utils.source_extraction module wraps tree‑sitter to return a syntax tree where each node carries exact byte start/end offsets into the original file.
from codebase_rag.utils.source_extraction import TreeSitterParser
parser = TreeSitterParser.for_language("python")
tree = parser.parse_bytes(source_bytes) # Root node spans entire file
Unlike regex or line‑based approaches, this guarantees that structural boundaries—function bodies, class definitions, import blocks—are identified correctly regardless of formatting style.
Locate Stage: Resolving Qualified Names to AST Nodes
With the AST in hand, FileEditor delegates node lookup to codebase_rag.graph_updater. The find_node_by_qualified_name method traverses the hierarchy using scoped identifiers like my_pkg.my_mod.my_func.
from codebase_rag.graph_updater import GraphUpdater
updater = GraphUpdater(ast_cache)
target_node = updater.find_node_by_qualified_name("utils.helper.process_data")
# Returns: node with .start_byte and .end_byte attributes
This resolution is cache‑aware: the BoundedASTCache stores parsed trees indexed by file path, so repeated edits to the same file avoid redundant parsing.
Diff Stage: Byte‑Level Patch Generation
The core surgical operation lives in tools/file_editor.py. The apply_patch_to_file method:
- Extracts the original source slice using
node.start_byteandnode.end_byte - Generates a
difflib.unified_diffbetween old and new text - Applies the diff in‑place while preserving surrounding whitespace and comments
from tools.file_editor import FileEditor
editor = FileEditor(project_root="/path/to/project")
result = editor.replace_code_surgically(
qualified_name="utils.helper.process_data",
new_source="""
def process_data(items):
# Optimized path
return [x.value for x in items if x.active]
"""
)
Because the patch boundaries derive from AST offsets, the edit never drifts—even if imports are added above or docstrings reformatted below the target function.
Verify Stage: Post‑Edit AST Validation
FileEditor re‑patches defensively. After writing the file, it:
- Re‑parses via
source_extraction.parse_file - Verifies the modified node's type matches expectations
- Rolls back and raises on syntax corruption
# Simplified validation logic from file_editor.py
new_tree = parser.parse_file(file_path)
new_node = find_corresponding_node(new_tree, qualified_name)
assert new_node.type == original_node.type, "AST structure corrupted"
This atomicity ensures the codebase remains valid after every operation.
Practical Examples
Rename a Function Implementation
editor.replace_code_surgically(
qualified_name="analytics.metrics.compute_score",
new_source="""
def compute_score(predictions, labels):
return f1_score(labels, predictions, average="weighted")
"""
)
The AST node for compute_score is located, its entire definition replaced, and the file re‑parsed to confirm the function still exists.
Insert an Import Statement
editor.edit_file(
file_path="src/main.py",
before_node="import os", # AST node reference, not line number
new_source="import pathlib\n",
)
before_node resolves to an actual import node; the insertion offset is computed from node.start_byte, ensuring correct placement even with multiple import groups.
Delete an Obsolete Constant
editor.replace_code_surgically(
qualified_name="config.LEGACY_TIMEOUT",
new_source="", # Empty replacement = deletion
)
The AST node spanning the constant definition is excised precisely, leaving surrounding assignments intact.
Key Source Files
| File | Purpose |
|---|---|
tools/file_editor.py |
FileEditor class, apply_patch_to_file, high‑level edit API |
codebase_rag/utils/source_extraction.py |
Tree‑sitter parsing, byte‑offset extraction |
codebase_rag/graph_updater.py |
Node lookup by qualified name, cache invalidation |
codebase_rag/graph_loader.py |
Bulk AST loading into BoundedASTCache |
codebase_rag/types_defs.py |
ASTCacheProtocol, node type definitions |
Summary
- AST parsing via tree‑sitter eliminates line‑number fragility
- Byte‑level offsets from
source_extractionanchor every edit GraphUpdaterresolves qualified names to concrete nodesapply_patch_to_filegenerates and applies unified diffs surgically- Re‑parsing validation guarantees syntactic integrity after changes
- Cache invalidation keeps the RAG index synchronized with file state
Frequently Asked Questions
What makes AST‑based patching "surgical"?
Traditional tools rely on line numbers or pattern matching, which break when code is reformatted. AST‑based patching uses byte offsets tied to syntax nodes, so edits target exact structural boundaries—functions, classes, imports—regardless of surrounding changes.
Does FileEditor support languages other than Python?
The architecture is language‑agnostic through tree‑sitter. TreeSitterParser.for_language() accepts any grammar the tree‑sitter ecosystem provides. The code‑graph‑rag repository currently optimizes for Python, but the source_extraction module is designed for multi‑language extension.
How does FileEditor handle patch conflicts?
Edits are atomic: the file is read, modified, and written within a single operation. If re‑parsing fails, the original file is restored. Concurrent modifications are not handled—FileEditor assumes single‑process access or external file locking.
What happens to the RAG index after a file edit?
GraphUpdater invalidates affected entries in BoundedASTCache and triggers incremental re‑indexing. Subsequent retrievals see the updated AST immediately, keeping the knowledge graph consistent with the source codebase.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →