@windyroad/itil 2.5.0 → 2.5.1-preview.1273
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.
- package/.claude-plugin/plugin.json +1 -12
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +0 -1
- package/hooks/hooks.json +0 -9
- package/hooks/itil-codex-dispatch.sh +0 -1
- package/hooks/itil-readme-refresh-discipline.sh +0 -1
- package/hooks/itil-rfc-trailer-advisory.sh +1 -1
- package/hooks/lib/readme-refresh-detect.sh +0 -1
- package/package.json +1 -1
- package/skills/manage-problem/SKILL.md +5 -5
- package/skills/work-problems/SKILL.md +13 -14
- package/skills-codex/manage-problem/SKILL.md +5 -5
- package/skills-codex/work-problems/SKILL.md +1 -1
- package/hooks/itil-changeset-discipline.sh +0 -126
- package/hooks/lib/changeset-detect.sh +0 -339
|
@@ -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.
|
|
489
|
+
"version": "2.5.1"
|
|
501
490
|
}
|
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
|
@@ -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
|
|
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
|
@@ -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
|
|
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,
|
|
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
|
-
**
|
|
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,
|
|
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**:
|
|
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
|
|
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`
|
|
998
|
-
2. If
|
|
999
|
-
3.
|
|
1000
|
-
4. **Post-release K→V auto-transition (the ".known-error.md → .verifying.md transition not happening consistently at release time" problem)**: if step
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
1383
|
-
- **the "
|
|
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
|
|
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,
|
|
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
|
-
**
|
|
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,
|
|
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**:
|
|
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
|
-
-
|
|
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
|
-
}
|