Closes the four reviewer-flagged gaps from the previous round:
1. **Phase F wired into the watcher / `codebase_update`.**
`rebuildGraph(path, { skipSymbolGraph: true })` now exposes a
file-import-only build mode. `services/indexer.ts` calls it +
`updateChangedFilesSymbolGraph(...)` when meta exists AND ≤ 50 files
changed (`INCREMENTAL_SYMBOL_THRESHOLD`); falls back to full rebuild
above that. Measured speedup on a 1000-file synthetic repo: full
rebuild 6.55 s → Phase F single-file update 197 ms (~33×).
2. **Real end-to-end scale test.**
New `tests/integration/symbol-graph-scale.test.ts` generates 1000
synthetic Python files × 20 symbols/file (20k symbols) against a real
Qdrant, asserts (a) full rebuild within budget, (b) cold listSymbols /
getImpactRadius queries within budget, (c) Phase F update ≥ 4× faster
than full rebuild. `SCALE_LARGE=1` pushes to 10k files / 200k symbols.
3. **Smoke benchmark numbers captured.**
New `scripts/benchmark-graph.ts` runs `rebuildGraph` against any
target dir and emits JSON + a Markdown row. Numbers for SocratiCode
itself (82 files / 571 symbols / 9914 call edges / 0.90 s / 167 MB
RSS) and the synthetic 1000-file repo are now in DEVELOPER.md
§ "Real-world benchmark numbers".
4. **Logger test flake fixed.**
`services/logger.ts` exposes `setLogLevel` / `getLogLevel`;
`tests/unit/logger.test.ts` pins the level in beforeEach and restores
in afterEach. Verified deterministic with `SOCRATICODE_LOG_LEVEL=debug`
set in the shell environment.
### Bug discovered + fixed by the new benchmark
Running `scripts/benchmark-graph.ts` against SocratiCode itself crashed
the symbol graph build with `TypeError: existing.push is not a function`.
Root cause: shard maps used `shard[name]` bracket access on a plain
`{}`, which returned `Object.prototype.constructor` (a function) for
common method names like `constructor`, `toString`, `hasOwnProperty`.
Fixed by guarding all reads with `Object.hasOwn` in
`services/code-graph.ts` and `services/symbol-graph-incremental.ts`.
Added a regression test in
`tests/integration/symbol-graph-incremental.test.ts`.
### QA
- Biome lint: clean (auto-fixed 1 file).
- VS Code Problems panel: clean.
- Unit tests: 676/676 pass (29 files); reproducible.
- Integration tests touched: 45/45 pass (incremental, scale,
indexer, code-graph).
- CodeRabbit review: no findings.
- Snyk Code: 0 issues.
### Doc updates
- DEVELOPER.md: removed "watcher still triggers full rebuild" wording,
added "Real-world benchmark numbers" subsection with measured table.
- CHANGELOG.md: removed "Known Limitations" block; added new
Bug Fixes entries (prototype keys, logger flake) and a Performance
entry for the wired Phase F path with measured numbers.
Addresses six gaps in the prior Impact Analysis work:
1. **Scale benchmarks** (was missing): tests/unit/symbol-graph-scale.test.ts
exercises sharding/hashing at 10k–100k symbol volumes with loose
regression thresholds (>10× slowdown to fail).
2. **Per-language symbol-extraction tests** (was ~15% of plan):
tests/unit/graph-symbols.test.ts now covers Rust, Java/Kotlin/Scala (JVM),
C#, C/C++, Ruby, PHP, Swift, Bash, and the regex fallback path.
3. **Phase F (per-file incremental updates)** — implemented as
src/services/symbol-graph-incremental.ts with
updateChangedFilesSymbolGraph(). Re-extracts changed files, diffs against
persisted payloads via contentHash, patches only affected name (≤27) and
reverse-call (≤256) shards, and updates meta counts incrementally.
Integration tests in tests/integration/symbol-graph-incremental.test.ts.
Watcher wiring is documented as a follow-up in CHANGELOG (still does
full rebuild on save).
4. **Integration tests for the four new MCP tools**: codebase_impact,
codebase_flow, codebase_symbol, codebase_symbols added to
tests/integration/tools.test.ts.
5. **Symbol-graph store unit tests**: tests/unit/symbol-graph-store.test.ts
covers nameShardKey, allNameShardKeys, reverseShardKey, reverseShardHex,
contentHashOf — 14 tests.
6. **Bug fix**: Java/Kotlin/Swift/Scala silently failed because ast-grep
throws 'Invalid Kind' when a queried node-kind doesn't exist for that
grammar (e.g. object_declaration is Kotlin-only). The outer try/catch in
extractSymbolsAndCalls swallowed the error and returned only <module>.
Fixed via safeFindAll wrapper applied to all 36 call sites in
src/services/graph-symbols.ts.
Also fixes a CodeRabbit-flagged comment/code mismatch in
src/services/graph-entrypoints.ts (now actually checks for it/describe
test names as the comment claimed).
Tests: 676 unit pass. Lint clean. CodeRabbit clean. Snyk clean.
- Use Object.hasOwn() instead of `in` for log level validation
- Normalize relativePath in shouldIgnore() for Windows compatibility
- Reject zero/negative/decimal values in embedding config (Number() instead of parseInt())
- Update aggregate test count to 765
- Harden code-graph test assertion with explicit toBeDefined()
When SOCRATICODE_PROJECT_ID is set to share a Qdrant collection across
git worktrees, the indexer still used absolute paths as keys in the file
hash map, chunk IDs, and for deleting file chunks. This caused every
worktree to see all files as "new" and trigger a full re-index, defeating
the purpose of the shared project ID feature.
Switch all internal keying from absolute paths to relative paths:
- chunkId() now hashes on relativePath for stable IDs across worktrees
- File hash map (change detection) keyed by relativePath
- deleteFileChunks() filters on relativePath Qdrant field
- Deleted file detection uses relative path sets
Absolute paths are now only used for actual file I/O (stat, readFile).