diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ab1d8..bfc0ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/). * MINOR version when you add functionality in a backwards-compatible manner, and * PATCH version when you make backwards-compatible bug fixes. +## Unreleased + +- feat: `vault-cli:plan-task`'s fix loop now applies a gap that carries a stated fix and no genuine alternative, and asks the operator only for a real fork. It previously put every auditor finding to the operator as an `AskUserQuestion`, so a gap whose only defensible outcome was the one already written down still cost an interrupt β€” two workers hit it on 2026-10-17, and in both the operator picked the recommended option every time while the goal's manager independently gave the same answers. (A third was reported by the manager but carries no `AskUserQuestion` in its transcript, so it is not counted.) The loop now classifies each gap by one question ("is there a second answer I would defend?"), applies one-fix gaps via `Edit` and reports them as `πŸ”§ Applied: …`, and reserves the question for two-or-more genuine outcomes or an operator-only decision. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is named as a one-fix gap wearing a menu. Applied fixes still re-enter the same `Score: X β†’ Y` cycle and the `β‰₯ 8` gate is unchanged; the record is kept β€” every applied fix is printed and any that changed a criterion's claim is recorded in the task's `# Progress`, so the classification removes the question and never the audit trail. Three gap classes still always ask: an unevidenced premise (the fix is a read only the operator can vouch for), a misread operator quote, and a scope change. `--non-interactive` is untouched β€” its short-circuit still edits nothing. Rationale: `agent-cmd/no-user-prompts`, applied to plan-task's own fix loop. + ## v0.169.5 - fix: `vault-cli:work-on-task-assistant` no longer states conclusions from evidence it merely grepped, and its report block list is declared closed. Dispatched 2026-10-17 to find a task and its guides, the agent added an unsanctioned `πŸ”¬ Independent read-only corroboration` block asserting the router log held "exactly one request" in a window that held 16 (five PIDs across four builds), and that "the absence of any worker-attributable line means the request did not arrive" β€” the opposite of the truth, since that router sampler-gates successful requests out of the log while always logging failures. The owning session had to re-derive both claims, and taken at face value they would have inverted the diagnosis. A second rule requires any inference to be marked `(inference β€” unverified)`: a log the agent did not write may be sampled, truncated, or shared by concurrent writers, and its silence rarely means absence. diff --git a/commands/execute-task.md b/commands/execute-task.md index 408c8bb..95a5412 100644 --- a/commands/execute-task.md +++ b/commands/execute-task.md @@ -204,7 +204,7 @@ Task lifecycle: 1. `/vault-cli:create-task` β€” capture (lenient) 2. `/vault-cli:work-on-task` β€” orient (status + guides + daily note), then auto-chain plan β†’ execute (interactive) -3. `/vault-cli:plan-task` β€” sharpen (5 hard gates); never flips phase +3. `/vault-cli:plan-task` β€” sharpen (9 hard gates); never flips phase 4. **`/vault-cli:execute-task`** β€” the sole gate; flips planning β†’ execution + prints first subtask + DoD reminder β€” this command 5. Start work β€” while working, use any of: - `/vault-cli:update-task` β€” log completed work, sync to daily note / parent goal diff --git a/commands/plan-task.md b/commands/plan-task.md index 84d091b..e4c4ee4 100644 --- a/commands/plan-task.md +++ b/commands/plan-task.md @@ -18,7 +18,7 @@ Under NO-ASK, every point that would ask instead **reports and stops**: - **Step 6 gaps** β†’ do not enter the fix loop, do not `Edit` the task from assumed answers. Skip to step 7 and exit on the `⚠` branch, listing each unresolved gap as a bullet phrased as the question you would have asked, so the operator can answer it on resume. - **Step 5 soft KISS warning** β†’ print it as a note; it never blocks exit on its own. -Everything else is unchanged: entry-contract flips (step 3), the auditor run (step 4), and the five gate checks (step 5) all behave identically. A task whose gates pass clean produces the same `βœ… Plan ready` in both modes β€” NO-ASK only changes what happens to a task that has real gaps. +Everything else is unchanged: entry-contract flips (step 3), the auditor run (step 4), and the nine gate checks (step 5) all behave identically. A task whose gates pass clean produces the same `βœ… Plan ready` in both modes β€” NO-ASK only changes what happens to a task that has real gaps. ## When to use @@ -82,7 +82,7 @@ Task tool with: ### 5. Check the non-negotiables -Six checks beyond the auditor's general scoring β€” first five are hard (any failure β†’ mandatory question in step 6, can't exit on auditor score alone), sixth is a soft warning. +Ten checks beyond the auditor's general scoring β€” nine hard (any failure β†’ a step 6 gap, classified there as apply-or-ask; can't exit on auditor score alone), plus the KISS ceiling as the one soft warning. This list is the source of truth for the count; if you add or remove a check, update every "nine" below in the same pass. **Hard:** @@ -145,20 +145,20 @@ Six checks beyond the auditor's general scoring β€” first five are hard (any fai **Note the scope of this item.** It sits inside a block that is skipped for non-shipping-class tasks (next line), yet the defect it catches is not shipping-specific β€” the 2026-09-21 case was a diagnosis task, where this gate never ran. `agents/task-auditor.md` Β§ 15 carries the same check and runs on every task; this item is the shipping-class enforcement, not the universal home. Skip this whole check for non-shipping-class tasks (pure research, decision, doc-only with no published artifact). -- **Subtask-goal alignment** β€” every `# Tasks` checkbox must either (a) map by topic to β‰₯ 1 `# Success Criteria` outcome, or (b) be the e2e verify subtask. Flag any orphan as a scope-creep candidate; in step 6 the owner can link it to an SC, move it to `# Out of Scope`, or split it into a separate task. -- **Blast radius named** β€” if any subtask pushes to a registry, deploys, mutates a cluster, or needs a credential/secret, the task must name the external system AND the account written to (e.g. *"pushes `docker.io/bborbe/` under the bborbe Docker Hub account"*). A credential requirement with no named target is a scope gap: the owner discovers what was automated at the secrets request, after the work has shipped. Observed 2026-08-27 β€” a publish-on-tag CI was designed, merged and released; the owner objected (*"Is the agent trying to push a docker image? I don't think that I want this"*) only when its Docker Hub secrets were requested, costing two reversal PRs for a net deletion. Flag β†’ mandatory question in step 6. -- **Resolution steps are resolvable** β€” a subtask naming a *lookup, join or key* (*"resolve the gate to its store `item_id`"*, *"look up the record by X"*) must be checked against whether that key actually exists and is derivable **by the actor that will run it**. This is the one hard check that is about the plan's *premise* rather than its shape: a subtask can name a concrete verb, a concrete artifact and a concrete outcome and still be unsatisfiable by construction. Ask of each: *do the two records share a field, and does the caller hold it?* Observed 2026-09-21 β€” a subtask read *"the sweep resolves the gate it is about to publish to a store `item_id`"*. No such join existed: the store wrote `item_id` itself, the producer's dedup key was a hash of a value the manager never holds, and the third candidate field was documented as `` β€” two kinds of value in one field, so it could not anchor an identity either. The same session's e2e-verify subtask also named no procedure and no expected result. **Two unsatisfiable-by-construction subtasks in one task is the signal that this check is missing, not that the author was careless** β€” and the cost is paid at execution, where the premise has already collapsed and the task file needs revising mid-flight. Flag β†’ mandatory question in step 6. +- **Subtask-goal alignment** β€” every `# Tasks` checkbox must either (a) map by topic to β‰₯ 1 `# Success Criteria` outcome, or (b) be the e2e verify subtask. Flag any orphan as a scope-creep candidate; in step 6 the owner can link it to an SC, move it to `# Out of Scope`, or split it into a separate task β€” a step 6 fork, since those are three defensible outcomes. +- **Blast radius named** β€” if any subtask pushes to a registry, deploys, mutates a cluster, or needs a credential/secret, the task must name the external system AND the account written to (e.g. *"pushes `docker.io/bborbe/` under the bborbe Docker Hub account"*). A credential requirement with no named target is a scope gap: the owner discovers what was automated at the secrets request, after the work has shipped. Observed 2026-08-27 β€” a publish-on-tag CI was designed, merged and released; the owner objected (*"Is the agent trying to push a docker image? I don't think that I want this"*) only when its Docker Hub secrets were requested, costing two reversal PRs for a net deletion. Flag β†’ a step 6 gap, applied or asked per its classification there. +- **Resolution steps are resolvable** β€” a subtask naming a *lookup, join or key* (*"resolve the gate to its store `item_id`"*, *"look up the record by X"*) must be checked against whether that key actually exists and is derivable **by the actor that will run it**. This is the one hard check that is about the plan's *premise* rather than its shape: a subtask can name a concrete verb, a concrete artifact and a concrete outcome and still be unsatisfiable by construction. Ask of each: *do the two records share a field, and does the caller hold it?* Observed 2026-09-21 β€” a subtask read *"the sweep resolves the gate it is about to publish to a store `item_id`"*. No such join existed: the store wrote `item_id` itself, the producer's dedup key was a hash of a value the manager never holds, and the third candidate field was documented as `` β€” two kinds of value in one field, so it could not anchor an identity either. The same session's e2e-verify subtask also named no procedure and no expected result. **Two unsatisfiable-by-construction subtasks in one task is the signal that this check is missing, not that the author was careless** β€” and the cost is paid at execution, where the premise has already collapsed and the task file needs revising mid-flight. Flag β†’ a step 6 gap, applied or asked per its classification there. -- **Premise matches the ask** β€” when `# Impact` quotes the operator verbatim, re-read the quote against Summary + Success Criteria. Flag when the quote admits more than one reading, or when an SC/constraint excludes a plausible reading of it (e.g. *"must NOT be named X"* when the quote says X). A plan can score 9/10 on shape and still build the wrong thing β€” the task writer's interpretation is not the operator's words. Observed 2026-09-23: *"add a open skill"* was read as an open-items skill (name `open` banned); the operator meant moving `/open` β†’ `/supervisor:open` β€” found only after release. Flag β†’ mandatory question in step 6, quoting both readings. -- **Empirical premises are evidenced** β€” a `# Impact` or Summary asserting a fact about the running system (*"nothing else reads this DB"*, *"the data is disposable"*, *"X is unused"*, *"the functional window is one minute"*) is a premise the plan rests on, and the shape checks never reach it. This is the *system* sibling of the premise check above: that one compares the operator's quote to the plan, this one asks whether the plan's own factual premise is evidenced in the file. Ask of each: *does the task name the read that establishes this β€” a query, a log line, a config?* Absent it, flag β†’ mandatory question in step 6. Observed: a task claimed *"the data is disposable… nothing else reads this DB"* and was audited 9/10 three times; the claim was false, and the change was built, merged and deployed, then reverted. -- **Steps are runnable by the actor that owns the task** β€” a criterion or subtask naming a command the executing session's **tier** may not run is unsatisfiable by construction, however concrete it reads. `claude-supervisor`'s tiers are the common case: `/supervisor:open` and the `manager-*` / `fleet-*` verbs are manager-only, and a worker session must not run them. This is the *authority* sibling of the premise check above β€” that one asks whether the actor holds the **key**, this one whether it holds the **right**. Ask of each named command: *may the session that owns this task run it β€” and if not, does the criterion name who does?* Observed 2026-09-30 β€” an SC required *"one live run shows both halves … read from that session's own output"*. The command was `/supervisor:open --flagged`, the task was worker-anchored, and the criterion was satisfiable only after the run was routed to a manager session. The plan had passed every shape check. Flag β†’ mandatory question in step 6. +- **Premise matches the ask** β€” when `# Impact` quotes the operator verbatim, re-read the quote against Summary + Success Criteria. Flag when the quote admits more than one reading, or when an SC/constraint excludes a plausible reading of it (e.g. *"must NOT be named X"* when the quote says X). A plan can score 9/10 on shape and still build the wrong thing β€” the task writer's interpretation is not the operator's words. Observed 2026-09-23: *"add a open skill"* was read as an open-items skill (name `open` banned); the operator meant moving `/open` β†’ `/supervisor:open` β€” found only after release. Flag β†’ a step 6 fork, never a one-fix gap (the operator's own words are the decision), quoting both readings. +- **Empirical premises are evidenced** β€” a `# Impact` or Summary asserting a fact about the running system (*"nothing else reads this DB"*, *"the data is disposable"*, *"X is unused"*, *"the functional window is one minute"*) is a premise the plan rests on, and the shape checks never reach it. This is the *system* sibling of the premise check above: that one compares the operator's quote to the plan, this one asks whether the plan's own factual premise is evidenced in the file. Ask of each: *does the task name the read that establishes this β€” a query, a log line, a config?* Absent it, flag β†’ a step 6 fork (unevidenced premise β€” see Β§ 6 *Three gaps always ask*); apply only when the read is yours to run. Observed: a task claimed *"the data is disposable… nothing else reads this DB"* and was audited 9/10 three times; the claim was false, and the change was built, merged and deployed, then reverted. +- **Steps are runnable by the actor that owns the task** β€” a criterion or subtask naming a command the executing session's **tier** may not run is unsatisfiable by construction, however concrete it reads. `claude-supervisor`'s tiers are the common case: `/supervisor:open` and the `manager-*` / `fleet-*` verbs are manager-only, and a worker session must not run them. This is the *authority* sibling of the premise check above β€” that one asks whether the actor holds the **key**, this one whether it holds the **right**. Ask of each named command: *may the session that owns this task run it β€” and if not, does the criterion name who does?* Observed 2026-09-30 β€” an SC required *"one live run shows both halves … read from that session's own output"*. The command was `/supervisor:open --flagged`, the task was worker-anchored, and the criterion was satisfiable only after the run was routed to a manager session. The plan had passed every shape check. Flag β†’ a step 6 gap, applied or asked per its classification there. **Routing a step to another tier has the same test, in reverse.** A step re-assigned to a manager must be one the manager contract permits β€” a manager never performs a row's work (no verification, no investigation) and never spawns `role: manager`. A worker's own SC probe stays the worker's; when it cannot run it, route it to the operator, not up the tier. Observed 2026-10-10: SC probes routed to a topic manager were declined at execution, forcing a re-ask. **Soft:** - **KISS ceiling** β€” if `# Tasks` has > 8 checkboxes, warn: *"task may be too large for one session β€” consider splitting, moving items to `# Out of Scope`, or promoting to a goal."* Owner decides; task can still proceed to execution. -Any hard check failing β†’ mandatory question in step 6; can't exit on auditor score alone. Soft check failing β†’ surfaced as a question in step 6 but doesn't block exit if owner says proceed. +Any hard check failing β†’ a step 6 gap, resolved there by its classification (one-fix gaps applied, forks asked); can't exit on auditor score alone. Soft check failing β†’ surfaced in step 6 but doesn't block exit if owner says proceed. ### 6. Surface gaps + fix loop @@ -193,7 +193,20 @@ Any hard check failing β†’ mandatory question in step 6; can't exit on auditor s Only findings the auditor labelled `[template-level]` are verdict-coverable. An `[instance-level]` finding always asks, verdict or not. -Translate findings (auditor + non-negotiable checks) into questions. Rules: +**Classify every gap before you put it to anyone β€” a one-fix gap is applied, not asked.** Read each gap (auditor finding or failed non-negotiable) and sort it by one question: *is there a second answer I would defend?* (Under `--non-interactive` none of this applies β€” the NO-ASK short-circuit above still governs, and nothing is `Edit`ed.) + +- **One-fix gap β†’ apply it, then report it.** The gap carries a stated fix and no genuine alternative β€” a missing `# Definition of Done`, a baseline to rebase, a window to pin, a criterion whose evidence shape is wrong. Apply the stated fix via `Edit` (re-running the step 2 ownership gate first) and print `πŸ”§ Applied: β€” `. Do not open an `AskUserQuestion` for it. +- **Real fork β†’ ask.** Two or more genuinely defensible outcomes, or a decision only the operator can make β€” scope, a production-touching step, which of two homes a rule belongs in. These are what the rules below are for. + +**The distinguishing test: write the second answer down before you ask.** State the alternative you would have offered. Cannot name one you would defend β†’ one-fix gap, apply it. Can name two β†’ fork, ask. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is a one-fix gap wearing a menu: "do nothing" is a refusal to decide, not an alternative, and an operator who picks option 1 every time is telling you the menu should not have been built. This is `agent-cmd/no-user-prompts` reaching a case its own trigger already names β€” it covers `commands/**/*.md`, and plan-task's own fix loop is the prompt. + +**Report; never silently rewrite.** The classification removes the *question*, never the *record*. Print every applied fix, and when one changed a criterion's claim, record it in the task's `# Progress` β€” the operator must be able to see what plan-task decided on their behalf and reverse it. A silent rewrite is the failure mode this trades against; do not reintroduce it for brevity. + +**Applied fixes re-enter the loop.** They take the same `Score: X β†’ Y` cycle as an answered question β€” re-run the auditor after each batch, and keep looping until score β‰₯ 8 AND all nine hard non-negotiables pass. The gate does not relax because nobody was asked. + +**Three gaps always ask, whatever they look like.** An **unevidenced premise** the plan rests on β€” the fix is a *read*, and only the operator can say whether it was taken. A **misread operator quote**, where `# Impact` quotes the operator and the plan's reading is one of two plausible ones β€” those are the operator's own words, so only they can settle it. And a **scope** change, where "apply the obvious fix" and "this task should not do that at all" are both live. For a premise, do the read first when it is cheap and in-scope, then report the result rather than asking; ask only when the read is not yours to run. + +Translate the remaining findings (auditor + non-negotiable checks) into questions. Rules: - Max 3 questions per turn - Each question is short (one sentence) + tight options (single yes/no OR 2-4 numbered options) @@ -201,7 +214,7 @@ Translate findings (auditor + non-negotiable checks) into questions. Rules: - Quote the offending line/section so owner sees what triggered the question - Use `AskUserQuestion` for the actual ask -Apply each answer via `Edit` β€” re-running the step 2 ownership gate first (see there). Re-run auditor after each batch. Print delta `Score: X β†’ Y`. Loop until score β‰₯ 8 AND all five hard non-negotiables pass OR owner says "good enough." A current template verdict (see the template-verdict check above) satisfies the score gate for the findings it covers. +Apply each answer via `Edit` β€” re-running the step 2 ownership gate first (see there). Re-run auditor after each batch. Print delta `Score: X β†’ Y`. Loop until score β‰₯ 8 AND all nine hard non-negotiables pass OR owner says "good enough." A current template verdict (see the template-verdict check above) satisfies the score gate for the findings it covers. **Propagate, don't just patch.** A reworded criterion is usually restated verbatim in `# Tasks` and `# Definition of Done`, and sometimes in the Summary and `# Out of Scope`. Edit every section that restates it in the same pass β€” a fix applied to the criterion alone leaves the plan still carrying the claim you just removed, and the next audit reports it as a *fresh* defect rather than the same one. That reads as a new problem, so the loop repairs the restatement instead of noticing the half-application, and burns a round per section. When an answer changes a criterion's claim, grep the task file for the old wording before re-running the auditor. @@ -225,11 +238,11 @@ Under NO-ASK the score is whatever the auditor returned (nothing was fixed, so t ## Notes -- **Scope is focused on what blocks safe execution.** Plan-task enforces five hard planning-gate checks (SC defined, subtasks reach goal, e2e verify subtask, subtask-goal alignment, blast radius named) plus the KISS ceiling as a soft sixth, because each one prevents a specific failure mode: missing outcomes, missing path, dishonest-tick verification, scope creep, an unnamed blast radius, oversize task. Other heuristics (MVP framing, Out-of-Scope capture quality, evidence shape) stay in `task-auditor` and `task-writing.md` as canonical rules β€” surfaced via the auditor in step 4, not promoted to dedicated gates. Letting the auditor enforce general structure while plan-task enforces the five named gates keeps the command short and the gates legible. +- **Scope is focused on what blocks safe execution.** Plan-task enforces nine hard planning-gate checks β€” SC defined, subtasks reach goal, e2e verify subtask, subtask-goal alignment, blast radius named, resolution steps resolvable, premise matches the ask, empirical premises evidenced, steps runnable by the owning actor β€” plus the KISS ceiling as a soft tenth, because each one prevents a specific failure mode: missing outcomes, missing path, dishonest-tick verification, scope creep, an unnamed blast radius, an unsatisfiable-by-construction premise, a misread operator quote, an unevidenced system claim, a step the owning tier may not run, and oversize task. Other heuristics (MVP framing, Out-of-Scope capture quality) stay in `task-auditor` and `task-writing.md` as canonical rules β€” surfaced via the auditor in step 4, not promoted to dedicated gates. Letting the auditor enforce general structure while plan-task enforces the nine named gates keeps the gates legible. - **Questions stay tight, with consequence visible.** 2-3 lines of setup β†’ short options. "Tight" doesn't mean stripping context β€” owner must see what each answer *changes*. Quote the offending line, name the trade-off, then options. - **Subtask granularity = session-sized.** When proposing or sharpening `# Tasks` items, target *work-block size* (a session's worth of work), not CLI-step size. Aim for 3-6 items per task. Reject auditor-suggested over-decomposition like "run precommit / open PR / merge PR" as separate subtasks β€” those collapse into one "ship the change" block. - **Reads `~/.claude/plugins/marketplaces/vault-cli/docs/task-writing.md` as the canonical rule source** β€” same rules `task-auditor` enforces. -- **Conversational on purpose.** Owner is the judge of substance. Plan-task never silently rewrites; every change comes from an explicit answer. This is exactly why `--non-interactive` refuses to guess: with no owner to answer, the honest move is to stop and list the gaps, never to invent answers and edit the task. +- **Conversational on purpose β€” about substance, not about ratification.** Owner is the judge of substance: a genuine fork always goes to them. What changed is that a gap carrying one stated fix and no alternative is *applied* and reported rather than put to them as a question β€” asking there was a request to ratify, not a question, and it cost an interrupt per gap. Plan-task still never *silently* rewrites: every applied fix is printed in the run's output, and any that changed a criterion's claim is recorded in `# Progress`. This is also why `--non-interactive` refuses to guess: with no owner to answer, the honest move is to stop and list the gaps, never to invent answers and edit the task. - **Entry contract.** A row at `phase: todo` is not planned by plan-task: it refuses, tells the operator the row is waiting in their approval inbox, and asks whether to approve it β€” running `vault-cli task approve` itself on the operator's explicit yes, and never without it. `vault-cli task approve` performs the `todo β†’ planning` flip and records `approved_by`/`approved_at` β€” plan-task itself never flips a phase. No `/work-on-task` prerequisite for the approval itself. An **empty** phase is a different state, not a stricter one: it is not the approval inbox, `task approve` cannot accept it, and the entry path (`vault-cli task work-on`) is what advances it β€” plan-task reports the row as unentered and stops, rather than approving it or advancing the phase itself. - **No phase flip.** plan-task never transitions phase; it validates and hands off to `/vault-cli:execute-task`, which owns the `planning β†’ execution` flip. The `todo β†’ planning` entry flip happens only through `vault-cli task approve` β€” step 3 writes no phase, it refuses an unapproved row and at most promotes `next` β†’ `in_progress`. - **Mechanical fixes stay in `/audit-task`.** This command is for substance (SC, subtasks, goal alignment), not formatting. @@ -240,7 +253,7 @@ Task lifecycle: 1. `/vault-cli:create-task` β€” capture (lenient) 2. `/vault-cli:work-on-task` β€” orient (status + guides + daily note), then auto-chain into this command (both modes; headless callers pass `--non-interactive` through) -3. **`/vault-cli:plan-task`** β€” sharpen (5 hard gates); never flips `planning β†’ execution` β€” this command +3. **`/vault-cli:plan-task`** β€” sharpen (9 hard gates); never flips `planning β†’ execution` β€” this command 4. `/vault-cli:execute-task` β€” gate planning β†’ execution; flips phase + prints first subtask + DoD reminder 5. Start work β€” while working, use any of: - `/vault-cli:update-task` β€” log completed work, sync to daily note / parent goal