How detect_changes Maps Git Diffs to Affected Symbols and Risks
The detect_changes function in DeusData/codebase-memory-mcp converts raw git diff output into a precise inventory of modified symbols by parsing unified diff hunks with zero context, querying a pre-computed line-to-symbol index, and emitting a structured ChangeSet for downstream risk analysis.
The detect_changes component serves as the critical bridge between version control and static analysis in the DeusData/codebase-memory-mcp repository. It consumes the output of git diff --unified=0 and identifies exactly which functions, methods, structs, and variables have been modified, enabling targeted re-analysis rather than full codebase scans.
The Three-Stage Detection Pipeline
The implementation follows a tightly-coupled three-stage pipeline orchestrated by src/detectchanges.c. Each stage transforms the data from raw text to structured symbols ready for risk assessment.
Stage 1: Parsing the Diff with Zero Context
The process begins in src/diff/parser.c where parse_git_diff() ingests the raw git output. The parser specifically expects the output of git diff --unified=0, which produces hunks with zero context lines to ensure line numbers map exactly to edited lines without false positives.
The parser strips the diff --git a/... b/... headers and isolates the @@ -oldStart,oldLen +newStart,newLen @@ hunk markers. It builds a map of file → [{oldLine, newLine, length}] describing the exact changed ranges, storing these in DiffHunk structures. The function discards context lines (starting with spaces) and retains only added (+) and removed (-) lines while preserving original line numbers for accurate mapping.
Stage 2: Locating Symbols via AST Traversal
With hunks prepared, detect_changes iterates over each file and invokes find_symbols_in_range(file, start, end) from src/symbols/matcher.c. This stage leverages a line-to-symbol index built during the initial repository scan (see src/symbols/indexer.c) and cached in the .cache directory.
The matcher walks the Abstract Syntax Tree (AST) for the affected file and tests each node against the diff range using the condition node.start ≤ diffLine ≤ node.end. It selects the deepest node satisfying this condition, ensuring that a change inside a nested function reports that specific function rather than just the containing class or file. This depth resolution guarantees precise symbol identification for accurate risk calculation.
Stage 3: Constructing the ChangeSet
For each located symbol, the system builds a SymbolChange entry containing the fully-qualified name, symbol kind (function, type, variable, etc.), file path, and hunk location. These entries aggregate into a ChangeSet structure defined in detectchanges.h.
The serialize_changeset() function in src/detectchanges.c converts this structure to JSON, decoupling the C implementation from downstream consumers. This serialization enables the incremental analysis engine and UI components to process the changes without binding to internal data structures.
Integration with Analysis and UI Systems
The ChangeSet produced by detect_changes feeds two major subsystems:
- Incremental Analysis: The
src/analysis/incremental.cmodule consumes the change set to re-run static analysis only on affected symbols, reducing computational overhead from O(#symbols) to O(#changed-hunks). - Real-time UI: The WebSocket endpoint in
src/server/ws.cstreams the JSON-serialized changes to the frontend, enabling real-time visualization of code risks as developers modify files.
Implementation Example
The following C code demonstrates invoking the detection pipeline from a command-line interface:
// Example: invoking detectChanges from the CLI
int main(int argc, char **argv) {
const char *diff = argv[1]; // path to a git diff file
ChangeSet *cs = detect_changes(diff); // <-- core function
char *json = serialize_changeset(cs);
puts(json);
free(json);
free_changeset(cs);
}
For Go-based integrations, the library exposes bindings via cgo:
// Example: Go wrapper that calls the C library via cgo
import "C"
func DetectChanges(diffPath string) ([]SymbolChange, error) {
cDiff := C.CString(diffPath)
defer C.free(unsafe.Pointer(cDiff))
cs := C.detect_changes(cDiff)
if cs == nil {
return nil, fmt.Errorf("detectChanges failed")
}
// Convert the C ChangeSet to Go structs (omitted for brevity)
return nil, nil
}
Summary
detect_changesorchestrates the pipeline insrc/detectchanges.c, bridging git diffs and symbol analysis.- Zero-context parsing (
--unified=0) insrc/diff/parser.censures line-accurate mapping without false positives from context lines. - AST depth resolution in
src/symbols/matcher.cidentifies the most specific symbol (e.g., nested functions) affected by each change. - Pre-computed indexing via
src/symbols/indexer.cenables O(#changed-hunks) performance by caching the line-to-symbol mapping. - JSON serialization decouples the detection engine from incremental analysis (
src/analysis/incremental.c) and UI updates (src/server/ws.c).
Frequently Asked Questions
Why does detect_changes use --unified=0 instead of standard diffs?
The --unified=0 flag produces hunks containing only the changed lines with zero context. This eliminates context lines that would otherwise complicate line-number mapping, ensuring that the oldStart and newStart values in hunk headers correspond exactly to modified code. This precision prevents false positives where unchanged context lines might be mistaken for affected symbols.
How does the system handle nested functions or methods?
The find_symbols_in_range() function in src/symbols/matcher.c implements depth resolution by walking the AST and selecting the deepest node whose line range intersects the diff hunk. When a change occurs inside a nested function, the system identifies that specific function rather than reporting only the parent class or file, enabling granular risk assessment.
What is the line-to-symbol index and how is it maintained?
The line-to-symbol index is a persistent mapping built by src/symbols/indexer.c during the initial codebase scan and stored in the .cache directory. It maps file paths and line numbers to their corresponding AST nodes (functions, variables, types). This cache is reused across detect_changes invocations, making symbol lookup O(1) per line rather than requiring full AST reconstruction on every diff.
How does the ChangeSet integrate with risk analysis?
The ChangeSet structure feeds src/analysis/incremental.c, which selectively re-runs static analysis rules only on modified symbols. This incremental approach identifies which changes introduce new risks (e.g., security vulnerabilities, breaking changes) without re-analyzing the entire codebase. The results are then streamed via src/server/ws.c to risk dashboards in real-time.
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 →