name: explore-per-symbol-sizing date: 2026-05-29 23:20 project: codegraph branch: main
Current state: DONE + shipped. PR #569 squash-merged to main (b026e64); local is on main, dist/ rebuilt, working tree clean. README benchmarks + averages + header, CHANGELOG, and docs/design/adaptive-explore-sizing.md all updated with the new full-7-repo sweep. The only loose end: two squash-merged feature branches still linger (feat/adaptive-explore-sizing from #564, feat/explore-per-symbol-sizing from #569) — local and remote — because squash-merges don't register as "merged" in git's ancestor sense.
Immediate next step: Delete those two merged branches (local + remote), or pick up one of the Open-threads frontiers (Gin's small WITH-cost bump, alamofire DataRequest residual, or stabilizing per-repo benchmark numbers with median-of-8).
Suggested next message: "Delete the merged branches feat/adaptive-explore-sizing and feat/explore-per-symbol-sizing — local and remote."
Make codegraph_explore's cost a clear win on every README benchmark repo, especially the two laggards the README showed thinnest (Django 9% cheaper, OkHttp 4%). The optimization target per CLAUDE.md is tool-calls/reads + latency (NOT raw cost) — but the user explicitly wanted the cost margins up too. Definition of done = both laggards clearly cheaper with ~0 reads, no regression elsewhere, README refreshed, shipped. Achieved.
src/mcp/tools.ts (handleExplore + buildFlowFromNamedSymbols): explore sizes output to the answer, not the file count. Builds on PR #564's gate (off-spine + polymorphic-sibling, with a named-callable spare + supertype-family override).tools.ts):
buildFlowFromNamedSymbols now returns uniqueNamedNodeIds (callables whose token had ≤3 defs). The whole-file spare uses it, so as_sql (110 defs) no longer keeps every Compiler/Expression variant full; getResponseWithInterceptorChain (1 def) still spares RealCall.prio() < 99 (on-spine=0, unique-named=1, fileDefinesSuper && named=2), signatures for the rest. Bounded: bodyCap = maxCharsPerFile*2, SIG_MAX = max(12, maxSymbolsInFileHeader*2). Header tag flips to · focused (…) when any body shown, else · skeleton (…).budget.excludeLowValueFiles gate on the isLowValue hard-exclude (was <500-file tiers only); guards (query-mentions-tests, ≥2 non-test remain) kept.rangeNodes even if the gather missed them; rank named ranges at importance 9 (above glue 6 / connected 3); fileBudget = min(maxCharsPerFile, maxOutputChars - totalChars - 200) in cluster selection so high-importance named clusters survive instead of being source-order-trimmed.f1b14f0) was the prior round: named-callable spare + supertype-family override (fixed the read-back regression where RealCall.kt / compiler.py were skeletonized then Read back).DataRequest residual is NOT cleanly closable. A "spare a file when the agent names its class" type-spare broke OkHttp (it spared all 5 interceptor classes → 0 skeletons). A named sibling class is structurally indistinguishable from "the one main type." Left as-is (alamofire is 28% cheaper; ~1 DataRequest read/run).probe-explore.mjs query forms a different spine than the agent's real query → it hid both the Django and the OkHttp read-backs. (Dead-end #6 in the design doc.)npm run build before probing/A/B — probes + the A/B MCP server load dist/, not src/. Corpus indexes (/tmp/codegraph-corpus/*) are valid without re-index since all changes are query-time.adaptive-sizing-skeletonizing.md handoff is gone from main's working dir — it was untracked, got swept into commit 3c38729 on feat/adaptive-explore-sizing, so it lives only on that branch now. Deleting that branch deletes it (it's obsolete — that work shipped).npm-shim test failures are pre-existing/network (lack --probe-net on the global binary) — not a regression; don't let them block.npm run build (must be green).node scripts/agent-eval/probe-explore.mjs /tmp/codegraph-corpus/<repo> "<symbol-bag query>" → inspect #### file — … · focused/skeleton headers + sizes. okhttp = 5 · skeleton; django compiler.py · focused with def execute_sql/def as_sql/def _fetch_all bodies present; excalidraw/tokio/vscode/gin = 0 skeleton/focused (inert).bash /tmp/ab-one.sh <repo> <runs> "<question>" → writes /tmp/ab-readme/<repo>/run<n>/. Aggregate one repo: node /tmp/one-agg.mjs <repo>. Full 7: RUNS=4 bash scripts/agent-eval/bench-readme.sh then node scripts/agent-eval/parse-bench-readme.mjs /tmp/ab-readme (averages) + node /tmp/full-agg.mjs (per-repo reads/grep/tools/cost/time).npx vitest run __tests__/adaptive-explore-sizing.test.ts → 8/8 (skeleton, named-callable spare=RealCall, supertype-family override→focused=codec.ts, uniqueness/shared-method, on-spine exemplar full, distinct step full, flag=0 disables).main, last commit b026e64 feat(mcp): per-symbol adaptive codegraph_explore sizing (#569)..claude/handoffs/ entry).feat/adaptive-explore-sizing (#564) + feat/explore-per-symbol-sizing (#569), both local + remote.feat/adaptive-explore-sizing, feat/explore-per-symbol-sizing.DataRequest ~1 read/run — only closable by distinguishing "main type" from "interchangeable sibling," which has no clean static signal (don't re-attempt the type-spare; it breaks okhttp).query.py/Session.swift-style files; the fileBudget fix helped Django but the merge/selection logic is still the lever for other repos.__tests__/adaptive-explore-sizing.test.ts (OkHttp-in-miniature fixture); validated via probe; 5 cases. (This was for PR #564's gate.)Outcome: Test landed; led into re-validating the README.
Claude: A/B sweeps via bench-readme.sh; diagnosed read-backs (RealCall via Lockable 9-impl mixin; compiler.py base+subclasses). Shipped PR #564 (named-callable spare + supertype-family override). Then A+B+C (uniqueness + per-symbol focused + test exclusion).
Outcome: Django/OkHttp flipped to wins; alamofire type-spare attempt broke okhttp → reverted.
Claude: Cluster-debug showed _fetch_all (L2237) never formed a cluster + got source-order-trimmed. Fixed via named-injection + importance 9 + fileBudget. A/B: Django reads tail 3→1, 11%→17%; controls (okhttp 14%, excalidraw 31% / 0 reads) held. Committed e71eb90.
Outcome: Django residual closed.
Claude: Ran final full-7 sweep (bench-readme.sh, 56/56). Clarified a confusing "WITHOUT got cheaper" phrasing (WITH cg is cheaper in all 7; the % is the gap, which shrinks when the native baseline is cheap that batch). User said publish this batch.
Outcome: Updated README (headline 25%/62%, average line, 7 summary rows, 7 detail tables, methodology date) + CHANGELOG + design doc. Built clean branch off origin/main (dropping the already-squashed commits + the handoff artifact), pushed, opened PR #569, squash-merged → b026e64. Synced local to main, rebuilt dist. Offered branch cleanup → user ran /handoff save.