How the detect_changes Tool Maps Git Diffs to Symbols with Risk Classification

The detect_changes tool in DeusData/codebase-memory-mcp converts git diffs into structured JSON lists of changed files and impacted code symbols, delegating actual risk classification to downstream BFS-based tools like trace_path that map hop distance to risk levels.

The detect_changes MCP (Model Context Protocol) tool bridges version control and code graph analysis by transforming raw git output into actionable symbol metadata. According to the codebase-memory-mcp source, this C-based utility aggregates committed changes, unstaged modifications, and untracked files, then queries the internal graph store to identify which functions, classes, and methods reside in those files.

Understanding the detect_changes Pipeline

The tool operates as a three-stage pipeline: collecting the diff, normalizing paths, and resolving symbols. Each stage handles specific edge cases inherent to git workflows.

Collecting the Complete Git Diff

To ensure no modification is missed, detect_changes executes a compound shell command that merges three distinct git streams. In src/mcp/mcp.c (lines 5670-5776), the tool constructs a platform-specific command string:

snprintf(cmd, sizeof(cmd),
#if defined(_WIN32)
    "git -C \"%s\" diff --name-only \"%s\"...HEAD 2>NUL & "
    "git -C \"%s\" diff --name-only 2>NUL & "
    "git --no-optional-locks -C \"%s\" status --porcelain "
    "--untracked-files=normal 2>NUL",
#else
    "{ git -C '%s' diff --name-only '%s'...HEAD 2>/dev/null; "
    "git -C '%s' diff --name-only 2>/dev/null; "
    "git --no-optional-locks -C '%s' status --porcelain "
    "--untracked-files=normal 2>/dev/null; } | sort -u",
#endif
    root_path, base_branch, root_path, root_path);

This captures:

  • Committed changes via git diff <base>...HEAD
  • Unstaged tracked changes via git diff --name-only
  • Untracked and newly staged files via git status --porcelain

On POSIX systems, the output streams are piped through sort -u to deduplicate entries, ensuring a file modified both in the index and working tree appears only once.

Normalizing File Paths from Porcelain Output

git status --porcelain prefixes lines with two-character status codes (?? for untracked, R for renamed, M for modified). The path normalization logic in src/mcp/mcp.c (lines 1414-1428) strips these prefixes and handles rename arrows:

if (len > PAIR_LEN && line[PAIR_LEN] == ' '
    && strchr(" MADRCU?!", line[0]) && strchr(" MADRCU?!", line[1])) {
    path_line = line + PAIR_LEN + SKIP_ONE;
    char *arrow = strstr(path_line, " -> ");
    if (arrow) {
        path_line = arrow + ARROW_LEN;
    }
}

When a rename is detected (R old/path -> new/path), the code advances the pointer past the arrow, keeping only the destination path. This ensures the symbol lookup operates on the current filesystem state.

Mapping Files to Semantic Symbols

Once the file list is sanitized, detect_changes resolves each path to its constituent code symbols using the graph store's file index.

Filtering Graph Nodes by File

The function detect_add_impacted_symbols (defined in src/mcp/mcp.c, lines 76-90) queries the store for all nodes associated with a specific file path:

static void detect_add_impacted_symbols(cbm_store_t *store,
                                        const char *project,
                                        const char *file,
                                        yyjson_mut_doc *doc,
                                        yyjson_mut_val *impacted) {
    cbm_node_t *nodes = NULL;
    int ncount = 0;
    cbm_store_find_nodes_by_file(store, project, file, &nodes, &ncount);
    // ... filtering and JSON construction
}

This leverages cbm_store_find_nodes_by_file (implemented in src/store/store.c) to retrieve the complete set of graph nodes defined within the file boundary.

Excluding Container Nodes

The tool deliberately filters out structural containers to focus on actionable code elements. As implemented in the iteration loop:

for (int i = 0; i < ncount; i++) {
    if (nodes[i].label && strcmp(nodes[i].label, "File") != 0 &&
        strcmp(nodes[i].label, "Folder") != 0 && 
        strcmp(nodes[i].label, "Project") != 0) {
        yyjson_mut_val *item = yyjson_mut_obj(doc);
        yyjson_mut_obj_add_strcpy(doc, item, "name", 
            nodes[i].name ? nodes[i].name : "");
        yyjson_mut_obj_add_strcpy(doc, item, "label", nodes[i].label);
        yyjson_mut_obj_add_strcpy(doc, item, "file", file);
        yyjson_mut_arr_add_val(impacted, item);
    }
}

Only semantic nodes—functions, classes, methods, routes, and similar constructs—are included. Generic filesystem entities like File, Folder, or Project nodes are discarded, ensuring the impacted_symbols array contains actual code units that could require testing or review.

Risk Classification Architecture

A critical architectural distinction separates detect_changes from risk analysis: the tool identifies what changed, while companion tools determine how risky those changes are.

Why detect_changes Does Not Assign Risk Labels

The detect_changes response includes a depth field indicating the BFS traversal depth requested, but it does not populate risk labels. Risk classification (CRITICAL, HIGH, MEDIUM, LOW) requires graph traversal to calculate hop distance from changed symbols to other system components.

As implemented in src/store/store.c (line 3226), the risk mapping is performed by a dedicated helper:

cbm_risk_level_t cbm_hop_to_risk(int hop);

This function translates integer hop counts into enumerated risk levels, but it is invoked by BFS-based tools rather than the diff detector.

The BFS Risk Calculation Pipeline

To obtain risk-annotated results, you combine detect_changes with trace_path. The typical workflow:

  1. Call detect_changes to obtain the list of impacted_symbols
  2. Iterate through symbols and call trace_path with risk_labels=true
  3. Receive annotated graphs where each node includes a risk field derived from its graph distance to the original changed symbol

The trace_path tool performs a breadth-first search outward from the seed symbol, using cbm_hop_to_risk to classify each discovered node based on its proximity to the change.

Practical Usage Examples

CLI Execution for Change Detection

Map current uncommitted changes against the main branch:

codebase-memory-mcp cli detect_changes \
    '{"project":"my-repo","base_branch":"main"}'

The tool returns a JSON structure:

{
  "changed_files": [
    "src/main.c",
    "src/util.c"
  ],
  "changed_count": 2,
  "impacted_symbols": [
    {"name":"main","label":"Function","file":"src/main.c"},
    {"name":"util_init","label":"Function","file":"src/util.c"}
  ],
  "depth": 5
}

Obtaining Risk-Annotated Impact Analysis

Since detect_changes returns symbols without risk data, pipe the output into trace_path for classification:


# Extract changed symbol names

SYMBOLS=$(codebase-memory-mcp cli detect_changes \
    '{"project":"my-repo"}' | jq -r '.impacted_symbols[].name')

# Query risk for each symbol

for sym in $SYMBOLS; do
    codebase-memory-mcp cli trace_path \
        "{\"project\":\"my-repo\",\"function_name\":\"$sym\",\"risk_labels\":true}"
done

The trace_path response includes the risk field:

{
  "name":"process_data",
  "label":"Function",
  "risk":"HIGH"
}

JSON-RPC Endpoint Integration

For programmatic access, POST to the MCP endpoint:

POST /rpc HTTP/1.1
Content-Type: application/json

{
  "jsonrpc":"2.0",
  "method":"detect_changes",
  "params":{
    "project":"my-repo",
    "base_branch":"main",
    "scope":"symbols"
  },
  "id":1
}

The server returns the same payload structure as the CLI, suitable for integration into CI/CD pipelines or IDE extensions.

Summary

  • detect_changes aggregates git diffs from three sources (committed, unstaged, untracked) using a compound shell command constructed in src/mcp/mcp.c.
  • Path normalization handles git status --porcelain format codes and rename arrows, extracting clean filesystem paths.
  • Symbol resolution queries cbm_store_find_nodes_by_file to map files to semantic graph nodes, filtering out File, Folder, and Project containers.
  • Risk classification is intentionally decoupled; detect_changes provides the symbol list, while trace_path uses cbm_hop_to_risk in src/store/store.c to calculate risk levels based on BFS hop distance.
  • The tool outputs yyjson-structured JSON containing changed_files, changed_count, impacted_symbols, and depth parameters.

Frequently Asked Questions

Does detect_changes calculate risk levels automatically?

No. The detect_changes tool only identifies which symbols reside in changed files. It outputs a depth parameter indicating how far downstream tools should traverse, but actual risk labels (CRITICAL, HIGH, MEDIUM, LOW) are computed by trace_path or similar BFS-based tools that call cbm_hop_to_risk based on graph distance from the changed symbols.

What types of git changes does the tool detect?

The tool detects three categories of changes: committed changes relative to a base branch (git diff base...HEAD), unstaged modifications to tracked files (git diff --name-only), and untracked or newly staged files (git status --porcelain). This ensures comprehensive coverage whether you are working with dirty working trees, staged changes, or commits awaiting merge.

How does detect_changes handle renamed files?

When git status --porcelain reports a rename (status code R), the normalization logic in src/mcp/mcp.c detects the -> arrow pattern and extracts only the destination path. This ensures symbol lookups occur against the current filename rather than the obsolete path, preventing lookup failures in the graph store.

Can I use detect_changes without the symbol resolution step?

Yes. By specifying "scope":"files" in the request parameters, you can limit the output to the changed_files array without invoking cbm_store_find_nodes_by_file. This mode is useful when you only need the file list for linting or build system invalidation, bypassing the graph store query entirely.

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 →