fix(agent): recover the answer behind a bare "above" pointer #28
+7
-1
@@ -3,6 +3,7 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
||||||
)
|
)
|
||||||
@@ -190,7 +191,12 @@ func bareAbovePointer(t string) bool {
|
|||||||
}
|
}
|
||||||
start := 0
|
start := 0
|
||||||
|
|
|||||||
if k := strings.LastIndexAny(t[:loc[0]], clauseBoundaryChars); k >= 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]:])
|
rest := strings.TrimSpace(t[:start]) + " " + strings.TrimSpace(t[loc[1]:])
|
||||||
return bareRemainderRe.MatchString(strings.Trim(rest, pointerResidueCutset))
|
return bareRemainderRe.MatchString(strings.Trim(rest, pointerResidueCutset))
|
||||||
|
|||||||
@@ -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) {
|
func TestIsCitationsOnly(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Reference in New Issue
Block a user
🟠 em dash in clauseBoundaryChars makes start=k+1 land mid-rune, so a bare "Done — … above." closer defeats bareRemainderRe and is not recovered
correctness, error-handling · flagged by 1 model
agent/finalize.go:192— em dash inclauseBoundaryCharsmakesstart = k+1land mid-rune, so a short bare "Done — … above." closer is not recovered.🪰 Gadfly · advisory