Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/).

- fix: scenario 005's "no approval turn" assertion could not distinguish a real failure from two unrelated causes, and two walks on 2026-08-16 duly contradicted each other. A prompt between resume and invocation has three sources: the classification bug (what the scenario tests), the trust-folder gate (a precondition the walk never proved), and — new since v0.110.0/v0.111.1 — the Phase 5.5 permission-mode precheck, which now runs for *every* task. The assertion now names all three and tells the walker to identify which before recording FAIL, plus a dedicated `🔐 Permission mode:` assertion: the fixture's only command is `/vault-cli:next-task`, so Phase 5.5's own rule ("emit nothing … do not warn on read-only or docs-only tasks") means seeing it is a Phase 5.5 trigger defect to file separately, not a classification failure. Also hardened two things the contradicting walks got wrong: the trust precondition must be *proven in the environment the walk runs in* (a fresh tmux / isolated HOME / sub-agent shell does not inherit it), and `claude_session_id` is now marked necessary-but-not-sufficient — one walk reported PASS from it alone after losing the scrollback. `PLUGIN_VER` is now required in the result, since this flow's behavior moved twice in a single day.

## Unreleased

- fix: `task complete`'s refusal message reported only the pending count, so a task blocked purely by in-progress `[/]` items failed with the literal text `incomplete subtasks: 0 pending` — a count of zero given as the reason for refusing. The guard has always refused on `pending > 0 || inProgress > 0`, but the message interpolated `pending` alone. It now reads `incomplete subtasks: N pending, M in-progress`. Misleading at exactly the moment someone is trying to close out work correctly; found alongside the missing `--force` flag (v0.111.0), first reported 2026-08-11.

## v0.111.1

- fix: the permission-mode precheck added in v0.110.0 never fired for ops tasks. It sat inside Phase 5, which is gated on the code-task heuristic (`fix|implement|refactor|add|bug|deploy|build`), so a task whose entire body is `kubectl delete` skipped Phase 5 wholesale and the precheck with it — every `decommission` / `renew` / `rebuild` / `migrate` task was silently excluded, which is exactly the ops work that needs the switch. Moved to its own Phase 5.5 that runs for all tasks and keys on the task's own commands. Caught by exercising v0.110.0 on "Decommission MinIO on Hell" the same day it shipped.
Expand Down
7 changes: 6 additions & 1 deletion pkg/ops/complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,12 @@ func (c *completeOperation) checkSubtaskCompletion(
Completed: completed,
Total: total,
}
return result, true, errors.Errorf(ctx, "incomplete subtasks: %d pending", pending)
return result, true, errors.Errorf(
ctx,
"incomplete subtasks: %d pending, %d in-progress",
pending,
inProgress,
)
}

// handleRecurringTask handles completion of a recurring task.
Expand Down
24 changes: 24 additions & 0 deletions pkg/ops/complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ var _ = Describe("CompleteOperation", func() {
})
})

Context("task blocked only by in-progress checkboxes", func() {
BeforeEach(func() {
task = domain.NewTask(
map[string]any{"status": "todo"},
domain.FileMetadata{Name: taskName},
domain.Content("# Tasks\n\n- [x] done\n- [/] one\n- [/] two\n- [/] three\n"),
)
mockTaskStorage.FindTaskByNameReturns(task, nil)
force = false
})

It("returns error", func() {
Expect(err).NotTo(BeNil())
})

It("names the in-progress count, not just pending", func() {
Expect(err.Error()).To(ContainSubstring("0 pending, 3 in-progress"))
})

It("does not write the task", func() {
Expect(mockTaskStorage.WriteTaskCallCount()).To(Equal(0))
})
})

Context("task with incomplete checkboxes", func() {
BeforeEach(func() {
task = domain.NewTask(
Expand Down
Loading