@windyroad/itil 0.49.0 → 0.49.2-preview.661

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.
@@ -497,5 +497,5 @@
497
497
  }
498
498
  },
499
499
  "name": "wr-itil",
500
- "version": "0.49.0"
500
+ "version": "0.49.2"
501
501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.49.0",
3
+ "version": "0.49.2-preview.661",
4
4
  "description": "ITIL-aligned IT service management for Claude Code (problem, and future incident/change skills)",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"
@@ -733,6 +733,40 @@ Update the "Status" field in the file to "Known Error".
733
733
 
734
734
  When the fix for a Known Error ships, transition the ticket in a single commit.
735
735
 
736
+ #### Conditional-deferral check BEFORE the rename (P184)
737
+
738
+ BEFORE the `git mv` to `.verifying.md`, scan the `.known-error.md` ticket body for **phase-tracking sections with unticked checkboxes whose deferral conditions have now lifted**. Conditional-deferral language ("Phase N SHIP deferred to post-Phase-M-graduation" / "deferred-pending-X-graduation" / "Phase N deferred until Y") names a CONDITION — it is NOT terminal. When the gating condition fires (Phase M graduates, dependency Y ships), the conditionally-deferred work is back IN SCOPE; transitioning K→V while it remains unticked silently loses the work. P184's driver case (P170 Phase 2 SHIP deferred to post-Phase-1-graduation) is the canonical regression — the agent's NLP parsed "deferred" as terminal without checking the conditional clause, transitioned the parent ticket, and would have lost the Phase 2 work if the user hadn't asked an orthogonal question that surfaced the misreading.
739
+
740
+ **Detection** (run in order):
741
+
742
+ 1. Grep the ticket body for phase-tracking section headers — regex `^### (Phase|Slice|Tier) [0-9]+` covers the canonical shapes.
743
+ 2. For each detected section, count the unticked `- [ ]` checkboxes inside it (up to the next `^### ` boundary or EOF).
744
+ 3. Grep the body (any section, not just the phase-tracking one) for conditional-deferral markers:
745
+ - `deferred (?:to|pending|until) (?:post-)?[A-Za-z0-9-]+(?:-graduation)?`
746
+ - `Phase [0-9]+ (?:SHIP )?deferred`
747
+ - `deferred-pending-[a-z-]+`
748
+ 4. For each conditional-deferral marker, resolve whether the **gating condition** has fired. The gating condition typically names another phase, ticket, or RFC — check whether that artefact has reached `.closed.md` / `.verifying.md` (for tickets), or `closed` lifecycle (for RFCs / stories per ADR-060).
749
+
750
+ **Halt-and-route** (when ANY conditional deferral has lifted with unticked work remaining):
751
+
752
+ Emit a structured report naming each deferred section + the lifted condition + the unticked task count, then **halt the transition**:
753
+
754
+ - **Interactive**: fire `AskUserQuestion` with `header: "Conditional deferral lifted"` + options:
755
+ 1. `Re-open Phase N — work the deferred tasks now (Recommended)` — halt the K→V transition; route to working the deferred Phase N tasks; revert to Known Error.
756
+ 2. `Confirm Phase N permanently out of scope — proceed with K→V` — user explicit acknowledgement that the deferral was misclassified as conditional and is in fact terminal; proceed with the K→V transition; append `<!-- P184: user-confirmed Phase N permanently OOS -->` marker to the deferred section so re-detection skips it.
757
+ 3. `Split Phase N into a new ticket — proceed with K→V on this one` — halt the K→V transition; route to `/wr-itil:capture-problem` for a new ticket carrying the Phase N scope; once captured, resume the K→V transition on the original ticket.
758
+ - **AFK** (per ADR-013 Rule 6 + P352 queue-and-continue universal default): queue an `outstanding_questions` entry naming the local ticket ID + the lifted-condition citation + the unticked task count + the three options above. **Do NOT auto-transition.** The orchestrator main turn surfaces the queued question at loop end via the existing batched-`AskUserQuestion` end-of-loop gate. Brief the substance BEFORE referencing IDs per `feedback_brief_before_id.md` — the user reads the prompt without project filesystem access.
759
+
760
+ **Proceed silently** (no halt) when:
761
+ - No phase-tracking sections exist in the ticket body (the common case).
762
+ - Phase-tracking sections exist but every task is ticked.
763
+ - Phase-tracking sections exist with unticked tasks BUT the deferral marker explicitly states "permanently out of scope" / "won't fix" / "rejected" without a conditional clause.
764
+ - The conditional-deferral marker carries the `<!-- P184: user-confirmed Phase N permanently OOS -->` marker from a prior surfacing.
765
+
766
+ **Why halt-and-route not silent-default-with-marker**: P184's failure mode is "work silently lost if user doesn't notice"; the P063 silent-default-with-marker shape leaves the lost-work-detection burden on the user reading the marker in the Verification Queue. The halt-and-route shape catches the failure at the transition surface where the loss occurs. Authority: ADR-044 category 2 (deviation-approval) — the agent surfaces a deviation candidate with citations + evidence + a proposed shape; user picks. Driver: user direction in P184 Workaround line 37 — *"explicitly ask the user 'is Phase N still deferred or is it now in-scope?' before transitioning when the ticket body shows phase-tracking sections."*
767
+
768
+ This check fires BEFORE the P330 Release-vehicle seed below — halt-on-conditional-deferral is the outer gate, seed-and-rename is the inner mechanic.
769
+
736
770
  **Seed `Release vehicle` reference BEFORE the rename (P330).** BEFORE the `git mv` to `.verifying.md`, edit the `.known-error.md` ticket body to append a `**Release vehicle**: .changeset/<name>.md` paragraph at the END of the `## Fix Strategy` section (create the section if absent). The `<name>.md` is the kebab-case slug of the changeset file the fix commit authored under `.changeset/` (e.g. `wr-itil-p330-option-b.md`). The seed eliminates the `wr-itil-derive-release-vehicle <NNN>` helper's exit-2 routing on standalone K→V iters — the helper greps the ticket body for `.changeset/<name>.md` and exits 2 when absent; seeding the reference at fix-ship time (when the changeset name is fresh in scope, since the fix commit just created it) makes the helper exit 0 deterministically on first call. The exit-2 recovery routing documented in `/wr-itil:transition-problem` Step 6 remains as the legacy-ticket fallback. Matches the user's documented workaround pattern across 3 of 4 standalone K→V dogfoods in the 2026-05-30 session (P316 / P281 / P302 — see P330 § Symptoms).
737
771
 
738
772
  > **Two P057 staging-trap windows on K→V (seed + rename).** The seed Edit on `.known-error.md` is the FIRST P057 window; the Edit that updates Status / writes `## Fix Released` AFTER the `git mv` is the SECOND. Consolidate staging into a SINGLE `git add docs/problems/verifying/<NNN>-<title>.md` AFTER both Edits + the `git mv`. `git mv` operates on the index entry — the body content the index references at rename time is the post-seed content, so the seed Edit's content is carried across the rename automatically; the single final `git add` re-stages the post-rename file with the post-`Edit` Status + `## Fix Released` content. The seed step does NOT introduce a separate `git add` of the `known-error/` path — staging discipline stays single-call by riding the rename's index entry.
@@ -119,16 +119,18 @@ Keep the new fragment ≤ 1024 bytes (soft cap) and certainly ≤ 5120 bytes (ha
119
119
 
120
120
  **Rationale (P134)**: this skill previously documented the line as "an ever-growing prose paragraph". That convention is what produced the 76-KB line-3 that broke the Read tool entirely. The reconcile path was a load-bearing site of the bloat — every reconcile that happened under the old convention re-wrote line 3 unbounded. The new discipline closes the surface for reconcile parity with `manage-problem` Step 5 P094, Step 6 P094, Step 7 P062, and the sibling `transition-problem`, `transition-problems`, `review-problems` skills.
121
121
 
122
- ### Step 6. Commit (when invoked from an AFK orchestrator subprocess)
122
+ ### Step 6. Commit
123
123
 
124
- In AFK mode (per ADR-013 Rule 6), commit the reconciled README in a dedicated single-purpose commit:
124
+ Commit the reconciled README in a dedicated single-purpose commit — unconditionally, regardless of interactive vs AFK invocation mode, per ADR-014 ("governance skills commit their own work"):
125
125
 
126
126
  ```bash
127
127
  git add docs/problems/README.md
128
128
  git commit -m "chore(problems): reconcile README against filesystem (P118)"
129
129
  ```
130
130
 
131
- When invoked interactively, do NOT auto-commit — present a diff summary to the user and let them stage + commit. The reconciled state should always be staged together (no partial reconciliation) — when the agent has applied N edits in Step 4, all N belong in the same commit.
131
+ The reconciled state should always be staged together (no partial reconciliation) — when the agent has applied N edits in Step 4, all N belong in the same commit. Interactive and AFK invocations behave identically: the commit decision is **framework-mediated** per ADR-014 (the policy already decided governance skills commit their own work), NOT user direction-setting per ADR-044's authority taxonomy. A per-invocation consent surface here would re-ask a decision the framework has already resolved (P172 + lazy-AskUserQuestion under ADR-044).
132
+
133
+ The ADR-013 Rule 6 fail-safe is **risk-gated** (above-appetite + `AskUserQuestion` unavailable → skip commit), not **mode-gated** (interactive vs AFK). Reconciliation is pure mechanical README refresh with no risk-above-appetite branch, so Rule 6 simply does not fire here.
132
134
 
133
135
  ## ADR alignment
134
136
 
@@ -136,7 +138,7 @@ When invoked interactively, do NOT auto-commit — present a diff summary to the
136
138
  - **ADR-022** (Verification Pending lifecycle status conventions) — Confirmation criterion 3 extended to "and matches the Verification Queue table in `README.md` modulo narrative content".
137
139
  - **ADR-038** (Progressive disclosure for governance tooling context) — script output is per-row terse (≤150 bytes per drift entry); the agent expands narrative-aware edits on demand.
138
140
  - **ADR-005** (Plugin testing strategy) — script-level bats lives at `packages/itil/scripts/test/reconcile-readme.bats`; ADR-037 (skill testing) governs this skill's own contract bats.
139
- - **ADR-013** (Structured interaction) — Rule 6 (non-interactive fail-safe) governs the AFK auto-apply branch.
141
+ - **ADR-013** (Structured interaction) — Rule 6 (non-interactive fail-safe) governs the parse-error halt in Step 1 (exit code 2). Rule 6 is risk-gated, not mode-gated; reconciliation's Step 6 commit fires unconditionally per ADR-014 (P172). Rule 6 does NOT carve out the Step 6 commit on mode grounds.
140
142
 
141
143
  ## Confirmation
142
144
 
@@ -144,7 +146,7 @@ This skill's contract holds when:
144
146
  1. The script `packages/itil/scripts/reconcile-readme.sh` is read-only — no live README mutation in the script layer (mutation only in this skill's Step 4, via the Edit tool).
145
147
  2. Each agent-applied edit preserves the README's narrative content (prose paragraph at top, Closed section free text).
146
148
  3. After Step 4 + Step 5, a re-run of the script reports exit 0 (clean).
147
- 4. In AFK mode, the reconciled README rides a single commit (Step 6 single-purpose commit).
149
+ 4. The reconciled README rides a single commit (Step 6 single-purpose commit) regardless of invocation mode — interactive and AFK behave identically per ADR-014 governance-skill commit contract (P172).
148
150
  5. The skill is invoked from `/wr-itil:manage-problem` Step 0, `/wr-itil:work-problems` Step 0, AND direct user invocation — no other invocation surface (e.g., `/wr-itil:transition-problem` does NOT call this skill; per architect verdict P062 already covers transition-time refresh inside the same commit, redundant preflight here would pay the cost on every transition).
149
151
 
150
152
  ## Related
@@ -158,3 +160,5 @@ This skill's contract holds when:
158
160
  - `docs/decisions/022-problem-lifecycle-verification-pending-status.proposed.md` — Confirmation criterion 3 extension.
159
161
  - **P094** (`docs/problems/094-...closed.md`) — refresh-on-create. Composes; this skill is robustness on top, not supersession.
160
162
  - **P062** (`docs/problems/062-...closed.md`) — refresh-on-transition. Composes; same.
163
+ - **P172** (`docs/problems/open/172-skill-contract-interactive-vs-afk-commit-gating-anti-pattern-contradicts-adr-014.md`) — removed the Step 6 interactive-vs-AFK commit-gating carve-out 2026-06-09. The carve-out contradicted ADR-014 and produced uncommitted reconciliations across months of AFK-equivalent sessions before the FFS-grade correction surfaced it.
164
+ - `docs/decisions/044-decision-delegation-contract.proposed.md` — framework-resolution boundary; the commit decision is framework-mediated (ADR-014), not user direction-setting.
@@ -149,3 +149,53 @@ setup() {
149
149
  run grep -iE "re-run.*script|re.run.*reconcile-readme|re-run.*reconcile" "$SKILL_FILE"
150
150
  [ "$status" -eq 0 ]
151
151
  }
152
+
153
+ # ── Step 6 unconditional-commit contract (P172) ─────────────────────────────
154
+
155
+ @test "reconcile-readme: Step 6 does NOT carry an interactive-vs-AFK commit-gating carve-out (P172)" {
156
+ # P172: prior Step 6 prose carved out "When invoked interactively, do
157
+ # NOT auto-commit — present a diff summary to the user and let them
158
+ # stage + commit". This contradicts ADR-014 ("governance skills commit
159
+ # their own work") which is framework-mediated, not user direction-
160
+ # setting. User pinned FFS-grade: "I haven't committed anything for
161
+ # months. You do all the commits". The contract holds when the prose
162
+ # does NOT re-introduce the mode-gated suppression on the Step 6 commit.
163
+ #
164
+ # Negative assertion: no "do NOT auto-commit", "do not auto-commit",
165
+ # or "let.*user.*stage" phrasings that would re-introduce the carve-out.
166
+ # Risk-gated phrasing (above appetite, AskUserQuestion unavailable) is
167
+ # policy-correct (ADR-013 Rule 6) and excluded — but reconcile-readme
168
+ # has no risk-above-appetite branch (pure mechanical README refresh).
169
+ run grep -iE "do NOT auto-commit|do not auto-commit|let (the|them|user).{0,20}stage.*commit|when invoked interactively.{0,30}(commit|stage)" "$SKILL_FILE"
170
+ [ "$status" -ne 0 ]
171
+ }
172
+
173
+ @test "reconcile-readme: Step 6 names ADR-014 as the unconditional-commit authority (P172)" {
174
+ # Positive assertion: Step 6 must cite ADR-014 ("governance skills
175
+ # commit their own work") as the authority for the unconditional
176
+ # commit. The prose must also explicitly state the commit fires
177
+ # regardless of invocation mode so the contract is discoverable.
178
+ #
179
+ # Extract Step 6 region: from the line AFTER "### Step 6." through to
180
+ # the next H2/H3 heading. Awk range patterns where start and end can
181
+ # match the same line collapse to one line, so we advance start past
182
+ # the heading line via a flag.
183
+ STEP6="$(awk '
184
+ /^### Step 6\./ { in_step=1; next }
185
+ in_step && /^(### |## )/ { in_step=0 }
186
+ in_step { print }
187
+ ' "$SKILL_FILE")"
188
+ [ -n "$STEP6" ]
189
+ # ADR-014 cited inside Step 6.
190
+ echo "$STEP6" | grep -F "ADR-014"
191
+ # Unconditional / regardless-of-mode phrasing inside Step 6.
192
+ echo "$STEP6" | grep -iE "unconditional|regardless of (invocation )?mode|framework-mediated"
193
+ }
194
+
195
+ @test "reconcile-readme: Step 6 names interactive and AFK commit behaviour as identical (P172)" {
196
+ # The Confirmation criterion 4 (or Step 6 prose) must explicitly assert
197
+ # interactive and AFK commit behaviour are identical. This is the
198
+ # behavioural contract that the unified commit shape obeys.
199
+ run grep -iE "interactive and AFK behave identically|regardless of invocation mode|interactive.*AFK.*identical|identical.*interactive.*AFK" "$SKILL_FILE"
200
+ [ "$status" -eq 0 ]
201
+ }
@@ -88,6 +88,33 @@ Destination-specific pre-flight checks gate the transition. If any check fails,
88
88
 
89
89
  - [ ] The fix has been implemented (the transition typically rides with the `fix(<scope>): ... (closes P<NNN>)` commit)
90
90
  - [ ] A release marker is available (version, commit SHA, or date) so the `## Fix Released` section can name it
91
+ - [ ] **Conditional-deferral check (P184)** — see the dedicated subsection below; halt the transition if any conditional deferral has lifted with unticked work remaining
92
+
93
+ #### Conditional-deferral check on K→V (P184) — copy-not-move sibling
94
+
95
+ This subsection is the **copy-not-move sibling** of `/wr-itil:manage-problem` SKILL.md Step 7's "Conditional-deferral check BEFORE the rename (P184)" block per [ADR-010](../../../docs/decisions/010-rename-wr-problem-to-wr-itil.proposed.md) amended "Split-skill execution ownership" rule (P093). Both surfaces must carry the check; drift between the two copies re-opens P184's silent-loss failure mode on whichever surface lacks the check.
96
+
97
+ BEFORE the `git mv` to `.verifying.md`, scan the `.known-error.md` ticket body for **phase-tracking sections with unticked checkboxes whose deferral conditions have now lifted**. Conditional-deferral language ("Phase N SHIP deferred to post-Phase-M-graduation" / "deferred-pending-X-graduation" / "Phase N deferred until Y") names a CONDITION — it is NOT terminal. When the gating condition fires (Phase M graduates, dependency Y ships), the conditionally-deferred work is back IN SCOPE; transitioning K→V while it remains unticked silently loses the work. P184's driver case (P170 Phase 2 SHIP deferred to post-Phase-1-graduation) is the canonical regression — the agent's NLP parsed "deferred" as terminal without checking the conditional clause and would have lost the Phase 2 work if the user hadn't asked an orthogonal question.
98
+
99
+ **Detection** (run in order):
100
+
101
+ 1. Grep the ticket body for phase-tracking section headers — regex `^### (Phase|Slice|Tier) [0-9]+` covers the canonical shapes.
102
+ 2. For each detected section, count the unticked `- [ ]` checkboxes inside it (up to the next `^### ` boundary or EOF).
103
+ 3. Grep the body for conditional-deferral markers: `deferred (?:to|pending|until) (?:post-)?[A-Za-z0-9-]+(?:-graduation)?`, `Phase [0-9]+ (?:SHIP )?deferred`, `deferred-pending-[a-z-]+`.
104
+ 4. For each conditional-deferral marker, resolve whether the **gating condition** has fired — check whether the named phase/ticket/RFC has reached `.closed.md` / `.verifying.md` (for tickets) or `closed` lifecycle (for RFCs / stories per ADR-060).
105
+
106
+ **Halt-and-route** (when ANY conditional deferral has lifted with unticked work remaining):
107
+
108
+ Emit a structured report naming each deferred section + the lifted condition + the unticked task count, then **halt the transition**. Per ADR-044 category 2 (deviation-approval), this is NOT a per-transition lazy ask — it is a class-of-behaviour deviation surface that the user owns.
109
+
110
+ - **Interactive**: fire `AskUserQuestion` with `header: "Conditional deferral lifted"` + three options: (1) re-open Phase N and work the deferred tasks now (recommended; revert to Known Error); (2) confirm Phase N is permanently out of scope (proceed with K→V; append `<!-- P184: user-confirmed Phase N permanently OOS -->` marker to suppress re-detection); (3) split Phase N into a new ticket (halt K→V; route to `/wr-itil:capture-problem`).
111
+ - **AFK** (per ADR-013 Rule 6 + P352 queue-and-continue): queue an `outstanding_questions` entry naming the local ticket ID + the lifted-condition citation + the unticked task count + the three options. **Do NOT auto-transition.** Brief the substance BEFORE referencing IDs per `feedback_brief_before_id.md`.
112
+
113
+ **Proceed silently** (no halt) when no phase-tracking sections exist, every task in such sections is ticked, the deferral marker explicitly states "permanently out of scope" / "won't fix" / "rejected" without a conditional clause, OR the section carries the `<!-- P184: user-confirmed Phase N permanently OOS -->` marker from a prior surfacing.
114
+
115
+ **Why halt-and-route not silent-default-with-marker**: P184's failure mode is "work silently lost if user doesn't notice"; the halt-and-route shape catches the failure at the transition surface where the loss occurs. Driver: user direction in P184 Workaround section — *"explicitly ask the user 'is Phase N still deferred or is it now in-scope?' before transitioning when the ticket body shows phase-tracking sections."*
116
+
117
+ This check fires BEFORE the P330 Release-vehicle seed step in Step 6 — halt-on-conditional-deferral is the outer gate, seed-and-rename is the inner mechanic.
91
118
 
92
119
  **Verification Pending → Closed** (`<status>` = `close`) requires:
93
120