Conversation
🌱 graft blast radiusNothing outside this diff depends on it. 1 area changed; no indexed dependents at depth 2. Who knows this code — 2 people across 1 area
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no Test signal per changed area — 1 ✗Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
Open the interactive graph → — click an area to see its dependent symbols at file:line. |
Frankie-Xu
left a comment
There was a problem hiding this comment.
Reviewing against #332 (parse-side peel, Fixes #327) and the closed #270, not as a third peel.
Prompt reorder. Putting id= last so a verbatim echo is unambiguous is a real improvement for new logs. It will conflict with #274's userContent (still - id=<id> | kind | lines …) and does not by itself fix the captured #259 / #327 samples, which used the old layout.
normalizeId vs #332. Keep the #332 peel as the compatibility layer. Two reasons:
- On an old-layout echo
id=a.ts | file | lines L1-L5,lastIndexOf(id=)is 0, so this returnsa.ts | file | lines L1-L5instead ofa.ts. Theprevious layouttest uses a line without theid=prefix (tts/jobs.cjs | file | …), so it does not cover the #327 Grok sample. #332 cuts at the first|before strippingid=, which does. - A second
parseResultsrewriter stacked on #332 / #274 is three-way conflict for little extra coverage once the cut-at-|peel exists.
The prompt tweak is additive if it lands after a peel, not a replacement. I would not have this close #259 / #327.
Not requesting a competing peel from here.
What happens
Against a local model (qwen3.8:27b on Ollama, OpenAI-compatible endpoint), the
meaning pass reported four of sixteen files as total misses:
The model was neither truncated nor confused: for
tts/jobs.cjsit answeredwith
finish_reason: tool_callsafter 113 completion tokens, with a perfectlyusable summary. The reply was discarded anyway.
Why
userContent()insrc/ai/crux.tsbuilds each target line as:The
id=field has no terminator — visually the id runs to the end of the line.The system prompt asks for that id verbatim:
So the model returned the whole line as the id. Captured at the wire
(requested id → returned id):
enrich.tsthen looks upresults.get(node.id), misses every node, andappliedstays 0, which is reported as a total miss:The
finish_reason=nullin the message is the tell:classifyCruxMissreturnednull(at least one non-empty summary was parsed), solastMisswas unset. Themodel did its job; only the key did not match.
Fix
Both ends, so neither depends on the other:
id=is the LAST field on a target line. "Everything afterid=" is thenexactly the id, with no quoting rule for the model to get wrong:
normalizeId()repairs a whole-line echo on the way in, for the newlayout (
… | id=<id>) and the previous one (<id> | kind | …). Ids areminted as
`${ctx.rel}#${scope.join(".")}`ingraph/extract.ts, soneither
|norid=can occur inside a real id and both cuts are safe.A model that already returned bare ids is unaffected:
normalizeIdreturns sucha string unchanged.
Scope
src/ai/crux.tsonly. No behaviour change for a model that answers correctlytoday; no new option, no config.
Test
test/crux-id-normalization.test.tscovers: the target line ends with the id;a bare id passes through; a whole-line echo of either layout is repaired; and a
repaired reply is not classified as a miss.
Notes
Observed with qwen3.8:27b via Ollama, but nothing about it is provider-specific
— it is an ambiguous prompt format, and any model that follows "verbatim"
literally can hit it. Cost here was the whole meaning tier for the four affected
files.