diff --git a/internal/context/prompt_test.go b/internal/context/prompt_test.go index b442d67e..7356cbc7 100644 --- a/internal/context/prompt_test.go +++ b/internal/context/prompt_test.go @@ -125,6 +125,12 @@ func TestDefaultToolUsagePromptIncludesPermissionAndAntiLoopGuidance(t *testing. if !strings.Contains(toolUsage, "`todo_write`") { t.Fatalf("expected Tool Usage to mention todo_write for task state, got %q", toolUsage) } + if !strings.Contains(toolUsage, "If the user clearly switches to a different task") { + t.Fatalf("expected Tool Usage to describe task-switch todo handling, got %q", toolUsage) + } + if !strings.Contains(toolUsage, "mark it `canceled` before planning or executing the new task") { + t.Fatalf("expected Tool Usage to require canceling stale todos on task switches, got %q", toolUsage) + } if !strings.Contains(toolUsage, "Execute todos sequentially in the main loop") { t.Fatalf("expected Tool Usage to enforce sequential todo execution, got %q", toolUsage) } diff --git a/internal/context/source_todos.go b/internal/context/source_todos.go index 50f7f55c..cb58cbf2 100644 --- a/internal/context/source_todos.go +++ b/internal/context/source_todos.go @@ -82,7 +82,8 @@ func (todosSource) Sections(ctx context.Context, input BuildInput) ([]promptSect lines = append(lines, "", "stale_todo_reminder: If any todo above is no longer relevant to the current task,", - "cancel it via todo_write set_status=canceled before signaling completion.", + "or the user clearly switches to a different task, use todo_write to mark it completed", + "only if the work is actually done; otherwise set_status=canceled before moving on.", ) return []promptSection{ diff --git a/internal/context/source_todos_test.go b/internal/context/source_todos_test.go index e467ea95..5276b9fe 100644 --- a/internal/context/source_todos_test.go +++ b/internal/context/source_todos_test.go @@ -54,13 +54,19 @@ func TestTodosSourceSections(t *testing.T) { if sections[0].Title != "Todo State" { t.Fatalf("title = %q, want %q", sections[0].Title, "Todo State") } - if strings.Contains(sections[0].Content, "done") { + if strings.Contains(sections[0].Content, `id="done"`) { t.Fatalf("expected terminal todo filtered, got %q", sections[0].Content) } lines := strings.Split(sections[0].Content, "\n") if len(lines) < 2 || !strings.Contains(lines[0], "in-progress") { t.Fatalf("expected in_progress todo first, got %q", sections[0].Content) } + if !strings.Contains(sections[0].Content, "user clearly switches to a different task") { + t.Fatalf("expected stale todo reminder to mention task switching, got %q", sections[0].Content) + } + if !strings.Contains(sections[0].Content, "only if the work is actually done") { + t.Fatalf("expected stale todo reminder to distinguish completed from canceled, got %q", sections[0].Content) + } } func TestTodosSourceSectionsBoundaries(t *testing.T) { diff --git a/internal/promptasset/templates/core/tool_usage.md b/internal/promptasset/templates/core/tool_usage.md index e80cf99f..410c4ea2 100644 --- a/internal/promptasset/templates/core/tool_usage.md +++ b/internal/promptasset/templates/core/tool_usage.md @@ -24,6 +24,7 @@ - For multi-step implementation, debugging, refactoring, or long-running work, keep task state explicit via `todo_write` (plan/add/update/set_status/claim/complete/fail) instead of relying on implicit memory. - Create todos that map to real acceptance work, not vague activity. - Required todos are acceptance-relevant and must converge before finalization. +- If the user clearly switches to a different task, do not carry unfinished todos forward blindly: mark each old todo `completed` only when the work is actually done, otherwise mark it `canceled` before planning or executing the new task. - `todo_write` parameters must match schema strictly: `id` must be a string (for example, `"3"` instead of `3`). - `todo_write` does not auto-dispatch subagents. Setting todo metadata does not trigger execution by itself. - `todo_write` `set_status` requires: `{"action":"set_status","id":"","status":"pending|in_progress|blocked|completed|failed|canceled"}`.