@windyroad/itil 0.47.8 → 0.47.9-preview.552

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.47.8"
500
+ "version": "0.47.9"
501
501
  }
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Generated by scripts/sync-shim-wrappers.sh from
3
+ # packages/shared/lib/shim-wrapper-template.sh. DO NOT EDIT individual
4
+ # shim files in packages/*/bin/wr-* directly; edit the template + run
5
+ # `npm run sync:shim-wrappers` to regenerate.
6
+ #
7
+ # Resolution (ADR-080):
8
+ # 1. If the wrapper's parent dir is semver-shaped, treat as installed-
9
+ # cache execution and resolve to the highest-version sibling's
10
+ # scripts/ entry below.
11
+ # 2. Otherwise (parent dir is e.g. `architect`), treat as source-
12
+ # monorepo execution and dispatch to own scripts/. The source-repo-
13
+ # guard `exec` is the anchor parsed by
14
+ # packages/retrospective/scripts/check-tarball-shipped-shims.sh.
15
+ # 3. If the cache parent contains zero semver-shaped siblings, exit
16
+ # 127 with a stderr message naming the cache parent (per SQ-080-2).
17
+ #
18
+ # @adr ADR-080 (highest-version-wins shim wrapper plugin scaffold)
19
+ # @adr ADR-049 (plugin-bundled scripts resolve via bin/ on $PATH — amended)
20
+ # @problem P343 (mid-session staleness window)
21
+
22
+ set -euo pipefail
23
+
24
+ SHIM_DIR="$(cd "$(dirname "$0")" && pwd)"
25
+ OWN_VERSION_DIR="$(dirname "$SHIM_DIR")"
26
+ OWN_VERSION_NAME="$(basename "$OWN_VERSION_DIR")"
27
+ CACHE_PARENT="$(dirname "$OWN_VERSION_DIR")"
28
+
29
+ SEMVER_RE='^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'
30
+
31
+ # Source-repo guard: own parent dir is NOT semver → dispatch to own scripts/.
32
+ if ! [[ "$OWN_VERSION_NAME" =~ $SEMVER_RE ]]; then
33
+ exec "$SHIM_DIR/../scripts/check-ticket-jtbd-ratification.sh" "$@"
34
+ fi
35
+
36
+ # Cache execution: pick the highest-semver sibling under CACHE_PARENT.
37
+ HIGHEST=""
38
+ while IFS= read -r dir; do
39
+ name="$(basename "$dir")"
40
+ [[ "$name" =~ $SEMVER_RE ]] || continue
41
+ if [[ -z "$HIGHEST" ]] || [[ "$(printf '%s\n%s\n' "$HIGHEST" "$name" | sort -V | tail -1)" == "$name" ]]; then
42
+ HIGHEST="$name"
43
+ fi
44
+ done < <(find "$CACHE_PARENT" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
45
+
46
+ if [[ -z "$HIGHEST" ]]; then
47
+ printf 'wr-shim: no cached versions in %s\n' "$CACHE_PARENT" >&2
48
+ exit 127
49
+ fi
50
+
51
+ exec "$CACHE_PARENT/$HIGHEST/scripts/check-ticket-jtbd-ratification.sh" "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.47.8",
3
+ "version": "0.47.9-preview.552",
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,92 @@
1
+ #!/usr/bin/env bash
2
+ # wr-itil — predicate: are the JTBDs cited by a problem ticket all ratified?
3
+ # (RFC-016 / P344 — orchestrator-layer mirror of ADR-068 surface 3)
4
+ #
5
+ # `/wr-itil:work-problems` Step 3.5 invokes this script on the selected
6
+ # ticket BEFORE dispatching the iter-subprocess. The per-iter JTBD review
7
+ # subagent already catches the same class via the [Unratified Dependency]
8
+ # verdict (ADR-068 surface 3), but only AFTER spending iter-dispatch cost.
9
+ # This script shifts the predicate left to the orchestrator layer for the
10
+ # cost of one grep + one shim call per cited JTBD.
11
+ #
12
+ # Polarity (RFC-016 § Scope — outer-script polarity INVERTED vs the inner
13
+ # per-JTBD predicate it dispatches):
14
+ # exit 0 = all cited JTBDs ratified OR ticket cites none → orchestrator
15
+ # proceeds with the dispatch. No stdout.
16
+ # exit 1 = ≥1 cited JTBD unratified → orchestrator routes to Step 4
17
+ # user-answerable skip + queues outstanding_question.
18
+ # One ID per stdout line; `JTBD-NNN (unresolved)` for IDs whose
19
+ # per-JTBD predicate returned exit 2 (no matching file).
20
+ # exit 2 = ticket file missing / unreadable. No stdout; reason on stderr.
21
+ #
22
+ # Silent-pass on missing per-JTBD shim: when `wr-jtbd-is-job-or-persona-
23
+ # unconfirmed` is not on PATH (degenerate adopter case per ADR-031 —
24
+ # adopter installed @windyroad/itil without @windyroad/jtbd), the script
25
+ # silent-passes (exit 0). The iter-subprocess JTBD subagent is the
26
+ # authoritative second-source; orchestrator-layer pre-check is optimisation.
27
+ #
28
+ # Usage:
29
+ # check-ticket-jtbd-ratification.sh <ticket-file> [JTBD_DIR]
30
+ # <ticket-file> = path to a problem-ticket .md file (relative or absolute)
31
+ # JTBD_DIR defaults to docs/jtbd
32
+ #
33
+ # @rfc RFC-016 (P344 — work-problems Step 3.5 JTBD ratification predicate)
34
+ # @adr ADR-068 (JTBD + persona human-oversight marker — surface 3 mirrored)
35
+ # @adr ADR-074 (substance-confirm-before-build — JTBD-as-driver symmetric)
36
+ # @adr ADR-049 (PATH shim grammar)
37
+ # @problem P344
38
+
39
+ set -uo pipefail
40
+
41
+ TICKET="${1:-}"
42
+ JTBD_DIR="${2:-docs/jtbd}"
43
+
44
+ [ -n "$TICKET" ] || {
45
+ echo "check-ticket-jtbd-ratification: missing <ticket-file>" >&2
46
+ exit 2
47
+ }
48
+
49
+ [ -f "$TICKET" ] || {
50
+ echo "check-ticket-jtbd-ratification: no such ticket file: $TICKET" >&2
51
+ exit 2
52
+ }
53
+
54
+ # ADR-031 silent-pass: if the per-JTBD predicate shim is absent, the
55
+ # orchestrator-layer pre-check cannot run. The iter-subprocess JTBD
56
+ # subagent will catch any unratified-dep at the inner layer; this script
57
+ # is the optimisation surface, not the authoritative gate.
58
+ if ! command -v wr-jtbd-is-job-or-persona-unconfirmed >/dev/null 2>&1; then
59
+ exit 0
60
+ fi
61
+
62
+ # Extract cited JTBD IDs from the ticket body. Match `JTBD-NNN` (with or
63
+ # without leading zero) — the canonical citation form across Decision
64
+ # Drivers / **JTBD**: frontmatter / **Persona**: references. Dedupe so
65
+ # the same JTBD cited multiple times only runs the predicate once.
66
+ cited="$(grep -oE 'JTBD-[0-9]+' "$TICKET" 2>/dev/null | sort -u || true)"
67
+
68
+ [ -n "$cited" ] || exit 0 # vacuous-pass: no JTBDs cited at all
69
+
70
+ unratified=""
71
+ while IFS= read -r jtbd_id; do
72
+ [ -n "$jtbd_id" ] || continue
73
+ # Call the inner per-JTBD predicate. INNER POLARITY:
74
+ # inner exit 0 = unconfirmed (build-upon guard SHOULD fire) → unratified
75
+ # inner exit 1 = confirmed or superseded → ratified
76
+ # inner exit 2 = not found / unparseable ref → unresolved
77
+ wr-jtbd-is-job-or-persona-unconfirmed "$jtbd_id" "$JTBD_DIR" >/dev/null 2>&1
78
+ inner_status=$?
79
+ case $inner_status in
80
+ 0) unratified="${unratified}${jtbd_id}"$'\n' ;;
81
+ 1) ;; # ratified — silent
82
+ 2) unratified="${unratified}${jtbd_id} (unresolved)"$'\n' ;;
83
+ *) unratified="${unratified}${jtbd_id} (predicate error: exit ${inner_status})"$'\n' ;;
84
+ esac
85
+ done <<< "$cited"
86
+
87
+ if [ -n "$unratified" ]; then
88
+ printf '%s' "$unratified"
89
+ exit 1
90
+ fi
91
+
92
+ exit 0
@@ -292,6 +292,35 @@ Within the highest non-empty tier, select the problem with the highest WSJF scor
292
292
 
293
293
  The full selection order is therefore: **tier** (Critical-bypass → Inbound-reported → Internal), then the within-tier ladder `(WSJF desc, Known-Error-first, Effort-divisor asc, Reported-date asc, ID asc)`. <!-- REPORTED-FIRST-TIER-SOURCE: /wr-itil:work-problems SKILL.md Step 3 (ADR-076) -->
294
294
 
295
+ ### Step 3.5: JTBD ratification predicate-check (per RFC-016 / P344)
296
+
297
+ After Step 3 selects a candidate ticket and before Step 4 classifies it for dispatch, predicate-check the cited JTBDs of the selected ticket. The per-iter JTBD review subagent (ADR-068 surface 3 — the `[Unratified Dependency]` verdict) catches the same class INSIDE the iter subprocess, but only after spending iter-dispatch cost (~$3-5 + 5-10 min per skip). This step shifts the predicate left to the orchestrator layer for the cost of one grep + per-JTBD shim call — analogous to how Step 0b pre-flights inbound-discovery staleness rather than letting iters discover it. Driving exemplar: 2026-05-31 session 9 iter 5 dispatched P082 against unratified JTBD-001 + JTBD-006; the iter correctly skipped per ADR-074 substance-confirm-before-build, but the per-dispatch cost was wasted.
298
+
299
+ **Mechanism:**
300
+
301
+ ```bash
302
+ wr-itil-check-ticket-jtbd-ratification "<selected-ticket-path>"
303
+ predicate_exit=$?
304
+ ```
305
+
306
+ `wr-itil-check-ticket-jtbd-ratification` is the ADR-049 / ADR-080 `$PATH` shim that dispatches `packages/itil/scripts/check-ticket-jtbd-ratification.sh`. The script extracts cited `JTBD-NNN` IDs from the ticket body (Decision Drivers / `**JTBD**:` / `**Persona**:` references) and delegates per-JTBD ratification to `wr-jtbd-is-job-or-persona-unconfirmed` (the ADR-068 surface 3 single-artifact predicate). Polarity is INVERTED vs the inner predicate — the outer script answers "are all cited JTBDs ratified?" rather than "is THIS one unconfirmed?". Behavioural contract asserted by `test/work-problems-step-3-5-jtbd-ratification-predicate.bats`.
307
+
308
+ Exit-code routing:
309
+
310
+ | `predicate_exit` | Meaning | Action |
311
+ |---|---|---|
312
+ | `0` | All cited JTBDs ratified, OR ticket cites no JTBDs, OR per-JTBD shim missing (ADR-031 silent-pass) | Proceed to Step 4 normally — the ticket is dispatchable. |
313
+ | `1` | ≥1 cited JTBD unratified (or unresolved) — IDs on stdout, one per line; `JTBD-NNN (unresolved)` for inner exit-2 cases | Route the ticket to Step 4's user-answerable skip path (`skip_reason_category: user-answerable`). Queue an `outstanding_questions` entry (`category: "direction"`) naming the unratified JTBDs + ticket ID + remedy: *"Run `/wr-jtbd:confirm-jobs-and-personas` to ratify the cited jobs/personas, then re-invoke `/wr-itil:work-problems`."* Loop back to Step 3 to re-run the tier-first selection over the remaining backlog minus the skipped ticket. |
314
+ | `2` | Ticket file missing / unreadable | Halt the loop with the structured Prior-Session State report — this is the same shape as the README-reconciliation Exit 2 halt at Step 0 (deeper repair needed, not mechanical reconciliation). |
315
+
316
+ **Loopback tier preservation**: re-run the Step 3 tier-first selection over the remaining backlog minus the skipped ticket. Tier order (Critical-bypass → Inbound-reported → Internal) and within-tier WSJF ladder are preserved per ADR-076. If every actionable ticket is filtered out by Step 3.5, Step 2 stop-condition #1 (no actionable problems) fires naturally and the accumulated `outstanding_questions` entries surface at Step 2.4 gate (a) per the existing batched-`AskUserQuestion` contract.
317
+
318
+ **Why orchestrator-layer, not iter-layer**: the inner per-iter JTBD subagent stays in place (defence-in-depth — the iter-layer is the authoritative second-source, not a replacement surface). The orchestrator predicate is the optimisation: cheap pre-check eliminates wasted dispatch when the answer is knowable from a `grep` + frontmatter read. The two surfaces are not redundant — they cover different failure modes (orchestrator: shift-left cost optimisation; iter: substance-confirm-before-build governance gate per ADR-074). When the orchestrator silent-passes (predicate-exit 0 via missing-shim degenerate case per ADR-031), the iter-layer still catches any unratified-dep correctly.
319
+
320
+ **AFK authorisation per ADR-013 Rule 6**: this is a pure read-only predicate-check (no writes, no commits, no external comms). No `AskUserQuestion` at this step — the routing is deterministic per the table above. The user-answerable question accumulates in the queue file and surfaces at Step 2.4 gate (a) per the existing batched contract. Per ADR-044 framework-resolution boundary: routing is framework-resolved (mechanical); user input is preserved at the loop-end surface where it belongs.
321
+
322
+ **Compose-with**: ADR-068 (surface 3 single-artifact predicate — mirrored to orchestrator), ADR-074 (substance-confirm-before-build — JTBD-as-driver symmetric sibling to ADR-as-driver), ADR-076 (tier-first selection preserved by the loopback), ADR-031 (degenerate adopter silent-pass when per-JTBD shim absent), ADR-049 / ADR-080 (PATH shim grammar + highest-version-wins wrapper), ADR-014 (no commit at this step — predicate is read-only). The sibling-class gap for ADRs cited as Decision Drivers (ADR-074 master class) is RFC-016 § Deferred item 1 — captured for follow-on after this Step 3.5 dogfoods.
323
+
295
324
  ### Step 4: Classify each problem
296
325
 
297
326
  Read the problem file and apply these deterministic rules:
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # Step 3.5 behavioural fixture per RFC-016 + P344:
4
+ # work-problems orchestrator predicate-checks the cited JTBDs of the
5
+ # selected ticket BEFORE dispatching the iter-worker. The predicate
6
+ # decision lives in `packages/itil/scripts/check-ticket-jtbd-ratification.sh`
7
+ # so the SKILL.md Step 3.5 prose is a thin source-and-call wrapper around
8
+ # a behaviorally-testable shell script (P081 / user feedback: prefer
9
+ # behavioural over structural-grep tests).
10
+ #
11
+ # Polarity (RFC-016 § Scope):
12
+ # exit 0 = all cited JTBDs ratified (or none cited) → orchestrator proceeds
13
+ # with the dispatch
14
+ # exit 1 = ≥1 cited JTBD unratified; one ID per stdout line, `(unresolved)`
15
+ # tag for the per-JTBD predicate's exit-2 cases → orchestrator
16
+ # routes to user-answerable skip + queues outstanding_question
17
+ # exit 2 = ticket file missing / unreadable → halt
18
+ #
19
+ # Cases covered (per RFC-016 § Verification + § Tasks bats coverage):
20
+ # 1. Helper exists at the contracted PATH-shim location.
21
+ # 2. Ticket cites no JTBDs at all → exit 0 (vacuous-pass).
22
+ # 3. Ticket cites a ratified JTBD (frontmatter `human-oversight: confirmed`)
23
+ # → exit 0.
24
+ # 4. Ticket cites an unratified JTBD → exit 1 + ID on stdout.
25
+ # 5. Ticket cites an unresolved JTBD ID (no matching JTBD file) → exit 1
26
+ # + `(unresolved)` tag.
27
+ # 6. Ticket file missing → exit 2.
28
+ # 7. Missing per-JTBD predicate shim → silent-pass exit 0 (ADR-031
29
+ # degenerate adopter case).
30
+
31
+ setup() {
32
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
33
+ SHIM="$REPO_ROOT/packages/itil/bin/wr-itil-check-ticket-jtbd-ratification"
34
+
35
+ FIXTURE="$(mktemp -d)"
36
+ mkdir -p "$FIXTURE/docs/jtbd/developer"
37
+ mkdir -p "$FIXTURE/docs/problems/open"
38
+
39
+ # Shim search PATH: put the real per-JTBD predicate shim on PATH so the
40
+ # helper can dispatch it. The helper resolves shims through PATH per
41
+ # ADR-049 — no source-relative paths.
42
+ export PATH="$REPO_ROOT/packages/jtbd/bin:$PATH"
43
+ }
44
+
45
+ teardown() {
46
+ rm -rf "$FIXTURE"
47
+ }
48
+
49
+ @test "shim exists at the contracted PATH location" {
50
+ [ -f "$SHIM" ]
51
+ [ -x "$SHIM" ]
52
+ }
53
+
54
+ @test "case 1: ticket cites no JTBDs → exit 0 (vacuous-pass)" {
55
+ cat > "$FIXTURE/docs/problems/open/999-no-jtbds.md" <<'EOF'
56
+ # Problem 999: ticket with no JTBD citations
57
+
58
+ **Status**: Open
59
+
60
+ ## Description
61
+
62
+ This ticket has no Decision Drivers section and no JTBD citations.
63
+ EOF
64
+ cd "$FIXTURE"
65
+ run "$SHIM" "docs/problems/open/999-no-jtbds.md"
66
+ [ "$status" -eq 0 ]
67
+ [ -z "$output" ]
68
+ }
69
+
70
+ @test "case 2: ticket cites a ratified JTBD → exit 0" {
71
+ cat > "$FIXTURE/docs/jtbd/developer/JTBD-501-ratified.md" <<'EOF'
72
+ ---
73
+ human-oversight: confirmed
74
+ ---
75
+
76
+ # JTBD-501: A ratified job
77
+ EOF
78
+ cat > "$FIXTURE/docs/problems/open/998-cites-ratified.md" <<'EOF'
79
+ # Problem 998: cites a ratified JTBD
80
+
81
+ ## Decision Drivers
82
+
83
+ - JTBD-501 — A ratified job
84
+ EOF
85
+ cd "$FIXTURE"
86
+ run "$SHIM" "docs/problems/open/998-cites-ratified.md"
87
+ [ "$status" -eq 0 ]
88
+ [ -z "$output" ]
89
+ }
90
+
91
+ @test "case 3: ticket cites an unratified JTBD → exit 1 + ID stdout" {
92
+ # Unratified = no `human-oversight: confirmed` frontmatter.
93
+ cat > "$FIXTURE/docs/jtbd/developer/JTBD-502-unratified.md" <<'EOF'
94
+ ---
95
+ status: proposed
96
+ ---
97
+
98
+ # JTBD-502: An unratified job (no human-oversight: confirmed)
99
+ EOF
100
+ cat > "$FIXTURE/docs/problems/open/997-cites-unratified.md" <<'EOF'
101
+ # Problem 997: cites an unratified JTBD
102
+
103
+ ## Decision Drivers
104
+
105
+ - JTBD-502 — An unratified job
106
+ EOF
107
+ cd "$FIXTURE"
108
+ run "$SHIM" "docs/problems/open/997-cites-unratified.md"
109
+ [ "$status" -eq 1 ]
110
+ [[ "$output" == *"JTBD-502"* ]]
111
+ }
112
+
113
+ @test "case 4: ticket cites an unresolved JTBD ID → exit 1 + (unresolved) tag" {
114
+ cat > "$FIXTURE/docs/problems/open/996-cites-unresolved.md" <<'EOF'
115
+ # Problem 996: cites an unresolved JTBD ID
116
+
117
+ ## Decision Drivers
118
+
119
+ - JTBD-999 — Does not exist
120
+ EOF
121
+ cd "$FIXTURE"
122
+ run "$SHIM" "docs/problems/open/996-cites-unresolved.md"
123
+ [ "$status" -eq 1 ]
124
+ [[ "$output" == *"JTBD-999"* ]]
125
+ [[ "$output" == *"(unresolved)"* ]]
126
+ }
127
+
128
+ @test "case 5: ticket file missing → exit 2" {
129
+ cd "$FIXTURE"
130
+ run "$SHIM" "docs/problems/open/nonexistent.md"
131
+ [ "$status" -eq 2 ]
132
+ }
133
+
134
+ @test "case 6: missing per-JTBD predicate shim → silent-pass exit 0" {
135
+ # Strip ALL per-JTBD predicate shim locations from PATH (source-repo
136
+ # packages/jtbd/bin AND the global plugin install cache); degenerate
137
+ # adopter case (ADR-031). The helper should silent-pass (exit 0) rather
138
+ # than fail.
139
+ cat > "$FIXTURE/docs/problems/open/995-cites-unratified.md" <<'EOF'
140
+ # Problem 995: cites an unratified JTBD but shim is missing
141
+
142
+ ## Decision Drivers
143
+
144
+ - JTBD-502 — An unratified job
145
+ EOF
146
+ cd "$FIXTURE"
147
+ # Strip every PATH entry that exposes wr-jtbd-is-job-or-persona-unconfirmed.
148
+ # Both packages/jtbd/bin (source-repo) and ~/.claude/plugins/cache/.../bin
149
+ # (plugin install) must go.
150
+ STRIPPED_PATH=""
151
+ while IFS= read -r entry; do
152
+ [ -z "$entry" ] && continue
153
+ if [ -x "$entry/wr-jtbd-is-job-or-persona-unconfirmed" ]; then
154
+ continue
155
+ fi
156
+ STRIPPED_PATH="${STRIPPED_PATH}${entry}:"
157
+ done < <(printf '%s' "$PATH" | tr ':' '\n')
158
+ PATH="${STRIPPED_PATH%:}" run "$SHIM" "docs/problems/open/995-cites-unratified.md"
159
+ [ "$status" -eq 0 ]
160
+ }
161
+
162
+ @test "case 7: ticket cites multiple JTBDs, one unratified → exit 1 + unratified ID only" {
163
+ cat > "$FIXTURE/docs/jtbd/developer/JTBD-503-ratified.md" <<'EOF'
164
+ ---
165
+ human-oversight: confirmed
166
+ ---
167
+
168
+ # JTBD-503: A ratified job
169
+ EOF
170
+ cat > "$FIXTURE/docs/jtbd/developer/JTBD-504-unratified.md" <<'EOF'
171
+ ---
172
+ status: proposed
173
+ ---
174
+
175
+ # JTBD-504: An unratified job
176
+ EOF
177
+ cat > "$FIXTURE/docs/problems/open/994-cites-mixed.md" <<'EOF'
178
+ # Problem 994: cites both ratified and unratified JTBDs
179
+
180
+ ## Decision Drivers
181
+
182
+ - JTBD-503 — A ratified job
183
+ - JTBD-504 — An unratified job
184
+ EOF
185
+ cd "$FIXTURE"
186
+ run "$SHIM" "docs/problems/open/994-cites-mixed.md"
187
+ [ "$status" -eq 1 ]
188
+ [[ "$output" == *"JTBD-504"* ]]
189
+ # Ratified IDs are NOT echoed — only the unratified set.
190
+ [[ "$output" != *"JTBD-503"* ]]
191
+ }