# How detect_changes Maps Git Diff to Affected Symbols with Risk Classification

> Learn how detect_changes maps git diff to affected symbols. It normalizes git status and queries your code graph to find impacted code with downstream risk classification.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: how-to-guide
- Published: 2026-07-10

---

**The `detect_changes` MCP tool aggregates committed, unstaged, and untracked file changes via shell git commands, normalizes porcelain status output to extract clean paths, and queries the internal code graph to identify impacted symbols, while actual risk classification (CRITICAL/HIGH/MEDIUM/LOW) is computed downstream by BFS-based tools like `trace_path` using the `cbm_hop_to_risk` helper.**

The `detect_changes` tool in the DeusData/codebase-memory-mcp repository serves as the bridge between version control state and semantic code analysis. It transforms raw git diff output into a structured JSON payload that identifies exactly which functions, methods, and classes are affected by recent changes. Understanding this mapping pipeline—and the separation between change detection and risk calculation—is essential for integrating precise impact analysis into CI/CD workflows.

## Collecting the Diff from Three Git Streams

The tool constructs a comprehensive shell command in [`src/mcp/mcp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) (lines 5670–5776) that executes three distinct git operations to capture the complete working state. This ensures no changes are missed regardless of whether they are committed, staged, or completely untracked.

```c
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);

```

The three streams capture:
1. **Committed changes** – `git diff <base_branch>...HEAD` compares the current HEAD against the specified base branch
2. **Unstaged modifications** – `git diff --name-only` captures tracked files with uncommitted changes
3. **Untracked and newly staged files** – `git status --porcelain --untracked-files=normal` identifies added files and new blobs

On POSIX systems, the output is piped through `sort -u` to deduplicate entries before processing.

## Normalizing Paths and Status Codes

Raw git output requires parsing to extract usable file paths. The porcelain status format prefixes lines with two-character status codes (e.g., `??` for untracked, `A ` for added, ` M` for modified) and uses arrow notation for renames (`old -> new`).

The normalization logic in [`src/mcp/mcp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) (lines 1414–1428) handles this by checking for valid status prefixes and stripping them:

```c
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;
    }
}

```

This processing ensures that renamed files resolve to their destination path and that only clean file paths enter the `changed_files` array.

## Mapping Files to Affected Symbols

Once file paths are isolated, `detect_changes` queries the code graph store to identify which semantic symbols reside in those files. The function `detect_add_impacted_symbols` in [`src/mcp/mcp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) (lines 76–90) iterates through each changed file and retrieves associated nodes using `cbm_store_find_nodes_by_file`.

```c
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);
    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);
        }
    }
    cbm_store_free_nodes(nodes, ncount);
}

```

The filter explicitly excludes container nodes labeled `File`, `Folder`, or `Project`, returning only semantic symbols such as functions, methods, classes, and routes. Each symbol is serialized into the `impacted_symbols` array with its name, type label, and originating file path.

## Understanding Risk Classification

It is critical to note that `detect_changes` itself **does not** compute risk labels. The tool provides the foundation for risk analysis by identifying the starting nodes (changed symbols) and accepting a `depth` parameter, but the actual risk classification occurs in downstream BFS-based tools.

### The Role of BFS Depth in Risk Calculation

The `depth` field in the JSON output specifies how many hops to traverse through the call graph when analyzing impact. This value is consumed by tools like `trace_path` when performing breadth-first search outward from the changed symbols.

### Translating Hop Distance to Risk Levels

Risk classification follows the principle that direct changes are highest risk, while transitive dependencies decrease in risk with distance. The helper function `cbm_hop_to_risk` in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) (line 3226) translates graph distance into categorical risk levels:

- **CRITICAL** – Hop 0 (the changed symbol itself)
- **HIGH** – Immediate callers/callees
- **MEDIUM** – Secondary dependencies
- **LOW** – Distant transitive relationships

To obtain risk-annotated results, you combine `detect_changes` (to identify starting symbols) with `trace_path(risk_labels=true)` (to traverse the graph and apply the `cbm_hop_to_risk` mapping).

## Practical Usage Examples

### Basic CLI Detection

Invoke the tool from the command line to map current uncommitted changes against the main branch:

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

```

The JSON output includes the changed file count and impacted symbols:

```json
{
  "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
}

```

### Generating Risk-Annotated Impact Reports

First capture the changed symbols, then iterate through them to generate risk classifications:

```bash

# Extract changed symbol names

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

# Run trace_path with risk classification for each symbol

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

```

The `trace_path` response includes a `risk` field derived from the hop distance:

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

```

### JSON-RPC Programmatic Access

For integration with build systems or IDEs, call the method via JSON-RPC:

```json
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 structured payload, allowing automated parsing of affected symbols before passing them to downstream risk analysis tools.

## Summary

- **`detect_changes` aggregates three git streams** (committed vs base, unstaged tracked, and untracked/staged) to capture the complete working tree state
- **Path normalization** strips porcelain status codes and resolves rename arrows to clean file paths in [`src/mcp/mcp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c)
- **Symbol mapping** queries `cbm_store_find_nodes_by_file` to identify semantic entities (functions, methods, classes) while filtering out File/Folder/Project container nodes
- **Risk classification is decoupled** from change detection; `detect_changes` provides the `depth` parameter and symbol list, while `trace_path` uses `cbm_hop_to_risk` in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) to translate BFS hop counts into CRITICAL/HIGH/MEDIUM/LOW labels
- The tool outputs **yyjson-structured JSON** containing changed files, impacted symbols, and traversal depth for downstream processing

## Frequently Asked Questions

### Does detect_changes calculate risk levels itself?

No, `detect_changes` only identifies which symbols reside in changed files and accepts a `depth` parameter. Risk classification is performed by downstream tools like `trace_path` when called with `risk_labels=true`, which uses the `cbm_hop_to_risk` helper function to translate graph distance into CRITICAL, HIGH, MEDIUM, or LOW labels.

### What symbol types are excluded from the impacted_symbols list?

The tool explicitly filters out container nodes labeled `File`, `Folder`, and `Project`. Only semantic symbols such as functions, methods, classes, and routes are included in the `impacted_symbols` array returned in the JSON payload.

### How does the tool handle renamed files in git?

When parsing `git status --porcelain` output, the code detects rename arrows (` -> `) in the path string. If present, it extracts and keeps only the destination path (the new filename), ensuring that the symbol lookup references the correct current file location rather than the obsolete preimage.

### What git references can I use for the base_branch parameter?

The `base_branch` parameter accepts any valid git reference that the three-dot diff syntax supports, including branch names (e.g., `main`, `develop`), tags (e.g., `v1.0`), or commit SHAs. The tool passes this reference directly to `git diff <base>...HEAD` to determine the set of changed files.