fix(explore): guarantee an agent-named symbol renders, wherever it sits (CG-38)

`codegraph_explore` never returned `queueMessage` (L1087) or
`flushQueuedMessages` (L1102) from a 1,414-line file, on a symbol bag or a
prose question, even with that file at rank #1 holding 67% of the envelope —
the agent got a same-stem `QueuedMessage` interface at L70 and had to Read the
file for the functions it had named. Pre-existing at every build including
pre-epic (controlled bisect, index held fixed).

Two independent causes:

1. `buildFlowFromNamedSymbols` returns the Flow prose AND the set of node ids
   the agent named — and the latter is the whole guarantee, since it injects a
   named def into its file's cluster ranges at importance 9. Its bail-outs
   returned EMPTY, zeroing the identity whenever there was nothing to PRINT.
   Two sibling closures that never call each other produce no chain, no synth
   hop and no boundary, so both defs lost importance 9 and the file rendered
   from its head. `identityOnly()` now separates the two, gated on
   shape-precise tokens so a prose word that exact-matches a callable cannot
   promote itself.

2. The ceiling trim filled in SOURCE order, so an over-ceiling render always
   dropped the END of a large file first. The shrink HAD kept both symbols
   (1022-1121); the trim cut back to 839. `windowToCeiling` now takes the
   spine call site plus every importance>=9 member as focus lines, tries the
   full ceiling first, and splits the held-back reserve evenly with
   carry-forward — greedy-in-source-order reproduced the bug one level down.

The shrink's loose size estimate is left alone deliberately, and the comment
now says why: making it exact was built and measured WORSE (it stops at the
last member that fits whole and the released bytes carry forward to
lower-ranked files, costing payroll-go's `s.store.Upsert`). `bound()` clamps to
the ceiling anyway, so the slack costs no bytes; it just must not pick the
survivors, which is what the trim now handles.

The measurement gap this closes: every existing probe is aggregate — envelope
share, per-file spend, source totals, file counts — and all are green on a
response that returns 25K from the right file and omits the named function.
`probe-named-symbol.mjs` checks the definition LINE against the response's
rendered lines, per symbol.

Suite envelope byte-identical to main on all six repos; probe-allocation 4/4,
no starvation flags; 180 files / 2,997 tests green. Fixture: 7/7 fail on main,
7/7 pass here, deterministic over 4 runs per arm.
This commit is contained in:
Colby McHenry
2026-08-06 21:11:51 -05:00
parent 969ea1ec37
commit 89c53ddf24
13 changed files with 4778 additions and 43 deletions
+17 -12
View File
@@ -52,13 +52,13 @@ change at all**.
Deterministic across the 6-repo suite: no repo truncates, none loses a file,
okhttp gains one, every repo lands at or under the 25,000 hard ceiling.
## Open
## Follow-up — CG-38 (closed)
**CG-38** — agent-named symbols in the tail of a large file never render. On the
**Agent-named symbols in the tail of a large file never rendered.** On the
motivating repo, `queueMessage` (line 1087) and `flushQueuedMessages` (1102) in a
1,414-line file are absent from the response on both prose and symbol-bag
queries, even when that file wins rank #1 with 67% of the envelope. The response
returns the `QueuedMessage` *interface* at line 70 — a fuzzy near-match on the
1,414-line file were absent from the response on both prose and symbol-bag
queries, even when that file won rank #1 with 67% of the envelope. The response
returned the `QueuedMessage` *interface* at line 70 — a fuzzy near-match on the
query token — instead of the function.
**Pre-existing, not caused by this epic.** A controlled bisect (index held fixed,
@@ -67,15 +67,20 @@ lines here and CG-36 rendering 463; the symbols render at neither. The epic
strictly improves the case. An earlier claim that the epic regressed it was
wrong — it compared runs across two different indexes.
Sharpest lead: an earlier index of the same repo with the `.d.ts` **not** flagged
generated rendered 581 lines including both symbols on the pre-epic engine, where
the current flagged index renders 12. A penalty on one file should not shrink an
unrelated top-ranked file's render; `rankPenalty` scales `fileGraphScore`, which
moves the relevance gate and reshuffles the admitted set.
Two independent causes, both longstanding: `buildFlowFromNamedSymbols` discarded
the named-symbol IDENTITY along with the narrative whenever the named symbols did
not form a call chain, so the importance-9 injection never ran; and the ceiling
trim cut in SOURCE order, so a named def at the end of a large file was always the
first thing dropped. Full account, plus the ranker-penalty lead (real, and
orthogonal — the defs are absent at both `generated` flag states on the old build
and present at both on the new one): `explore-tail-render-cg38.md`.
This epic's probes measure envelope share, starvation, source totals and file
counts. **None measures "did the agent-named symbol render"** — which is why this
survived the whole epic. CG-38 requires the fixture that closes that gap.
counts. **None measured "did the agent-named symbol render"** — which is why this
survived the whole epic. `scripts/agent-eval/probe-named-symbol.mjs`,
`__tests__/fixtures/tail-render-ts` and
`__tests__/explore-named-symbol-render.test.ts` close that gap: per-symbol and
binary, checking the definition LINE against the response's rendered lines.
CG-36's own measurement is worth carrying forward, because the issue named the wrong
fix point: both real cases (`query.py`, `RealInterceptorChain.kt`) lost on