From 015764ec7bf54562891baf93c309caa8c534fbcf Mon Sep 17 00:00:00 2001 From: kami Date: Fri, 28 Aug 2026 16:08:10 +0400 Subject: [PATCH] State the handoff length limit on every field it bounds The prompt gave "at most 200 characters" for NEXT, WHY and REMAINING. The validator applies it to OPEN Q and LEARNED as well, and run 10 lost a lease to a 219-character OPEN Q against a limit nobody had stated for that field. Third instance of one shape: a constraint the code enforces and no brief mentions. The other two were the research finding id and the dead-end separator. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1 --- internal/herdr/adapter.go | 4 ++-- internal/herdr/adapter_test.go | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/herdr/adapter.go b/internal/herdr/adapter.go index 7aefe0a..b09c9d8 100644 --- a/internal/herdr/adapter.go +++ b/internal/herdr/adapter.go @@ -199,8 +199,8 @@ NEXT: the single next action (one line, at most 200 characters). WHY: why that is next (one line, at most 200 characters). REMAINING: outstanding items, one line each, at most 200 characters each. If none: NONE. DEAD ENDS: approaches tried that failed — "X → why it failed", one per line. Either arrow, and the words "tried"/"failed because" are optional. If none: NONE. -OPEN Q: unresolved decisions, one line each. If none: NONE. -LEARNED: constraints discovered that are NOT in TASK.md, one line each. If none: NONE. +OPEN Q: unresolved decisions, one line each, at most 200 characters each. If none: NONE. +LEARNED: constraints discovered that are NOT in TASK.md, one line each, at most 200 characters each. If none: NONE. Do NOT include: what you completed (the diff shows it), the goal or done-criteria (TASK.md holds them), git SHAs/branches/paths, or a prose summary. No headings and no report. Do not edit TASK.md.` diff --git a/internal/herdr/adapter_test.go b/internal/herdr/adapter_test.go index 5796ad3..0748c43 100644 --- a/internal/herdr/adapter_test.go +++ b/internal/herdr/adapter_test.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" ) @@ -408,3 +409,22 @@ LEARNED: e t.Error("a dead end with no separator was accepted") } } + +// Every field the validator bounds must say so. Run 10 lost a lease to a +// 219-character OPEN Q against a limit the prompt stated for NEXT, WHY and +// REMAINING only. +func TestHandoffPromptStatesTheLimitOnEveryBoundedField(t *testing.T) { + for _, field := range []string{"NEXT", "WHY", "REMAINING", "OPEN Q", "LEARNED"} { + i := strings.Index(handoffPrompt, field+":") + if i < 0 { + t.Fatalf("the prompt never names %s", field) + } + line := handoffPrompt[i:] + if j := strings.Index(line, "\n"); j >= 0 { + line = line[:j] + } + if !strings.Contains(line, "200 characters") { + t.Errorf("%s is bounded at 200 but the prompt never says so: %q", field, line) + } + } +}