From 285789c89b823e8af6e0081b1b21cf702bd6dcff Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Sat, 22 Aug 2026 00:32:45 -0400 Subject: [PATCH] test(agent): enumerate the orientations instead of patching them one by one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- agent/finalize.go | 8 +++++- agent/finalize_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/agent/finalize.go b/agent/finalize.go index 1b5fd50..1d89bba 100644 --- a/agent/finalize.go +++ b/agent/finalize.go @@ -3,6 +3,7 @@ package agent import ( "regexp" "strings" + "unicode/utf8" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" ) @@ -190,7 +191,12 @@ func bareAbovePointer(t string) bool { } start := 0 if k := strings.LastIndexAny(t[:loc[0]], clauseBoundaryChars); k >= 0 { - start = k + 1 + // k is the BYTE index of the boundary rune's first byte, and one of + // those runes is a 3-byte em dash — k+1 would slice into the middle of + // it and leave a stray continuation byte in the remainder, which then + // never trims away and makes a genuinely bare pointer look occupied. + _, w := utf8.DecodeRuneInString(t[k:]) + start = k + w } rest := strings.TrimSpace(t[:start]) + " " + strings.TrimSpace(t[loc[1]:]) return bareRemainderRe.MatchString(strings.Trim(rest, pointerResidueCutset)) diff --git a/agent/finalize_test.go b/agent/finalize_test.go index 821a3b7..7ba434d 100644 --- a/agent/finalize_test.go +++ b/agent/finalize_test.go @@ -146,6 +146,71 @@ func TestPointsAbove(t *testing.T) { } } +// TestBareAbovePointerOrientations is the anti-drift harness for the one rule +// that kept coming back: a deictic pointer only makes a terminal disposable +// when the pointer is ALL there is. +// +// Three consecutive review rounds found that 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 orientation that +// round was about, and each fix was tested only in the direction I had just +// thought of. Enumerating the placements is the fix a fourth patch would not +// have been: every pointer form is now checked against every position content +// can occupy, so a new pointer form or a new placement covers the whole grid +// rather than one cell of it. +// +// The rule under test is a single line: bare IFF there is no content. +func TestBareAbovePointerOrientations(t *testing.T) { + // Each pointer form, written so it reads naturally both as a whole + // sentence and as a trailing clause. + pointers := map[string]struct{ sentence, clause string }{ + "demonstrative": {"That's the chain above.", "as shown above"}, + "imperative": {"See the note above.", "per the note above"}, + "copular": {"The breakdown is above.", "which is above"}, + } + // Where the answer can sit relative to the pointer. "" = nowhere: the + // pointer is alone, which is the only bare case. + const answer = "Ship Tuesday" + placements := map[string]func(p struct{ sentence, clause string }) string{ + "alone": func(p struct{ sentence, clause string }) string { return p.sentence }, + "alone as a clause": func(p struct{ sentence, clause string }) string { return p.clause }, + "before, same sentence": func(p struct{ sentence, clause string }) string { return answer + ", " + p.clause + "." }, + "before, own sentence": func(p struct{ sentence, clause string }) string { return answer + ". " + p.sentence }, + "before, own line": func(p struct{ sentence, clause string }) string { return answer + "\n" + p.sentence }, + "before, list item": func(p struct{ sentence, clause string }) string { return "- " + answer + "\n- " + p.sentence }, + "after, same sentence": func(p struct{ sentence, clause string }) string { return p.clause + ", " + answer + "." }, + "after, own sentence": func(p struct{ sentence, clause string }) string { return p.sentence + " " + answer + "." }, + "after, own line": func(p struct{ sentence, clause string }) string { return p.sentence + "\n" + answer + "." }, + "after a filler opener": func(p struct{ sentence, clause string }) string { return "OK. " + answer + ", " + p.clause + "." }, + "filler then pointer": func(p struct{ sentence, clause string }) string { return "OK. " + p.sentence }, + "done-dash then pointer": func(p struct{ sentence, clause string }) string { + return "Done — " + p.clause + "." + }, + } + // The only terminals with no answer in them. + bare := map[string]bool{ + "alone": true, + "alone as a clause": true, + "filler then pointer": true, + "done-dash then pointer": true, + } + + for pname, p := range pointers { + for placement, build := range placements { + t.Run(pname+"/"+placement, func(t *testing.T) { + in := build(p) + if !pointsAbove(in) { + t.Fatalf("setup is not a pointer at all, so the case proves nothing: %q", in) + } + want := bare[placement] + if got := bareAbovePointer(in); got != want { + t.Errorf("bareAbovePointer(%q) = %v, want %v", in, got, want) + } + }) + } + } +} + func TestIsCitationsOnly(t *testing.T) { cases := []struct { name string