diff --git a/internal/agentctx/agentctx.go b/internal/agentctx/agentctx.go index 37a2506..f1555e2 100644 --- a/internal/agentctx/agentctx.go +++ b/internal/agentctx/agentctx.go @@ -132,6 +132,9 @@ func phaseRequestBrief(phase domain.WorkPhase) string { if schema := phaseSealSchema[phase]; schema != "" { fmt.Fprintf(&b, "\nIt must decode as this shape. Optional keys may be omitted, but no key may hold a different type:\n\n%s\n", schema) } + if rules := phaseSealRules[phase]; rules != "" { + fmt.Fprintf(&b, "\n%s\n", rules) + } } b.WriteString("\nAn accepted request ends this session and starts the next phase with your sealed result. Saying you are ready in the pane is not a request and nothing reads it.\n") // How to finish. review's only transition is backwards to implement, so a @@ -163,6 +166,13 @@ var phaseSealFile = map[domain.WorkPhase]string{ // agent. TestPhaseSealSchemasDecode keeps these honest: each one is decoded by // the same function the worker uses, so a struct change that is not mirrored // here fails the build rather than a live run. +// phaseSealRules states the constraints the shape alone cannot show. Run 9 +// was refused for writing "F1" as a finding id, a rule the decoder enforced +// and no brief had ever stated. +var phaseSealRules = map[domain.WorkPhase]string{ + domain.WorkPhaseResearch: "A finding id is lowercase letters, digits, dash or underscore, at most 64 characters, and unique. The plan cites these, so they are names rather than labels.", +} + var phaseSealSchema = map[domain.WorkPhase]string{ domain.WorkPhaseResearch: ` { "findings": [{"id": "", "claim": "", "evidence": "", "confidence": "fact|inference|assumption"}], diff --git a/internal/agentctx/agentctx_test.go b/internal/agentctx/agentctx_test.go index 3885a15..53579f4 100644 --- a/internal/agentctx/agentctx_test.go +++ b/internal/agentctx/agentctx_test.go @@ -696,3 +696,14 @@ func TestAllPhasesStaleIsNotReportedAsFinished(t *testing.T) { t.Errorf("a plan verified at HEAD was not reported as finished:\n%s", current) } } + +// The shape cannot show a format rule, and run 9 was refused for writing "F1" +// as a finding id against a constraint no brief had ever stated. +func TestResearchBriefStatesTheFindingIDRule(t *testing.T) { + brief := phaseRequestBrief(domain.WorkPhaseResearch) + for _, want := range []string{"lowercase letters", "at most 64 characters", "unique"} { + if !strings.Contains(brief, want) { + t.Errorf("research brief never states %q", want) + } + } +}