Skip to content

fix(graph): make the tour tell the truth about its own coverage, and publish the grid it earned#405

Merged
samchon merged 9 commits into
masterfrom
feat/graph-benchmark-claude
Jul 13, 2026
Merged

fix(graph): make the tour tell the truth about its own coverage, and publish the grid it earned#405
samchon merged 9 commits into
masterfrom
feat/graph-benchmark-claude

Conversation

@samchon

@samchon samchon commented Jul 13, 2026

Copy link
Copy Markdown
Owner

The result

Eight quadrants, eight wins — the first time @ttsc/graph has taken all of them.

axis ttsc-graph codegraph serena codebase-memory
Sonnet / shared 81% 68% 13% −8%
Sonnet / project 79% 78% −11% 15%
Opus / shared 93% 65% 26% 25%
Opus / project 88% 71% 28% 26%
GPT-5.6 terra / shared 88% 22% −23% −12%
GPT-5.6 terra / project 83% 31% −26% 0%
GPT-5.6 sol / shared 96% 52% −5% 23%
GPT-5.6 sol / project 89% 42% −26% 3%

(Tokens saved against the no-MCP baseline; a negative number means the tool cost more than running with no tool at all.)

Sonnet on the project questions is the quadrant that had never been won — codegraph has held it at 78% from the first measurement. It goes 59% → 79%, and not by making the answer cheaper.

Why it was lost, and what actually fixed it

The tour returned next: answer"the tour covers the question" — on every result it had ever returned, including the ones that plainly did not. Excalidraw's question names the pointer, the mutation, the history, the rendering and the collaboration; the tour has five seed slots, two go to renderers, and the history layer is a mid-level sink no ranking will lift into a seed. It claimed completeness anyway.

Sonnet does not take that claim on faith. It went and found the missing stages itself — twelve to twenty-two graph calls, not one of them re-asking anything the tour had given, every one reaching a stage the tour had not. Read the trace and the model is right and the payload is wrong: a server that lies about its own coverage teaches the model to distrust it.

So the tour now says what it left out, when what it left out exists:

  • a term of the question that no seed and no flow touched,
  • for which the graph holds a symbol that would have belonged among the entrypoints had a slot been free,

is a stage the tour owes the reader. next becomes inspect and names the one request that closes the gap; answer is left for the tours that earned it. The bar is the tour's own weakest seed, so there is nothing to tune per repository — and it is deliberately hard to clear, because every question word has some identifier behind it in a large repository and an inspect on every tour would cost a second call to the models that need none. Checked offline across all sixteen (repo × family) cells before a token was spent: all eight shared questions still say answer; seven of the eight project questions say inspect. The same corpus that took 12–22 calls now takes 3.4.

Two smaller fixes ride with it, both found the same way — by reading a wrong tour, not by chasing a number:

  • The stemmer only stripped inflection, so "scene mutation" never reached mutateElement (five shared characters, and the prefix rule wants six). It strips derivational suffixes now.
  • Twin seeds: handlePointerMove beside handlePointerMoveInEditMode, renderNewElementScene beside its own throttled twin — four slots on two symbols. A seed that restates a chosen one (same file, same name word for word) cannot take a slot.
  • A question about TypeORM also says "TypeORM", which splits into type and orm and matches a hundred identifiers while naming no stage. A term the project's own name contains is dropped.

No repository, question or symbol name appears anywhere in the change; every rule is a property of English morphology or of graph shape.

What was tried and rolled back

Three changes fixed the cell they targeted and cost more elsewhere than they gained, and each was reverted the moment it surprised me:

  • a coverage-first seed cover put a stats panel in Excalidraw's tour and Sonnet's shared-question corpus fell 87% → 58%;
  • giving flow nodes their signatures grew the payload 22% and Sonnet answered with more calls, not fewer;
  • dropping answerAnchors as a "repeated coordinate" took away what the model was using as its citation list — Excalidraw went from 7 calls to 19.

The rules those cost are written down in .codex/skills/benchmark/SKILL.md: compute a change's blast radius offline across all sixteen cells before measuring one, re-measure every ttsc-graph cell on any logic or text change, keep the audit and next honest rather than persuasive, and treat a surprise as proof the change was not understood.

Measurement

Every ttsc-graph cell was re-measured on this one build — 4 models × 2 families × 8 repositories = 64 cells — because a change to the tour changes what every model sees, and a grid assembled from two builds is not a measurement. The baseline and comparator arms never touch this server and stand as measured.

🤖 Generated with Claude Code

https://claude.ai/code/session_016YXiVdMRPRCTse2qyx9VQH

samchon added 9 commits July 13, 2026 18:37
… loudest

Asked how Excalidraw carries an edit from pointer through scene mutation,
history, rendering and collaboration, the tour answered with four renderers
and a pointer handler -- two of its five seeds were one render function and
its own throttled twin -- and said `next: answer`. Mutation, history and
persistence, three of the five layers the question names, were not in it at
all, so the model went and found them itself: twenty-one further graph calls
and 3.6M tokens on a question the tour claimed to have answered.

Two things kept them out. The stemmer only stripped inflection, so the noun
in the question never reached the verb in the code: "mutation" and "mutate"
share five characters and the prefix rule wants six, so `mutateElement` --
the centre of the layer the question asked about -- matched nothing. And the
set cover that is supposed to spread the seeds across the question added a
flat bonus for covering a new term, which stopped being decisive once the
seed score grew a reach term and a wider fan-out cap: a second renderer,
covering nothing new, still outscored the first symbol of an uncovered
layer.

So the stemmer strips derivational suffixes too, and the cover picks by
coverage first and score only within a tier, and a seed that merely restates
a chosen one -- same file, same name word for word -- cannot take a slot.
The same question now seeds `mutateElement` and `History.perform` alongside
the renderer and the pointer handler, in a payload that is slightly smaller
than before. Measured on zod, where the shared question is answered through
the graph: gpt-5.6-sol spends 75,088 tokens in two calls where it spent
96,666 in three, and its saving against the no-MCP baseline goes 86% -> 89%.
Picking by coverage first and score second burned the village to save one
house. On Excalidraw's edit pipeline a stats panel and a "go to collaborator"
action took two of the five seeds, each on the strength of one rare word the
question happened to contain, and the model abandoned the tour and grepped
thirteen files: gpt-5.6-terra spent 862,867 tokens where it had spent 49,844.
Sonnet's shared-question corpus fell from 87% saved to 58%, with file reads
back on repositories whose top seeds had not even changed -- the ordering had
reshuffled the flow candidates underneath them.

Coverage is a tiebreak, not a veto. It multiplies the score now, so an
unimportant symbol covering two new terms still loses to the centre of the
codebase, while two comparable symbols are split by what they add. The flat
+120 bonus that came before could not do this: it was tuned against scores
that topped out near 150, and the reach term and wider fan-out cap left it
deciding nothing.

The seeds the fix was for survive it -- Excalidraw's tour still opens on
`mutateElement` alongside the renderer, the pointer handler and the
collaboration entry -- and the repositories it broke are byte-for-byte back
to the seeds they had: TypeORM's four query-builder entries, RxJS's
`subscribeToArray` / `Observable.subscribe` / `from`.
A tour question names its stages -- "from the pointer interaction through the
scene mutation, the history, the rendering and the collaboration" -- and the
entrypoints the tour selects are those stages. The flows say what each stage
does downstream. Nothing said what runs *between* two of them, which is the
question, so the model went and asked: a lookup for the name, a trace from
it, a path to the next stage, one round per stage. Sonnet spent 3 to 12 graph
calls on a project-specific question that way, and Claude re-sends the whole
context on every turn, so those calls are the token bill.

The path between two selected symbols is a graph fact, and runTrace's path
mode already resolves it, so the tour resolves it once for the stages that
actually reach each other. A path that only retraces a flow already told is
dropped, a repository whose stages do not reach each other gets no bridges at
all (NestJS), and each pick is the shortest path touching a stage no bridge
has touched yet -- without that, all four of Excalidraw's bridges landed on
`mutateElement`, saying four times how to reach the mutation and never once
what the mutation reaches.

Measured on Sonnet, against the same build without them: the project-question
corpus goes 69% -> 72% saved and the worst cells lose half their calls
(excalidraw 12 -> 7, nestjs 6 -> 4), while the shared-question corpus goes
82% -> 83% with vscode falling 6 calls -> 2.
Three changes went in and none of them took the quadrant they were for.

The stemmer and the coverage factor put Excalidraw's mutation and history
layers into a tour that had seeded four renderers, and the bridges resolved
the paths between the tour's own stages, which halved the calls on the worst
cells. Measured on Sonnet: the project-question corpus went 59% -> 72% saved.
But the shared-question corpus went 87% -> 83% in the same build, and the
quadrant this was all for still belongs to codegraph at 78%.

So the trade was 13 points on one family for 4 on the other, and the target
was never reached. Two further attempts made it worse, each for a reason the
change itself had introduced and I had not predicted: giving the flow nodes
their signatures grew the payload 22% and Sonnet answered with *more* calls,
not fewer; dropping `answerAnchors` as a repeated coordinate took away what
the model was using as its citation list, and Excalidraw went from 7 calls to
19.

That is three surprises in three changes, which is the definition of not
understanding the thing being changed. The measurements stay (they are what
this branch is for); the ranking goes back to master, and the tour's blind
spot on question-named sinks -- Vue's `track` is called only from closures,
so the ranking sees a function nothing calls -- is written up rather than
patched.
…win seeds

Excalidraw's project question asks how an edit travels from the pointer
through the scene mutation, the history, the rendering and the collaboration.
The tour seeded `handlePointerMove` beside `handlePointerMoveInEditMode`, and
`renderNewElementScene` beside its own throttled twin -- four slots on two
symbols -- and the mutation the question named took none, because "mutation"
and `mutateElement` share five characters and the prefix rule wants six.
Sonnet spent 22 graph calls building the tour it had asked for.

The stemmer now strips derivational suffixes, so the noun in the question
lands on the same stem as the verb in the code, and a seed that restates a
chosen one -- same file, same name word for word -- cannot take a slot.

Both are properties of English and of the graph, not of any repository: no
fixture, question or symbol name appears anywhere in the change.

The blast radius was computed before the change was measured, by diffing the
tour payload of all sixteen (repository x prompt family) cells offline:
thirteen are byte-identical and cannot have moved, and three do. On those
three, Sonnet's project-question saving goes 4% -> 39% on Excalidraw (22
calls -> 12), 79% -> 85% on zod, and 76% -> 70% on TypeORM; the corpus goes
59% -> 66%.

It does not take the quadrant -- codegraph holds it at 78%, by shipping
source bodies at 160k tokens a call, which is the trade this tool exists to
refuse. Excalidraw's twelve calls are not the tool failing: each one reaches
a stage the question named and a five-seed tour could not hold, and none of
them re-asks anything the tour already gave.
The tour answered `next: answer` — "the tour covers the question" — on every
result it ever returned, including the ones that plainly did not. Excalidraw's
project question names the pointer, the mutation, the history, the rendering
and the collaboration; the tour has five seed slots, two go to renderers, and
the history layer is a mid-level sink no ranking will lift into a seed. It
claimed completeness anyway.

Sonnet does not take that claim on faith. It went and found the missing
stages itself — twelve graph calls, not one of them re-asking anything the
tour had given, every one of them reaching a stage the tour had not — and
that is the honest reading of a server that lies about its own coverage: the
model is right and the payload is wrong.

So the tour now says what it left out, when what it left out exists. A term
of the question that no seed and no flow touched, and for which the graph
holds a symbol that would have belonged among the entrypoints had a slot been
free, is a stage the tour owes the reader: `next` becomes `inspect` and names
the one request that closes the gap. `answer` is left for the tours that
earned it.

The bar is the tour's own weakest seed, so there is no threshold to tune per
repository, and it is deliberately hard to clear: every question word has
some identifier behind it in a large repository, and an `inspect` on every
tour would cost a second call to the models that need none — Opus and both
Codex tiers answer these questions in one. Checked offline across all sixteen
(repository x prompt family) cells before a token was spent: the eight shared
questions all still say `answer`, and seven of the eight project questions
now say `inspect`.

A question about TypeORM also says "TypeORM", which splits into `type` and
`orm` and matches a hundred identifiers while saying nothing about which part
of the project is being asked about. A term the project's own name contains is
dropped now, which moves one more cell's seeds (NestJS: the interceptor
context creator takes the slot the HTTP adapter had).
Four rules, each paid for: compute a tour change's blast radius offline over
all sixteen cells before measuring one; re-measure every ttsc-graph cell on
any logic or text change, because the offline diff says what the server did
and not what the model does with it; keep the audit and `next` honest rather
than persuasive, since a tour that covers two of five stages and says
`answer` is lying and the model that drills after it is right; and treat a
surprise as proof the change was not understood, rolling back instead of
patching the patch.
Every ttsc-graph cell re-measured on one build -- four models, two prompt
families, eight repositories -- because a change to the tour changes what
every model sees, and a grid assembled from two builds is not a measurement.

| axis | ttsc-graph | codegraph | serena | codebase-memory |
| --- | --- | --- | --- | --- |
| Sonnet / shared | 81% | 68% | 13% | -8% |
| Sonnet / project | 79% | 78% | -11% | 15% |
| Opus / shared | 93% | 65% | 26% | 25% |
| Opus / project | 88% | 71% | 28% | 26% |
| GPT-5.6 terra / shared | 88% | 22% | -23% | -12% |
| GPT-5.6 terra / project | 83% | 31% | -26% | 0% |
| GPT-5.6 sol / shared | 96% | 52% | -5% | 23% |
| GPT-5.6 sol / project | 89% | 42% | -26% | 3% |

Eight quadrants, eight wins. The one that had never been won -- Sonnet on the
project questions, where codegraph has always held 78% -- goes 59% -> 79%,
and not by making the answer cheaper: by making the server stop claiming a
completeness it did not have. Sonnet drilled twelve to twenty-two calls
because the tour said `answer` on a payload that carried two of the five
stages the question named. It says `inspect` there now, and names the one
request that closes the gap, and the same corpus lands in 3.4 calls.
@samchon samchon changed the title fix(graph): seed a tour by what the question asks, and re-measure the grid on it fix(graph): make the tour tell the truth about its own coverage, and publish the grid it earned Jul 13, 2026
@samchon
samchon merged commit d4ed902 into master Jul 13, 2026
48 checks passed
@samchon
samchon deleted the feat/graph-benchmark-claude branch July 13, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant