Summary
An incremental re-index (index_repository on an already-indexed project) names File nodes by their repo-relative path, while a full build names them by basename. Node upserts match by qualified name, so re-indexing a touched file renames its File node in place — the same working tree yields two different graphs depending on whether it was indexed fresh or incrementally, and the divergence grows with every edited file (both added and modified files are affected).
Because File nodes are USAGE-edge sources, this breaks name-based traversal: walking inbound USAGE transitively dead-ends at any touched file's node, silently dropping everything that reaches a target through a recently added/modified class.
Reproduction (v0.9.0, macOS arm64; also present on current main)
BIN=./codebase-memory-mcp; T=$(mktemp -d); I=$(mktemp -d); F=$(mktemp -d)
mkdir -p "$T/src"
printf 'public class Bee { public void buzz() {} }\n' > "$T/src/Bee.java"
printf 'public class Aye { private final Bee bee = new Bee(); }\n' > "$T/src/Aye.java"
CBM_CACHE_DIR="$I" "$BIN" cli index_repository "{\"repo_path\":\"$T\"}" # initial
printf 'public class Cee { private final Bee bee = new Bee(); }\n' > "$T/src/Cee.java" # ADD
printf '// touched\n' >> "$T/src/Aye.java" # MODIFY
CBM_CACHE_DIR="$I" "$BIN" cli index_repository "{\"repo_path\":\"$T\"}" # incremental
CBM_CACHE_DIR="$F" "$BIN" cli index_repository "{\"repo_path\":\"$T\"}" # full, same tree
# then per cache: query_graph "MATCH (n:File) RETURN n.name"
Result:
| cache |
File node names |
| incremental |
Bee.java, src/Cee.java, src/Aye.java |
| full (same tree) |
Cee.java, Aye.java, Bee.java |
USAGE edges into Bee correspondingly come from src/Cee.java (incremental) vs Cee.java (full).
Root cause
pipeline_incremental.c ("Create File nodes for changed files (purge deleted them)") passes changed_files[i].rel_path as the name argument of cbm_gbuf_upsert_node, whereas pipeline.c ("Create File node") computes the basename first. The incremental site also loses the {"extension": ...} props the full build sets. Introduced with the RAM-first incremental rework (80680ea); given the comment says it re-creates what purge deleted, this looks like an argument slip rather than intent.
Fix
PR to follow: mirror the full pipeline's node shape at the incremental call site (basename + extension props), plus a regression test in the incremental suite (incr_file_node_name_stays_basename) — red without the fix, green with it; full incremental suite passes (162/162).
Summary
An incremental re-index (
index_repositoryon an already-indexed project) names File nodes by their repo-relative path, while a full build names them by basename. Node upserts match by qualified name, so re-indexing a touched file renames its File node in place — the same working tree yields two different graphs depending on whether it was indexed fresh or incrementally, and the divergence grows with every edited file (both added and modified files are affected).Because File nodes are USAGE-edge sources, this breaks name-based traversal: walking inbound
USAGEtransitively dead-ends at any touched file's node, silently dropping everything that reaches a target through a recently added/modified class.Reproduction (v0.9.0, macOS arm64; also present on current main)
Result:
Bee.java,src/Cee.java,src/Aye.javaCee.java,Aye.java,Bee.javaUSAGE edges into
Beecorrespondingly come fromsrc/Cee.java(incremental) vsCee.java(full).Root cause
pipeline_incremental.c("Create File nodes for changed files (purge deleted them)") passeschanged_files[i].rel_pathas the name argument ofcbm_gbuf_upsert_node, whereaspipeline.c("Create File node") computes the basename first. The incremental site also loses the{"extension": ...}props the full build sets. Introduced with the RAM-first incremental rework (80680ea); given the comment says it re-creates what purge deleted, this looks like an argument slip rather than intent.Fix
PR to follow: mirror the full pipeline's node shape at the incremental call site (basename + extension props), plus a regression test in the incremental suite (
incr_file_node_name_stays_basename) — red without the fix, green with it; full incremental suite passes (162/162).