README
¶
bench/ — baseline harness for feature PR validation
Repo-level benchmark harness that captures measured pre/post metrics for incoming feature PRs (graphify-inspired improvements: confidence tags, orientation report, rationale extraction, graph diff, similarity edges).
Scope: this directory is not the historical language coverage benchmark in docs/benchmarks.md. This directory tests repo-level metrics against locally pinned fixture repositories (declared in an uncommitted fixtures.json, see below), and is designed to be re-run after each feature PR with compare.py diffing before/after.
Quick start
# Rebuild the binary first (required — deployed binary may lag)
cd /path/to/code-graph
CGO_ENABLED=1 go build -o bin/code-graph.exe ./cmd/code-graph/
# One-time fixture setup: create the main-pinned worktree for the self-hosting fixture
git worktree add "$HOME/worktrees/code-graph-main" main
./bin/code-graph.exe cli --raw index_repository \
'{"repo_path":"'$HOME'/worktrees/code-graph-main"}'
# Full baseline across all 4 fixtures
python bench/harness.py --output bench/baseline_$(date +%F).json \
--binary ./bin/code-graph.exe
# Skip re-index (use whatever's cached in ~/.cache/code-graph/)
python bench/harness.py --output bench/baseline_$(date +%F).json \
--binary ./bin/code-graph.exe --skip-index
# Smoke test (Q1+Q2 only on one repo, fast feedback)
python bench/harness.py --output bench/smoke.json --smoke --repo mcp-servers \
--binary ./bin/code-graph.exe --skip-index
# Diff two baselines (before vs after a feature PR)
python bench/compare.py bench/baseline_2026-04-22.json bench/after_pr1.json
Opt-in: Voyage embeddings during indexing
By default the harness sets VOYAGE_API_KEY="" during indexing to skip the
embeddings pass. This avoids a known indefinite stall in the embeddings HTTP
loop (see Known issues below). Semantic-search (search_code_semantic)
will return empty until a fresh index with embeddings runs.
# Opt in — only when semantic_search data is needed (still subject to stall risk)
python bench/harness.py --output bench/baseline.json \
--binary ./bin/code-graph.exe --with-embeddings
Files
| File | Purpose |
|---|---|
fixtures.json |
Local, not committed: pinned benchmark repos (paths + SHAs). Create it from the schema described below before running the harness |
questions.json |
20-question standard suite (Q1-Q12 baseline, Q13-Q20 feature probes) |
harness.py |
Runs the suite against a binary, emits JSON |
compare.py |
Diff two JSON baselines |
transcripts.py |
Phase 0b — scans ~/.claude/projects/ for Claude Code sessions that used mcp__code-search__* or mcp__code-graph__* tools, writes one JSONL record per session with tool-call sequence and token totals. Used as the A/B population for PR 3 (PreToolUse hook effectiveness) and PR 4/7 agent-answer quality. |
baseline_YYYY-MM-DD.json, transcripts_YYYY-MM-DD.jsonl |
Captured outputs (gitignored; regenerate on demand) |
What the harness captures per question
- Wall-clock latency (ms)
- Grade:
PASS/PARTIAL/FAIL/N/A/ERROR - Result hash (SHA256 prefix) — detects silent behavior changes between runs
- Short preview of the raw result
Grading notes
N/Ais used when:- A feature PR hasn't merged yet (for Q13-Q20)
- The installed binary doesn't contain a tool the source does (binary-lag case)
- The question doesn't apply to the repo
ERRORis reserved for unexpected tool errors on tools that should workPASSrequires the question'sexpect_contains/expect_min_results/expect_non_emptypredicate to hold
Known issues + workarounds
1. Embeddings-pass indefinite stall (VOYAGE_API_KEY set)
Symptom: on larger repos (~4k+ nodes), the indexer logs phase=embeddings pct=97 detail="generating embeddings" and produces no further output for many minutes. The process is alive but no pass.embeddings.progress log fires. Observed 2026-04-22 on rmf-corsair at 8+ minutes.
Root cause (not confirmed — deferred to a separate investigation):
- Not DNS/IPv6 — reproduced with
GODEBUG=netdns=go+v4. - The pass has no overall timeout; only a per-request 120s
http.Client.Timeoutinvoyage_client.go. If requests return slowly but successfully, the pass can run for an unbounded time on a large batch queue with no user-visible progress.
Harness workaround: default VOYAGE_API_KEY="" during index_repository calls (Harness.with_embeddings=False by default). Semantic-search index is not populated; everything else indexes fine.
Opt-in: --with-embeddings flag. Still subject to stall risk.
Proper fix (not in this PR): add a bounded context.WithTimeout around the embeddings pass in pass_embeddings.go; emit progress log per inner batch (currently only per outer batch of 128); expose a CODE_GRAPH_SKIP_EMBEDDINGS env var for unambiguous opt-out.
2. Binary version lag
The harness runs against whatever --binary path points to. If you pass ~/bin/code-graph.exe, it may be stale. Always build fresh from the current code-graph source before capturing a baseline (see Quick Start above). Stale binary produces false N/As for tools that exist in source (explain_symbol, get_review_context, etc.) and polluted comparisons.
3. Git worktree path mangling on Windows Git Bash
git worktree add ~/tmp/foo main produces a worktree at C:/c/Users/user/tmp/foo (the ~ expansion confuses git's path handling on Windows). Use explicit Windows-style absolute paths: git worktree add "$HOME/worktrees/code-graph-main" main (the $HOME env var expands correctly because it's shell-expanded before git sees it).
4. Fixture paths are portable
fixtures.json uses $HOME-prefixed paths ($HOME/Documents/GitHub/...). The harness expands both ~ and $VAR via os.path.expandvars(os.path.expanduser(...)) so the same fixture file works on any machine with the expected directory layout. Don't hard-code absolute paths.
5. Stale code-graph processes after a stall kill
If a stalled index is killed via taskkill /F, orphan processes can linger. Clean with:
MSYS_NO_PATHCONV=1 taskkill /F /IM code-graph.exe
Then delete_project to clear any 0-node stub left in the registry:
./bin/code-graph.exe cli --raw delete_project \
'{"project_name":"c-Users-user-Documents-GitHub-<repo>"}'
The harness itself has a stall watchdog (--index-timeout, default 600s) that surfaces a clean error instead of orphaning the subprocess.
Fixture repo SHAs
Captured in fixtures.json. A baseline run prints a WARN if any fixture's HEAD SHA has drifted from the pinned value, and a WARN if any fixture has uncommitted changes. Baselines from drifted/dirty repos are still written but flagged — do not use them as reference points for PR diffing.
Question ID coverage matrix
| ID | Tool | Baseline | PR 1 | PR 2 | PR 3 | PR 4 | PR 5 | PR 7 |
|---|---|---|---|---|---|---|---|---|
| Q1-Q12 | Core existing tools | ✅ | - | - | - | - | - | - |
| Q13 | query_graph w/ confidence filter |
- | ✅ | - | - | - | - | - |
| Q14 | get_architecture w/ communities+cohesion |
- | - | ✅ | - | - | - | - |
| Q15 | ARCHITECTURE_REPORT.md file check |
- | - | - | ✅ | - | - | - |
| Q16 | find_rationale |
- | - | - | - | ✅ | - | - |
| Q17 | diff_graph |
- | - | - | - | - | ✅ | - |
| Q18 | find_similar_functions |
- | - | - | - | - | - | ✅ |
| Q19 | explain_symbol w/ rationale |
- | - | - | - | ✅ | - | - |
| Q20 | get_review_context w/ diff |
- | - | - | - | - | ✅ | - |
Baseline findings (2026-04-22, freshly-built binary from main + feat/phase0)
Clean baselines captured:
mcp-servers@ 81fa7d5 (2,920 nodes / 7,318 edges): 15 PASS / 5 N/A (N/As are the 5 feature-PR probes)mcp-infra@ 8173017 (668 nodes / 1,096 edges): 15 PASS / 5 N/A
Pending baselines (needs index_repository first — harness was run with --skip-index):
rmf-corsair@ f545976: not yet indexedcode-graph@ c9b1195: on this feature branch (dirty) — baseline should be captured frommain
Findings that reshape the feature PR plan
-
PR 2 scope is substantially smaller than planned. Current
get_architecturealready returnsclusterswith per-clustercohesionscores. Sample from mcp-servers:{id:1, label:"msgraph_mcp", members:94, cohesion:0.96875}. 157 Community nodes exist in the graph. Remaining PR 2 work: verify the oversized-community split threshold (>25% of graph) actually fires. May drop from M effort to XS, or be removed entirely if split logic is already present. -
Tool surface has shifted since plan was written.
list_directoryremoved — replaced bysearch_graph(label="File")in Q12.- Schema key
edge_typesrenamed torelationship_typesinget_graph_schemaoutput. get_architectureoutput shape changed — no top-levelnode_labels; seeclusters,boundaries,hotspots,packageskeys instead.- Cypher parser rejects
<>(not-equal) — Q10 adjusted to not use it.
-
Q19 and Q20 already pass on current source.
explain_symbolandget_review_contextexist. PR 4 and PR 5's scope is additive enhancement to these existing tools (rationale extraction; graph-diff inclusion) rather than building from scratch.
Phase 0b — transcript replay corpus
Reads Claude Code session transcripts from ~/.claude/projects/ and
summarizes every session that invoked mcp__code-search__* or
mcp__code-graph__* tools. One JSONL record per qualifying session.
Usage
# default: last 14 days, write bench/transcripts_YYYY-MM-DD.jsonl, print stats
python bench/transcripts.py --stats
# dry-run — count without writing
python bench/transcripts.py --count-only
# custom window + output
python bench/transcripts.py --days 30 --output bench/scan.jsonl
# sample N random sessions (seed=42) for hand-labeling outcomes
python bench/transcripts.py --sample 30 --output bench/sample_hand-label.jsonl
Baseline captured 2026-04-22 (14-day window, 299 transcripts scanned)
| Metric | Value |
|---|---|
| Qualifying sessions (≥10 tool calls + uses code-search or code-graph) | 30 (25 main + 5 subagent) |
| MCP usage mix | 19 use both, 9 code-search only, 2 code-graph only |
| Median tool calls per session | 295 |
| Total tool calls in corpus | 12,171 |
| Pre-graph grep rate | 23/30 = 76.7% |
| Total tokens in corpus | 7.85 billion (input + output + cache) |
What pre_graph_grep measures
For each session we compute first_graph_call_index = the position of the
first mcp__code-search__* or mcp__code-graph__* tool call. If any
Glob/Grep/Read call precedes that index, the session counts as
"pre-graph grep" (the behavior PR 3's PreToolUse hook aims to reduce). On
the current main-branch baseline, 76.7% of sessions exhibit this.
A/B usage (post-PR-3)
After PR 3 ships, re-run transcripts.py over the next 14-day window and
compare pre_graph_grep rate. Target delta: ≥40 pp reduction (baseline
76.7% → target ≤ 37%). Stop-ship criterion for PR 3 is met only if the
delta is observable across a meaningfully-sized sample (≥30 sessions).
Measuring PR 3 (PreToolUse orientation hook) — A/B protocol
Script: bench/compare_pregrep.py — compares the pre_graph_grep rate
between a baseline corpus (pre-install) and a post-install corpus.
Protocol
- Capture a baseline (already done in PR #51 — 76.7% on 14-day
window, 30 qualifying sessions):
python bench/transcripts.py --output bench/transcripts_pre.jsonl - Install the hook in your live Claude Code environment:
code-graph install # writes ~/.claude/hooks/codebase-memory-orientation.sh and # registers a PreToolUse matcher Glob|Grep in ~/.claude/settings.json - Restart Claude Code.
- Use Claude Code normally for ≥14 days (comparable sample to baseline).
- Capture the post-install corpus:
python bench/transcripts.py --output bench/transcripts_post.jsonl - Run the comparison:
Or without a pre-corpus file, against the flat baseline rate:python bench/compare_pregrep.py \ --baseline bench/transcripts_pre.jsonl \ --post bench/transcripts_post.jsonlpython bench/compare_pregrep.py \ --post bench/transcripts_post.jsonl \ --baseline-rate 0.767
Stop-ship criteria (PR 3)
- Post
pre_graph_greprate ≤ 37% AND - Delta ≥ 40 percentage points AND
- Post corpus size ≥ 30 qualifying sessions
Script exits 0 on PASS, 1 on FAIL, and prints a per-session-length breakdown (short / medium / long) so you can see whether the hook helps long sessions more than short ones — an expected pattern, since long sessions have more Glob/Grep opportunities for the hook to fire on.
Follow-up PRs
- Phase 0c: PR ground-truth set (separate PR)
- CI integration: gate feature PRs on
compare.pyoutput (future)
Directories
¶
| Path | Synopsis |
|---|---|
|
research
|
|
|
eval_rank_localize
command
eval_rank_localize is a one-shot evaluation harness for rank_by_query (PageRank), code_localize (LocAgent BFS), and code_localize_agent (LLM-driven LocAgent loop).
|
eval_rank_localize is a one-shot evaluation harness for rank_by_query (PageRank), code_localize (LocAgent BFS), and code_localize_agent (LLM-driven LocAgent loop). |
|
indexing_throughput
command
indexing_throughput measures the wall time, peak memory, and per-phase breakdown of code-graph's pipeline.Run() against a target directory.
|
indexing_throughput measures the wall time, peak memory, and per-phase breakdown of code-graph's pipeline.Run() against a target directory. |
|
mcp_latency_baseline
command
mcp_latency_baseline measures per-tool invocation latency for the code-graph MCP tool surface.
|
mcp_latency_baseline measures per-tool invocation latency for the code-graph MCP tool surface. |