@windyroad/itil 0.14.0 → 0.15.0

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "wr-itil",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "ITIL-aligned IT service management for Claude Code"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
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"
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: wr-itil:close-incident
3
+ description: Close a restored incident — gated on the Linked Problem reaching Known Error, Verifying, or Closed (or a ## No Problem justification). Renames .restored.md to .closed.md.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Close Incident
8
+
9
+ Close a restored incident. Closing is the final lifecycle transition — once closed, the incident file is archival and does not appear in active incident views. The close operation is gated on the Linked Problem reaching an acceptable terminal state, or on the incident carrying a documented "No Problem" justification. The gate exists so the problem-handoff invariant (JTBD-201) is preserved: every incident either tracks to a root cause or carries an explicit note that none is required.
10
+
11
+ This skill is the P071 phased-landing split of `/wr-itil:manage-incident <I> close` per ADR-010 amended Skill Granularity rule: one skill per distinct user intent. The argument `<I>` is a data parameter, permitted under the amendment. The original `/wr-itil:manage-incident <I> close` subcommand route remains as a thin-router forwarder during the deprecation window but is scheduled for removal in `@windyroad/itil`'s next major version.
12
+
13
+ ## Arguments
14
+
15
+ `/wr-itil:close-incident <I###>` — one positional data parameter:
16
+
17
+ - `<I###>` — the incident ID (e.g. `I007` or bare `007`). Resolves to `docs/incidents/<I###>-*.restored.md` (primary path) or `docs/incidents/<I###>-*.closed.md` (idempotent — already-closed short-circuits).
18
+
19
+ If `$ARGUMENTS` is empty or malformed, read the message from the arguments and report the expected shape. No AskUserQuestion — the gate itself is a hard check with a message, and an empty-arguments case is a user misinvocation, not a decisional branch.
20
+
21
+ ## Close gate (ADR-011 + ADR-022)
22
+
23
+ The close operation checks the Linked Problem's file suffix:
24
+
25
+ | Linked problem state | File suffix | Close allowed? | Rationale |
26
+ |----------------------|-------------|----------------|-----------|
27
+ | Open | `.open.md` | **Blocked** | Root cause work not yet converged. The problem may still reclassify, re-scope, or need a fix before the incident can be safely archived. |
28
+ | Known Error | `.known-error.md` | Allowed | Root cause is identified and understood; a workaround exists or the risk is accepted. Incident may close. |
29
+ | Verifying | `.verifying.md` | Allowed (ADR-022) | Fix released, root cause confirmed, post-release verification pending. Per ADR-022, verifying satisfies the "Restored → Closed" handoff at least as well as Known Error did under the old contract. |
30
+ | Closed | `.closed.md` | Allowed | Problem fully resolved. Incident may close. |
31
+
32
+ If the incident carries a `## No Problem` section (instead of a Linked Problem), the gate is bypassed — the user has documented why no problem record is required.
33
+
34
+ ## Steps
35
+
36
+ ### 1. Parse arguments
37
+
38
+ Extract `<I###>` from `$ARGUMENTS`. Normalise:
39
+
40
+ - Accept `I007`, `i007`, `007`, `7` → canonicalise to `I007` (uppercase I + zero-padded 3 digits).
41
+ - If missing, report "Usage: `/wr-itil:close-incident <I###>`" and exit.
42
+
43
+ ### 2. Locate the incident file
44
+
45
+ ```bash
46
+ ls docs/incidents/<I###>-*.restored.md docs/incidents/<I###>-*.closed.md 2>/dev/null
47
+ ```
48
+
49
+ - If no file matches, report "No restored incident `<I###>` found. If the incident is still mitigating, run `/wr-itil:restore-incident <I###>` first. If no incident with this ID exists, check `/wr-itil:list-incidents`." and exit.
50
+ - If a `.closed.md` file matches, this is an idempotent re-invocation — report "Incident `<I###>` is already closed." and exit without further action.
51
+ - If a `.restored.md` file matches, proceed to Step 3.
52
+ - If both suffixes somehow match (should not happen under the naming convention), report the ambiguity and exit.
53
+
54
+ ### 3. Read the Linked Problem (or No Problem) section
55
+
56
+ Read the incident file and locate either:
57
+
58
+ - A `## Linked Problem` section with a line like `P<NNN> (<title>) — <status>`, OR
59
+ - A `## No Problem` section with a justification.
60
+
61
+ Branch on which section is present:
62
+
63
+ **Case A — `## Linked Problem` present**:
64
+
65
+ 1. Extract the problem ID `P<NNN>` from the section.
66
+ 2. Locate the problem file:
67
+
68
+ ```bash
69
+ ls docs/problems/<NNN>-*.md 2>/dev/null | head -1
70
+ ```
71
+
72
+ Accept both `P<NNN>-*.md` and `<NNN>-*.md` naming conventions for robustness.
73
+
74
+ 3. Read the problem file's suffix:
75
+ - `.open.md` → close is **blocked**. Report: "Linked problem `P<NNN>` is still Open. Transition it to Known Error (via `/wr-itil:transition-problem P<NNN> known-error`), or let the release verification pipeline take it to `.verifying.md` before closing this incident. If the problem is genuinely not required, update the Linked Problem section to a No Problem justification instead." and exit.
76
+ - `.known-error.md`, `.verifying.md`, or `.closed.md` → close is **allowed**. Proceed to Step 4.
77
+
78
+ **Case B — `## No Problem` present**:
79
+
80
+ Close is **allowed**. Proceed to Step 4.
81
+
82
+ **Case C — neither section present**:
83
+
84
+ Report: "Incident `<I###>` has no Linked Problem or No Problem section. Run `/wr-itil:restore-incident <I###>` to complete the restore handoff first (which writes one of those sections), or run `/wr-itil:link-incident <I###> P<MMM>` to attach an existing problem, or edit the file manually to add a No Problem section." and exit.
85
+
86
+ ### 4. Transition to closed
87
+
88
+ Compute a UTC timestamp (e.g. `2026-04-21T14:37Z`). Then:
89
+
90
+ 1. `git mv docs/incidents/<I###>-<title>.restored.md docs/incidents/<I###>-<title>.closed.md`
91
+ 2. Update the `**Status**:` field from `Restored` to `Closed` via `Edit`.
92
+ 3. Append to the `## Timeline` section:
93
+
94
+ ```markdown
95
+ - [<timestamp> UTC] Incident closed
96
+ ```
97
+
98
+ ### 5. Quality checks
99
+
100
+ After the close, verify:
101
+
102
+ - **Status consistency**: `**Status**:` field matches the filename suffix (`Closed` + `.closed.md`).
103
+ - **Timeline monotonicity**: the "Incident closed" entry's timestamp is ≥ the last existing timeline entry's timestamp.
104
+ - **Post-close sections present**: either `## Linked Problem` with an acceptable terminal-state problem, or `## No Problem` with a justification.
105
+
106
+ ### 6. Report
107
+
108
+ Report:
109
+
110
+ - The file path closed.
111
+ - The incident ID and title.
112
+ - The transition (Restored → Closed).
113
+ - The linked problem ID (if any) and its state at close-time, OR the No Problem justification.
114
+ - Any quality-check warnings.
115
+
116
+ ### 7. Commit the completed work (ADR-014)
117
+
118
+ Per ADR-014, governance skills commit their own work.
119
+
120
+ 1. `git add` the renamed incident file.
121
+ 2. Delegate to `wr-risk-scorer:pipeline` (subagent_type: `wr-risk-scorer:pipeline`) to assess the staged changes and create a bypass marker. If the subagent type is not available (spawned subagent surface), invoke `/wr-risk-scorer:assess-release` via the Skill tool instead — per ADR-015 it wraps the same pipeline subagent.
122
+ 3. `git commit -m "docs(incidents): close I<NNN>"`.
123
+ 4. If risk is above appetite: report the above-appetite state clearly and exit without committing (close-incident does not carry `AskUserQuestion` in its allowed-tools — the gate is a hard check). The user can re-run with `--force` semantics by invoking the commit manually if they choose.
124
+
125
+ ### 8. Auto-release when changesets are queued (ADR-020)
126
+
127
+ **Skip this step if the skill is running inside an AFK orchestrator.** Orchestrators handle release cadence themselves per ADR-018 (Step 6.5). When in doubt, defer to the orchestrator by skipping this step.
128
+
129
+ Otherwise, after the commit in step 7 lands, drain the release queue so the close lands on npm without requiring manual user action.
130
+
131
+ **Mechanism — delegate, do not re-implement scoring (per ADR-015):**
132
+
133
+ 1. Invoke the release scorer. Two paths are valid:
134
+ - **Primary**: delegate to subagent type `wr-risk-scorer:pipeline` via the Agent tool.
135
+ - **Fallback**: if that subagent type is not available, invoke skill `/wr-risk-scorer:assess-release` via the Skill tool.
136
+ 2. Read the returned `RISK_SCORES: commit=X push=Y release=Z` line.
137
+ 3. **Drain condition**: if `push` and `release` are both within appetite (≤ 4/25, "Low" band per `RISK-POLICY.md`), AND `.changeset/` is non-empty, proceed to the drain action. Otherwise, skip the drain and report the unreleased state.
138
+
139
+ **Drain action (non-interactive, policy-authorised per ADR-013 Rule 6):**
140
+
141
+ 1. Run `npm run push:watch` (push + wait for CI to pass).
142
+ 2. If `.changeset/` remains non-empty after push (i.e. a release PR is pending), run `npm run release:watch` (merge the release PR + wait for npm publish).
143
+ 3. Report the release: "Released <package>@<version>. Close record is now live on npm."
144
+
145
+ **Failure handling**: if `release:watch` fails (CI failure, publish failure), stop and report the failure clearly. Do not retry non-interactively — the user must intervene.
146
+
147
+ **Above-appetite branch**: if push/release risk is above appetite, skip the drain and report: "Release skipped — risk above appetite. Run `npm run push:watch` and `npm run release:watch` manually when ready."
148
+
149
+ ## Ownership boundary
150
+
151
+ `close-incident` writes the `## Timeline` close entry, the Status field, and the `.restored.md → .closed.md` rename. It does NOT:
152
+
153
+ - Restore the incident (that is `/wr-itil:restore-incident <I###>` — slice 6b).
154
+ - Link a problem to the incident (that is `/wr-itil:link-incident <I###> P<MMM>` — slice 6d).
155
+ - Record a mitigation attempt (that is `/wr-itil:mitigate-incident <I###> <action>` — slice 6a).
156
+ - Transition the linked problem's status (that is `/wr-itil:transition-problem P<NNN> <status>`).
157
+ - Rename a `.investigating.md` or `.mitigating.md` file — the incident must already be `.restored.md`.
158
+ - Prompt the user for decisions — the gate is a hard check with a message, never a prompt. If a decisional branch is needed (e.g. "close anyway without a linked problem"), the user edits the incident file to add a No Problem section and re-runs.
159
+
160
+ If the user wants any of the above, the skill reports the appropriate sibling and exits.
161
+
162
+ ## Related
163
+
164
+ - **P071** (`docs/problems/071-argument-based-skill-subcommands-are-not-discoverable.open.md`) — originating ticket. This skill is slice 6c of the P071 phased-landing plan.
165
+ - **ADR-010 amended** (`docs/decisions/010-rename-wr-problem-to-wr-itil.proposed.md` — Skill Granularity section) — canonical skill-split naming + forwarder contract + `deprecated-arguments: true` frontmatter flag.
166
+ - **ADR-011** (`docs/decisions/011-manage-incident-skill.proposed.md`) — incident lifecycle file-suffix conventions (`.investigating.md` / `.mitigating.md` / `.restored.md` / `.closed.md`) + close-gate-on-linked-problem rule.
167
+ - **ADR-022** (`docs/decisions/022-problem-lifecycle-verification-pending-status.proposed.md`) — `.verifying.md` status; extends the close gate to accept `.verifying.md` alongside `.known-error.md` and `.closed.md`.
168
+ - **ADR-013** Rule 1 — structured user interaction (forwarder emits systemMessage for deprecation notice; gate-blocks are hard-fail messages, not prompts).
169
+ - **ADR-013** Rule 6 — policy-within-appetite non-interactive actions (release drain).
170
+ - **ADR-014** — governance skills commit their own work.
171
+ - **ADR-015** — release scorer delegation pattern.
172
+ - **ADR-020** — auto-release when changesets are queued.
173
+ - **ADR-037** (`docs/decisions/037-skill-testing-strategy.proposed.md`) — contract-assertion bats pattern applied to this skill.
174
+ - **JTBD-001** (`docs/jtbd/solo-developer/JTBD-001-enforce-governance.proposed.md`) — discoverable surface via `/wr-itil:` autocomplete.
175
+ - **JTBD-101** (`docs/jtbd/plugin-developer/JTBD-101-extend-suite.proposed.md`) — one skill per distinct user intent.
176
+ - **JTBD-201** (`docs/jtbd/tech-lead/JTBD-201-restore-service-fast.proposed.md`) — close gate preserves the problem-handoff audit trail.
177
+ - `packages/itil/skills/manage-incident/SKILL.md` — hosts the thin-router forwarder for the deprecated `manage-incident <I###> close` form.
178
+ - `packages/itil/skills/restore-incident/SKILL.md` — slice 6b precedent; close-incident is the next transition after restore.
179
+ - `packages/itil/skills/transition-problem/SKILL.md` — the skill used to advance a linked problem from `.open.md` to `.known-error.md` so the incident close gate can pass.
180
+
181
+ $ARGUMENTS
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bats
2
+ # Contract assertions for /wr-itil:close-incident (P071 split slice 6c).
3
+ #
4
+ # This skill hosts the "close a restored incident" user intent previously
5
+ # hidden behind /wr-itil:manage-incident <I> close. It checks the Linked
6
+ # Problem's file suffix and, if the problem is in an acceptable terminal
7
+ # state (.known-error.md / .verifying.md / .closed.md) OR the incident
8
+ # documents a No Problem justification, renames the incident file from
9
+ # .restored.md to .closed.md and updates the Status field.
10
+ #
11
+ # Structural assertion — Permitted Exception to the source-grep ban
12
+ # (ADR-005 / P011 / ADR-037 contract-assertion pattern).
13
+ #
14
+ # @problem P071
15
+ # @jtbd JTBD-001 (enforce governance without slowing down — discoverable surface)
16
+ # @jtbd JTBD-101 (extend the suite with clear patterns — one skill per distinct user intent)
17
+ # @jtbd JTBD-201 (restore service fast with an audit trail — close gate preserves the problem-handoff link)
18
+ #
19
+ # Cross-reference:
20
+ # P071: docs/problems/071-argument-based-skill-subcommands-are-not-discoverable.open.md
21
+ # ADR-010 amended (Skill Granularity section) — split naming + forwarder contract
22
+ # ADR-011 — manage-incident skill-wrapping precedent (close gate on linked problem)
23
+ # ADR-022 — problem lifecycle verification-pending status (.verifying.md accepted alongside .known-error.md / .closed.md)
24
+ # ADR-037 — contract-assertion bats pattern
25
+
26
+ setup() {
27
+ SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
28
+ SKILL_FILE="${SKILL_DIR}/SKILL.md"
29
+ }
30
+
31
+ @test "SKILL.md exists and has frontmatter" {
32
+ [ -f "$SKILL_FILE" ]
33
+ run head -1 "$SKILL_FILE"
34
+ [ "$status" -eq 0 ]
35
+ [ "$output" = "---" ]
36
+ }
37
+
38
+ @test "SKILL.md frontmatter name is wr-itil:close-incident (P071 + ADR-010 amended)" {
39
+ # Split naming convention per ADR-010 amendment: <verb>-<object> pair.
40
+ # The new skill's name must match the phased-landing plan pinned in P071.
41
+ run grep -n "^name: wr-itil:close-incident$" "$SKILL_FILE"
42
+ [ "$status" -eq 0 ]
43
+ }
44
+
45
+ @test "SKILL.md frontmatter description names the close intent (P071)" {
46
+ # Description must name "close" and "incident" so Claude Code autocomplete
47
+ # surfaces the user intent rather than a generic name.
48
+ run grep -inE "^description:.*clos.*incident|^description:.*incident.*clos" "$SKILL_FILE"
49
+ [ "$status" -eq 0 ]
50
+ }
51
+
52
+ @test "SKILL.md frontmatter allowed-tools grants file-mutation surface (P071 — close requires rename + edit)" {
53
+ # close-incident renames .restored.md → .closed.md and updates the
54
+ # Status field. Unlike restore-incident, the linked-problem gate is a
55
+ # hard check (not a prompt), so no AskUserQuestion is required. No
56
+ # cross-skill invocation either, so no Skill tool is required.
57
+ run grep -nE "^allowed-tools:" "$SKILL_FILE"
58
+ [ "$status" -eq 0 ]
59
+ run grep -nE "^allowed-tools:.*Write" "$SKILL_FILE"
60
+ [ "$status" -eq 0 ]
61
+ run grep -nE "^allowed-tools:.*Edit" "$SKILL_FILE"
62
+ [ "$status" -eq 0 ]
63
+ run grep -nE "^allowed-tools:.*Bash" "$SKILL_FILE"
64
+ [ "$status" -eq 0 ]
65
+ }
66
+
67
+ @test "SKILL.md documents the .restored.md → .closed.md rename (P071 + ADR-011)" {
68
+ # The close transition renames the incident file from .restored.md
69
+ # to .closed.md. The SKILL.md must name both suffixes explicitly so
70
+ # the file-suffix contract is legible.
71
+ run grep -inE "\.restored\.md" "$SKILL_FILE"
72
+ [ "$status" -eq 0 ]
73
+ run grep -inE "\.closed\.md" "$SKILL_FILE"
74
+ [ "$status" -eq 0 ]
75
+ }
76
+
77
+ @test "SKILL.md documents the Linked-Problem gate with .known-error.md allowance (P071 + ADR-011)" {
78
+ # Per ADR-011, the close gate accepts a linked problem in
79
+ # .known-error.md state. Older form of the gate — must still be
80
+ # named explicitly post-ADR-022.
81
+ run grep -inE "known-error" "$SKILL_FILE"
82
+ [ "$status" -eq 0 ]
83
+ }
84
+
85
+ @test "SKILL.md documents the Linked-Problem gate .verifying.md allowance (P071 + ADR-022)" {
86
+ # Per ADR-022, .verifying.md (fix released, root cause confirmed,
87
+ # awaiting verification) also satisfies the incident-close gate.
88
+ # The SKILL.md must name this allowance explicitly so the ADR-022
89
+ # extension is preserved post-split.
90
+ run grep -inE "\.verifying\.md|verifying" "$SKILL_FILE"
91
+ [ "$status" -eq 0 ]
92
+ }
93
+
94
+ @test "SKILL.md documents the No Problem justification path (P071 + ADR-011)" {
95
+ # Per ADR-011, the incident may carry a ## No Problem section with a
96
+ # justification (e.g. "one-off cosmic-bit-flip"). In that case the
97
+ # linked-problem gate is bypassed. The SKILL.md must document the
98
+ # No Problem acceptance so the audit trail invariant is preserved.
99
+ run grep -inE "No Problem|no problem" "$SKILL_FILE"
100
+ [ "$status" -eq 0 ]
101
+ }
102
+
103
+ @test "SKILL.md documents the .open.md block condition (P071 + ADR-011)" {
104
+ # Per ADR-011, a linked problem in .open.md state blocks the close.
105
+ # The user must transition the problem to Known Error (or the
106
+ # verification pipeline must complete) before the incident can close.
107
+ # The SKILL.md must name this blocking condition explicitly.
108
+ run grep -inE "\.open\.md|Open" "$SKILL_FILE"
109
+ [ "$status" -eq 0 ]
110
+ }
111
+
112
+ @test "SKILL.md cites P071 and ADR-010 amended (P071 + ADR-025)" {
113
+ # ADR-025 inheritance per ADR-037: contract-assertion bats should reflect
114
+ # traceability cites on the skill spec document.
115
+ run grep -inE "P071|ADR-010" "$SKILL_FILE"
116
+ [ "$status" -eq 0 ]
117
+ }
118
+
119
+ @test "SKILL.md cites ADR-011 + ADR-022 for the close gate (P071 + ADR-011 + ADR-022)" {
120
+ # close-incident inherits the Linked-Problem gate from ADR-011 and the
121
+ # .verifying.md extension from ADR-022. Both must be cited so the
122
+ # precedent chain is legible.
123
+ run grep -inE "ADR-011" "$SKILL_FILE"
124
+ [ "$status" -eq 0 ]
125
+ run grep -inE "ADR-022" "$SKILL_FILE"
126
+ [ "$status" -eq 0 ]
127
+ }
128
+
129
+ @test "SKILL.md does not carry a deprecated-arguments frontmatter flag (clean-split skill)" {
130
+ # close-incident is a clean-split skill with no argument-subcommands
131
+ # itself (its argument is a data parameter — incident ID only).
132
+ # ADR-010 amendment's `deprecated-arguments: true` flag is only valid
133
+ # on host skills with forwarder routes. close-incident is a
134
+ # forwarder TARGET, not a host. It must NOT carry the flag.
135
+ run grep -E "^deprecated-arguments:" "$SKILL_FILE"
136
+ [ "$status" -ne 0 ]
137
+ }
138
+
139
+ @test "SKILL.md does not use word-argument subcommand branching (P071 regression guard)" {
140
+ # The whole point of P071: Claude Code autocomplete does not surface
141
+ # word-argument subcommands. A clean-split skill must not reintroduce
142
+ # word-arg subcommand routing. The data parameter <I> is a string,
143
+ # not a verb keyword.
144
+ run grep -inE "If arguments start with \"(list|mitigate|restore|close|link)\"|If arguments contain \"(list|mitigate|restore|close|link)\"" "$SKILL_FILE"
145
+ [ "$status" -ne 0 ]
146
+ }
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: wr-itil:link-incident
3
+ description: Link an incident to an existing problem — writes or updates the ## Linked Problem section on the incident file with the problem's ID, title, and current status.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Link Incident
8
+
9
+ Attach an existing problem record to an incident. This is the standalone link operation — the `/wr-itil:restore-incident` skill already handles the link-at-restore flow (writing the `## Linked Problem` section as part of problem-handoff). The `link-incident` skill is for after-the-fact links: when an incident was closed with a `## No Problem` note that later turned out to be wrong, when multiple incidents share a newly-created problem and need to be linked, or when a problem existed in parallel but was not attached during restore.
10
+
11
+ This skill is the P071 phased-landing split of `/wr-itil:manage-incident <I> link P<M>` per ADR-010 amended Skill Granularity rule: one skill per distinct user intent. The arguments `<I>` (incident ID) and `<P>` (problem ID) are data parameters, permitted under the amendment. The original `/wr-itil:manage-incident <I> link P<M>` subcommand route remains as a thin-router forwarder during the deprecation window but is scheduled for removal in `@windyroad/itil`'s next major version.
12
+
13
+ ## Arguments
14
+
15
+ `/wr-itil:link-incident <I###> P<MMM>` — two positional data parameters:
16
+
17
+ - `<I###>` — the incident ID (e.g. `I007` or bare `007`). Resolves to `docs/incidents/<I###>-*.{investigating,mitigating,restored,closed}.md`.
18
+ - `P<MMM>` — the problem ID (e.g. `P071` or bare `071`). Resolves to `docs/problems/<MMM>-*.md` (any lifecycle suffix).
19
+
20
+ If either argument is missing or malformed, report the expected shape and exit. No AskUserQuestion — both are data parameters, not decisional branches.
21
+
22
+ ## Steps
23
+
24
+ ### 1. Parse arguments
25
+
26
+ Extract `<I###>` and `P<MMM>` from `$ARGUMENTS`. Normalise:
27
+
28
+ - Accept `I007`, `i007`, `007`, `7` → canonicalise the incident ID to `I007`.
29
+ - Accept `P071`, `p071`, `071`, `71` → canonicalise the problem ID to `P071`.
30
+ - If either is missing, report "Usage: `/wr-itil:link-incident <I###> P<MMM>`" and exit.
31
+
32
+ ### 2. Locate the incident file
33
+
34
+ ```bash
35
+ ls docs/incidents/<I###>-*.md 2>/dev/null | head -1
36
+ ```
37
+
38
+ The link operation works on any incident status — investigating, mitigating, restored, or closed. (Closed incidents are rare to link retroactively but not impossible — the audit-trail requirement is preserved either way.)
39
+
40
+ - If no file matches, report "No incident `<I###>` found. Check `/wr-itil:list-incidents` for active incidents or search `docs/incidents/` for closed incidents." and exit.
41
+ - If multiple files match (should not happen under the naming convention), report the ambiguity and exit.
42
+
43
+ ### 3. Locate the problem file
44
+
45
+ ```bash
46
+ ls docs/problems/<MMM>-*.md 2>/dev/null | head -1
47
+ ```
48
+
49
+ Accept any lifecycle suffix: `.open.md`, `.known-error.md`, `.verifying.md`, `.closed.md`.
50
+
51
+ - If no file matches, report "No problem `P<MMM>` found. Check `/wr-itil:list-problems` for the current backlog." and exit.
52
+ - Read the problem file's title from the `# Problem <MMM>: <Title>` header line.
53
+ - Read the problem file's status from the filename suffix (Open / Known Error / Verifying / Closed).
54
+
55
+ ### 4. Write or update the Linked Problem section
56
+
57
+ Construct the link entry:
58
+
59
+ ```markdown
60
+ ## Linked Problem
61
+ P<MMM> (<title>) — <status>
62
+ ```
63
+
64
+ where `<status>` is the human-readable label derived from the file suffix (`Open`, `Known Error`, `Verifying`, `Closed`).
65
+
66
+ Branch on whether the incident already has a link:
67
+
68
+ **Case A — no `## Linked Problem` or `## No Problem` section present**:
69
+
70
+ Append the `## Linked Problem` section at the end of the incident file.
71
+
72
+ **Case B — `## Linked Problem` section already present**:
73
+
74
+ Replace the existing section's body with the new link entry. Keep the `## Linked Problem` heading. If the existing link matches exactly (same `P<MMM>`, same title, same status), report "Incident `<I###>` is already linked to `P<MMM>` with the same status — no change." and exit without modifying the file.
75
+
76
+ **Case C — `## No Problem` section present**:
77
+
78
+ This is a retroactive-link-after-no-problem case. Replace the `## No Problem` section with a `## Linked Problem` section carrying the new link entry. Also append a `[<timestamp> UTC] Retroactive link to P<MMM>` entry to the `## Timeline` section so the audit trail records the revision.
79
+
80
+ ### 5. Quality checks
81
+
82
+ After the link, verify:
83
+
84
+ - Exactly one of `## Linked Problem` or `## No Problem` exists on the incident file (never both, never neither).
85
+ - The `## Linked Problem` body matches the canonical shape `P<MMM> (<title>) — <status>`.
86
+ - The problem file referenced actually exists in `docs/problems/`.
87
+
88
+ ### 6. Report
89
+
90
+ Report:
91
+
92
+ - The incident ID and file path modified.
93
+ - The problem ID, title, and status linked.
94
+ - Whether the link was a new attach (Case A), an update (Case B), or a retroactive conversion from No Problem (Case C).
95
+ - Any quality-check warnings.
96
+
97
+ ### 7. Commit the completed work (ADR-014)
98
+
99
+ Per ADR-014, governance skills commit their own work.
100
+
101
+ 1. `git add` the modified incident file.
102
+ 2. Delegate to `wr-risk-scorer:pipeline` (subagent_type: `wr-risk-scorer:pipeline`) to assess the staged changes and create a bypass marker. If the subagent type is not available (spawned subagent surface), invoke `/wr-risk-scorer:assess-release` via the Skill tool instead — per ADR-015 it wraps the same pipeline subagent.
103
+ 3. `git commit -m "docs(incidents): link I<NNN> to P<MMM>"`.
104
+ 4. If risk is above appetite: report the above-appetite state clearly and exit without committing. The user can re-run the commit manually if they choose.
105
+
106
+ ### 8. Auto-release when changesets are queued (ADR-020)
107
+
108
+ **Skip this step if the skill is running inside an AFK orchestrator.** Orchestrators handle release cadence themselves per ADR-018 (Step 6.5). When in doubt, defer to the orchestrator by skipping this step.
109
+
110
+ Otherwise, after the commit in step 7 lands, drain the release queue so the link lands on npm without requiring manual user action.
111
+
112
+ **Mechanism — delegate, do not re-implement scoring (per ADR-015):**
113
+
114
+ 1. Invoke the release scorer. Two paths are valid:
115
+ - **Primary**: delegate to subagent type `wr-risk-scorer:pipeline` via the Agent tool.
116
+ - **Fallback**: if that subagent type is not available, invoke skill `/wr-risk-scorer:assess-release` via the Skill tool.
117
+ 2. Read the returned `RISK_SCORES: commit=X push=Y release=Z` line.
118
+ 3. **Drain condition**: if `push` and `release` are both within appetite (≤ 4/25, "Low" band per `RISK-POLICY.md`), AND `.changeset/` is non-empty, proceed to the drain action. Otherwise, skip the drain and report the unreleased state.
119
+
120
+ **Drain action (non-interactive, policy-authorised per ADR-013 Rule 6):**
121
+
122
+ 1. Run `npm run push:watch` (push + wait for CI to pass).
123
+ 2. If `.changeset/` remains non-empty after push (i.e. a release PR is pending), run `npm run release:watch` (merge the release PR + wait for npm publish).
124
+ 3. Report the release: "Released <package>@<version>. Link record is now live on npm."
125
+
126
+ **Failure handling**: if `release:watch` fails (CI failure, publish failure), stop and report the failure clearly. Do not retry non-interactively — the user must intervene.
127
+
128
+ **Above-appetite branch**: if push/release risk is above appetite, skip the drain and report: "Release skipped — risk above appetite. Run `npm run push:watch` and `npm run release:watch` manually when ready."
129
+
130
+ ## Ownership boundary
131
+
132
+ `link-incident` writes (or updates) the `## Linked Problem` section on an incident file and, in Case C only, appends a retroactive-link Timeline entry. It does NOT:
133
+
134
+ - Create a new problem (that is `/wr-itil:manage-problem` with no args).
135
+ - Update the problem's body (that is `/wr-itil:manage-problem P<MMM>`).
136
+ - Transition the problem's status (that is `/wr-itil:transition-problem P<MMM> <status>`).
137
+ - Transition the incident's status (that is `/wr-itil:restore-incident` or `/wr-itil:close-incident`).
138
+ - Record a mitigation (that is `/wr-itil:mitigate-incident`).
139
+ - Prompt the user for decisions — both arguments are data parameters; any missing-argument case is a hard-fail message, not a decisional branch.
140
+
141
+ If the user wants any of the above, the skill reports the appropriate sibling and exits.
142
+
143
+ ## Related
144
+
145
+ - **P071** (`docs/problems/071-argument-based-skill-subcommands-are-not-discoverable.open.md`) — originating ticket. This skill is slice 6d of the P071 phased-landing plan.
146
+ - **ADR-010 amended** (`docs/decisions/010-rename-wr-problem-to-wr-itil.proposed.md` — Skill Granularity section) — canonical skill-split naming + forwarder contract + `deprecated-arguments: true` frontmatter flag.
147
+ - **ADR-011** (`docs/decisions/011-manage-incident-skill.proposed.md`) — incident lifecycle file-suffix conventions + Linked Problem section convention.
148
+ - **ADR-013** Rule 1 — structured user interaction (forwarder emits systemMessage for deprecation; missing-argument is a hard-fail message, not a prompt).
149
+ - **ADR-014** — governance skills commit their own work.
150
+ - **ADR-015** — release scorer delegation pattern.
151
+ - **ADR-020** — auto-release when changesets are queued.
152
+ - **ADR-037** (`docs/decisions/037-skill-testing-strategy.proposed.md`) — contract-assertion bats pattern applied to this skill.
153
+ - **JTBD-001** (`docs/jtbd/solo-developer/JTBD-001-enforce-governance.proposed.md`) — discoverable surface via `/wr-itil:` autocomplete.
154
+ - **JTBD-101** (`docs/jtbd/plugin-developer/JTBD-101-extend-suite.proposed.md`) — one skill per distinct user intent.
155
+ - **JTBD-201** (`docs/jtbd/tech-lead/JTBD-201-restore-service-fast.proposed.md`) — Linked Problem traceability preserved post-split.
156
+ - `packages/itil/skills/manage-incident/SKILL.md` — hosts the thin-router forwarder for the deprecated `manage-incident <I###> link P<MMM>` form.
157
+ - `packages/itil/skills/restore-incident/SKILL.md` — slice 6b precedent; writes the `## Linked Problem` section as part of problem-handoff. `link-incident` is the standalone-after-the-fact equivalent.
158
+ - `packages/itil/skills/manage-problem/SKILL.md` — the source of truth for problem titles / statuses read during link.
159
+
160
+ $ARGUMENTS
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bats
2
+ # Contract assertions for /wr-itil:link-incident (P071 split slice 6d).
3
+ #
4
+ # This skill hosts the "link an incident to a problem" user intent
5
+ # previously hidden behind /wr-itil:manage-incident <I> link P<M>. It
6
+ # verifies the target problem file exists, then writes (or updates) the
7
+ # ## Linked Problem section of the incident file with the problem ID,
8
+ # title, and current status.
9
+ #
10
+ # Structural assertion — Permitted Exception to the source-grep ban
11
+ # (ADR-005 / P011 / ADR-037 contract-assertion pattern).
12
+ #
13
+ # @problem P071
14
+ # @jtbd JTBD-001 (enforce governance without slowing down — discoverable surface)
15
+ # @jtbd JTBD-101 (extend the suite with clear patterns — one skill per distinct user intent)
16
+ # @jtbd JTBD-201 (restore service fast with an audit trail — Linked Problem traceability)
17
+ #
18
+ # Cross-reference:
19
+ # P071: docs/problems/071-argument-based-skill-subcommands-are-not-discoverable.open.md
20
+ # ADR-010 amended (Skill Granularity section) — split naming + forwarder contract
21
+ # ADR-011 — manage-incident skill-wrapping precedent (Linked Problem section)
22
+ # ADR-037 — contract-assertion bats pattern
23
+
24
+ setup() {
25
+ SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
26
+ SKILL_FILE="${SKILL_DIR}/SKILL.md"
27
+ }
28
+
29
+ @test "SKILL.md exists and has frontmatter" {
30
+ [ -f "$SKILL_FILE" ]
31
+ run head -1 "$SKILL_FILE"
32
+ [ "$status" -eq 0 ]
33
+ [ "$output" = "---" ]
34
+ }
35
+
36
+ @test "SKILL.md frontmatter name is wr-itil:link-incident (P071 + ADR-010 amended)" {
37
+ # Split naming convention per ADR-010 amendment: <verb>-<object> pair.
38
+ # The new skill's name must match the phased-landing plan pinned in P071.
39
+ run grep -n "^name: wr-itil:link-incident$" "$SKILL_FILE"
40
+ [ "$status" -eq 0 ]
41
+ }
42
+
43
+ @test "SKILL.md frontmatter description names the link intent (P071)" {
44
+ # Description must name "link" and "incident" / "problem" so Claude Code
45
+ # autocomplete surfaces the user intent rather than a generic name.
46
+ run grep -inE "^description:.*link.*(incident|problem)|^description:.*(incident|problem).*link" "$SKILL_FILE"
47
+ [ "$status" -eq 0 ]
48
+ }
49
+
50
+ @test "SKILL.md frontmatter allowed-tools grants file-mutation surface (P071 — link requires read + edit)" {
51
+ # link-incident verifies the problem file exists (Read + Glob/Bash),
52
+ # reads the incident file (Read), and writes the Linked Problem
53
+ # section (Edit / Write). No Skill tool (no cross-skill invocation),
54
+ # no AskUserQuestion (both args are data parameters).
55
+ run grep -nE "^allowed-tools:" "$SKILL_FILE"
56
+ [ "$status" -eq 0 ]
57
+ run grep -nE "^allowed-tools:.*Write" "$SKILL_FILE"
58
+ [ "$status" -eq 0 ]
59
+ run grep -nE "^allowed-tools:.*Edit" "$SKILL_FILE"
60
+ [ "$status" -eq 0 ]
61
+ run grep -nE "^allowed-tools:.*Bash" "$SKILL_FILE"
62
+ [ "$status" -eq 0 ]
63
+ }
64
+
65
+ @test "SKILL.md documents the P<MMM> problem-file lookup (P071 + ADR-011)" {
66
+ # The link operation verifies docs/problems/P<MMM>-*.md exists before
67
+ # writing the Linked Problem section. The SKILL.md must name the
68
+ # problem-file lookup explicitly so the cross-directory dependency is
69
+ # legible.
70
+ run grep -inE "docs/problems|P<MMM>|P<NNN>|problem file" "$SKILL_FILE"
71
+ [ "$status" -eq 0 ]
72
+ }
73
+
74
+ @test "SKILL.md documents the ## Linked Problem section write (P071 + ADR-011)" {
75
+ # The link operation writes (or updates) the ## Linked Problem section
76
+ # with `P<MMM> (<title>) — <status>`. The SKILL.md must name the
77
+ # section explicitly so the audit-trail invariant (JTBD-201) is
78
+ # preserved post-split.
79
+ run grep -inE "Linked Problem" "$SKILL_FILE"
80
+ [ "$status" -eq 0 ]
81
+ }
82
+
83
+ @test "SKILL.md documents both data-parameter arguments (P071 — incident ID + problem ID)" {
84
+ # link-incident takes two positional data parameters: the incident ID
85
+ # (I###) and the problem ID (P###). The SKILL.md must document both
86
+ # so the argument shape is legible.
87
+ run grep -inE "I<NNN>|I<###>|incident ID|I007|<I###>" "$SKILL_FILE"
88
+ [ "$status" -eq 0 ]
89
+ run grep -inE "P<MMM>|P<###>|P<NNN>|problem ID" "$SKILL_FILE"
90
+ [ "$status" -eq 0 ]
91
+ }
92
+
93
+ @test "SKILL.md cites P071 and ADR-010 amended (P071 + ADR-025)" {
94
+ # ADR-025 inheritance per ADR-037: contract-assertion bats should reflect
95
+ # traceability cites on the skill spec document.
96
+ run grep -inE "P071|ADR-010" "$SKILL_FILE"
97
+ [ "$status" -eq 0 ]
98
+ }
99
+
100
+ @test "SKILL.md cites ADR-011 for the Linked Problem section convention (P071 + ADR-011)" {
101
+ # link-incident inherits the Linked Problem section convention from
102
+ # ADR-011. The SKILL.md must cite ADR-011 so the precedent chain is
103
+ # legible.
104
+ run grep -inE "ADR-011" "$SKILL_FILE"
105
+ [ "$status" -eq 0 ]
106
+ }
107
+
108
+ @test "SKILL.md does not carry a deprecated-arguments frontmatter flag (clean-split skill)" {
109
+ # link-incident is a clean-split skill with no argument-subcommands
110
+ # itself (its arguments are data parameters — incident ID + problem ID).
111
+ # ADR-010 amendment's `deprecated-arguments: true` flag is only valid
112
+ # on host skills with forwarder routes. link-incident is a forwarder
113
+ # TARGET, not a host. It must NOT carry the flag.
114
+ run grep -E "^deprecated-arguments:" "$SKILL_FILE"
115
+ [ "$status" -ne 0 ]
116
+ }
117
+
118
+ @test "SKILL.md does not use word-argument subcommand branching (P071 regression guard)" {
119
+ # The whole point of P071: Claude Code autocomplete does not surface
120
+ # word-argument subcommands. A clean-split skill must not reintroduce
121
+ # word-arg subcommand routing. The data parameters <I> and <P> are
122
+ # strings, not verb keywords.
123
+ run grep -inE "If arguments start with \"(list|mitigate|restore|close|link)\"|If arguments contain \"(list|mitigate|restore|close|link)\"" "$SKILL_FILE"
124
+ [ "$status" -ne 0 ]
125
+ }