@windyroad/itil 0.35.14-preview.415 → 0.35.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.35.14-preview.415",
3
+ "version": "0.35.14",
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,74 @@
1
+ #!/usr/bin/env bash
2
+ # check-rfc-rejected-alternatives.sh — ADR-052 behavioural lint enforcing
3
+ # ADR-070 (RFCs hold no independent decisions).
4
+ #
5
+ # Invariant (ADR-070 § Confirmation): no RFC body in the RFC directory
6
+ # contains a "Considered Options / Alternatives Rejected" block WITHOUT a
7
+ # matching `adrs:` frontmatter reference. ADR-070 line 44 names the
8
+ # machine-detectable tell: "an RFC body containing a rejected-alternatives
9
+ # block with no matching `adrs:` reference is a decision masquerading as
10
+ # scope." Contested choices belong in an ADR (referenced via `adrs:`),
11
+ # never re-argued in the RFC body.
12
+ #
13
+ # This is an ARTEFACT-STATE behavioural check (ADR-052): given an RFC
14
+ # corpus directory, it inspects the on-disk RFC bodies + frontmatter and
15
+ # reports violations. It is NOT a structural grep of any SKILL.md / agent
16
+ # prose. Detection targets a markdown HEADING block (`## ... Considered
17
+ # Options ...` / `## ... Alternatives Rejected ...`), never a prose mention
18
+ # of the phrase (e.g. a retrofit note explaining the section was removed).
19
+ #
20
+ # Usage: check-rfc-rejected-alternatives.sh [rfcs-dir] (default docs/rfcs)
21
+ # Exit: 0 = clean (no violations); 1 = ≥1 violation; 2 = usage/dir error.
22
+ #
23
+ # Scope: docs/rfcs/ only. ADRs (docs/decisions/) legitimately carry
24
+ # "Considered Options" headings — they ARE the decision ledger; this lint
25
+ # never scans them.
26
+ #
27
+ # @adr ADR-070 (RFCs hold no independent decisions — the invariant)
28
+ # @adr ADR-052 (behavioural-tests-default — artefact-state assertion)
29
+ # @adr ADR-049 (plugin-bundled scripts; adopters run this over their docs/rfcs)
30
+ # @problem P310 (RFC decisions invisible to the ADR-066 oversight net)
31
+
32
+ set -euo pipefail
33
+
34
+ rfcs_dir="${1:-docs/rfcs}"
35
+
36
+ if [ ! -d "$rfcs_dir" ]; then
37
+ echo "check-rfc-rejected-alternatives: not a directory: $rfcs_dir" >&2
38
+ exit 2
39
+ fi
40
+
41
+ # Heading-block detector: a markdown ATX heading (1-6 '#') whose text
42
+ # contains "Considered Options" or "Alternatives Rejected" (case-insensitive).
43
+ # Anchored to '^#' so a prose/blockquote mention of the phrase never matches.
44
+ heading_re='^#{1,6}[[:space:]]+.*([Cc]onsidered [Oo]ptions|[Aa]lternatives [Rr]ejected)'
45
+
46
+ # adrs: frontmatter is non-empty when its line carries ≥1 ADR-<NNN> token.
47
+ # (`adrs:` only appears as a line-leading key in the YAML frontmatter; an
48
+ # RFC body never starts a line with `adrs:`.)
49
+ adrs_re='^adrs:.*ADR-[0-9]'
50
+
51
+ violations=0
52
+ scanned=0
53
+
54
+ # Iterate RFC files (RFC-*.md) in the directory. Sorted for stable output.
55
+ shopt -s nullglob
56
+ for f in "$rfcs_dir"/RFC-*.md; do
57
+ scanned=$((scanned + 1))
58
+ if grep -qE "$heading_re" "$f"; then
59
+ if ! grep -qE "$adrs_re" "$f"; then
60
+ line=$(grep -nE "$heading_re" "$f" | head -1 | cut -d: -f1)
61
+ echo "VIOLATION $f:${line} rejected-alternatives block with empty/absent adrs: frontmatter (ADR-070)"
62
+ violations=$((violations + 1))
63
+ fi
64
+ fi
65
+ done
66
+ shopt -u nullglob
67
+
68
+ if [ "$violations" -gt 0 ]; then
69
+ echo "check-rfc-rejected-alternatives: $violations violation(s) across $scanned RFC(s) — an RFC carrying a rejected-alternatives block must reference its governing ADR(s) in adrs: (ADR-070)." >&2
70
+ exit 1
71
+ fi
72
+
73
+ echo "check-rfc-rejected-alternatives: clean ($scanned RFC(s) scanned; no rejected-alternatives block without adrs: reference)."
74
+ exit 0
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # @problem P310 — RFCs carry independent decisions invisible to the ADR-066
4
+ # human-oversight net. ADR-070 closes the blind spot; this lint enforces it.
5
+ # @adr ADR-070 (RFCs hold no independent decisions — the invariant under test)
6
+ # @adr ADR-052 (behavioural-tests-default — this is an artefact-state behavioural
7
+ # assertion: it RUNS the checker against fixture RFC corpora + the real corpus
8
+ # and asserts the verdict (exit code + output). It does NOT structurally grep
9
+ # the checker's own source, the SKILL.md, or any agent prose — that would be
10
+ # the P081 structural-test-disguised-as-behavioural anti-pattern.)
11
+ # @adr ADR-071 (every fix via RFC — composes; the lint guards the RFC corpus)
12
+ #
13
+ # Contract under test (ADR-070 § Confirmation): no RFC body in docs/rfcs/
14
+ # contains a "Considered Options / Alternatives Rejected" HEADING block
15
+ # without a matching `adrs:` frontmatter reference. The detector targets a
16
+ # markdown heading, never a prose mention of the phrase.
17
+
18
+ setup() {
19
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
20
+ SCRIPT="$REPO_ROOT/packages/itil/scripts/check-rfc-rejected-alternatives.sh"
21
+ FIXTURE_DIR="$(mktemp -d)"
22
+ }
23
+
24
+ teardown() {
25
+ rm -rf "$FIXTURE_DIR"
26
+ }
27
+
28
+ # ── Fixture helper: write an RFC file with given adrs-line + body ────────────
29
+ write_rfc() {
30
+ local name="$1" adrs_line="$2" body="$3"
31
+ cat > "$FIXTURE_DIR/$name" <<EOF
32
+ ---
33
+ status: accepted
34
+ rfc-id: ${name%.md}
35
+ problems: [P999]
36
+ $adrs_line
37
+ stories: []
38
+ ---
39
+
40
+ # ${name%.md}: fixture
41
+
42
+ ## Summary
43
+
44
+ A fixture RFC.
45
+
46
+ $body
47
+ EOF
48
+ }
49
+
50
+ @test "violation: rejected-alternatives heading block WITH empty adrs: -> exit 1 + VIOLATION" {
51
+ write_rfc "RFC-901-bad.md" "adrs: []" $'## Considered Options / Alternatives Rejected\n\n- F1 alternative rejected: foo.'
52
+ run bash "$SCRIPT" "$FIXTURE_DIR"
53
+ [ "$status" -eq 1 ]
54
+ [[ "$output" == *"VIOLATION"* ]]
55
+ [[ "$output" == *"RFC-901-bad.md"* ]]
56
+ }
57
+
58
+ @test "allowed: rejected-alternatives heading block WITH a matching adrs: reference -> exit 0 (clean)" {
59
+ write_rfc "RFC-902-homed.md" "adrs: [ADR-072, ADR-073]" $'## Considered Options / Alternatives Rejected\n\n- The contested choice is recorded in ADR-072; this block references it.'
60
+ run bash "$SCRIPT" "$FIXTURE_DIR"
61
+ [ "$status" -eq 0 ]
62
+ [[ "$output" == *"clean"* ]]
63
+ }
64
+
65
+ @test "clean: no rejected-alternatives block, empty adrs: -> exit 0" {
66
+ write_rfc "RFC-903-scope.md" "adrs: []" $'## Scope\n\nPure scope + decomposition + traces. No decisions here.'
67
+ run bash "$SCRIPT" "$FIXTURE_DIR"
68
+ [ "$status" -eq 0 ]
69
+ [[ "$output" == *"clean"* ]]
70
+ }
71
+
72
+ @test "prose mention (not a heading) of the phrase does NOT trigger -> exit 0" {
73
+ # Guards the RFC-005 retrofit-banner false positive: a blockquote/prose line
74
+ # mentioning the struck section must not be flagged.
75
+ write_rfc "RFC-904-retrofit.md" "adrs: []" $'> Retrofitted: this RFC originally carried a "Considered Options / Alternatives Rejected" section, now struck per ADR-070.'
76
+ run bash "$SCRIPT" "$FIXTURE_DIR"
77
+ [ "$status" -eq 0 ]
78
+ [[ "$output" == *"clean"* ]]
79
+ }
80
+
81
+ @test "variant heading 'Alternatives Rejected' WITH empty adrs: -> exit 1" {
82
+ write_rfc "RFC-905-variant.md" "adrs: []" $'### Alternatives Rejected\n\n- rejected: bar.'
83
+ run bash "$SCRIPT" "$FIXTURE_DIR"
84
+ [ "$status" -eq 1 ]
85
+ [[ "$output" == *"RFC-905-variant.md"* ]]
86
+ }
87
+
88
+ @test "mixed corpus: one violation among clean RFCs -> exit 1, names only the offender" {
89
+ write_rfc "RFC-906-ok.md" "adrs: [ADR-001]" $'## Considered Options\n\n- references ADR-001.'
90
+ write_rfc "RFC-907-bad.md" "adrs: []" $'## Considered Options\n\n- no adr.'
91
+ write_rfc "RFC-908-scope.md" "adrs: []" $'## Scope\n\nclean.'
92
+ run bash "$SCRIPT" "$FIXTURE_DIR"
93
+ [ "$status" -eq 1 ]
94
+ [[ "$output" == *"RFC-907-bad.md"* ]]
95
+ [[ "$output" != *"RFC-906-ok.md"* ]]
96
+ [[ "$output" != *"RFC-908-scope.md"* ]]
97
+ }
98
+
99
+ @test "non-existent directory -> exit 2 (usage error)" {
100
+ run bash "$SCRIPT" "$FIXTURE_DIR/does-not-exist"
101
+ [ "$status" -eq 2 ]
102
+ }
103
+
104
+ @test "dogfood: the real docs/rfcs/ corpus is clean -> exit 0" {
105
+ run bash "$SCRIPT" "$REPO_ROOT/docs/rfcs"
106
+ [ "$status" -eq 0 ]
107
+ [[ "$output" == *"clean"* ]]
108
+ }
@@ -10,7 +10,7 @@ Capture a Request for Change (RFC) ticket quickly during foreground work. Lightw
10
10
 
11
11
  This skill is one half of the capture-then-manage RFC framework introduced by ADR-060 (Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology, accepted 2026-05-05). The other half is `/wr-itil:manage-rfc` (heavyweight intake + lifecycle management).
12
12
 
13
- **Related JTBDs**: JTBD-008 (primary — Decompose a Fix Into Coordinated Changes; this skill IS the capture-time decomposition surface), JTBD-001 (extended scope — change-set-level governance), JTBD-101 (atomic-fix-adopter friction guard capture-rfc remains opt-in, never auto-fires on atomic captures).
13
+ **Related JTBDs**: JTBD-008 (primary — Decompose a Fix Into Coordinated Changes; this skill IS the capture-time decomposition surface), JTBD-001 (extended scope — change-set-level governance), JTBD-101 (atomic-fix-adopter every fix goes through an RFC per ADR-071; capture-rfc is invoked deliberately, not auto-fired, because RFC scope is direction-setting per ADR-073 — NOT because atomic fixes skip ceremony).
14
14
 
15
15
  ## When to invoke
16
16
 
@@ -27,7 +27,7 @@ This skill is one half of the capture-then-manage RFC framework introduced by AD
27
27
 
28
28
  **Positional**: `<problem-trace> <description>` where `<problem-trace>` is `P<NNN>` or `P<NNN>,P<NNN>,...` (no spaces inside the trace; multiple problems comma-separated).
29
29
 
30
- **Optional flag (Phase 2)**: `--stories STORY-<NNN>,STORY-<NNN>,...` — ORDERED execution sequence per ADR-060 line 262. Cardinality 0..N: atomic RFCs OMIT the flag and capture-rfc populates `stories: []` in frontmatter (JTBD-101 friction guard atomic RFCs are first-class); story-decomposed RFCs supply the ordered list. The flag accepts STORY-IDs that don't yet resolve to files (forward-reference is permitted at capture; the existence check happens at `manage-rfc <NNN> accepted` transition per ADR-060 working-the-problem flow line 304).
30
+ **Optional flag (Phase 2)**: `--stories STORY-<NNN>,STORY-<NNN>,...` — ORDERED execution sequence per ADR-060 line 262. Cardinality 0..N: an RFC whose work is not decomposed into stories OMITS the flag and capture-rfc populates `stories: []` in frontmatter (a structural state, NOT a reduced-ceremony pathevery fix goes through an RFC per ADR-071); story-decomposed RFCs supply the ordered list. The flag accepts STORY-IDs that don't yet resolve to files (forward-reference is permitted at capture; the existence check happens at `manage-rfc <NNN> accepted` transition per ADR-060 working-the-problem flow line 304).
31
31
 
32
32
  ```
33
33
  /wr-itil:capture-rfc P168 Pipeline consume-catalog and bootstrap-from-reports — multi-commit retrofit
@@ -225,7 +225,7 @@ done
225
225
 
226
226
  The helper (`packages/itil/scripts/update-problem-rfcs-section.sh`) is idempotent: running over a current section is a no-op. Lazy-empty discipline applies (zero traced RFCs → section absent) — capture-rfc invocations always have ≥ 1 trace at this step, so this surface always emits a populated section. The `git add` is conditional on the helper actually modifying the file — `cmp -s` no-op-on-current is the helper's idempotency contract; `git add` of an unchanged file is also a no-op.
227
227
 
228
- **Phase 2 — render `## Stories` body section on the new RFC** (when `--stories` was provided): the just-written RFC file carries `stories: [STORY-NNN, ...]` in frontmatter; the helper `update-rfc-references-section.sh <rfc-file> "Stories"` renders the forward-trace `## Stories` body section from that frontmatter array in execution order per ADR-060 line 270. Lazy-empty discipline applies — when `stories: []` (atomic RFC, JTBD-101 friction guard), the helper omits the section entirely:
228
+ **Phase 2 — render `## Stories` body section on the new RFC** (when `--stories` was provided): the just-written RFC file carries `stories: [STORY-NNN, ...]` in frontmatter; the helper `update-rfc-references-section.sh <rfc-file> "Stories"` renders the forward-trace `## Stories` body section from that frontmatter array in execution order per ADR-060 line 270. Lazy-empty discipline applies — when `stories: []` (an RFC not decomposed into stories), the helper omits the section entirely:
229
229
 
230
230
  ```bash
231
231
  if [ -n "$stories_trace" ]; then
@@ -288,7 +288,7 @@ The two skills share the `/tmp/wr-itil-rfc-capture-grep-${SESSION_ID}` create-ga
288
288
  - **`docs/plans/170-rfc-framework-story-map.md`** — Slice 2 task B5.T3 lands this skill.
289
289
  - **JTBD-008** — Decompose a Fix Into Coordinated Changes. Primary persona-anchor.
290
290
  - **JTBD-001** (extended scope) — change-set-level governance composition.
291
- - **JTBD-101** (atomic-fix-adopter friction guard) capture-rfc remains opt-in aside-invocation.
291
+ - **JTBD-101** (atomic-fix-adopter) every fix goes through an RFC (ADR-071); capture-rfc is a deliberate aside-invocation, not auto-fired (RFC scope is direction-setting per ADR-073).
292
292
  - **`docs/rfcs/README.md`** — RFC tier lifecycle index + frontmatter shape spec (Slice 2 tasks B5.T1 + B5.T2 — committed `adc53c8`).
293
293
  - **ADR-014** — governance skills commit their own work. Single-commit grain per capture.
294
294
  - **ADR-022** — problem lifecycle conventions; RFC lifecycle mirrors.
@@ -8,7 +8,7 @@ allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Task
8
8
 
9
9
  Create, update, or transition RFC tickets following the Problem-RFC-Story framework introduced by ADR-060 (accepted 2026-05-05). This skill is the heavyweight counterpart to `/wr-itil:capture-rfc` — it owns the full intake flow, lifecycle transitions, batch review, and README refresh.
10
10
 
11
- **Related JTBDs**: JTBD-008 (primary — Decompose a Fix Into Coordinated Changes; this skill governs the lifecycle of decomposed work), JTBD-001 (extended scope — change-set-level governance), JTBD-101 (atomic-fix-adopter friction guard RFC ceremony only fires on opt-in invocations).
11
+ **Related JTBDs**: JTBD-008 (primary — Decompose a Fix Into Coordinated Changes; this skill governs the lifecycle of decomposed work), JTBD-001 (extended scope — change-set-level governance), JTBD-101 (atomic-fix-adopter every fix goes through an RFC per ADR-071; the RFC skills are invoked deliberately, not auto-fired, because RFC scope is direction-setting per ADR-073 — NOT because atomic fixes skip ceremony).
12
12
 
13
13
  ## RFC Lifecycle
14
14
 
@@ -96,6 +96,8 @@ Apply the update — typical edits:
96
96
  - Adding `## Related` entries.
97
97
  - Updating `decision-makers` or `adrs` frontmatter when ADRs are referenced mid-RFC execution.
98
98
 
99
+ **Do NOT add a "Considered Options / Alternatives Rejected" section to the RFC body.** Per ADR-070 (RFCs hold no independent decisions), every contested choice among ≥ 2 viable options is recorded as an ADR (inheriting the ADR-064 confirm gate + ADR-066 oversight marker) and referenced in the RFC's `adrs:` frontmatter — never re-argued in the RFC body. An RFC carries only scope, decomposition (sequencing/breakdown of already-decided work), and traces. The ADR-052 behavioural lint hard-fails any RFC body that contains a rejected-alternatives block without a matching `adrs:` reference.
100
+
99
101
  #### README refresh on conditional update (P094 mirror)
100
102
 
101
103
  If the update changed any ranking-bearing field (Status, Severity-via-problems, Effort, WSJF), regenerate `docs/rfcs/README.md` in-place reflecting the new ranking and stage it in the same commit. If the edit touched only `## Summary`, `## Scope`, `## Tasks`, `## Related`, or other non-ranking sections, skip the refresh.
@@ -150,7 +152,7 @@ for pid_token in $(awk '/^problems:/{gsub(/[][]/,"");gsub(/,/,"\n");for(i=2;i<=N
150
152
  done
151
153
  ```
152
154
 
153
- The helper (`packages/itil/scripts/update-problem-rfcs-section.sh`) is idempotent and applies lazy-empty discipline (zero traced RFCs → section absent — protects atomic-fix-adopter friction guard per JTBD-101). After the transition, the helper:
155
+ The helper (`packages/itil/scripts/update-problem-rfcs-section.sh`) is idempotent and applies lazy-empty discipline (zero traced RFCs → section absent — a structural rendering rule, not a ceremony exemption; a problem traces ≥ 1 RFC once it reaches fix-time per ADR-071 / I13). After the transition, the helper:
154
156
  - Updates the row's `Status` column to the new lifecycle status.
155
157
  - Removes the row when this transition de-traces a problem (frontmatter `problems:` edit removed the entry).
156
158
  - No-op when the table is already current (idempotent contract).
@@ -159,7 +161,7 @@ The trailer hook (`itil-rfc-trailer-advisory.sh`) sits on top of this skill-side
159
161
 
160
162
  #### Forward trace — `## Stories` body section (Phase 2)
161
163
 
162
- Per ADR-060 line 270 + line 296: every transition that touches the RFC body refreshes the RFC's own `## Stories` body section from its frontmatter `stories:` array. The forward-trace surface renders the ordered execution sequence as inline links to the story files, lazy-empty when `stories: []` (atomic RFC JTBD-101 friction guard). The helper is the Slice 2b sibling `update-rfc-references-section.sh`:
164
+ Per ADR-060 line 270 + line 296: every transition that touches the RFC body refreshes the RFC's own `## Stories` body section from its frontmatter `stories:` array. The forward-trace surface renders the ordered execution sequence as inline links to the story files, lazy-empty when `stories: []` (an RFC not decomposed into stories). The helper is the Slice 2b sibling `update-rfc-references-section.sh`:
163
165
 
164
166
  ```bash
165
167
  bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-rfc-references-section.sh" "$rfc_file" "Stories"
@@ -251,7 +253,7 @@ The two skills share the `/tmp/wr-itil-rfc-capture-grep-${SESSION_ID}` create-ga
251
253
  - **ADR-060** — Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology.
252
254
  - **P170** — driver problem ticket.
253
255
  - **`docs/plans/170-rfc-framework-story-map.md`** — Slice 2 task B5.T4 lands this skill.
254
- - **JTBD-008** (primary), JTBD-001 (extended scope), JTBD-101 (atomic-fix-adopter friction guard).
256
+ - **JTBD-008** (primary), JTBD-001 (extended scope), JTBD-101 (atomic-fix-adopter every fix via RFC per ADR-071).
255
257
  - **`docs/rfcs/README.md`** — lifecycle index + frontmatter shape (Slice 2 B5.T1 + B5.T2 — `adc53c8`).
256
258
  - **`packages/itil/skills/capture-rfc/SKILL.md`** — sibling lightweight capture skill.
257
259
  - **`packages/itil/skills/manage-problem/SKILL.md`** — heavyweight counterpart at the problem tier; structural template for this skill.