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

> Learn how the detect_changes tool maps git diffs to code symbols and risk classifications. It converts changes to JSON, enabling downstream analysis for risk assessment.

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

---

**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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) (lines 5670-5776), the tool constructs a platform-specific command string:

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

```

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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) (lines 1414-1428) strips these prefixes and handles rename arrows:

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

```

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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c), lines 76-90) queries the store for all nodes associated with a specific file path:

```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);
    // ... filtering and JSON construction
}

```

This leverages `cbm_store_find_nodes_by_file` (implemented in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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:

```c
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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) (line 3226), the risk mapping is performed by a dedicated helper:

```c
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:

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

```

The tool returns a JSON structure:

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

```

### Obtaining Risk-Annotated Impact Analysis

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

```bash

# 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:

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

```

### JSON-RPC Endpoint Integration

For programmatic access, POST to the MCP endpoint:

```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 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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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.