fix(agent): recover the answer behind a bare "above" pointer #28
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7fd239c8cd |
fix(agent): "In short supply" is not a short version
Round 5. Two of the eight findings are the em-dash mid-rune slice, which
the orientation matrix had already caught and fixed in
|
||
|
|
bb2196ecb2 |
test(agent): the orientation matrix iterated maps, so its 36 cells shuffled
Caught on the sibling mort PR (gadfly/sonnet) and the same mistake was
here: map iteration is randomised, so the matrix printed its cells in a
different order every run and two failing runs could not be diffed. The
"which placements are bare" lookup was a second map keyed by name, which
also let a placement and its expectation drift apart by a typo.
Both are now one slice of {name, bare, build}, so the expectation sits
on the row it belongs to and the order is fixed.
|
||
|
|
285789c89b |
test(agent): enumerate the orientations instead of patching them one by one
Three consecutive rounds found the same rule broken in a different orientation — content after the pointer, then content before it in the same sentence — because each round's cases only covered the direction that round was about, and each fix was then tested only in the direction I had just thought of. A fourth patch was not the answer; the missing thing was a harness. TestBareAbovePointerOrientations crosses every pointer form with every position content can occupy: alone, before in the same sentence, before in its own sentence, on its own line, as a list item, after in each of those, and behind a filler opener. 36 cells, one assertion — bare IFF there is no content — and each cell first asserts the input really is a pointer, so a mistyped fixture fails loudly instead of passing vacuously. It earned itself immediately: three cells failed on the first run, and the bug was mine and shipped. clauseBoundaryChars gained the em dash last round, and the cut did start = k + 1 — but LastIndexAny returns the BYTE index of the boundary rune, and an em dash is three bytes. "Done — as shown above." sliced mid-rune, left a stray continuation byte in the remainder that no trim removes, and a genuinely bare pointer stopped being recovered. Now advances by the rune's width. Break-check: sixteen mutations, each killed by a named test, control survives. Two had to be reformulated after the harness started failing mutations that were only "killed" by breaking the build — including the one for this very fix. |
||
|
|
184627c570 |
fix(agent): gadfly round 4 — the answer can come BEFORE the pointer
My round-3 fix has the same defect it fixed, mirrored. bareAbovePointer
cut back to the previous FULL STOP, not the previous clause, so anything
sharing the reference's sentence ahead of it was swallowed:
"Ship Tuesday, as shown above." bare=true
"OK. The verdict is guilty, as detailed above." bare=true
"The answer is sixty minutes, as computed above." bare=true
All three read as pure pointers and were replaced by an earlier turn.
The comment called it a clause cut; the code cut a sentence. It survived
a round because every case I had written put the answer AFTER the
pointer — I fixed the direction I had thought of and tested only that.
clauseBoundaryChars now includes , ; : and the em dash.
Three more, all real:
- pointsAbove and bareAbovePointer each did their own FindStringIndex
plus offset check, so the two could disagree about what a pointer is.
One aboveRefLoc now answers that.
- fillerWords was extracted to stop drift while its separator class was
still copy-pasted beside it — the same defect one token over. fillerSep
shares it too.
- A missing blank comment line ran two paragraphs together.
The padded 96-byte "bare pointer" fixture is gone rather than bent a
third time. Its premise does not survive the rule: a genuinely bare
pointer is its own clause and nothing else, so it is SHORT, and the two
recovery bars cannot disagree about a short terminal. I had twice
reshaped that string to keep a case alive whose scenario the code no
longer admits. The modeBackRef path stays covered by the bare-pointer
cases in TestIsWeakFinal and the "(Already answered above.)" end-to-end.
Break-check: fifteen mutations including a regression to the sentence
cut, each killed by a named test; the harness now also fails a mutation
that only breaks the build.
|
||
|
|
1756910ef0 |
fix(agent): gadfly round 3 — the pointer must be all there is
Round 2 fixed this class on isSummaryCloser and left its twin standing
on isWeakFinal. Three of the four findings are that twin, and every one
of them reproduced:
"That's the chain above. Ship Tuesday." weak=true
"See the summary above. Option B wins." weak=true
"Anything above 100 boils. See the note above." weak=true
"Anything above, say, 40 degrees is a problem." weak=true
Each of those is 37-58 bytes with the answer sitting right next to the
pointer, and each was disposable — a >=200-byte earlier turn replaced it
and "Ship Tuesday" went in the bin. pointsAbove asks whether a pointer is
PRESENT; disposability needs it to be ALL THERE IS.
bareAbovePointer cuts the reference's own clause — from the end of the
previous sentence through the match — and requires what remains to be
filler: nothing, punctuation, or a throat-clearing "Done —". Cutting the
clause rather than testing position is what makes the other two shapes
safe for free: a mixed terminal keeps its first sentence, and a
comparative "above," with an interjection keeps the rest of its own.
The fourth finding (kimi) is the same idea one level up:
compressionMarkerRe matched anywhere, so "Given the analysis above, the
bottom line is that we need a different vendor" read as an announced
summary when it is a conclusion. The marker must now OPEN a sentence.
Both classes now enforce one rule from opposite ends: a terminal is
disposable only when it carries no answer of its own — proved in
isWeakFinal by nothing standing beside the pointer, and in
isSummaryCloser by the model declaring what stands beside it a
compression.
One of my own round-1b fixtures had to change: "…is above, so there is
no point repeating it" is a pure pointer to a human, but prose after the
reference is indistinguishable from an answer, so the rule correctly
stops treating it as disposable. Replaced with a genuinely bare 96-byte
pointer, still sized so the two recovery bars disagree about it.
Break-check: thirteen mutations, each killed by a named test, control
survives. M12 initially "passed" by failing to compile — reformulated so
it builds, and TestIsWeakFinal/prepositional-then-deictic-with-content
kills it properly.
|
||
|
|
3f1c016e74 |
fix(agent): gadfly round 2 — a pointer is not a compression
Four findings, all real.
**A deictic closer now needs a compression marker too** (opus,
correctness). The pointsAbove branch of isSummaryCloser required only a
back-reference, so a terminal like "Given the analysis above, I
recommend option B because X" — a pointer followed by a CONCLUSION the
earlier turn never contained — would be discarded in favour of that
turn, throwing away the answer. The ack shape does not have this problem
because "Citations are logged." carries nothing; a bare pointer does not
carry that guarantee.
Both halves are now required: the pointer says the full answer is
elsewhere, and compressionMarkerRe ("Short version:", "TL;DR", "In
short") is the model saying what sits beside it is a condensation rather
than new reasoning. #1611's closer has both. A marker without a pointer
stays out of scope for the reason already documented — a user who asked
for brevity gets exactly that shape. Unmatched closers keep today's
behaviour, so the narrowing fails closed.
**CRLF** (opus, error-handling): "above\r\n" was not clause-final, so a
CRLF transcript quietly lost every line-final deictic. CR joins LF in the
terminator set.
**A comment wrapped mid-phrase** so that "// -style" read as a list
marker (sonnet) — reflowed as part of rewriting that doc block.
**Process provenance in a test comment** (sonnet): "(gadfly, 3 models)"
is an execution log, not an invariant. The lesson survives, the
attribution does not.
Break-check is ten mutations now — dropping CR and dropping the
compression requirement are each killed by their own named cases — with
the control surviving.
|
||
|
|
7dccb233ad |
docs(agent): drop the dangling isBackRef reference
Round 2 (glm): the isSummaryCloser comment still explained itself in
terms of "the whole isBackRef class" — a symbol deleted in
|
||
|
|
9534e442fb |
test(agent): gadfly round 1b — pin the exact bound and the bar asymmetry
Sonnet's three findings, all on the tests rather than the rule: - The "at the bound" case sat at index 115 (and 101 after the first round), never at 120 itself, so <= vs < was never exercised. Both sides of the boundary are now pinned — index 120 accepted, 121 rejected — and an off-by-one mutation is killed by the new case. - A SHORT (<=120-byte) deictic closer takes the modeBackRef bar, not the summary closer's mandatory dwarf ratio, even though it can carry a scrap of answer content. That asymmetry is deliberate and pre-existing (a "see above" closer has always had it): inside 120 bytes there is no room for both a pointer and a real answer. Pinned with a case sized so the two bars actually DISAGREE — 3x98 = 294 > the 275-byte prior turn, so the summary bar would reject what the back-ref bar accepts. The first draft of this fixture was 83 bytes and passed under either bar, proving nothing. - The isBackRef doc finding was sonnet's fourth independent report of it; already fixed in the previous commit. Break-check now runs eight mutations, including the off-by-one and a swap of the classifier ordering; each is killed by a named test and the control survives. |
||
|
|
f97c2b78c2 |
fix(agent): gadfly round 1 — the hyphen made "above-board" a back-reference
Three of four reviewers independently found the same defect: `\b` holds between "above" and "-", so the literal hyphen in aboveRefRe's terminator class made every hyphenated compound clause-final. "above-average", "above-board", "above-ground" all read as deictic pointers, putting a legitimate short closer at risk of being discarded. My own test used the SPACE-separated "above average" — it tested the neighbour, not the named path, which is exactly why the class survived a round of review. Hyphen dropped from the class; the four compounds are now table cases. Two more findings, both real: - backRefHeadChars and weakFinalMaxChars were two unlinked 120 literals that the comment called "the same guard". Now defined by reference, with a test pinning the identity. - isBackRef claimed to be the shared extension point for both shapes but had a single caller — isSummaryCloser deliberately uses pointsAbove directly, because backRefRe's fixed phrases match ANYWHERE and are only safe under the 120-byte weak cap. Folding the two together for tidiness would widen the gate, not deduplicate it. Helper deleted, the doc moved onto pointsAbove where the real sharing is, and it now says why backRefRe is not shared. Also shared the front-loaded-analysis fixture between the table and the end-to-end test (kimi), and de-hollowed the offset-bound cases: they were sized as backRefHeadChars±n, so they moved with the constant they were meant to pin — a break-check that widened the bound to 100000 sailed through. Literal lengths now, plus an explicit identity assertion. Break-check: six mutations, each killed by a named test; control survives. |
||
|
|
bcba9667bd |
fix(agent): recover the answer behind a bare "above" pointer
mort issue #1611: run 8eea3e82 front-loaded a 2,245-char analysis into its cite-call turn and closed with 220 bytes — "Done — that's the full chain above. Short version: …". The user got the 220 bytes and a pointer at a chain that was never posted. finalOutput already had three shapes for this pathology, and the closer matched none of them: too long for the weak-final cap (220 > 120), no citations heading, and no "Citations are logged." ack to open the summary-closer class. So it was delivered verbatim. The three shapes were each a separate vocabulary of ack phrases, which is why a fourth phrasing walked straight through. Two of them are really one signal — the terminal DEFERS, telling us the answer is somewhere the user cannot see — differing only in whether the terminal also carries content of its own. That signal is now isBackRef, shared by both, so a new phrasing is added once and covered in the bare and the "+ compression" variant at the same time. Its open-ended half is aboveRefRe: a DEICTIC "above", separated from the preposition by what follows the word. The deictic use ends its clause ("that's the full chain above.", "as shown above,"); the preposition always continues into a noun phrase ("above 100°C", "above the fold", "above all, …"). pointsAbove additionally requires the reference in the terminal's first 120 bytes — with almost no text before it in THIS message, it cannot be pointing at the message's own content. isSummaryCloser now opens on either the citations ack or pointsAbove. Recovery is unchanged: the mandatory dwarf ratio and the user-message scan boundary still gate it, so a closer only loses to a prior turn in the same user turn that is clearly the fuller original. Break-checked: reverting either classifier, dropping the clause-final rule, or dropping the offset bound each kills a named test; the unmutated control survives. |