feat: enforce one-task-per-session contract (closer panel + session-close anchor check)#18
Conversation
sync-progress: new Phase 6 appends the state-closer panel below the report when a task was completed in Phase 4 (auto 4a OR confirmed 4b); skips on PR-only / progress-only paths. complete-task: MODE=interactive step 2e appends the same panel after the ✅ report; MODE=tool step 3e explicitly forbids it (JSON output stays clean). Prevents the drift where Claude invented its own closer pointing at /vault-cli:next-task — wrong for one-task-per-session workflow.
New Phase 4.5 scans the session's touched vault tasks (from Phase 1 scope) and surfaces any with status: in_progress as outstanding — with concrete next-action hints: complete, defer, or set hold/aborted. Scope is TOUCHED tasks only — untouched [/] items on today's daily note belong to other sessions or the orchestrator and are intentionally NOT flagged (forcing the user to clear unrelated work would break one-task-per-session). Closes the loop opposite to the closer change in this PR: complete-task / sync-progress tell you to close the session; session-close refuses to call it clean if the anchor task isn't actually done.
PR-review feedback (slash-command-auditor): - Add `allowed-tools` to complete-task.md frontmatter — was missing entirely; command calls `vault-cli` and reads task files - Drop AskUserQuestion from the interactive incomplete-items gate; replace with abort + hint to re-run with --force. Reduces friction on the common path (just complete) without losing the safety gate for partial completions. - Add --force flag, parsed in step 1. MODE=tool unchanged (always sets phase: human_review on incomplete, never completes). Also: - Flatten CHANGELOG Unreleased nested sub-bullets to top-level entries so every bullet under ## Unreleased starts with a conventional prefix (the auto-release watcher's parser requires it).
…oss-ref PR-review feedback (agent-cmd/single-source-of-truth): the 4-bullet 'why session-close, not next-task' rationale was duplicated verbatim in both complete-task.md step 2e and sync-progress.md Phase 6. Replace complete-task's copy with a one-line cross-reference to sync-progress Phase 6. sync-progress is the canonical home (more complete flow). Eliminates the drift risk where future edits update one copy and miss the other.
There was a problem hiding this comment.
Sandbox is blocking. Let me proceed with the in-session review since I have all the changed files.
PR Review: feat: enforce one-task-per-session contract (closer panel + session-close anchor check)
Changed Files Reviewed
commands/complete-task.mdcommands/session-close.mdcommands/sync-progress.mdCHANGELOG.md
Must Fix (Critical)
None. No security vulnerabilities, no correctness bugs, no architectural violations in the diff.
Should Fix (Important)
1. session-close.md — Phase 4.5: No error handling for vault-cli task get failure
vault-cli task get "$T" status --output jsonIf vault-cli is unavailable, the binary errors, or the JSON is malformed, the phase silently continues without surfacing any warning. The phase iterates touched tasks and calls this for each — a single failure should at minimum produce a warning rather than silently skipping the check.
File: commands/session-close.md:136-138
Nice to Have (Optional)
2. sync-progress.md — Phase 6 closer panel: skip-condition rationale could be more explicit
The Phase 6 skip rule says: "Skip the closer entirely when Phase 4 did NOT complete a task — i.e. PR-only sync, progress-only sync, or any path where the active task is still in_progress."
The PR-only and progress-only paths are clear. However, the in_progress path is ambiguous: if Phase 4 runs but doesn't complete a task (e.g., criteria 1–4 not all met, falls through to AskUserQuestion, user says "Hold"), the closer should indeed be skipped. The current language covers this but could distinguish "Phase 4 skipped" from "Phase 4 ran but chose not to complete" to prevent future misinterpretation.
File: commands/sync-progress.md:244
3. complete-task.md — Step 2e: cross-ref rationale is correct but fragile
The step 2e closer panel cross-references sync-progress.md Phase 6 for the rationale ("same contract applies"). If one doc is updated and the other isn't, they'll drift. The PR itself addresses this for this change, but there's no mechanical enforcement (e.g., a lint rule or test that asserts both panels are identical). Consider extracting the panel text into a shared include or asserting parity in make precommit.
File: commands/complete-task.md:50
4. CHANGELOG.md — Unreleased section correctly captures all four changes
The changelog accurately describes: (1) Phase 6 + step 2e ⚪ DONE closer, (2) session-close Phase 4.5 anchor check, (3) complete-task AskUserQuestion→abort refactor, (4) --force flag. Well-structured.
Precommit Note
precommit skipped (selector mode) — CI covers lint+test
The project has make precommit which runs lint+test+checks. No Go code changed — all changes are markdown command docs + CHANGELOG. The precommit check for version alignment (check-versions) would pass since no version files were modified.
Selector Mode Traceability
| Rule | Owner | Triggered? | Finding |
|---|---|---|---|
incomplete-items-gate |
go-quality-assistant |
commands/complete-task.md |
✅ The refactor from AskUserQuestion to abort-with-hint is correctly implemented |
closer-panel-contract |
go-architecture-assistant |
sync-progress.md, complete-task.md |
✅ Both emit ⚪ DONE panel; MODE=tool correctly suppressed |
session-anchor-check |
go-architecture-assistant |
session-close.md |
vault-cli task get failure |
Summary
The PR correctly implements the one-task-per-session contract: sync-progress and complete-task now emit a ⚪ DONE closer pointing at session-close, and session-close Phase 4.5 verifies the anchor task was actually completed before calling it clean. The complete-task refactor from interactive prompt to abort-with-hint reduces friction on the common path. One actionable gap: Phase 4.5 should handle vault-cli task get failures gracefully rather than silently continuing.
{
"verdict": "request-changes",
"summary": "The one-task-per-session contract is correctly implemented across all three commands, but Phase 4.5 in session-close lacks error handling when vault-cli task get fails, silently skipping the anchor-task check on error.",
"comments": [
{
"file": "commands/session-close.md",
"line": 136,
"severity": "major",
"message": "Should Fix: Phase 4.5 calls `vault-cli task get \"$T\" status --output json` with no error handling. If vault-cli fails or returns unexpected output, the phase silently continues without warning. Consider adding: capture stderr, check exit code, and surface a warning if the anchor-task check cannot be performed."
},
{
"file": "commands/sync-progress.md",
"line": 244,
"severity": "nit",
"message": "Nice to Have: Phase 6 skip rule conflates 'Phase 4 skipped' and 'Phase 4 ran but chose not to complete' — both skip the closer but have different root causes. Adding a brief distinction would prevent future misinterpretation."
},
{
"file": "commands/complete-task.md",
"line": 50,
"severity": "nit",
"message": "Nice to Have: The closer panel cross-references sync-progress.md Phase 6 for rationale. No mechanical enforcement ensures both stay in sync. Consider a precommit check that asserts panel text parity between the two files."
}
],
"concerns_addressed": [
"correctness: Phase 6 closer panel skip logic is correctly implemented — PR-only and progress-only paths skip, in_progress path skips, task-complete path emits",
"correctness: Phase 4.5 anchor check correctly iterates touched tasks and interprets status values — but error handling gap identified",
"correctness: Incomplete-items gate correctly refactored from AskUserQuestion to abort-with-hint; --force flag bypasses gate in interactive mode; MODE=tool always sets phase=human_review",
"tests: precommit uses shell/assertion checks; no Go test files changed; all changes are markdown docs — precommit CI covers what would be testable"
]
}…nding PR-review feedback from bot (MAJOR): Phase 4.5 called `vault-cli task get` with no error handling, silently skipping the anchor-task check on failure. Capture exit code + stderr; on non-zero exit OR JSON parse failure, surface the unverified task as its own outstanding line in Phase 9 (don't collapse into a generic warning — user must see scope). Unverified status means the anchor-task safety gate is unverified — exactly the failure mode this phase guards against.
superseded by new automated review
There was a problem hiding this comment.
{
"verdict": "approve",
"summary": "PR #18 enforces the one-task-per-session contract via three coordinated changes: (1) `complete-task` and `sync-progress` emit a `⚪ DONE` state-closer pointing at `session-close` after a task completes, replacing the prior drift where Claude invented `next-task`; (2) `session-close` Phase 4.5 now scans touched vault tasks and surfaces any still `in_progress` as outstanding before close, closing the loop opposite to the closer; (3) the `AskUserQuestion` gate in `complete-task` is replaced with a non-interactive abort hint plus a `--force` bypass. All three concerns from the plan are correctly addressed: Phase 4.5 is scoped to TOUCHED tasks only and surfaces failed lookups; Phase 6 panel only fires when Phase 4 actually completed a task; MODE=tool path is explicitly guarded from emitting the closer.",
"comments": [],
"concerns_addressed": [
"correctness: session-close Phase 4.5 correctly scopes to touched tasks only, does not check daily-note [/] items, and surfaces failed status lookups as outstanding (8b25dc1)",
"correctness: sync-progress Phase 6 panel only fires when Phase 4 completed a task — PR-only and progress-only sync paths skip the closer per explicit skip logic",
"correctness: complete-task MODE=tool step 3e explicitly suppresses the ⚪ DONE closer; vault-cli task complete call in MODE=tool is JSON-only"
]
}
Summary
Three slash commands updated to enforce the one-task-per-session contract end-to-end:
/vault-cli:sync-progress— new Phase 6 appends a⚪ DONEstate-closer panel below the report when Phase 4 actually completed a task (auto via 4a or confirmed via 4b). PR-only and progress-only sync paths skip it./vault-cli:complete-task— MODE=interactive step 2e appends the same panel after✅ Task completed. MODE=tool step 3e explicitly forbids it — JSON output stays clean./vault-cli:session-close— new Phase 4.5 scans the session's touched vault tasks; any task stillstatus: in_progresssurfaces as outstanding with concrete next-actions (/complete-task,/defer-task, or set statushold/aborted). Scoped to TOUCHED tasks only — untouched[/]items on today's daily note belong to other sessions or the orchestrator and are intentionally NOT flagged.Plus a related cleanup driven by
/coding:pr-review(commit 3):complete-taskfrontmatter gets the missingallowed-toolsblock (Bash(vault-cli:*),Read).complete-taskinteractive incomplete-items gate replaces AskUserQuestion with abort +--forcehint — less friction on the common path, same safety on partial completions.## Unreleasednow has a conventional prefix so the auto-release watcher parses cleanly).Why
Two related drift modes were observed:
/vault-cli:sync-progressand/vault-cli:complete-task, Claude kept inventing closer panels recommending/vault-cli:next-task— wrong for one-task-per-session, where queued daily-note items get fresh sessions via the orchestrator, never appended to the current one./vault-cli:session-closehappily printed✅ Nothing to do — safe to close Claudeeven when the session's anchor task was stillin_progress— undermining the same contract from the opposite side.The closer panel tells you to close the session; the session-close anchor check refuses to call it clean if the task isn't actually done. Both rationales are documented inline so future readers (human + LLM) don't re-derive them.
Closer shape (verbatim)
Review-loop dismissals (documented)
must stay inline because it analyzes the parent conversation. Out of scope for this PR; not introduced by this diff.feat: session-close warns when touched task still in_progress—warnsis 3rd-person, not bare imperative). Reword would require amend + force-push; declined. Master uses--mergecommits, so the merge commit subject is the user-visible record on master; this commit subject only lives in the merge commit's parent.Test plan
make precommitgreen locally (after each commit)/vault-cli:sync-progresson a completed task →⚪ DONEpanel withapprove: /vault-cli:session-close/vault-cli:complete-taskinteractively on a complete task → same closer after✅ Task completed/vault-cli:complete-task --tool→ JSON only, no closer/vault-cli:complete-taskon an incomplete task → abort with--forcehint, no prompt/vault-cli:session-closeon a session that touched a still-in_progress task → Phase 4.5 surfaces it; on a session with a completed touched task → no false positivegit fetch origin --tags && git tag --list | tail -1shows new vX.Y.Z (auto-release watcher);.claude-plugin/plugin.jsonversion matches🤖 Generated with Claude Code