@windyroad/itil 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,17 +50,6 @@
50
50
  },
51
51
  "schema_version": "2.0"
52
52
  },
53
- "itil-changeset-discipline": {
54
- "band": "Experimental",
55
- "computed_at": "2026-05-18T11:42:50Z",
56
- "evidence": {
57
- "breaking_change_age_days": null,
58
- "closed_tickets_window": 99,
59
- "days_shipped": 33,
60
- "invocations_30d": null
61
- },
62
- "schema_version": "2.0"
63
- },
64
53
  "itil-claude-space-protection": {
65
54
  "band": "Experimental",
66
55
  "computed_at": "2026-05-18T11:42:50Z",
@@ -497,5 +486,5 @@
497
486
  }
498
487
  },
499
488
  "name": "wr-itil",
500
- "version": "2.5.0"
489
+ "version": "2.5.1"
501
490
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wr-itil",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "ITIL problem-management workflows for AI coding agents",
5
5
  "author": {
6
6
  "name": "Windy Road Technology",
package/README.md CHANGED
@@ -79,7 +79,6 @@ See [the "Add `manage-incident` Skill to `wr-itil` Plugin" architecture rule](..
79
79
  | `itil-claude-space-protection.sh` | Write, Edit | Prevents project-generated artefacts from being written under `.claude/` (the "Agents write project-generated artefacts under `.claude/` (user-controlled config space) — gate exclusions are read tolerance, not write permission" problem) |
80
80
  | `p057-staging-trap-detect.sh` | Bash | Detects the "Problem 057: `git mv` + Edit + `git add` staging-ordering trap drops content edits from the commit" problem staging trap during ticket transitions |
81
81
  | `pre-publish-intake-gate.sh` | Bash | Blocks `npm publish` when downstream OSS intake scaffolding is missing (the "Scaffold downstream OSS intake — skill + layered triggers" architecture rule) |
82
- | `itil-changeset-discipline.sh` | Bash | Gates `git commit` on changeset coverage for source-package changes (the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem) |
83
82
  | `itil-assistant-output-review.sh` | Stop | Reviews assistant output at session end for ITIL-discipline patterns |
84
83
 
85
84
  ## Skills
package/hooks/hooks.json CHANGED
@@ -127,15 +127,6 @@
127
127
  }
128
128
  ]
129
129
  },
130
- {
131
- "matcher": "Bash",
132
- "hooks": [
133
- {
134
- "type": "command",
135
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-changeset-discipline.sh"
136
- }
137
- ]
138
- },
139
130
  {
140
131
  "matcher": "Bash",
141
132
  "hooks": [
@@ -119,7 +119,6 @@ case "$EVENT" in
119
119
  run_hook itil-no-implement-draft-gate.sh
120
120
  run_hook itil-bash-polling-antipattern-detect.sh
121
121
  run_hook pre-publish-intake-gate.sh
122
- run_hook itil-changeset-discipline.sh
123
122
  run_hook itil-readme-refresh-discipline.sh
124
123
  ;;
125
124
  request_user_input|AskUserQuestion)
@@ -46,7 +46,6 @@
46
46
  # problem-094-wr-itil-manage-problem-does-not-refresh-docs-problems-readme-md-on-ticket-creation-problem — parent (README refresh on creation contract).
47
47
  # docs-problems-readme-md-drifts-from-filesystem-truth-across-sessions-despite-refresh-on-create-and-refresh-on-transition-both-closed-problem — sibling reconcile-readme recovery path.
48
48
  # staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem — sibling staging-trap hook (same enforcement-layer shape).
49
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem — sibling changeset-discipline hook (same shape).
50
49
  # readme-refresh-enforcement-gap-iter-subprocess-commits-can-land-a-verifying-md-rename-without-staging-the-corresponding-verification-queue-row-in-docs-problems-readme-md-problem — this hook.
51
50
  # hook-substring-matches-git-commit-anywhere-in-bash-command-not-just-actual-git-commit-invocations-problem — leading-executable-token command-detect helper.
52
51
 
@@ -46,7 +46,7 @@
46
46
  #
47
47
  # Cost: one git invocation per `git commit` Bash call (~30-60ms). No
48
48
  # marker (per-invocation deterministic; mirrors staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem staging-detect.sh
49
- # and afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem itil-changeset-discipline.sh precedent).
49
+ # and readme-refresh-enforcement-gap-iter-subprocess-commits-can-land-a-verifying-md-rename-without-staging-the-corresponding-verification-queue-row-in-docs-problems-readme-md-problem itil-readme-refresh-discipline.sh precedent).
50
50
  #
51
51
  # Command-shape detection delegates to
52
52
  # `lib/command-detect.sh::command_invokes_git_commit`, which strips
@@ -112,7 +112,6 @@
112
112
  # fact rescue this hook obviates).
113
113
  # staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem — sibling staging-trap helper (same enforcement-layer
114
114
  # shape — per-invocation deterministic, no markers).
115
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem — sibling changeset-discipline helper (same shape).
116
115
  # readme-refresh-enforcement-gap-iter-subprocess-commits-can-land-a-verifying-md-rename-without-staging-the-corresponding-verification-queue-row-in-docs-problems-readme-md-problem — this helper.
117
116
  # hook-does-not-recognise-risk-bypass-adr-031-migration-trailer-blocks-step-0a-auto-migrate-problem — RISK_BYPASS trailer allow-list bypass (this addition).
118
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "ITIL-aligned IT service management for Claude Code and Codex",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"
@@ -236,11 +236,11 @@ The Phase 2 working-the-problem traversal makes "implement the fix" concretely t
236
236
  5. **Commit with the `Refs: STORY-<NNN>` trailer** (single-trailer vocabulary per the "Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology" architecture rule line 307 + amendment 2026-05-10 nitpick N2 — same trailer verb whether the commit is the story's first implementation commit or a continuation). On the FIRST commit AFTER the capture commit (subject prefix discriminates: `feat(itil): capture STORY-NNN ...` is the capture; any other subject prefix is an implementation commit), `/wr-itil:manage-story` auto-transitions the story `draft → in-progress`. As acceptance criteria checkboxes are ticked across multiple commits, the same trailer continues to attribute the work.
237
237
  6. **Story `done` auto-transition**: when ALL acceptance-criteria checkboxes in the story body are ticked AND the linked RFC reaches `closed`, `/wr-itil:manage-story` auto-transitions the story `in-progress → done`. (When a story's RFC is still `in-progress` but the acceptance criteria are all ticked, the story stays at `in-progress` until the RFC closes — this preserves the trace coupling per the "Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology" architecture rule line 309.)
238
238
  7. **Pick the next not-done story** from the RFC's `stories:` array. Repeat from step 3.
239
- 8. **When all stories under all referenced RFCs are done** — the problem is fix-released. Include the problem doc closure in the final commit (`git mv` to `.verifying.md`, update Status) per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule. Push, create changeset, release per the lean release principle.
239
+ 8. **When all stories under all referenced RFCs are done** — push and verify the implementation pipeline first. When release preparation is intentional, create one complete cumulative changeset-only commit, release it, then include the problem's Verification Pending transition with the release evidence per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule.
240
240
 
241
241
  **Legacy empty-stories back-fill (per the "Every RFC has at least one story" architecture rule)**: a pre-the "Every RFC has at least one story" architecture rule RFC whose `stories:` is empty is a **back-fill** case, not an atomic fallback — the "Every RFC has at least one story" architecture rule requires ≥1 story, so the empty-stories atomic dispatch is removed. Decompose the fix into ≥1 story on the RFC's story map (add the story, transition it `accepted`), then traverse it via the normal story path above. The `Refs: RFC-<NNN>` trailer remains valid for **cross-cutting RFC work with no single story** (e.g. an RFC-level enforcement change spanning several files); it is no longer an atomic-empty-stories fallback. Legacy on-disk RFCs still carrying `stories: []` are surfaced for back-fill by `wr-itil-check-rfc-has-stories` at their next `manage-rfc accepted` transition.
242
242
 
243
- **Legacy direct-implementation path** (step 1 no-RFCs case): a Phase 1-shape Known Error whose Fix Strategy references no RFCs continues to work via the pre-Phase-2 flow — read the root cause analysis and fix strategy, implement the fix following the project's development workflow, include the problem doc closure in the fix commit (`git mv` to `.verifying.md`, update Status), push + changeset + release. This preserves backwards compatibility with all existing Known Error problems (which were captured before the RFC framework was Phase-1-graduated).
243
+ **Legacy direct-implementation path** (step 1 no-RFCs case): a Phase 1-shape Known Error whose Fix Strategy references no RFCs continues to work via the pre-Phase-2 flow — read the root cause analysis and fix strategy, implement the fix following the project's development workflow, push and verify the implementation, then intentionally prepare and release one cumulative changeset-only commit before transitioning the problem. This preserves backwards compatibility with all existing Known Error problems (which were captured before the RFC framework was Phase-1-graduated).
244
244
 
245
245
  **Scope expansion during work:** If investigation or architect review reveals that the problem's scope has grown significantly (e.g., effort re-sized from S to L, additional files discovered), use `AskUserQuestion` before continuing:
246
246
  - Option 1: `Continue with expanded scope` — keep working this problem at its new size
@@ -1150,13 +1150,13 @@ Commit the completed work per the "Governance Skills Commit Their Own Completed
1150
1150
  - Fix implemented: `fix(<scope>): <description> (closes P<NNN>)` — include problem file changes (rename to `.verifying.md` + `## Fix Released` section) in the same commit per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule
1151
1151
  5. If commit risk is above appetite: auto-apply scorer remediations per **the "Auto-apply scorer remediations to reach within appetite — open action-class vocabulary" architecture rule Rule 1** incrementally until residual commit risk is within appetite (at or below the effective appetite used by the scorer and gate), OR halt per the "Auto-apply scorer remediations to reach within appetite — open action-class vocabulary" architecture rule Rule 5 if the scorer cannot converge. **The skill MUST NOT commit above appetite, and MUST NOT call `AskUserQuestion` to ask whether to commit anyway** (the "Skills, agents, and hooks override RISK-POLICY appetite instead of applying it" problem/the ": Apply RISK-POLICY appetite faithfully across all surfaces" release design amendment 2026-06-24 — above-appetite is framework-mediated, never a category-3 one-time-override; same invariant the push/release branch at Step 12 already enforces). The "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 fail-safe (no `AskUserQuestion` available / non-interactive → skip the commit and report the uncommitted state) remains the terminal fallback. This applies only to the risk-above-appetite branch, not to the delegation-unavailable case above.
1152
1152
 
1153
- **Multi-commit slice changeset discipline (the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem Phase 2)**: when a single logical fix lands across multiple the "Governance Skills Commit Their Own Completed Work" architecture rule-grain commits targeting the same plugin (e.g. helper extraction in commit 1, callers wired in commit 2, SKILL note + transition in commit 3 — all `packages/<plugin>/`), author ONE changeset on the first commit in the slice. Subsequent same-plugin commits do NOT need their own changeset — the `itil-changeset-discipline.sh` hook's Check 2b recognises any `.changeset/*.md` already in the unpushed slice scope (`origin/<base>..HEAD` + untracked + modified-not-staged) that targets `"@windyroad/<plugin>": <any-bump>` and allows. This eliminates the per-commit changeset ceremony that previously produced N redundant `.changeset/*.md` files for one logical release entry (changesets-action collapses bump-class at version-package time, so per-commit changesets rendered N near-identical CHANGELOG bullets for one release). Once a changeset hits `origin/<base>` (drained at release time), it no longer counts — a fresh changeset is required for the next slice. Cross-plugin coverage is NOT permitted: an `@windyroad/itil` changeset does not satisfy a `packages/voice-tone/` commit.
1153
+ **Release-preparation boundary (the ": Commit-time changeset enforcement forces premature release metadata" problem)**: ordinary implementation commits do not include changesets and remain `STAGED`. After the exact implementation pipeline passes and release preparation is intentional, inspect the cumulative package scope and create one complete changeset-only commit. Never create speculative, placeholder, dormant-foundation, or “for later” changesets.
1154
1154
 
1155
1155
  ### 12. Auto-release when changesets are queued (the "Reuse valid cumulative risk assessments across pipeline actions" architecture rule)
1156
1156
 
1157
1157
  **Skip this step if the skill is running inside an AFK orchestrator** (e.g. `/wr-itil:work-problems`). Orchestrators handle release cadence themselves per the "Inter-iteration release cadence for AFK loops" architecture rule (Step 6.5). Detect via the presence of an orchestrator marker in the invoking prompt — look for phrases like "AFK", "work-problems", "batch-work", or the sentinel `ALL_DONE` convention. When in doubt, defer to the orchestrator by skipping this step.
1158
1158
 
1159
- Otherwise, after the commit in step 11 lands, drain the release queue so the fix actually lands on npm without requiring manual user action.
1159
+ Otherwise, push and verify the implementation commit first. Only after that exact pipeline passes, intentionally prepare the release by creating one complete cumulative changeset-only commit, then drain the release queue.
1160
1160
 
1161
1161
  **Mechanism — reuse a valid cumulative assessment; delegate when needed (the "Reuse valid cumulative risk assessments across pipeline actions" architecture rule, the "On-Demand Assessment Skills for Governance Plugins" architecture rule):**
1162
1162
 
@@ -1164,7 +1164,7 @@ Otherwise, after the commit in step 11 lands, drain the release queue so the fix
1164
1164
  - **Primary**: delegate to subagent type `wr-risk-scorer:pipeline` via the Agent tool.
1165
1165
  - **Fallback**: if that subagent type is not available, invoke skill `/wr-risk-scorer:assess-release` via the Skill tool. The skill wraps the same pipeline subagent.
1166
1166
  2. Read `RISK_SCORES: commit=X push=Y release=Z` from the valid assessment. Never substitute the commit score for a push or release score. The action-time `push:watch` and `release:watch` gates still check the relevant score, checkout, state, expiry, appetite, and CI; if a gate rejects stale or changed evidence, resolve the checkout binding or rescore before retrying.
1167
- 3. **Drain condition**: if `push` and `release` are both within appetite (at or below the effective appetite used by the scorer and gate), AND `.changeset/` is non-empty, proceed to the drain action. Otherwise, skip the drain and report the unreleased state.
1167
+ 3. **Drain condition**: first run `npm run push:watch` for unpushed implementation work without creating release metadata. After the exact implementation pipeline passes, if release preparation is intentional, create one complete cumulative changeset-only commit, refresh the assessment, and proceed when `push` and `release` are both within appetite. Otherwise, report the `STAGED` state without starting release work.
1168
1168
 
1169
1169
  **Drain action (non-interactive, policy-authorised per the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6):**
1170
1170
 
@@ -728,7 +728,7 @@ rm -f "$ITER_JSON" "$ITERATION_PROMPT_FILE"
728
728
 
729
729
  1. **Context (the unattended declaration — load-bearing carrier per the "Per-ticket goal anchors each AFK iteration" architecture rule)**: this is one iteration of the AFK work-problems loop. <!-- UNATTENDED-DECLARATION-SOURCE --> **The user is AFK and this run is unattended.** The orchestrator selected `P<NNN> (<title>)` as the highest-WSJF actionable ticket. This sentence is the **discriminator** the singular skill keys its pinned short-circuit on (`/wr-itil:work-problem` Step 1): prose cannot observe a TTY and a `claude -p` subprocess offers nothing to sniff, so the unattended state is **declared by the dispatcher**, never detected by the skill. Do not drop or reword the declaration — a rule keyed on something the skill cannot observe silently collapses to always or never.
730
730
  2. **Task**: run `/wr-itil:work-problem P<NNN>` — the singular skill, pinned to the ticket the orchestrator already selected. It is the loop's per-iteration execution unit (both SKILLs have long documented this; the "Per-ticket goal anchors each AFK iteration" architecture rule makes the dispatch match). It delegates the actual work to `/wr-itil:manage-problem <NNN>`, so the manage-problem workflow still runs verbatim — architect / jtbd / style-guide / voice-tone gate reviews and the commit gate (manage-problem Step 11) all apply. Because this subprocess has the Agent tool in its own surface, the normal review-via-subagent paths work — no inline-verdict fallback needed. Because the dispatch is pinned AND declares the run unattended, the singular skill skips its ranking-freshness check (it must not delegate a README refresh, must not prompt, must not commit a ranking rewrite inside this per-ticket unit of work).
731
- 3. **Constraints**: commit the completed work per the "Governance Skills Commit Their Own Completed Work" architecture rule. Do NOT push, do NOT run `push:watch`, do NOT run `release:watch` — the orchestrator's Step 6.5 owns release cadence. Do NOT invoke `capture-*` background skills mid-iter (AFK carve-out — the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule), **EXCEPT** (a) **retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem mechanical-stage carve-out** (see retro-on-exit constraint #4 below; same trust-boundary as `/wr-retrospective:run-retro` Step 4a verification close-on-evidence — the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem); and (b) **the I13 fix-time row draw** — when the propose-fix gate inside the delegated `/wr-itil:manage-problem` traversal detects a Known Error nothing yet proposes a fix for (`wr-itil-check-fix-rfc-trace` emits a `no-rfc-trace:` directive), the iter **draws a release row on a story map that already covers the journey**, gives it at least one story card, and makes that card's story name the problem in its own `problems:` list — then proceeds. **A fix proposal is a release row; it is never a new document under `docs/rfcs/`.** Take the identity from the directive, which comes from `wr-itil-next-rfc-id` — the single rule that sees rows, documents and git history at once, and the only one that will not re-issue an identity a row already holds. **UNLESS** an existing vehicle cited in the ticket is already this ticket's fix and merely lacks the trace edge, in which case the iter **wires** that edge — a card on the existing row, or the `problems:` array of a legacy document — rather than drawing a duplicate that fragments the fix across two vehicles (the "manage-problem I13 propose-fix gate auto-creates a new RFC instead of wiring an existing fix-vehicle's trace edge" problem; existing-vehicle-untraced sub-case; vehicle-vs-merely-related is a judgement read of citation context, structured-logged as `I13: wired P<NNN> trace edge into existing fix vehicle <ID>`; the load-bearing branch prose lives in the delegated `/wr-itil:manage-problem` I13 gate). This is NOT an aside-capture distraction: the row is the **mandatory vehicle for THIS iter’s own fix** (the "Every fix goes through an RFC" architecture rule), not a tangential observation — it is in-scope working of the current ticket, framework-mediated (NOT cat-1 direction-setting → NO `AskUserQuestion`, the "Agents over-ask in interactive sessions — conflating mechanical-stages with user-interactive-stages of multi-stage skill contracts (inverse-)" problem), and drawing a row onto a map a person has already approved inherits that approval rather than needing a fresh one. **Two things the iter must NOT do silently**: change an existing map in a way that would alter what its approval covers — a new activity column or a new job on the map’s traces, judged from `oversight_map_substance_keys()` in `lib/story-oversight.sh`, the one place those keys are enumerated — or pick a fix approach no existing decision record covers. Either of those queues ONE entry at `outstanding_questions` and the iter moves to the next problem; the loop is never stopped for it. The predicate can also refuse outright (exit 3), and the two refusals are handled differently: a map edited without being re-rendered is **mechanical** — re-render it with `wr-itil-render-story-map` and ask again, asking nobody — while a repository with no story maps at all triggers complete unconfirmed proposal authoring through `/wr-itil:capture-story-map` and `/wr-itil:capture-rfc`, without `AskUserQuestion`. The iter creates the initial activities, identified release row, cards, and story files; queues exactly one `outstanding_questions` item to ratify the completed map; blocks source and story implementation until ratification; and continues independent work. Structured-log the draw event to the iter summary (`notes`) per the ": Progress the Backlog While I'm Away" user outcome audit-trail. Do NOT use `ScheduleWakeup` under any circumstance (the "Problem 083: work-problems Step 5 iteration-worker prompt does not forbid ScheduleWakeup / time-deferring primitives — subagent can abandon synchronous-completion contract" problem — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (the "Decision-delegation contract — agents over-apply Rule 1's interactive default to framework-resolved decisions; codify the framework-resolution boundary + AFK loop's batched-questions-as-deliverable + lazy-AskUserQuestion measurement" problem / the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule): direction / deviation-approval / one-time-override / silent-framework observations queue at `ITERATION_SUMMARY.outstanding_questions` for loop-end batched presentation. **This includes the manage-problem substance-confirm-before-build guard (the ": Confirm a decision's substance before building dependent work on it" architecture rule (Confirm a decision's substance before building dependent work)):** when the propose-fix step detects that the fix builds on a born-`proposed` decision whose substance is unconfirmed (via `wr-architect-is-decision-unconfirmed`), the iter does NOT implement on it and does NOT ask mid-loop — it queues a `category: "direction"` entry naming the unconfirmed ADR + its Decision Outcome for loop-end confirmation, and routes the ticket to `action: skipped`, `skip_reason_category: user-answerable`. Building on the unconfirmed substance instead (or guessing the choice) is the "Agent implements dependent work on genuine new decisions before human-confirming their SUBSTANCE — surfaces only meta-questions" problem failure this guard exists to prevent. The queued substance-confirm is a legitimate cat-1 direction ask — it is NOT counted as lazy in the Step 2d Ask Hygiene Pass (the ": Confirm a decision's substance before building dependent work on it" architecture rule lazy-count exclusion). Per-iter `AskUserQuestion` calls are sub-contracting framework-resolved decisions back to the user (lazy deferral per Step 2d Ask Hygiene Pass classification). Non-interactive defaults apply per the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 + the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule's framework-resolution boundary. **Treat the user as transient** (the "`/wr-itil:work-problems` orchestrator defaults to subprocess dispatch even when the user is observably interactive — loses real-time presence advantage" problem): even when observably present at orchestrator dispatch time, the user may answer one question and disappear for hours; presence is not a reliable signal and is not the goal. The iter's job is to progress the ticket and accumulate questions for batched surfacing — not to ask "is it OK to proceed?" at a mechanical-stage boundary. **Do NOT poll `bats` output with a bats-console-summary regex against TAP-format output** (the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem — bash until-loop-deadlock antipattern). The bats-console-summary line `<N> tests, <M> failures` is emitted ONLY by bats's *default* (non-TAP) formatter; `bats --tap` does not emit a console summary, so a polling loop of shape `until [ -f $OUT ] && grep -qE '^[0-9]+ tests?,' $OUT; do sleep 5; done` spins forever after bats completes (silent deadlock — no error, no exit; recovery requires manual SIGTERM with metadata loss per the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "SIGTERM-clean-flush guarantee is conditional on subprocess having emitted ITERATION_SUMMARY before going idle — needs SKILL.md caveat + behavioural-test second-source for stuck-before-emit subclass" problem stuck-before-emit subclass). When you need to wait on a backgrounded bats run, prefer `wait $bg_pid` (Unix idiom — completion signaled by process exit, no regex required) or, for the Bash tool, `run_in_background=true` + `BashOutput` polling on the tool's exit-state field rather than regex-poll on stdout. If you genuinely must regex-poll TAP output, anchor on the TAP plan line `^[0-9]+\.\.[0-9]+` (e.g. `1..1455`) — TAP's plan line is emitted on completion and is format-stable across bats versions; the bats-console-summary line is not. The console-summary vs TAP-format divergence is the load-bearing detail: `bats` and `bats --tap` produce structurally different stdout, and the antipattern assumes the former when iter dispatch typically uses the latter. **Do NOT poll subprocess completion with `pgrep -f '<pattern>'` inside an `until` / `while` loop** (the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem — self-referential pgrep deadlock; sibling variant of the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem). `pgrep -f` matches against the FULL command line of every running process, so the polling loop's own `zsh -c` argument (which contains the literal `pgrep -f '<pattern>'` text) matches itself; with multiple concurrent polling loops, each loop matches the others and spins forever. Worked example of the antipattern: `until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done` — the 2026-05-16 the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem deadlock witness; 4 concurrent polling loops each matched the others' command lines while no actual bats process ran; 45 min wall-clock + $20-30 wasted before manual SIGTERM. The same self-reference shape applies to `while pgrep -f ...; do sleep; done` and to `until ! pkill -0 -f '<pattern>'` / `while pkill -0 -f '<pattern>'` (signal-0 polling). The structural fix is the same as the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem: prefer `wait $bg_pid` (Unix idiom — shell-native completion signal, no regex / no pgrep) or Bash-tool `run_in_background=true` + `BashOutput` polling (harness-tracked completion state). The hook `packages/itil/hooks/itil-bash-polling-antipattern-detect.sh` denies these shapes at PreToolUse:Bash, but the prompt rule belongs here too — structural enforcement + prompt discipline together close the class. **Do NOT leave a backgrounded task unreaped at turn-end** (`run_in_background: true` on an Agent or Bash tool call, or a `&`-detached shell job, whose completion you intend to observe in a *later* turn) inside iter dispatch contexts (the "Iter subprocess ends its turn waiting on a backgrounded task and never resumes — `claude -p` has no auto-resume; commit-bearing work is lost" problem — turn-end-mid-background work-loss; sibling-class to the "Problem 083: work-problems Step 5 iteration-worker prompt does not forbid ScheduleWakeup / time-deferring primitives — subagent can abandon synchronous-completion contract" problem / the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem / the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem). The iter subprocess is dispatched via `claude -p`, a single-shot CLI invocation with NO auto-resume affordance: its turn boundary IS its process boundary. A background task that outlives the turn never resumes — the iter exits at turn-end with the task incomplete and its own work staged but uncommitted (witnessed: iter 11 of a prior loop — $8.02 / 17 min / 8 staged files / 11 GREEN bats / ZERO commits; recovery required orchestrator main-turn salvage). **The prohibition is on the cross-turn / turn-end-survivor shape, NOT on backgrounding per se:** the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem-sanctioned idiom of launching `run_in_background=true` + `BashOutput`-poll-then-`wait $bg_pid` (or plain `wait $bg_pid` on a `&` job) **within the same turn** is fine — it reaps the task before turn-end. Use foreground-synchronous invocation instead: the Agent tool WITHOUT `run_in_background: true` (the result returns in-turn, so the commit step is reached), or intra-turn background that you `wait` on before the turn closes. The distinction from the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem polling antipatterns: those forbid *how* you wait (regex / pgrep poll loops); this forbids *deferring a task's completion past the turn boundary*, where `claude -p` has no notification re-entry to bring you back. The interactive Claude Code session masks this hazard (notification-driven re-entry); the AFK iter subprocess does not. **If the fix changes shippable code or package behaviour** (any path under `packages/<plugin>/{src,bin,hooks,skills,scripts,lib,agents}` excluding test paths — `test/`, `hooks/test/`, `scripts/test/` — and excluding `README.md` + `docs/*.md`), **the iter MUST author a `.changeset/*.md` entry in the same single the "Governance Skills Commit Their Own Completed Work" architecture rule-grain commit as the fix** (the changeset names the bumping plugin via the YAML frontmatter `"@windyroad/<plugin>": <patch|minor|major>` per the changesets-action contract). **Doc-only changes** (under `docs/`, `*.md`) **and test-only changes** (under any `test/` path) **that ship no behaviour MAY omit the changeset**. The orchestrator's Step 6.5 release-cadence drain runs `release:watch` only when `.changeset/` is non-empty after push — without an iter-authored changeset, code-shape fixes accumulate without ever shipping to npm (violating the ": Progress the Backlog While I'm Away" user outcome's audit-trail expectation + the ": Keep Plugins Current Across Projects" user outcome's "Keep Plugins Current" closure dependency). Hook `packages/itil/hooks/itil-changeset-discipline.sh` (the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem) provides hook-level enforcement at `git commit` time as defence-in-depth — but plugin hook execution depends on the marketplace cache carrying the current hook version, so the prompt-time constraint here MUST land independently (composes-with the hook; does NOT rely on the hook being installed). Inbound-reported from downstream consumer bbstats as their the "Briefing Tier 3 rotation repeat-deferral — 13 of 14 topic files over budget with 2 in MUST_SPLIT (≥2× ceiling) branch" problem — see [Related](#related) for `**Origin**: inbound-reported (bbstats#195)` per the "Inbound-reported problems rank ahead of internally-discovered problems via a sort tier" architecture rule. **`@jtbd the ": Progress the Backlog While I'm Away" user outcome`** (load-bearing) **`@jtbd the ": Keep Plugins Current Across Projects" user outcome`** (closure-dependent).
731
+ 3. **Constraints**: commit the completed work per the "Governance Skills Commit Their Own Completed Work" architecture rule. Do NOT push, do NOT run `push:watch`, do NOT run `release:watch` — the orchestrator's Step 6.5 owns release cadence. Do NOT invoke `capture-*` background skills mid-iter (AFK carve-out — the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule), **EXCEPT** (a) **retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem mechanical-stage carve-out** (see retro-on-exit constraint #4 below; same trust-boundary as `/wr-retrospective:run-retro` Step 4a verification close-on-evidence — the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem); and (b) **the I13 fix-time row draw** — when the propose-fix gate inside the delegated `/wr-itil:manage-problem` traversal detects a Known Error nothing yet proposes a fix for (`wr-itil-check-fix-rfc-trace` emits a `no-rfc-trace:` directive), the iter **draws a release row on a story map that already covers the journey**, gives it at least one story card, and makes that card's story name the problem in its own `problems:` list — then proceeds. **A fix proposal is a release row; it is never a new document under `docs/rfcs/`.** Take the identity from the directive, which comes from `wr-itil-next-rfc-id` — the single rule that sees rows, documents and git history at once, and the only one that will not re-issue an identity a row already holds. **UNLESS** an existing vehicle cited in the ticket is already this ticket's fix and merely lacks the trace edge, in which case the iter **wires** that edge — a card on the existing row, or the `problems:` array of a legacy document — rather than drawing a duplicate that fragments the fix across two vehicles (the "manage-problem I13 propose-fix gate auto-creates a new RFC instead of wiring an existing fix-vehicle's trace edge" problem; existing-vehicle-untraced sub-case; vehicle-vs-merely-related is a judgement read of citation context, structured-logged as `I13: wired P<NNN> trace edge into existing fix vehicle <ID>`; the load-bearing branch prose lives in the delegated `/wr-itil:manage-problem` I13 gate). This is NOT an aside-capture distraction: the row is the **mandatory vehicle for THIS iter’s own fix** (the "Every fix goes through an RFC" architecture rule), not a tangential observation — it is in-scope working of the current ticket, framework-mediated (NOT cat-1 direction-setting → NO `AskUserQuestion`, the "Agents over-ask in interactive sessions — conflating mechanical-stages with user-interactive-stages of multi-stage skill contracts (inverse-)" problem), and drawing a row onto a map a person has already approved inherits that approval rather than needing a fresh one. **Two things the iter must NOT do silently**: change an existing map in a way that would alter what its approval covers — a new activity column or a new job on the map’s traces, judged from `oversight_map_substance_keys()` in `lib/story-oversight.sh`, the one place those keys are enumerated — or pick a fix approach no existing decision record covers. Either of those queues ONE entry at `outstanding_questions` and the iter moves to the next problem; the loop is never stopped for it. The predicate can also refuse outright (exit 3), and the two refusals are handled differently: a map edited without being re-rendered is **mechanical** — re-render it with `wr-itil-render-story-map` and ask again, asking nobody — while a repository with no story maps at all triggers complete unconfirmed proposal authoring through `/wr-itil:capture-story-map` and `/wr-itil:capture-rfc`, without `AskUserQuestion`. The iter creates the initial activities, identified release row, cards, and story files; queues exactly one `outstanding_questions` item to ratify the completed map; blocks source and story implementation until ratification; and continues independent work. Structured-log the draw event to the iter summary (`notes`) per the ": Progress the Backlog While I'm Away" user outcome audit-trail. Do NOT use `ScheduleWakeup` under any circumstance (the "Problem 083: work-problems Step 5 iteration-worker prompt does not forbid ScheduleWakeup / time-deferring primitives — subagent can abandon synchronous-completion contract" problem — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (the "Decision-delegation contract — agents over-apply Rule 1's interactive default to framework-resolved decisions; codify the framework-resolution boundary + AFK loop's batched-questions-as-deliverable + lazy-AskUserQuestion measurement" problem / the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule): direction / deviation-approval / one-time-override / silent-framework observations queue at `ITERATION_SUMMARY.outstanding_questions` for loop-end batched presentation. **This includes the manage-problem substance-confirm-before-build guard (the ": Confirm a decision's substance before building dependent work on it" architecture rule (Confirm a decision's substance before building dependent work)):** when the propose-fix step detects that the fix builds on a born-`proposed` decision whose substance is unconfirmed (via `wr-architect-is-decision-unconfirmed`), the iter does NOT implement on it and does NOT ask mid-loop — it queues a `category: "direction"` entry naming the unconfirmed ADR + its Decision Outcome for loop-end confirmation, and routes the ticket to `action: skipped`, `skip_reason_category: user-answerable`. Building on the unconfirmed substance instead (or guessing the choice) is the "Agent implements dependent work on genuine new decisions before human-confirming their SUBSTANCE — surfaces only meta-questions" problem failure this guard exists to prevent. The queued substance-confirm is a legitimate cat-1 direction ask — it is NOT counted as lazy in the Step 2d Ask Hygiene Pass (the ": Confirm a decision's substance before building dependent work on it" architecture rule lazy-count exclusion). Per-iter `AskUserQuestion` calls are sub-contracting framework-resolved decisions back to the user (lazy deferral per Step 2d Ask Hygiene Pass classification). Non-interactive defaults apply per the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 + the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule's framework-resolution boundary. **Treat the user as transient** (the "`/wr-itil:work-problems` orchestrator defaults to subprocess dispatch even when the user is observably interactive — loses real-time presence advantage" problem): even when observably present at orchestrator dispatch time, the user may answer one question and disappear for hours; presence is not a reliable signal and is not the goal. The iter's job is to progress the ticket and accumulate questions for batched surfacing — not to ask "is it OK to proceed?" at a mechanical-stage boundary. **Do NOT poll `bats` output with a bats-console-summary regex against TAP-format output** (the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem — bash until-loop-deadlock antipattern). The bats-console-summary line `<N> tests, <M> failures` is emitted ONLY by bats's *default* (non-TAP) formatter; `bats --tap` does not emit a console summary, so a polling loop of shape `until [ -f $OUT ] && grep -qE '^[0-9]+ tests?,' $OUT; do sleep 5; done` spins forever after bats completes (silent deadlock — no error, no exit; recovery requires manual SIGTERM with metadata loss per the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "SIGTERM-clean-flush guarantee is conditional on subprocess having emitted ITERATION_SUMMARY before going idle — needs SKILL.md caveat + behavioural-test second-source for stuck-before-emit subclass" problem stuck-before-emit subclass). When you need to wait on a backgrounded bats run, prefer `wait $bg_pid` (Unix idiom — completion signaled by process exit, no regex required) or, for the Bash tool, `run_in_background=true` + `BashOutput` polling on the tool's exit-state field rather than regex-poll on stdout. If you genuinely must regex-poll TAP output, anchor on the TAP plan line `^[0-9]+\.\.[0-9]+` (e.g. `1..1455`) — TAP's plan line is emitted on completion and is format-stable across bats versions; the bats-console-summary line is not. The console-summary vs TAP-format divergence is the load-bearing detail: `bats` and `bats --tap` produce structurally different stdout, and the antipattern assumes the former when iter dispatch typically uses the latter. **Do NOT poll subprocess completion with `pgrep -f '<pattern>'` inside an `until` / `while` loop** (the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem — self-referential pgrep deadlock; sibling variant of the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem). `pgrep -f` matches against the FULL command line of every running process, so the polling loop's own `zsh -c` argument (which contains the literal `pgrep -f '<pattern>'` text) matches itself; with multiple concurrent polling loops, each loop matches the others and spins forever. Worked example of the antipattern: `until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done` — the 2026-05-16 the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem deadlock witness; 4 concurrent polling loops each matched the others' command lines while no actual bats process ran; 45 min wall-clock + $20-30 wasted before manual SIGTERM. The same self-reference shape applies to `while pgrep -f ...; do sleep; done` and to `until ! pkill -0 -f '<pattern>'` / `while pkill -0 -f '<pattern>'` (signal-0 polling). The structural fix is the same as the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem: prefer `wait $bg_pid` (Unix idiom — shell-native completion signal, no regex / no pgrep) or Bash-tool `run_in_background=true` + `BashOutput` polling (harness-tracked completion state). The hook `packages/itil/hooks/itil-bash-polling-antipattern-detect.sh` denies these shapes at PreToolUse:Bash, but the prompt rule belongs here too — structural enforcement + prompt discipline together close the class. **Do NOT leave a backgrounded task unreaped at turn-end** (`run_in_background: true` on an Agent or Bash tool call, or a `&`-detached shell job, whose completion you intend to observe in a *later* turn) inside iter dispatch contexts (the "Iter subprocess ends its turn waiting on a backgrounded task and never resumes — `claude -p` has no auto-resume; commit-bearing work is lost" problem — turn-end-mid-background work-loss; sibling-class to the "Problem 083: work-problems Step 5 iteration-worker prompt does not forbid ScheduleWakeup / time-deferring primitives — subagent can abandon synchronous-completion contract" problem / the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem / the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem). The iter subprocess is dispatched via `claude -p`, a single-shot CLI invocation with NO auto-resume affordance: its turn boundary IS its process boundary. A background task that outlives the turn never resumes — the iter exits at turn-end with the task incomplete and its own work staged but uncommitted (witnessed: iter 11 of a prior loop — $8.02 / 17 min / 8 staged files / 11 GREEN bats / ZERO commits; recovery required orchestrator main-turn salvage). **The prohibition is on the cross-turn / turn-end-survivor shape, NOT on backgrounding per se:** the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem-sanctioned idiom of launching `run_in_background=true` + `BashOutput`-poll-then-`wait $bg_pid` (or plain `wait $bg_pid` on a `&` job) **within the same turn** is fine — it reaps the task before turn-end. Use foreground-synchronous invocation instead: the Agent tool WITHOUT `run_in_background: true` (the result returns in-turn, so the commit step is reached), or intra-turn background that you `wait` on before the turn closes. The distinction from the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem polling antipatterns: those forbid *how* you wait (regex / pgrep poll loops); this forbids *deferring a task's completion past the turn boundary*, where `claude -p` has no notification re-entry to bring you back. The interactive Claude Code session masks this hazard (notification-driven re-entry); the AFK iter subprocess does not. **Release-preparation boundary (the ": Commit-time changeset enforcement forces premature release metadata" problem):** ordinary implementation commits MUST NOT include changesets and remain `STAGED`. After the exact implementation pipeline passes and release preparation is intentional, the orchestrator inspects the cumulative package scope and creates one complete changeset-only commit. Never create speculative, placeholder, dormant-foundation, or “for later” changesets. **`@jtbd the ": Progress the Backlog While I'm Away" user outcome`** (load-bearing) **`@jtbd the ": Keep Plugins Current Across Projects" user outcome`** (closure-dependent).
732
732
 
733
733
  4. **Retro-on-exit (the "Problem 086: AFK iteration subprocess does not run retro before returning — per-iteration lessons learnt are lost when the subprocess exits" problem) + retro-surfaced observation classification (the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem) + iter-owned BRIEFING commit (the "work-problems iteration boundary leaves run-retro BRIEFING.md edits uncommitted" problem)**: before emitting `ITERATION_SUMMARY`, invoke `/wr-retrospective:run-retro`. Retro runs INSIDE this subprocess so its Step 2b pipeline-instability scan has access to the iteration's rich tool-call history (hook misbehaviour, repeat-workaround patterns, subagent-delegation friction, release-path instability). Tickets retro creates ride a separate path: they delegate through `/wr-itil:manage-problem` which IS the "Governance Skills Commit Their Own Completed Work" architecture rule in-scope and self-commits each ticket per its own Step 11. Those commits land independently and the orchestrator picks them up on the next Step 1 scan.
734
734
 
@@ -989,18 +989,18 @@ After the iteration's commit lands but before starting the next iteration, check
989
989
  3. **Classify the residual + queue state (the "work-problems Step 6.5 "≤3 within appetite — no drain" clause defers low-risk releases, encoding accumulation" problem)**:
990
990
  - Use the scorer's **effective appetite** (`RISK_APPETITE` override, then `RISK-POLICY.md`, then default 5); a score equal to that appetite is within it.
991
991
  - **Above appetite (push or release score > effective appetite)** — route to the **Above-appetite branch** below. Do NOT drain. Do NOT proceed to Step 6.75 until either (a) the auto-apply loop re-converges within appetite and drain succeeds, or (b) Rule 5 halt fires.
992
- - **Within appetite (both scores ≤ effective appetite) AND there is releasable material** (any unpushed commits on `HEAD..origin/<base>` OR any entries in `.changeset/`) — drain the queue per the Drain action below, then proceed to Step 6.75. The release-action threshold is "is there something to release?", NOT "has accumulated risk reached the safety band?" Per user direction 2026-05-17 (the "work-problems Step 6.5 "≤3 within appetite — no drain" clause defers low-risk releases, encoding accumulation" problem Description): *"If it's low risk, you should release."* Low cost to release + low residual risk = release now; never accumulate.
992
+ - **Within appetite (both scores ≤ effective appetite) AND there is integration or release work** (any unpushed commits on `HEAD..origin/<base>` OR any entries in `.changeset/`) — drain the queue per the Drain action below, then proceed to Step 6.75. Integration comes first without release metadata; release preparation begins only after the exact implementation pipeline passes.
993
993
  - **Within appetite (both scores ≤ effective appetite) AND empty queue** (no unpushed commits AND no `.changeset/` entries) — no drain (literally nothing to release). Proceed to Step 6.75. This is the genuine no-op fast-path; the gate is *absence of releasable material*, not residual band.
994
994
 
995
995
  **Drain action (non-interactive, policy-authorised per the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6):**
996
996
 
997
- 1. Run `npm run push:watch` (push + wait for CI to pass).
998
- 2. If `.changeset/` is non-empty after push, run `npm run release:watch` (merge the release PR + wait for npm publish).
999
- 3. Resume the loop only after the release lands on npm.
1000
- 4. **Post-release K→V auto-transition (the ".known-error.md → .verifying.md transition not happening consistently at release time" problem)**: if step 2 actually ran AND succeeded (a release shipped to npm), fire the K→V auto-transition callback for `.known-error.md` tickets whose Release-vehicle citation matches a just-shipped changeset. See the **Post-release K→V auto-transition** subsection below for the full contract.
1001
- 5. **Post-release cache refresh (the "AFK iter subprocess plugin cache stale after release — just-shipped hook does not protect the next iter" problem)**: if step 2 actually ran AND succeeded (a release shipped to npm), chain `/install-updates` to refresh the plugin cache before the next iter dispatches. Skipped when step 2 was a no-op (empty `.changeset/` after push; no new plugin version exists). See the **Post-release cache refresh** subsection below for the full contract.
997
+ 1. Run `npm run push:watch` for ordinary implementation commits without adding a changeset, and wait for that exact pipeline to pass.
998
+ 2. If release preparation is intentional and the cumulative package scope has no queued changeset, create one complete changeset-only commit, refresh the risk assessment, then run `npm run push:watch` for that commit.
999
+ 3. If `.changeset/` remains non-empty, run `npm run release:watch` (merge the release PR + wait for npm publish). Otherwise resume with the implementation safely `STAGED`.
1000
+ 4. **Post-release K→V auto-transition (the ".known-error.md → .verifying.md transition not happening consistently at release time" problem)**: if step 3 actually ran AND succeeded (a release shipped to npm), fire the K→V auto-transition callback for `.known-error.md` tickets whose Release-vehicle citation matches a just-shipped changeset. See the **Post-release K→V auto-transition** subsection below for the full contract.
1001
+ 5. **Post-release cache refresh (the "AFK iter subprocess plugin cache stale after release — just-shipped hook does not protect the next iter" problem)**: if step 3 actually ran AND succeeded (a release shipped to npm), chain `/install-updates` to refresh the plugin cache before the next iter dispatches. Skipped when step 3 was a no-op (no release ran). See the **Post-release cache refresh** subsection below for the full contract.
1002
1002
 
1003
- **Post-release K→V auto-transition (the ".known-error.md → .verifying.md transition not happening consistently at release time" problem) — fires only after within-appetite Drain action step 2 (release:watch) succeeded:**
1003
+ **Post-release K→V auto-transition (the ".known-error.md → .verifying.md transition not happening consistently at release time" problem) — fires only after within-appetite Drain action step 3 (release:watch) succeeded:**
1004
1004
 
1005
1005
  the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule prescribes that Known Error tickets transition to Verification Pending on release, but until the ".known-error.md → .verifying.md transition not happening consistently at release time" problem there was no auto-fire surface to back-fill the transition once a fix ships. Iter subprocesses MUST NOT release (the orchestrator owns Step 6.5 per the iter dispatch constraints), so a fix that lands in iter N stays in `.known-error.md` until the orchestrator drains release in Step 6.5 — and prior to this callback, the K→V transition was silently deferred to "the next session" citing a misapplied the "`release-watch.sh` race condition — `gh pr list` queries before changesets/action GitHub workflow has created the release PR" problem amendment. The 2026-06-08 the "manage-problem has no cadence for checking upstream-bound tickets" problem empirical witness — `## Fix Released` populated with no K→V transition — confirmed the gap.
1006
1006
 
@@ -1012,13 +1012,13 @@ the "Problem lifecycle — add a Verification Pending status between Known Error
1012
1012
  4. After all candidates dispatched: emit one per-ticket transition outcome line to the iter summary in the form `K→V: P<NNN> | commit=<sha> | release=<vehicle>` (read from the dispatched transition-problem's `RELEASE_VEHICLE` block or Report-the-outcome stdout per Step 9 of transition-problem).
1013
1013
  5. Push the resulting K→V commits via `git push` (the release itself has already shipped — these are post-release audit-trail commits and do NOT require a second release:watch round-trip).
1014
1014
 
1015
- **Conditional on actual release**: only fires when `release:watch` actually published (step 2 of the Drain action above ran AND returned success). Skipped when `push:watch` ran alone (empty `.changeset/`; no new plugin version). Without this guard, the enumerator would scan `.known-error/` on every iter with no shipped changeset to match — wasted reads.
1015
+ **Conditional on actual release**: only fires when `release:watch` actually published (step 3 of the Drain action above ran AND returned success). Skipped when `push:watch` ran alone. Without this guard, the enumerator would scan `.known-error/` on every iter with no shipped changeset to match — wasted reads.
1016
1016
 
1017
1017
  **Non-blocking on individual transition failure**: if a dispatched `/wr-itil:transition-problem` fails (pre-flight reject, gate rejection, the "Problem 057: `git mv` + Edit + `git add` staging-ordering trap drops content edits from the commit" problem staging trap, derive helper transient error), the orchestrator logs the failure for that ticket and continues to the next candidate. A single transition failure MUST NOT halt the loop or block siblings in the same cohort. Persistent failures across multiple iters surface as accumulated `outstanding_questions` entries per the standard Step 2.5b discipline.
1018
1018
 
1019
1019
  **Policy authorisation (the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 5)**: rides the same Rule 5 silent-proceed that already covers `push:watch` / `release:watch` / `/install-updates` in the drain — the K→V auto-transition is mechanically downstream of release and shares its authorisation. The derive-helper-citation match against the just-shipped changeset is deterministic (filename equality), not a judgment call — squarely in the safe-default tier per the ": Progress the Backlog While I'm Away" user outcome "Decisions that would normally require my input are resolved using safe defaults".
1020
1020
 
1021
- **Mid-loop ask discipline (the "`/wr-itil:work-problems` orchestrator defaults to subprocess dispatch even when the user is observably interactive — loses real-time presence advantage" problem) preserved**: the dispatched transition-problem skill is wired to skip `AskUserQuestion` when invoked under AFK orchestrator context per its own the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 fail-safe (transition-problem SKILL.md Step 8 risk-above-appetite branch). The orchestrator MUST NOT introduce any `AskUserQuestion` call at the callback site — the per-candidate routing is framework-resolved per the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule, and the callback fires in a mechanical-stage transition between drain step 2 and step 5 (cache refresh).
1021
+ **Mid-loop ask discipline (the "`/wr-itil:work-problems` orchestrator defaults to subprocess dispatch even when the user is observably interactive — loses real-time presence advantage" problem) preserved**: the dispatched transition-problem skill is wired to skip `AskUserQuestion` when invoked under AFK orchestrator context per its own the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 fail-safe (transition-problem SKILL.md Step 8 risk-above-appetite branch). The orchestrator MUST NOT introduce any `AskUserQuestion` call at the callback site — the per-candidate routing is framework-resolved per the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule, and the callback fires in a mechanical-stage transition between drain step 3 and step 5 (cache refresh).
1022
1022
 
1023
1023
  **V→C is evidence-authorised, not maintainer-reserved (the "The Verification Pending → Closed transition is reserved for the maintainer, so evidence-based closure never fires and the verification queue grows without bound" problem)**: this callback fires for K→V (`known-error → verifying` — "fix released, awaiting verification"). It does not itself perform V→C. The V→C transition is available to the loop through the Step 4 classifier and the Step 6.1 verification drain, on the same terms every other surface uses: **cited evidence closes; inference never does; a recorded do-not-close marker blocks regardless**. The prior wording here reserved V→C for the maintainer's return, which — combined with the gate (0) blanket exclusion at Step 2.4 — left the verification queue with no agent-driven exit path at all (153 tickets, one populated evidence cell). That reservation was pre-the "Decision-delegation contract — agents over-apply Rule 1's interactive default to framework-resolved decisions; codify the framework-resolution boundary + AFK loop's batched-questions-as-deliverable + lazy-AskUserQuestion measurement" problem/the "VQ `Likely verified?` column uses age-based heuristic (≥14 days = yes) instead of session-observed evidence — sibling proxy-for-evidence anti-pattern to" problem residue and already contradicted the shipped `review-problems` Step 4 Bucket 1 and `run-retro` Step 4a sub-step 5, both of which close on evidence in AFK. What survives untouched is the honest half: a fix nobody exercised is not verified, and the maintainer remains the surface for contested evidence, partial fixes, and marker-blocked tickets.
1024
1024
 
@@ -1030,7 +1030,7 @@ Per the "Problem lifecycle — add a Verification Pending status between Known E
1030
1030
 
1031
1031
  After a successful release-cadence drain has shipped a new plugin version to npm, the orchestrator chains `/install-updates` to refresh the plugin cache before the next iter dispatches. Empirical evidence in `docs/briefing/afk-subprocess.md` ("Just-shipped gate-class hooks DON'T protect the immediate-next iter" entry) confirms iter subprocesses re-resolve plugin cache on spawn — so a just-shipped gate-class hook is inactive in the next iter unless the cache is refreshed first. The orchestrator IS the "restart" boundary for the next iter subprocess (each subprocess is a fresh `claude -p` per the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule + `afk-subprocess-mechanics.md`); the cache refresh between release:watch and next-iter dispatch is the load-bearing step.
1032
1032
 
1033
- - **Conditional on actual release**: only fires when `release:watch` actually published (step 2 of the Drain action above ran AND returned success). Skipped when `push:watch` ran alone (empty `.changeset/`; no new plugin version). Without this guard, every iter burns wall-clock + npm-API noise on a no-op cache refresh.
1033
+ - **Conditional on actual release**: only fires when `release:watch` actually published (step 3 of the Drain action above ran AND returned success). Skipped when `push:watch` ran alone. Without this guard, every iter burns wall-clock + npm-API noise on a no-op cache refresh.
1034
1034
  - **Non-blocking on /install-updates failure**: if `/install-updates` fails (transient marketplace fetch error, the "`/install-updates` Step 7 uses `claude plugin install` which silently no-ops when a plugin is already installed — updates never actually land" problem-class quirk re-emergence, cache-miss + Non-interactive fallback dry-run), the orchestrator logs the failure and continues the loop. Degrades to current behaviour — cache stays stale; next iter may recur the just-shipped issue, equivalent to pre-amendment behaviour. The cache-refresh chain MUST NOT halt the loop on `/install-updates` failure under any circumstance.
1035
1035
  - **Policy authorisation (the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 5)**: rides the same Rule 5 silent-proceed that already covers `push:watch` / `release:watch` in the drain — the post-release cache refresh is mechanically downstream of release and shares its authorisation. Composes with the "`/install-updates` Step 7 uses `claude plugin install` which silently no-ops when a plugin is already installed — updates never actually land" problem's claude-plugin-install no-op-when-already-installed factor (the chained `/install-updates` handles the uninstall+install dance per the "`/install-updates` Step 7 uses `claude plugin install` which silently no-ops when a plugin is already installed — updates never actually land" problem).
1036
1036
  - **Mid-loop ask discipline (the "`/wr-itil:work-problems` orchestrator defaults to subprocess dispatch even when the user is observably interactive — loses real-time presence advantage" problem) preserved**: if `/install-updates` Step 5b/5c consent gate fires (cache miss / scope delta / `INSTALL_UPDATES_RECONFIRM=1`), the orchestrator main turn treats this AS the **Non-interactive fallback** documented in `scripts/repo-local-skills/install-updates/SKILL.md` "Non-interactive fallback" subsection — log the dry-run output, do not interrupt the loop. The orchestrator's `.claude/.install-updates-consent` is normally present (install-updates Step 5a cache hit) so the gate fires silently. **the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule framework-resolution boundary** authorises this AskUserQuestion-available-but-forbidden routing: invocation between iters is a mechanical-stage transition the framework has resolved; surfacing it to the user would dilute the Step 2.5b accumulated-question discipline.
@@ -1379,6 +1379,5 @@ When every skipped ticket is in the `upstream-blocked` category (stop-condition
1379
1379
  - **the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule** (`docs/decisions/032-governance-skill-invocation-patterns.proposed.md`) — pattern taxonomy parent; Step 5 implements the AFK iteration-isolation wrapper — subprocess-boundary variant per the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem amendment (2026-04-21), refining the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem Agent-tool amendment. The "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem amendment remains in the ADR as the historical Agent-tool variant; the subprocess variant is the lead for new adopters.
1380
1380
  - **the "Skill testing strategy — contract-assertion bats companion to" architecture rule** (`docs/decisions/037-skill-testing-strategy.proposed.md`) — doc-lint bats contract-assertion pattern used by `test/work-problems-step-5-delegation.bats`.
1381
1381
  - **the "work-problems orchestrator carries prior-ticket Fix Strategy text into iter dispatch without re-grounding in design intent" problem** (`docs/problems/known-error/211-work-problems-orchestrator-carries-prior-ticket-fix-strategy-text-into-iter-dispatch-without-re-grounding.md`) — driver for Step 5 iteration-prompt-body's "Re-ground per iter" orchestrator-side construction invariant. The bug shape (reported as inbound from downstream consumer bbstats as their the "ADRs accumulate forward-chronology evidence inline (Phase 2 dogfood evidence, amendment history, cross-iter cross-references) — `decisions` bucket dominates context at 41% / 1.3 MiB" problem): the orchestrator builds each iter's dispatch prompt by reading the target ticket's `## Fix Strategy` section and citing it verbatim into the subprocess prompt; across iterations, prior-ticket Fix Strategy text leaks into subsequent dispatches without re-grounding in the new ticket's design intent, and iters land fixes anchored on the wrong design rationale. Fix: SKILL.md Step 5's "Iteration prompt body" section now carries an explicit re-grounding paragraph (immediately after the "self-contained" opener) that (a) names the per-iter re-ground invariant against current-ticket-ID + title only, (b) forbids inlining `## Fix Strategy` verbatim into the dispatch prompt (the subprocess reads it from disk via `/wr-itil:manage-problem`), (c) names the cross-iter leakage class (prior ticket ID, prior Fix Strategy text, prior outcome reason, prior commit SHA, prior retro findings, prior outstanding-questions), (d) names the construction shape (template-driven, reset per iter, no global accumulator). Behavioural second-source: `test/work-problems-step-5-prompt-body-re-grounding.bats` (structural-permitted per the "Behavioural-tests-default for skill testing" architecture rule Surface 2; tdd-review comment in fixture cites the "Problem 012: Skill Testing Harness Scope Undefined" problem as harness-gap). Composes with the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem (subprocess-boundary isolation — re-grounding is the symmetric orchestrator-side property of the subprocess's "no prior conversation context"), the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule (AFK iteration-isolation wrapper — re-grounding clarifies the wrapper's isolation intent on the orchestrator side), the ": Progress the Backlog While I'm Away" user outcome (load-bearing — audit trail degrades if iters work the wrong ticket's design rationale).
1382
- - **the "work-problems iter workers don't add changesets — fix commits accumulate without release" problem** (`docs/problems/known-error/206-work-problems-iter-workers-dont-add-changesets-fix-commits-accumulate-without-release.md`) — driver for Step 5 iter-prompt-body's explicit "if the fix changes shippable code, author a `.changeset/*.md` in the same commit" constraint (composes defence-in-depth with hook the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem's `git commit`-time enforcement). Inbound-reported by downstream consumer **bbstats** as their the "Briefing Tier 3 rotation repeat-deferral — 13 of 14 topic files over budget with 2 in MUST_SPLIT (≥2× ceiling) branch" problem (`**Origin**: inbound-reported (bbstats#195)` per the "Inbound-reported problems rank ahead of internally-discovered problems via a sort tier" architecture rule sort tier). Behavioural second-source: `test/work-problems-step-5-iter-changeset-required.bats` (structural-permitted per the "Behavioural-tests-default for skill testing" architecture rule; tdd-review comment in fixture).
1383
- - **the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem** (`docs/problems/verifying/141-iter-prompt-time-reminder-misses-40-percent-of-publishable-iters-hook-level-enforcement.md`) — sibling hook (`packages/itil/hooks/itil-changeset-discipline.sh`) that enforces the changeset-discipline rule at `git commit` time. The Step 5 iter-prompt-body constraint composes-with this hook; the prompt-time rule is load-bearing because plugin-hook execution depends on the marketplace cache carrying the current hook version (a fresh-cache adopter without the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem still gets the constraint via the prompt).
1384
- - **the ": Enforce Governance Without Slowing Down" user outcome**, **the ": Progress the Backlog While I'm Away" user outcome**, **the ": Keep Plugins Current Across Projects" user outcome**, **the "Extend the Suite with New Plugins" user outcome**, **the "Restore Service Fast with an Audit Trail" user outcome** — personas whose reliability expectations the iteration-isolation wrapper restores. the ": Progress the Backlog While I'm Away" user outcome (Progress the Backlog While I'm Away) + the ": Keep Plugins Current Across Projects" user outcome (Keep Plugins Current Across Projects) are the load-bearing pair for the "work-problems iter workers don't add changesets — fix commits accumulate without release" problem changeset-discipline constraint — the ": Progress the Backlog While I'm Away" user outcome requires the audit trail to stay accurate at release boundary; the ": Keep Plugins Current Across Projects" user outcome's closure depends on fixes actually shipping to npm.
1382
+ - **the ": Commit-time changeset enforcement forces premature release metadata" problem** (`docs/problems/known-error/554-commit-time-changeset-enforcement-forces-premature-release-metadata.md`) — retires commit-time enforcement and moves complete cumulative changeset creation to intentional release preparation after the exact implementation pipeline passes.
1383
+ - **the ": Enforce Governance Without Slowing Down" user outcome**, **the ": Progress the Backlog While I'm Away" user outcome**, **the ": Keep Plugins Current Across Projects" user outcome**, **the "Extend the Suite with New Plugins" user outcome**, **the "Restore Service Fast with an Audit Trail" user outcome** — personas whose reliability expectations the iteration-isolation wrapper restores. the ": Progress the Backlog While I'm Away" user outcome requires an accurate audit trail at the release boundary; the ": Keep Plugins Current Across Projects" user outcome depends on intentional releases actually shipping to npm.
@@ -247,11 +247,11 @@ The Phase 2 working-the-problem traversal makes "implement the fix" concretely t
247
247
  5. **Commit with the `Refs: STORY-<NNN>` trailer** (single-trailer vocabulary per the "Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology" architecture rule line 307 + amendment 2026-05-10 nitpick N2 — same trailer verb whether the commit is the story's first implementation commit or a continuation). On the FIRST commit AFTER the capture commit (subject prefix discriminates: `feat(itil): capture STORY-NNN ...` is the capture; any other subject prefix is an implementation commit), `/wr-itil:manage-story` auto-transitions the story `draft → in-progress`. As acceptance criteria checkboxes are ticked across multiple commits, the same trailer continues to attribute the work.
248
248
  6. **Story `done` auto-transition**: when ALL acceptance-criteria checkboxes in the story body are ticked AND the linked RFC reaches `closed`, `/wr-itil:manage-story` auto-transitions the story `in-progress → done`. (When a story's RFC is still `in-progress` but the acceptance criteria are all ticked, the story stays at `in-progress` until the RFC closes — this preserves the trace coupling per the "Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology" architecture rule line 309.)
249
249
  7. **Pick the next not-done story** from the RFC's `stories:` array. Repeat from step 3.
250
- 8. **When all stories under all referenced RFCs are done** — the problem is fix-released. Include the problem doc closure in the final commit (`git mv` to `.verifying.md`, update Status) per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule. Push, create changeset, release per the lean release principle.
250
+ 8. **When all stories under all referenced RFCs are done** — push and verify the implementation pipeline first. When release preparation is intentional, create one complete cumulative changeset-only commit, release it, then include the problem's Verification Pending transition with the release evidence per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule.
251
251
 
252
252
  **Legacy empty-stories back-fill (per the "Every RFC has at least one story" architecture rule)**: a pre-the "Every RFC has at least one story" architecture rule RFC whose `stories:` is empty is a **back-fill** case, not an atomic fallback — the "Every RFC has at least one story" architecture rule requires ≥1 story, so the empty-stories atomic dispatch is removed. Decompose the fix into ≥1 story on the RFC's story map (add the story, transition it `accepted`), then traverse it via the normal story path above. The `Refs: RFC-<NNN>` trailer remains valid for **cross-cutting RFC work with no single story** (e.g. an RFC-level enforcement change spanning several files); it is no longer an atomic-empty-stories fallback. Legacy on-disk RFCs still carrying `stories: []` are surfaced for back-fill by `<itil-plugin-root>/bin/wr-itil-check-rfc-has-stories` at their next `manage-rfc accepted` transition.
253
253
 
254
- **Legacy direct-implementation path** (step 1 no-RFCs case): a Phase 1-shape Known Error whose Fix Strategy references no RFCs continues to work via the pre-Phase-2 flow — read the root cause analysis and fix strategy, implement the fix following the project's development workflow, include the problem doc closure in the fix commit (`git mv` to `.verifying.md`, update Status), push + changeset + release. This preserves backwards compatibility with all existing Known Error problems (which were captured before the RFC framework was Phase-1-graduated).
254
+ **Legacy direct-implementation path** (step 1 no-RFCs case): a Phase 1-shape Known Error whose Fix Strategy references no RFCs continues to work via the pre-Phase-2 flow — read the root cause analysis and fix strategy, implement the fix following the project's development workflow, push and verify the implementation, then intentionally prepare and release one cumulative changeset-only commit before transitioning the problem. This preserves backwards compatibility with all existing Known Error problems (which were captured before the RFC framework was Phase-1-graduated).
255
255
 
256
256
  **Scope expansion during work:** If investigation or architect review reveals that the problem's scope has grown significantly (e.g., effort re-sized from S to L, additional files discovered), use `request_user_input` before continuing:
257
257
  - Option 1: `Continue with expanded scope` — keep working this problem at its new size
@@ -1161,13 +1161,13 @@ Commit the completed work per the "Governance Skills Commit Their Own Completed
1161
1161
  - Fix implemented: `fix(<scope>): <description> (closes P<NNN>)` — include problem file changes (rename to `.verifying.md` + `## Fix Released` section) in the same commit per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule
1162
1162
  5. If commit risk is above appetite: auto-apply scorer remediations per **the "Auto-apply scorer remediations to reach within appetite — open action-class vocabulary" architecture rule Rule 1** incrementally until residual commit risk is within appetite (at or below the effective appetite used by the scorer and gate), OR halt per the "Auto-apply scorer remediations to reach within appetite — open action-class vocabulary" architecture rule Rule 5 if the scorer cannot converge. **The skill MUST NOT commit above appetite, and MUST NOT call `request_user_input` to ask whether to commit anyway** (the "Skills, agents, and hooks override RISK-POLICY appetite instead of applying it" problem/the ": Apply RISK-POLICY appetite faithfully across all surfaces" release design amendment 2026-06-24 — above-appetite is framework-mediated, never a category-3 one-time-override; same invariant the push/release branch at Step 12 already enforces). The "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 fail-safe (no `request_user_input` available / non-interactive → skip the commit and report the uncommitted state) remains the terminal fallback. This applies only to the risk-above-appetite branch, not to the delegation-unavailable case above.
1163
1163
 
1164
- **Multi-commit slice changeset discipline (the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem Phase 2)**: when a single logical fix lands across multiple the "Governance Skills Commit Their Own Completed Work" architecture rule-grain commits targeting the same plugin (e.g. helper extraction in commit 1, callers wired in commit 2, SKILL note + transition in commit 3 — all `packages/<plugin>/`), author ONE changeset on the first commit in the slice. Subsequent same-plugin commits do NOT need their own changeset — the `itil-changeset-discipline.sh` hook's Check 2b recognises any `.changeset/*.md` already in the unpushed slice scope (`origin/<base>..HEAD` + untracked + modified-not-staged) that targets `"@windyroad/<plugin>": <any-bump>` and allows. This eliminates the per-commit changeset ceremony that previously produced N redundant `.changeset/*.md` files for one logical release entry (changesets-action collapses bump-class at version-package time, so per-commit changesets rendered N near-identical CHANGELOG bullets for one release). Once a changeset hits `origin/<base>` (drained at release time), it no longer counts — a fresh changeset is required for the next slice. Cross-plugin coverage is NOT permitted: an `@windyroad/itil` changeset does not satisfy a `packages/voice-tone/` commit.
1164
+ **Release-preparation boundary (the ": Commit-time changeset enforcement forces premature release metadata" problem)**: ordinary implementation commits do not include changesets and remain `STAGED`. After the exact implementation pipeline passes and release preparation is intentional, inspect the cumulative package scope and create one complete changeset-only commit. Never create speculative, placeholder, dormant-foundation, or “for later” changesets.
1165
1165
 
1166
1166
  ### 12. Auto-release when changesets are queued (the "Reuse valid cumulative risk assessments across pipeline actions" architecture rule)
1167
1167
 
1168
1168
  **Skip this step if the skill is running inside an AFK orchestrator** (e.g. `/wr-itil:work-problems`). Orchestrators handle release cadence themselves per the "Inter-iteration release cadence for AFK loops" architecture rule (Step 6.5). Detect via the presence of an orchestrator marker in the invoking prompt — look for phrases like "AFK", "work-problems", "batch-work", or the sentinel `ALL_DONE` convention. When in doubt, defer to the orchestrator by skipping this step.
1169
1169
 
1170
- Otherwise, after the commit in step 11 lands, drain the release queue so the fix actually lands on npm without requiring manual user action.
1170
+ Otherwise, push and verify the implementation commit first. Only after that exact pipeline passes, intentionally prepare the release by creating one complete cumulative changeset-only commit, then drain the release queue.
1171
1171
 
1172
1172
  **Mechanism — reuse a valid cumulative assessment; delegate when needed (the "Reuse valid cumulative risk assessments across pipeline actions" architecture rule, the "On-Demand Assessment Skills for Governance Plugins" architecture rule):**
1173
1173
 
@@ -1175,7 +1175,7 @@ Otherwise, after the commit in step 11 lands, drain the release queue so the fix
1175
1175
  - **Primary**: delegate to subagent type `wr-risk-scorer:pipeline` via the native Codex subagent tool.
1176
1176
  - **Fallback**: if that subagent type is not available, invoke skill `/wr-risk-scorer:assess-release` via the installed skill invocation. The skill wraps the same pipeline subagent.
1177
1177
  2. Read `RISK_SCORES: commit=X push=Y release=Z` from the valid assessment. Never substitute the commit score for a push or release score. The action-time `push:watch` and `release:watch` gates still check the relevant score, checkout, state, expiry, appetite, and CI; if a gate rejects stale or changed evidence, resolve the checkout binding or rescore before retrying.
1178
- 3. **Drain condition**: if `push` and `release` are both within appetite (at or below the effective appetite used by the scorer and gate), AND `.changeset/` is non-empty, proceed to the drain action. Otherwise, skip the drain and report the unreleased state.
1178
+ 3. **Drain condition**: first run `npm run push:watch` for unpushed implementation work without creating release metadata. After the exact implementation pipeline passes, if release preparation is intentional, create one complete cumulative changeset-only commit, refresh the assessment, and proceed when `push` and `release` are both within appetite. Otherwise, report the `STAGED` state without starting release work.
1179
1179
 
1180
1180
  **Drain action (non-interactive, policy-authorised per the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6):**
1181
1181
 
@@ -53,7 +53,7 @@ Build a self-contained prompt containing the selected ticket ID and title, the e
53
53
  - preserve unrelated work and use path-scoped staging;
54
54
  - load and obey the installed governance skills, agents, and hooks;
55
55
  - run focused tests and required architecture, JTBD, voice, accessibility, and risk checks when their gates apply;
56
- - add a changeset for shippable package behavior;
56
+ - commit ordinary implementation work without a changeset; after its exact pipeline passes and release preparation is intentional, add one complete cumulative changeset in a separate commit;
57
57
  - commit completed iteration work, but do not push or release;
58
58
  - invoke `/wr-retrospective:run-retro` before the final response, commit any retro-owned briefing refresh through its governed path, and continue to the summary even if retro reports a non-blocking failure;
59
59
  - end with one `ITERATION_SUMMARY` containing ticket, action, outcome, commit state, tests, risks, outstanding questions, remaining work, and notes.
@@ -1,126 +0,0 @@
1
- #!/bin/bash
2
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem: PreToolUse:Bash hook — denies `git commit` invocations whose
3
- # staged set includes `packages/<plugin>/` source files but no
4
- # `.changeset/*.md` is staged. Hook-level enforcement replaces the
5
- # unreliable iter-prompt-time changeset reminder (40% miss rate
6
- # observed in 2026-04-28 AFK loop session — see ticket).
7
- #
8
- # Detection delegates to `lib/changeset-detect.sh::detect_changeset_required`.
9
- # When the helper returns 1, this hook emits PreToolUse deny JSON
10
- # with the offending plugin slug inline and the literal `bun run
11
- # changeset` recovery command, satisfying structured-user-interaction-for-governance-skill-decisions-architecture-rule Rule 1's "deny
12
- # redirects to a recovery path" contract via the mechanical-recovery
13
- # shape (no skill wrapper required — authoring a changeset is a
14
- # single command).
15
- #
16
- # Command-shape detection delegates to
17
- # `lib/command-detect.sh::command_invokes_git_commit`, which strips
18
- # common prefix shapes (leading whitespace, env-var assignments,
19
- # `cd <path> &&`) and checks whether the residual leading token pair
20
- # is literally `git commit`. itil-changeset-discipline-sh-hook-substring-matches-git-commit-anywhere-in-bash-command-sibling-problem: replaced the prior substring match
21
- # `*"git commit"*` that misfired on non-commit Bash whose argument
22
- # vectors merely mentioned the phrase (grep / sed / cat-heredoc /
23
- # echo / `git log --grep`).
24
- #
25
- # Allow paths (exit 0 silently per hook-injection-budget-policy-for-pretooluse-and-posttooluse-hooks-architecture-rule Pattern 1):
26
- # - tool_name != "Bash" (only Bash invocations are gated)
27
- # - command is not a `git commit` invocation by leading-executable
28
- # semantics (helper returns 1)
29
- # - staged set is changeset-clean (helper returns 0)
30
- # - BYPASS_CHANGESET_GATE=1 env (helper returns 0 first)
31
- # - outside a git work tree (helper fails-open)
32
- # - parse failure on stdin (mirrors create-gate.sh fail-open)
33
- #
34
- # References:
35
- # plugin-testing-strategy-architecture-rule — plugin testing strategy (hook bats live under hooks/test/).
36
- # gate-marker-lifecycle-ttl-drift-not-stop-hook-reset-architecture-rule — gate marker lifecycle (this hook deliberately does NOT
37
- # use markers; detection is per-invocation deterministic
38
- # — same precedent as staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem `p057-staging-trap-detect.sh`).
39
- # structured-user-interaction-for-governance-skill-decisions-architecture-rule Rule 1 — deny redirects with mechanical recovery.
40
- # governance-skills-commit-their-own-completed-work-architecture-rule — governance skills commit their own work (the hook keeps
41
- # iter commits self-contained — no orchestrator-main-turn
42
- # back-fill needed).
43
- # inter-iteration-release-cadence-for-afk-loops-architecture-rule — inter-iteration release cadence (the hook strengthens
44
- # release-cadence integrity by ensuring every publishable
45
- # iter has a changeset to drain).
46
- # progressive-disclosure-once-per-session-budget-for-userpromptsubmit-governance-prose-architecture-rule — progressive disclosure / deny-message terseness budget.
47
- # hook-injection-budget-policy-for-pretooluse-and-posttooluse-hooks-architecture-rule — hook injection budget (Pattern 1 silent-on-pass; deny
48
- # band ≤300 bytes for this hook).
49
- # problem-073-no-voice-and-tone-check-or-risk-assessment-on-changeset-bodies-which-populate-changelog-md-release-prs-github-releases-and-npm-release-notes-problem — sibling changeset author-time gate (Write/Edit on
50
- # `.changeset/*.md`); composes-with as defence-in-depth.
51
- # staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem — sibling staging-trap hook (same enforcement-layer shape).
52
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem — this hook.
53
- # hook-substring-matches-git-commit-anywhere-in-bash-command-not-just-actual-git-commit-invocations-problem — shared `command_invokes_git_commit` helper landed for
54
- # `itil-readme-refresh-discipline.sh`; consumed here.
55
- # itil-changeset-discipline-sh-hook-substring-matches-git-commit-anywhere-in-bash-command-sibling-problem — sibling-hook refactor: substring-match → helper here.
56
-
57
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
58
- # shellcheck source=lib/changeset-detect.sh
59
- source "$SCRIPT_DIR/lib/changeset-detect.sh"
60
- # shellcheck source=lib/command-detect.sh
61
- source "$SCRIPT_DIR/lib/command-detect.sh"
62
-
63
- INPUT=$(cat)
64
-
65
- TOOL_NAME=$(echo "$INPUT" | python3 -c "
66
- import sys, json
67
- try:
68
- data = json.load(sys.stdin)
69
- print(data.get('tool_name', ''))
70
- except:
71
- print('')
72
- " 2>/dev/null || echo "")
73
-
74
- # Only gate Bash. Non-Bash tools bypass entirely.
75
- if [ "$TOOL_NAME" != "Bash" ]; then
76
- exit 0
77
- fi
78
-
79
- COMMAND=$(echo "$INPUT" | python3 -c "
80
- import sys, json
81
- try:
82
- data = json.load(sys.stdin)
83
- print(data.get('tool_input', {}).get('command', ''))
84
- except:
85
- print('')
86
- " 2>/dev/null || echo "")
87
-
88
- # Only fire on actual `git commit` invocations. Delegates to
89
- # `lib/command-detect.sh::command_invokes_git_commit`, which strips
90
- # common prefix shapes (leading whitespace, env-var assignments,
91
- # `cd <path> &&`) and checks whether the residual leading token pair
92
- # is literally `git commit`. itil-changeset-discipline-sh-hook-substring-matches-git-commit-anywhere-in-bash-command-sibling-problem: replaced the prior substring match
93
- # `*"git commit"*` that misfired on non-commit Bash whose argument
94
- # vectors merely mentioned the phrase (grep / sed / cat-heredoc /
95
- # echo / `git log --grep`).
96
- command_invokes_git_commit "$COMMAND" || exit 0
97
-
98
- # Run detection. Helper echoes offending plugin slug on stdout when
99
- # detected; returns 1 in that case. Returns 0 (allow) on no-trap,
100
- # bypass env, or fail-open (non-git tree, parse error). The COMMAND is
101
- # passed so Check 2b can change-scope to the commit's work-item ID (changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem):
102
- # an in-scope changeset for an UNRELATED ticket no longer covers this commit.
103
- TRAPPED_SLUG=$(detect_changeset_required "$COMMAND" 2>/dev/null) && exit 0
104
-
105
- # Trap detected — emit deny with terse recovery.
106
- # Voice-tone budget per hook-injection-budget-policy-for-pretooluse-and-posttooluse-hooks-architecture-rule deny-band ≤300 bytes total. Names the
107
- # plugin slug, the literal in-flight recovery command (`bun run
108
- # changeset` — staging ANY changeset satisfies the gate), and the afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem
109
- # cite. bypass-gate-env-vars-do-not-propagate-from-bash-subshell-to-pretooluse-hook-context-problem: the deny no longer advertises BYPASS_CHANGESET_GATE=1 as an
110
- # in-flight escape — that env var only takes effect when set in Claude
111
- # Code's process env BEFORE the session started; a mid-session Bash
112
- # export/inline assignment never reaches the hook process. The deny
113
- # states the bypass is pre-session-only so maintainers stop wasting a
114
- # turn trying it mid-session (the original bypass-gate-env-vars-do-not-propagate-from-bash-subshell-to-pretooluse-hook-context-problem cost).
115
- REASON="BLOCKED: afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem changeset discipline. packages/${TRAPPED_SLUG}/ source needs .changeset/*.md. Recovery: bun run changeset. Env bypass is pre-session only."
116
-
117
- cat <<EOF
118
- {
119
- "hookSpecificOutput": {
120
- "hookEventName": "PreToolUse",
121
- "permissionDecision": "deny",
122
- "permissionDecisionReason": "${REASON}"
123
- }
124
- }
125
- EOF
126
- exit 0
@@ -1,339 +0,0 @@
1
- #!/bin/bash
2
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem: shared changeset-discipline detection helper.
3
- #
4
- # `detect_changeset_required` returns 0 (no change required — allow) /
5
- # 1 (changeset required but not staged — caller should deny). On 1, the
6
- # offending plugin slug is echoed on stdout so callers can name it in
7
- # deny messages without re-parsing diff output.
8
- #
9
- # Trap shape (afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem):
10
- # `/wr-itil:work-problems` AFK iter subprocesses receive prompt-time
11
- # guidance to author a `.changeset/*.md` whenever they ship a
12
- # `packages/<plugin>/` change. Under context pressure (heavy SKILL.md
13
- # + ticket body + architect/JTBD prompt content) the reminder is
14
- # sometimes dropped — observed at 40% miss rate across 5 publishable
15
- # iters in the 2026-04-28 evidence session. Hook-level detection at
16
- # `git commit` time replaces the unreliable prompt-time signal.
17
- #
18
- # Detection logic:
19
- # - `git diff --staged --name-only` enumerates staged paths.
20
- # - Categorise each path:
21
- # * `.changeset/<name>.md` (excluding `README.md`) — counts as
22
- # a valid changeset.
23
- # * `packages/<slug>/...` — examined further:
24
- # - allow-list: `test/*`, `hooks/test/*`, `scripts/test/*`
25
- # (test code; no publishable behaviour change).
26
- # - allow-list: `README.md`.
27
- # - allow-list: `docs/<anything>.md` (per architect verdict
28
- # 2026-05-02 — `*.md` under `docs/` only; SKILL.md is the
29
- # publishable contract per skill-testing-strategy-contract-assertion-bats-companion-to-architecture-rule framing and is NOT in
30
- # the allow-list).
31
- # - otherwise: publishable source — record the slug.
32
- # * any other path: ignored (non-publishable surface — `.github/`,
33
- # root config, top-level `docs/`, etc.).
34
- # - If any path is publishable source:
35
- # * **Check 2a (Phase 1)**: a `.changeset/*.md` staged → allow.
36
- # * **Check 2b (Phase 2)**: an in-scope `.changeset/*.md`
37
- # targeting the plugin via YAML frontmatter
38
- # `"@windyroad/<slug>": <any-bump>` → allow. Scope =
39
- # in-unpushed-range additions (`<base>..HEAD`) + untracked
40
- # working-tree files + modified-not-staged working-tree files.
41
- # Base = `@{u}` (current branch upstream) with fallback to
42
- # `origin/main`. Once consumed onto origin (drained by
43
- # changesets-action), the changeset is gone and a fresh one
44
- # is required.
45
- # * Neither check satisfied → return 1 + echo the slug.
46
- #
47
- # Phase 2 rationale (afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem 2026-05-31): AFK orchestrator iters that
48
- # ship a multi-commit slice for one plugin (e.g. wr-itil-review-problems-has-no-path-to-close-tickets-that-are-no-longer-relevant-evidence-based-not-age-based-structural-outflow-gap-drives-monotonic-backlog-growth-problem Phase 3 across
49
- # 4 commits, 2 of which touched `packages/itil/`) should not author N
50
- # redundant changesets for one logical bump. changesets-action
51
- # collapses bump-class at version-package time, so per-commit
52
- # changesets render N CHANGELOG bullets for one release entry. Phase
53
- # 2's Check 2b lets the author write the changeset on the FIRST
54
- # commit; subsequent same-plugin commits naturally allow because the
55
- # changeset is already in the unpushed-range scope.
56
- #
57
- # Bypass:
58
- # - `BYPASS_CHANGESET_GATE=1` env var → return 0 (allow). For
59
- # legitimate non-publishable commits (e.g. CI-only changes
60
- # bundled with a small source tweak the agent has decided not
61
- # to release). Audit-traceable via shell history.
62
- #
63
- # Fail-open contract:
64
- # - Outside a git working tree, or when `git diff` fails for any
65
- # reason (parse error, broken index, permissions), return 0
66
- # (allow). Mirrors `lib/staging-detect.sh`'s exit-0 fallback —
67
- # a hook that fails-closed on hostile environments would block
68
- # legitimate commits in non-git contexts (e.g. agent-driven
69
- # scripts that happen to mention `git commit` in unrelated
70
- # contexts).
71
- #
72
- # Cost: one `git diff` invocation per check (~10ms on this repo's
73
- # working tree). Per-invocation deterministic — runs on every
74
- # `git commit` invocation rather than relying on per-tool-call
75
- # session state tracking. Mirrors the staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem `staging-detect.sh`
76
- # precedent (architect-approved no-marker design).
77
- #
78
- # References:
79
- # plugin-testing-strategy-architecture-rule — plugin testing strategy (hook bats live under
80
- # `hooks/test/` per problem-081-structural-source-content-tests-are-wasteful-tdd-agent-should-reject-them-and-require-behavioural-tests-framework-stub-enhancements-problem behavioural-test discipline).
81
- # structured-user-interaction-for-governance-skill-decisions-architecture-rule Rule 1 — deny redirects with mechanical recovery (the deny
82
- # text names the plugin slug + the literal `bun run
83
- # changeset` command + the BYPASS env var override).
84
- # governance-skills-commit-their-own-completed-work-architecture-rule — governance skills commit their own work (this hook
85
- # ensures iter commits stay self-contained per
86
- # governance-skills-commit-their-own-completed-work-architecture-rule single-commit grain).
87
- # inter-iteration-release-cadence-for-afk-loops-architecture-rule — inter-iteration release cadence (this hook strengthens
88
- # the cadence by ensuring every publishable iter has a
89
- # changeset to drain at release time).
90
- # progressive-disclosure-once-per-session-budget-for-userpromptsubmit-governance-prose-architecture-rule — progressive disclosure / deny-message terseness.
91
- # hook-injection-budget-policy-for-pretooluse-and-posttooluse-hooks-architecture-rule — hook injection budget (Pattern 1 silent-on-pass; deny
92
- # band ≤300 bytes for this hook).
93
- # problem-073-no-voice-and-tone-check-or-risk-assessment-on-changeset-bodies-which-populate-changelog-md-release-prs-github-releases-and-npm-release-notes-problem — sibling changeset author-time gate (different surface:
94
- # Write/Edit on `.changeset/*.md`). Composes-with as
95
- # defence-in-depth.
96
- # staging-trap-recurs-despite-documentation-hook-level-enforcement-candidate-problem — sibling staging-trap helper (same enforcement-layer
97
- # shape — per-invocation deterministic, no markers).
98
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem — this helper.
99
-
100
- # afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem Phase 2 helper — does any `.changeset/*.md` ALREADY in scope target the plugin
101
- # slug via its YAML frontmatter `"@windyroad/<slug>": <bump>` line?
102
- #
103
- # Scope = files reachable from HEAD but not from `origin/<base>`,
104
- # plus untracked working-tree changesets, plus modified-not-staged
105
- # changesets. Once a changeset is on `origin/<base>` (drained by
106
- # changesets-action at release time), it no longer counts — Check 2b
107
- # requires a fresh changeset for the next slice.
108
- #
109
- # Per-plugin granularity (NOT per-bump-class — changesets-action
110
- # collapses bump-class at version-package time when multiple
111
- # changesets for the same plugin merge; the published bump-class is
112
- # the maximum across the merged set).
113
- #
114
- # Base resolution: prefer the current branch's upstream (`@{u}`),
115
- # fall back to `origin/main`. If neither resolves (e.g. fresh
116
- # repo with no remotes), Check 2b returns 1 (no in-range scope to
117
- # inspect) — Phase 1 strict-deny behaviour is preserved.
118
- #
119
- # Returns: 0 (≥1 in-scope changeset targets the plugin → paths echoed on
120
- # stdout, newline-separated, for the caller's change-scope check)
121
- # 1 (no covering changeset found → caller falls through)
122
- _changeset_in_scope_covers_plugin() {
123
- local slug="$1"
124
- local base
125
- local candidates path found=""
126
-
127
- base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) \
128
- || base="origin/main"
129
- git rev-parse --verify --quiet "$base" >/dev/null 2>&1 || return 1
130
-
131
- # Enumerate candidate changeset files:
132
- # 1. In-range additions: changesets added in unpushed commits
133
- # (`<base>..HEAD`). A changeset later deleted in the same
134
- # range is filtered by the on-disk existence check below.
135
- # 2. Untracked: changesets in the working tree not yet tracked
136
- # by git (author wrote but did not stage).
137
- # 3. Modified-not-staged: changesets edited since their last
138
- # commit but not yet re-staged.
139
- # Excludes `*/README.md` meta-docs (mirrors the staged-path branch).
140
- candidates=$(
141
- {
142
- git log --diff-filter=A --name-only --pretty=format: "${base}..HEAD" \
143
- -- '.changeset/*.md' 2>/dev/null
144
- git ls-files --others --exclude-standard \
145
- -- '.changeset/*.md' 2>/dev/null
146
- git diff --name-only \
147
- -- '.changeset/*.md' 2>/dev/null
148
- } | grep -v '/README\.md$' | sort -u
149
- )
150
-
151
- [ -n "$candidates" ] || return 1
152
-
153
- while IFS= read -r path; do
154
- [ -n "$path" ] || continue
155
- [ -f "$path" ] || continue
156
- # Extract YAML frontmatter (lines between the first two `---`
157
- # markers) and match the canonical `"@windyroad/<slug>":` line.
158
- # awk scoping prevents false positives from prose body mentions.
159
- if awk '/^---[[:space:]]*$/ { c++; if (c == 1) next; if (c == 2) exit } c == 1 { print }' "$path" 2>/dev/null \
160
- | grep -qE "^\"@windyroad/${slug}\":[[:space:]]"; then
161
- found="${found}${path}
162
- "
163
- fi
164
- done <<EOF
165
- $candidates
166
- EOF
167
-
168
- [ -n "$found" ] || return 1
169
- printf '%s' "$found"
170
- return 0
171
- }
172
-
173
- # changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem helper — echo the space-separated, upper-cased, de-duplicated set of
174
- # work-item IDs found in the text passed as $1. Work-item identity = problem
175
- # ticket (`P<NNN>`), RFC (`RFC-<NNN>`), or story (`STORY-<NNN>`). ADR refs are
176
- # deliberately excluded — an ADR is cross-cutting context cited in passing, not
177
- # the identity of the change a changeset documents.
178
- #
179
- # Used to compare a committing change's ticket reference(s) (from the
180
- # git-commit COMMAND string) against an in-scope changeset's reference(s)
181
- # (filename + body). Matching is inclusive and case-insensitive: extracting a
182
- # spurious extra ID only widens the overlap (the allow direction) and can never
183
- # manufacture a false deny, which keeps Check 2b conservative.
184
- _work_item_ids() {
185
- printf '%s' "$1" \
186
- | grep -oiE '\b(P[0-9]+|RFC-[0-9]+|STORY-[0-9]+)\b' 2>/dev/null \
187
- | tr '[:lower:]' '[:upper:]' \
188
- | sort -u \
189
- | tr '\n' ' '
190
- }
191
-
192
- # Detect whether the current staged set requires a changeset that is
193
- # not satisfied by either staged Check 2a or in-scope Check 2b.
194
- #
195
- # $1 (optional) — the git-commit COMMAND string (or commit message). Its
196
- # work-item ID(s) are matched against in-scope changesets for the changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem
197
- # change-scoped Check 2b. Empty / omitted → Check 2b falls back to the
198
- # pre-changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem plugin-scoped behaviour (any covering changeset allows), which
199
- # is the conservative choice when no commit context is available.
200
- #
201
- # Echoes the offending plugin slug on stdout when detected.
202
- #
203
- # Returns:
204
- # 0 — no change required, BYPASS env set, fail-open, or an in-scope
205
- # changeset covers the plugin AND is change-scoped to it (Check 2b)
206
- # 1 — change required + no covering (or only unrelated-sibling) changeset
207
- # (caller should deny)
208
- detect_changeset_required() {
209
- local commit_msg="${1:-}"
210
- # Bypass via env var — single most-common legitimate escape.
211
- if [ "${BYPASS_CHANGESET_GATE:-}" = "1" ]; then
212
- return 0
213
- fi
214
-
215
- # Fail-open if not inside a git working tree.
216
- git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
217
-
218
- local staged
219
- staged=$(git diff --staged --name-only 2>/dev/null) || return 0
220
-
221
- # No staged paths — nothing to gate.
222
- [ -n "$staged" ] || return 0
223
-
224
- local has_changeset=0
225
- local plugin_source_slug=""
226
- local path rest slug subpath
227
-
228
- while IFS= read -r path; do
229
- [ -n "$path" ] || continue
230
-
231
- case "$path" in
232
- .changeset/README.md)
233
- # README in changeset dir is meta-doc, not a real changeset.
234
- ;;
235
- .changeset/*.md)
236
- has_changeset=1
237
- ;;
238
- packages/*)
239
- rest="${path#packages/}"
240
- slug="${rest%%/*}"
241
- # When the path has no further segments (e.g. `packages/foo`),
242
- # ${rest#*/} returns rest unchanged — defensive subpath fallback.
243
- if [ "$rest" = "$slug" ]; then
244
- subpath="$rest"
245
- else
246
- subpath="${rest#*/}"
247
- fi
248
-
249
- # Allow-list: test paths.
250
- case "$subpath" in
251
- test/*|hooks/test/*|scripts/test/*) continue ;;
252
- esac
253
-
254
- # Allow-list: package README.
255
- case "$subpath" in
256
- README.md) continue ;;
257
- esac
258
-
259
- # Allow-list: *.md under docs/ (any nesting depth).
260
- case "$subpath" in
261
- docs/*)
262
- case "$subpath" in
263
- *.md) continue ;;
264
- esac
265
- ;;
266
- esac
267
-
268
- # Anything else under packages/<slug>/ is publishable source.
269
- plugin_source_slug="$slug"
270
- ;;
271
- *)
272
- # Non-packages/ path: always allow.
273
- ;;
274
- esac
275
- done <<EOF
276
- $staged
277
- EOF
278
-
279
- # No publishable plugin source staged → allow.
280
- [ -n "$plugin_source_slug" ] || return 0
281
-
282
- # Check 2a — staged changeset satisfies (Phase 1 behaviour).
283
- if [ "$has_changeset" -eq 1 ]; then
284
- return 0
285
- fi
286
-
287
- # Check 2b (afk-iter-packages-plugin-commits-without-changesets-orchestrator-main-turn-back-fill-is-fragile-recovery-hook-level-enforcement-preferable-problem Phase 2 + changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem change-scoped) — an in-scope changeset
288
- # targeting the plugin satisfies, but only when it is change-scoped to
289
- # THIS commit. Scope = unpushed-range commits + untracked + modified-
290
- # not-staged working-tree files. Once consumed onto origin, the changeset
291
- # is gone and a fresh one is required.
292
- local covering
293
- if covering=$(_changeset_in_scope_covers_plugin "$plugin_source_slug"); then
294
- # changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem: tighten plugin-scoped → change-scoped. A plugin can carry a
295
- # changeset for an unrelated change; before changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem that wrongly covered
296
- # THIS commit, shipping it to npm with no CHANGELOG record of its own
297
- # (witnessed: latent-octal-eval-bug-in-next-id-formula-across-all-4-ticket-creator-skills-local-max-1-fails-with-value-too-great-for-base-when-local-max-reaches-099-problem's fix rode extract-risks-from-reports-sh-hardcodes-windy-road-agent-plugins-suite-branding-in-the-adopter-generated-docs-risks-readme-md-problem's changeset). Deny only on positive
298
- # evidence the covering changeset(s) belong to a DIFFERENT change: the
299
- # commit cites work-item ID(s), EVERY covering changeset cites work-item
300
- # ID(s), and none overlap. Any ambiguity allows — a ticket-less commit,
301
- # a prose-only changeset, or an ID overlap — so the governance-skills-commit-their-own-completed-work-architecture-rule batch-grain
302
- # (same-slice commits share a ticket) and prose-only / adopter changesets
303
- # are never over-fired.
304
- local commit_ids cs_path cs_ids id has_idless=0 overlap=0
305
- commit_ids=$(_work_item_ids "$commit_msg")
306
-
307
- # Commit cites no work-item ID → cannot change-scope; allow (pre-changeset-discipline-check-2b-is-plugin-scoped-not-change-scoped-plugin-source-commits-ship-undocumented-when-a-sibling-changeset-already-targets-the-plugin-problem).
308
- [ -n "$commit_ids" ] || return 0
309
-
310
- while IFS= read -r cs_path; do
311
- [ -n "$cs_path" ] || continue
312
- cs_ids=$(_work_item_ids "${cs_path}
313
- $(cat "$cs_path" 2>/dev/null)")
314
- if [ -z "$cs_ids" ]; then
315
- has_idless=1
316
- continue
317
- fi
318
- for id in $commit_ids; do
319
- case " $cs_ids " in
320
- *" $id "*) overlap=1; break ;;
321
- esac
322
- done
323
- [ "$overlap" -eq 1 ] && break
324
- done <<EOF
325
- $covering
326
- EOF
327
-
328
- # Overlapping ID (same change) or a prose-only covering changeset
329
- # (cannot prove it is for a different change) → allow.
330
- if [ "$overlap" -eq 1 ] || [ "$has_idless" -eq 1 ]; then
331
- return 0
332
- fi
333
- # Else: every covering changeset cites work-item ID(s), none matching
334
- # the commit → unrelated-sibling signature → fall through to deny.
335
- fi
336
-
337
- printf '%s\n' "$plugin_source_slug"
338
- return 1
339
- }