@windyroad/itil 0.35.15 → 0.35.16-preview.425

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.
@@ -484,5 +484,5 @@
484
484
  }
485
485
  },
486
486
  "name": "wr-itil",
487
- "version": "0.35.15"
487
+ "version": "0.35.16"
488
488
  }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical run-check-upstream-cache-staleness script.
3
+ exec "$(dirname "$0")/../scripts/run-check-upstream-cache-staleness.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical mark-create-gate script.
3
+ exec "$(dirname "$0")/../scripts/mark-create-gate.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical mark-rfc-capture-gate script.
3
+ exec "$(dirname "$0")/../scripts/mark-rfc-capture-gate.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical run-migrate-problems-layout script.
3
+ exec "$(dirname "$0")/../scripts/run-migrate-problems-layout.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical update-jtbd-references-section script.
3
+ exec "$(dirname "$0")/../scripts/update-jtbd-references-section.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical update-problem-references-section script.
3
+ exec "$(dirname "$0")/../scripts/update-problem-references-section.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical update-problem-rfcs-section script.
3
+ exec "$(dirname "$0")/../scripts/update-problem-rfcs-section.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical update-rfc-references-section script.
3
+ exec "$(dirname "$0")/../scripts/update-rfc-references-section.sh" "$@"
@@ -44,8 +44,9 @@
44
44
 
45
45
  # Echoes the runtime-SID marker path on stdout. Always exits 0.
46
46
  #
47
- # Usage:
48
- # source packages/itil/hooks/lib/runtime-sid.sh
47
+ # Usage (resolve relative to the caller's own location — NEVER repo-relative
48
+ # `packages/...`, which only exists in the source monorepo; P317/RFC-009):
49
+ # source "$(dirname "${BASH_SOURCE[0]}")/runtime-sid.sh"
49
50
  # path=$(runtime_sid_path)
50
51
  runtime_sid_path() {
51
52
  if [ -n "${SESSION_MARKER_DIR:-}" ]; then
@@ -57,8 +57,9 @@
57
57
  # Returns the canonical session UUID for the current invocation.
58
58
  # Echoes the UUID on stdout. Exit 0 if discovered, 1 if not.
59
59
  #
60
- # Usage:
61
- # source packages/itil/hooks/lib/session-id.sh
60
+ # Usage (resolve relative to the caller's own location — NEVER repo-relative
61
+ # `packages/...`, which only exists in the source monorepo; P317/RFC-009):
62
+ # source "$(dirname "${BASH_SOURCE[0]}")/session-id.sh" # or ../hooks/lib/ from a scripts/ wrapper
62
63
  # sid=$(get_current_session_id) || { echo "no SID available" >&2; exit 1; }
63
64
  get_current_session_id() {
64
65
  # Env-var fast path. CLAUDE_SESSION_ID is not exported in agent
@@ -203,9 +204,10 @@ get_current_session_id() {
203
204
  # Test overrides: SESSION_MARKER_DIR (marker dir, default /tmp) +
204
205
  # SESSION_CANDIDATE_WINDOW_MINS (window minutes, default 1440).
205
206
  #
206
- # Usage:
207
- # source packages/itil/hooks/lib/session-id.sh
208
- # source packages/itil/hooks/lib/create-gate.sh
207
+ # Usage — SKILLs call the `wr-itil-mark-create-gate` command (it does the
208
+ # source+pipe internally, adopter-safe); direct callers resolve the libs relative
209
+ # to their OWN location, NEVER repo-relative `packages/...` (P317/RFC-009):
210
+ # LIB="$(dirname "${BASH_SOURCE[0]}")"; source "$LIB/session-id.sh"; source "$LIB/create-gate.sh"
209
211
  # get_candidate_session_ids | mark_step2_complete_candidates
210
212
  get_candidate_session_ids() {
211
213
  local marker_dir="${SESSION_MARKER_DIR:-/tmp}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.35.15",
3
+ "version": "0.35.16-preview.425",
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,26 @@
1
+ #!/usr/bin/env bash
2
+ # wr-itil — write the problem-tier create-gate marker (P119) under every
3
+ # candidate session SID (P260 / ADR-050 Option C).
4
+ #
5
+ # Internalises the source+pipe that capture-problem / manage-problem Step 2
6
+ # previously ran INLINE with repo-relative `source packages/itil/hooks/lib/*.sh`
7
+ # — which only resolved in the source monorepo and broke in adopter installs
8
+ # (P317 / RFC-009). This script resolves its sibling libs RELATIVE TO ITS OWN
9
+ # LOCATION (`$(dirname)/../hooks/lib`), so it works wherever the plugin is
10
+ # installed. SKILLs invoke it by name via the `wr-itil-mark-create-gate` PATH
11
+ # shim (ADR-049) instead of sourcing repo-relative lib files.
12
+ set -uo pipefail
13
+
14
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../hooks/lib" 2>/dev/null && pwd)" || {
15
+ echo "wr-itil-mark-create-gate: cannot locate hooks/lib next to the script" >&2
16
+ exit 2
17
+ }
18
+
19
+ # shellcheck source=/dev/null
20
+ source "$LIB/session-id.sh"
21
+ # shellcheck source=/dev/null
22
+ source "$LIB/create-gate.sh"
23
+
24
+ # Writes /tmp/manage-problem-grep-<SID> under each recent candidate SID; returns
25
+ # non-zero only if no candidate SID was discoverable (fail-closed parity).
26
+ get_candidate_session_ids | mark_step2_complete_candidates
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ # wr-itil — write the RFC-tier create-gate marker (P119) under every candidate
3
+ # session SID (P260 / ADR-050 Option C). Adopter-safe sibling of
4
+ # wr-itil-mark-create-gate (P317 / RFC-009): resolves its libs relative to the
5
+ # script location, not cwd. SKILLs (capture-rfc / manage-rfc) invoke it by name
6
+ # via the `wr-itil-mark-rfc-capture-gate` PATH shim (ADR-049) before writing a
7
+ # docs/rfcs/RFC-*.proposed.md file.
8
+ #
9
+ # The RFC tier has no `_candidates` helper in create-gate.sh (only the single
10
+ # `mark_rfc_capture_complete`), so this script loops the candidate set itself —
11
+ # the RFC-tier analogue of `mark_step2_complete_candidates`.
12
+ set -uo pipefail
13
+
14
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../hooks/lib" 2>/dev/null && pwd)" || {
15
+ echo "wr-itil-mark-rfc-capture-gate: cannot locate hooks/lib next to the script" >&2
16
+ exit 2
17
+ }
18
+
19
+ # shellcheck source=/dev/null
20
+ source "$LIB/session-id.sh"
21
+ # shellcheck source=/dev/null
22
+ source "$LIB/create-gate.sh"
23
+
24
+ count=0
25
+ while IFS= read -r sid; do
26
+ [ -n "$sid" ] || continue
27
+ mark_rfc_capture_complete "$sid"
28
+ count=$((count + 1))
29
+ done < <(get_candidate_session_ids)
30
+
31
+ # Fail-closed parity with mark_step2_complete_candidates: non-zero if no SID.
32
+ [ "$count" -gt 0 ]
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # wr-itil — echo the inbound-discovery-preflight promotion reason, or empty if
3
+ # promotion is not warranted (P317/RFC-009).
4
+ #
5
+ # Adopter-safe wrapper: sources the canonical lib RELATIVE TO THIS SCRIPT
6
+ # (`$(dirname)/../lib`), then echoes the function's result on stdout (the SKILL
7
+ # captures it via `$(...)`). Replaces the SKILL-inline
8
+ # `source packages/itil/lib/check-upstream-cache-staleness.sh;
9
+ # preflight_reason="$(should_promote_inbound_discovery_preflight "$PWD")"`,
10
+ # which only resolved in the source monorepo (P317). SKILLs invoke this by name
11
+ # via the `wr-itil-check-upstream-cache-staleness` PATH shim (ADR-049).
12
+ # Operates on the directory given as $1 (defaults to $PWD).
13
+ set -uo pipefail
14
+
15
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
16
+ echo "wr-itil-check-upstream-cache-staleness: cannot locate lib next to the script" >&2
17
+ exit 2
18
+ }
19
+ # shellcheck source=/dev/null
20
+ source "$LIB/check-upstream-cache-staleness.sh"
21
+ should_promote_inbound_discovery_preflight "${1:-$PWD}"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ # wr-itil — run the problems-layout flat→per-state migration (P317/RFC-009).
3
+ #
4
+ # Adopter-safe wrapper: sources the canonical lib RELATIVE TO THIS SCRIPT
5
+ # (`$(dirname)/../lib`), then invokes the function. Replaces the SKILL-inline
6
+ # `source packages/itil/lib/migrate-problems-layout.sh; migrate_... "$PWD"`,
7
+ # which only resolved in the source monorepo (P317). SKILLs invoke this by name
8
+ # via the `wr-itil-migrate-problems-layout` PATH shim (ADR-049). The migration
9
+ # routine is idempotent + partial-migration-safe (no-op when no flat-layout
10
+ # files exist). Operates on the directory given as $1 (defaults to $PWD).
11
+ set -uo pipefail
12
+
13
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
14
+ echo "wr-itil-migrate-problems-layout: cannot locate lib next to the script" >&2
15
+ exit 2
16
+ }
17
+ # shellcheck source=/dev/null
18
+ source "$LIB/migrate-problems-layout.sh"
19
+ migrate_problems_to_per_state_layout "${1:-$PWD}"
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bats
2
+ # P317 / RFC-009 T2 + ADR-049: the KIND A wrapper commands resolve their
3
+ # canonical lib RELATIVE TO THE SCRIPT (`$(dirname)/../lib`), so they work in an
4
+ # adopter install — not just the source monorepo where `source packages/...`
5
+ # happened to resolve. Behavioural: simulate an install tree, invoke from a
6
+ # FOREIGN cwd, assert clean lib resolution.
7
+
8
+ setup() {
9
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
10
+ INSTALL="$(mktemp -d)"
11
+ mkdir -p "$INSTALL/scripts" "$INSTALL/lib" "$INSTALL/bin"
12
+ cp "$REPO_ROOT/packages/itil/scripts/run-migrate-problems-layout.sh" "$INSTALL/scripts/" 2>/dev/null || true
13
+ cp "$REPO_ROOT/packages/itil/scripts/run-check-upstream-cache-staleness.sh" "$INSTALL/scripts/" 2>/dev/null || true
14
+ cp "$REPO_ROOT/packages/itil/bin/wr-itil-migrate-problems-layout" "$INSTALL/bin/" 2>/dev/null || true
15
+ cp "$REPO_ROOT/packages/itil/bin/wr-itil-check-upstream-cache-staleness" "$INSTALL/bin/" 2>/dev/null || true
16
+ cp "$REPO_ROOT"/packages/itil/lib/*.sh "$INSTALL/lib/" 2>/dev/null || true
17
+ FOREIGN="$(mktemp -d)" # adopter repo cwd — no packages/ tree
18
+ }
19
+
20
+ teardown() {
21
+ rm -rf "$INSTALL" "$FOREIGN"
22
+ }
23
+
24
+ @test "wr-itil-migrate-problems-layout resolves its lib from a foreign cwd, no-ops on empty dir (P317)" {
25
+ cd "$FOREIGN"
26
+ run bash "$INSTALL/bin/wr-itil-migrate-problems-layout" "$FOREIGN"
27
+ [ "$status" -eq 0 ]
28
+ [[ "$output" != *"No such file"* ]]
29
+ [[ "$output" != *"command not found"* ]]
30
+ }
31
+
32
+ @test "wr-itil-check-upstream-cache-staleness resolves its lib from a foreign cwd (P317)" {
33
+ cd "$FOREIGN"
34
+ run bash "$INSTALL/bin/wr-itil-check-upstream-cache-staleness" "$FOREIGN"
35
+ [ "$status" -eq 0 ]
36
+ [[ "$output" != *"No such file"* ]]
37
+ [[ "$output" != *"command not found"* ]]
38
+ [[ "$output" != *"migrate_problems_to_per_state_layout: command not found"* ]]
39
+ }
40
+
41
+ @test "the direct script (not just the shim) also resolves its lib from a foreign cwd (P317)" {
42
+ cd "$FOREIGN"
43
+ run bash "$INSTALL/scripts/run-migrate-problems-layout.sh" "$FOREIGN"
44
+ [ "$status" -eq 0 ]
45
+ [[ "$output" != *"No such file"* ]]
46
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bats
2
+ # P317 / RFC-009 T1 + ADR-049: the create-gate marker commands resolve their
3
+ # sibling libs RELATIVE TO THE SCRIPT (adopter-safe), not relative to cwd. The
4
+ # bug they fix: SKILLs did `source packages/itil/hooks/lib/*.sh`, which only
5
+ # resolves in the source monorepo and breaks in adopter installs.
6
+ #
7
+ # Behavioural — simulates an adopter install by copying the shipped script +
8
+ # lib tree to a temp dir in the SAME relative layout, then invokes from a
9
+ # FOREIGN cwd (an "adopter repo" with no packages/ tree) and asserts success.
10
+
11
+ setup() {
12
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
13
+ INSTALL="$(mktemp -d)"
14
+ mkdir -p "$INSTALL/scripts" "$INSTALL/hooks/lib" "$INSTALL/bin"
15
+ cp "$REPO_ROOT/packages/itil/scripts/mark-create-gate.sh" "$INSTALL/scripts/" 2>/dev/null || true
16
+ cp "$REPO_ROOT/packages/itil/scripts/mark-rfc-capture-gate.sh" "$INSTALL/scripts/" 2>/dev/null || true
17
+ cp "$REPO_ROOT/packages/itil/bin/wr-itil-mark-create-gate" "$INSTALL/bin/" 2>/dev/null || true
18
+ cp "$REPO_ROOT/packages/itil/bin/wr-itil-mark-rfc-capture-gate" "$INSTALL/bin/" 2>/dev/null || true
19
+ cp "$REPO_ROOT"/packages/itil/hooks/lib/*.sh "$INSTALL/hooks/lib/" 2>/dev/null || true
20
+ FOREIGN="$(mktemp -d)" # an adopter repo cwd — no packages/ tree
21
+ MARKERS="$(mktemp -d)" # empty announce-marker dir so candidates = CLAUDE_SESSION_ID only
22
+ SID="p317-test-$$-${RANDOM}"
23
+ }
24
+
25
+ teardown() {
26
+ rm -rf "$INSTALL" "$FOREIGN" "$MARKERS"
27
+ rm -f "/tmp/manage-problem-grep-$SID" "/tmp/wr-itil-rfc-capture-grep-$SID"
28
+ }
29
+
30
+ @test "mark-create-gate.sh runs from a foreign cwd, resolving libs from its own location (P317)" {
31
+ cd "$FOREIGN"
32
+ run env CLAUDE_SESSION_ID="$SID" SESSION_MARKER_DIR="$MARKERS" bash "$INSTALL/scripts/mark-create-gate.sh"
33
+ [ "$status" -eq 0 ]
34
+ [[ "$output" != *"No such file"* ]]
35
+ [[ "$output" != *"command not found"* ]]
36
+ [ -f "/tmp/manage-problem-grep-$SID" ]
37
+ }
38
+
39
+ @test "wr-itil-mark-create-gate shim dispatches the script from a foreign cwd (P317/ADR-049)" {
40
+ cd "$FOREIGN"
41
+ run env CLAUDE_SESSION_ID="$SID" SESSION_MARKER_DIR="$MARKERS" bash "$INSTALL/bin/wr-itil-mark-create-gate"
42
+ [ "$status" -eq 0 ]
43
+ [ -f "/tmp/manage-problem-grep-$SID" ]
44
+ }
45
+
46
+ @test "mark-rfc-capture-gate.sh writes the RFC-tier marker from a foreign cwd (P317)" {
47
+ cd "$FOREIGN"
48
+ run env CLAUDE_SESSION_ID="$SID" SESSION_MARKER_DIR="$MARKERS" bash "$INSTALL/scripts/mark-rfc-capture-gate.sh"
49
+ [ "$status" -eq 0 ]
50
+ [[ "$output" != *"No such file"* ]]
51
+ [ -f "/tmp/wr-itil-rfc-capture-grep-$SID" ]
52
+ }
53
+
54
+ @test "wr-itil-mark-rfc-capture-gate shim dispatches from a foreign cwd (P317/ADR-049)" {
55
+ cd "$FOREIGN"
56
+ run env CLAUDE_SESSION_ID="$SID" SESSION_MARKER_DIR="$MARKERS" bash "$INSTALL/bin/wr-itil-mark-rfc-capture-gate"
57
+ [ "$status" -eq 0 ]
58
+ [ -f "/tmp/wr-itil-rfc-capture-grep-$SID" ]
59
+ }
@@ -156,12 +156,12 @@ If matches are found: list them in the final report. **Do NOT halt or branch.**
156
156
  **After the grep completes**, write the per-session create-gate marker so the `PreToolUse:Write` hook (P119) permits the subsequent Write of the new `.open.md` file. Per **P260 / ADR-050 Option C**, write it under EVERY recent candidate session SID (not just one) so a concurrent orchestrator+subprocess race cannot land the marker under the wrong UUID:
157
157
 
158
158
  ```bash
159
- source packages/itil/hooks/lib/session-id.sh
160
- source packages/itil/hooks/lib/create-gate.sh
161
- get_candidate_session_ids | mark_step2_complete_candidates
159
+ wr-itil-mark-create-gate
162
160
  ```
163
161
 
164
- The marker is shared between `manage-problem` and `capture-problem` per ADR-032 amendment same `/tmp/manage-problem-grep-${SESSION_ID}` path, idempotent across cross-skill ordering. `get_candidate_session_ids` enumerates the `get_current_session_id` pick (P124) plus every recent `/tmp/<system>-announced-<UUID>` UUID within a 24h mtime window, and `mark_step2_complete_candidates` writes the marker under each — so whichever SID the hook reads from the Write's stdin, a matching marker exists. This closes the P260 create-gate race that fires when the orchestrator main turn captures a ticket while a backgrounded iter subprocess holds the per-machine runtime-sid marker (last-writer-wins). The candidate set is bounded to recent same-machine markers — not a global fail-open (the P119 audit invariant holds: each marker still records that THIS session ran the duplicate-check grep). See `/wr-itil:manage-problem` Step 2 substep 7 for the full mechanism.
162
+ `wr-itil-mark-create-gate` is the ADR-049 `$PATH` shim that internalises the former inline `source packages/itil/hooks/lib/{session-id,create-gate}.sh` + `get_candidate_session_ids | mark_step2_complete_candidates`. It resolves those `hooks/lib` siblings RELATIVE TO THE SCRIPT, not cwd (P317/RFC-009) — so it works in adopter installs. NEVER `source packages/...` repo-relative from a SKILL; those paths only resolve in the source monorepo, not where the plugin is installed.
163
+
164
+ The marker is shared between `manage-problem` and `capture-problem` per ADR-032 amendment — same `/tmp/manage-problem-grep-${SESSION_ID}` path, idempotent across cross-skill ordering. Internally the command enumerates the `get_current_session_id` pick (P124) plus every recent `/tmp/<system>-announced-<UUID>` UUID within a 24h mtime window, and writes the marker under each — so whichever SID the hook reads from the Write's stdin, a matching marker exists. This closes the P260 create-gate race that fires when the orchestrator main turn captures a ticket while a backgrounded iter subprocess holds the per-machine runtime-sid marker (last-writer-wins). The candidate set is bounded to recent same-machine markers — not a global fail-open (the P119 audit invariant holds: each marker still records that THIS session ran the duplicate-check grep). See `/wr-itil:manage-problem` Step 2 substep 7 for the full mechanism.
165
165
 
166
166
  ### 3. Compute the next ID
167
167
 
@@ -135,6 +135,14 @@ For each resolved trace file, classify by suffix:
135
135
 
136
136
  **Why advisory and not hard-block at capture-time**: the bounded-escape contract in ADR-060 § Confirmation criterion 2 scopes I1 hard-block to `accepted → in-progress` and `→ verifying` lifecycle transitions; advisory-with-escalation only fires at `→ closed` transition. Capture-time tolerance for Closed/Parked traces is **load-bearing for the Phase 1 dogfood pass**: RFC-001 retro on P168 (P168 is `.verifying.md`) is structurally impossible without it. This is NOT a relaxation of I1; it is the bounded-escape window the ADR carved out at the right lifecycle phase. See ADR-060 § Confirmation criterion 5 (no semantic loss) + Phase 1 item 9 (retro migration as dogfood pass).
137
137
 
138
+ **After the trace validation passes**, write the RFC-tier create-gate marker so the `PreToolUse:Write` hook (P119, `manage-problem-enforce-create.sh`) permits the Step 5 Write of the new `docs/rfcs/RFC-*.proposed.md` file:
139
+
140
+ ```bash
141
+ wr-itil-mark-rfc-capture-gate
142
+ ```
143
+
144
+ `wr-itil-mark-rfc-capture-gate` is the ADR-049 `$PATH` shim (adopter-safe — resolves its `hooks/lib` siblings relative to the script, NOT cwd; P317/RFC-009) that writes `/tmp/wr-itil-rfc-capture-grep-${SESSION_ID}` under every recent candidate SID (P260 / ADR-050 Option C — same candidate-set discipline as `wr-itil-mark-create-gate`). NEVER `source packages/...` repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs. The marker is per-session and idempotent, so a single write covers multiple RFC creations this session (e.g. a multi-problem-trace split).
145
+
138
146
  ### 3. Compute next RFC ID
139
147
 
140
148
  Same `max(local, origin) + 1` formula as `capture-problem` Step 3, scanning `docs/rfcs/RFC-*.md` instead:
@@ -218,7 +226,7 @@ for pid_token in $(echo "$problem_trace" | tr ',' ' '); do
218
226
  # Dual-tolerant ticket discovery (RFC-002 migration window).
219
227
  problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
220
228
  [ -z "$problem_file" ] && continue
221
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-rfcs-section.sh" "$problem_file" docs/rfcs
229
+ wr-itil-update-problem-rfcs-section "$problem_file" docs/rfcs
222
230
  git add "$problem_file"
223
231
  done
224
232
  ```
@@ -229,7 +237,7 @@ The helper (`packages/itil/scripts/update-problem-rfcs-section.sh`) is idempoten
229
237
 
230
238
  ```bash
231
239
  if [ -n "$stories_trace" ]; then
232
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-rfc-references-section.sh" "docs/rfcs/RFC-${next}-${slug}.proposed.md" "Stories"
240
+ wr-itil-update-rfc-references-section "docs/rfcs/RFC-${next}-${slug}.proposed.md" "Stories"
233
241
  fi
234
242
  ```
235
243
 
@@ -260,7 +260,7 @@ for pid_token in $(echo "$problem_trace" | tr ',' ' '); do
260
260
  # Dual-tolerant ticket discovery (RFC-002 migration window).
261
261
  problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
262
262
  [ -z "$problem_file" ] && continue
263
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-references-section.sh" "$problem_file" "Stories"
263
+ wr-itil-update-problem-references-section "$problem_file" "Stories"
264
264
  git add "$problem_file"
265
265
  done
266
266
  ```
@@ -272,7 +272,7 @@ for jid_token in $(echo "$jtbd_trace" | tr ',' ' '); do
272
272
  jid_num="${jid_token#JTBD-}"
273
273
  jtbd_file=$(ls docs/jtbd/*/JTBD-${jid_num}-*.md 2>/dev/null | head -1)
274
274
  [ -z "$jtbd_file" ] && continue
275
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-jtbd-references-section.sh" "$jtbd_file" "Stories"
275
+ wr-itil-update-jtbd-references-section "$jtbd_file" "Stories"
276
276
  git add "$jtbd_file"
277
277
  done
278
278
  ```
@@ -284,7 +284,7 @@ for rid_token in $(echo "$rfc_trace" | tr ',' ' '); do
284
284
  [ -z "$rid_token" ] && continue
285
285
  rfc_file=$(ls docs/rfcs/${rid_token}-*.md 2>/dev/null | head -1)
286
286
  [ -z "$rfc_file" ] && continue
287
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-rfc-references-section.sh" "$rfc_file" "Stories"
287
+ wr-itil-update-rfc-references-section "$rfc_file" "Stories"
288
288
  git add "$rfc_file"
289
289
  done
290
290
  ```
@@ -170,14 +170,14 @@ for pid_token in $(echo "$problem_trace" | tr ',' ' '); do
170
170
  pid_num="${pid_token#P}"
171
171
  problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
172
172
  [ -z "$problem_file" ] && continue
173
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-references-section.sh" "$problem_file" "Story Maps"
173
+ wr-itil-update-problem-references-section "$problem_file" "Story Maps"
174
174
  git add "$problem_file"
175
175
  done
176
176
 
177
177
  for jid_token in $(echo "$jtbd_trace" | tr ',' ' '); do
178
178
  jtbd_file=$(ls docs/jtbd/*/${jid_token}-*.md 2>/dev/null | head -1)
179
179
  [ -z "$jtbd_file" ] && continue
180
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-jtbd-references-section.sh" "$jtbd_file" "Story Maps"
180
+ wr-itil-update-jtbd-references-section "$jtbd_file" "Story Maps"
181
181
  git add "$jtbd_file"
182
182
  done
183
183
 
@@ -212,12 +212,11 @@ The Phase 2 working-the-problem traversal makes "implement the fix" concretely t
212
212
  Before the README-reconciliation preflight (Step 0) and any other layout-dependent logic, source the shared shell migration routine and call the idempotent entrypoint:
213
213
 
214
214
  ```bash
215
- # Source the synced copy from this package's lib/ (canonical lives at
216
- # packages/shared/lib/migrate-problems-layout.sh per ADR-017 sync pattern).
217
- source packages/itil/lib/migrate-problems-layout.sh
218
- migrate_problems_to_per_state_layout "$PWD"
215
+ wr-itil-migrate-problems-layout "$PWD"
219
216
  ```
220
217
 
218
+ `wr-itil-migrate-problems-layout` is the ADR-049 `$PATH` shim (adopter-safe — resolves the canonical `lib/migrate-problems-layout.sh` relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline `source packages/itil/lib/migrate-problems-layout.sh; migrate_problems_to_per_state_layout "$PWD"`. NEVER `source packages/...` repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
219
+
221
220
  The routine is **idempotent and partial-migration-safe**. It no-ops when no flat-layout files (`docs/problems/*.<state>.md` at the top level of `docs/problems/`) are detected — the common case in this monorepo (post-Slice-5 T5a 2026-05-10) and in freshly-migrated adopter repos.
222
221
 
223
222
  On a flat-layout adopter repo (first invocation post-update — JTBD-101 plugin-developer auto-migration path), the routine:
@@ -337,12 +336,12 @@ Before creating, search existing problems for similar issues. The user may not k
337
336
  7. **After the grep completes** (whether duplicates were found or not), write the per-session create-gate marker so the `PreToolUse:Write` hook (`packages/itil/hooks/manage-problem-enforce-create.sh`, P119) allows the subsequent Write of the new `.open.md` file. The marker is `/tmp/manage-problem-grep-${SESSION_ID}`. Per **P260 / ADR-050 Option C**, the agent writes it under EVERY recent candidate session SID — not just one — by sourcing the discovery helpers (P124) and piping the candidate set into `mark_step2_complete_candidates`:
338
337
 
339
338
  ```bash
340
- source packages/itil/hooks/lib/session-id.sh
341
- source packages/itil/hooks/lib/create-gate.sh
342
- get_candidate_session_ids | mark_step2_complete_candidates
339
+ wr-itil-mark-create-gate
343
340
  ```
344
341
 
345
- **Why every candidate, not one (P260 / ADR-050 Option C)**: under `/wr-itil:work-problems` the orchestrator main turn fires PreToolUse hooks concurrently with its backgrounded iter subprocess (Step 5). Both sessions write the same per-machine runtime-sid marker (last-writer-wins), so the single-SID `get_current_session_id` can return the subprocess SID while the orchestrator's Write carries the orchestrator SID on its stdin — marker mismatch, create-gate deny. No agent-side algorithm can predict the right single SID from filesystem state alone (ADR-050 §Context). `get_candidate_session_ids` instead enumerates EVERY candidate SID the hook might read — the `get_current_session_id` pick (env-var > runtime-sid > announce-marker priority, P124) PLUS every recent `/tmp/<system>-announced-<UUID>` UUID within a 24h mtime window (ADR-038 announce markers, set on prompt 1 of every session by architect / jtbd / tdd / style-guide / voice-tone / itil-assistant-gate / itil-correction-detect hooks) and `mark_step2_complete_candidates` writes the marker under each. Whichever SID the hook reads from the Write's stdin, a matching marker provably exists. The candidate set is **bounded** to recent same-machine announce markers + the runtime-sid value NOT a global fail-open: the P119 audit invariant holds (every marker still records that THIS session ran the duplicate-check grep). The marker is per-session, so a single write covers all new tickets for the rest of this session, enabling Step 4b multi-concern splits and same-session unrelated-ticket creation without re-running the grep.
342
+ `wr-itil-mark-create-gate` is the ADR-049 `$PATH` shim (adopter-safe resolves its `hooks/lib` siblings relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline `source packages/itil/hooks/lib/{session-id,create-gate}.sh` + `get_candidate_session_ids | mark_step2_complete_candidates`. NEVER `source packages/...` repo-relative from a SKILLthose paths only resolve in the source monorepo, not adopter installs.
343
+
344
+ **Why every candidate, not one (P260 / ADR-050 Option C)**: under `/wr-itil:work-problems` the orchestrator main turn fires PreToolUse hooks concurrently with its backgrounded iter subprocess (Step 5). Both sessions write the same per-machine runtime-sid marker (last-writer-wins), so the single-SID `get_current_session_id` can return the subprocess SID while the orchestrator's Write carries the orchestrator SID on its stdin — marker mismatch, create-gate deny. No agent-side algorithm can predict the right single SID from filesystem state alone (ADR-050 §Context). The command instead enumerates EVERY candidate SID the hook might read — the `get_current_session_id` pick (env-var > runtime-sid > announce-marker priority, P124) PLUS every recent `/tmp/<system>-announced-<UUID>` UUID within a 24h mtime window (ADR-038 announce markers, set on prompt 1 of every session by architect / jtbd / tdd / style-guide / voice-tone / itil-assistant-gate / itil-correction-detect hooks) — and writes the marker under each. Whichever SID the hook reads from the Write's stdin, a matching marker provably exists. The candidate set is **bounded** to recent same-machine announce markers + the runtime-sid value — NOT a global fail-open: the P119 audit invariant holds (every marker still records that THIS session ran the duplicate-check grep). The marker is per-session, so a single write covers all new tickets for the rest of this session, enabling Step 4b multi-concern splits and same-session unrelated-ticket creation without re-running the grep.
346
345
 
347
346
  **Why helpers instead of inline `${CLAUDE_SESSION_ID:-default}`**: the agent's process does NOT export `CLAUDE_SESSION_ID` today; the hook side reads `session_id` from its stdin JSON payload (per the Claude Code PreToolUse contract). The prior fallback wrote the marker under `default` while the hook checked the real UUID — mismatch caused the Write deny on every first ticket of a session until the agent ad-hoc scraped a UUID-bearing marker. The helpers canonicalise that scrape so every agent context discovers candidate SIDs the same way. P124.
348
347
 
@@ -147,7 +147,7 @@ for pid_token in $(awk '/^problems:/{gsub(/[][]/,"");gsub(/,/,"\n");for(i=2;i<=N
147
147
  # subdir (`docs/problems/<state>/<NNN>-<title>.md`) layouts.
148
148
  problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
149
149
  [ -z "$problem_file" ] && continue
150
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-rfcs-section.sh" "$problem_file" docs/rfcs
150
+ wr-itil-update-problem-rfcs-section "$problem_file" docs/rfcs
151
151
  git add "$problem_file"
152
152
  done
153
153
  ```
@@ -164,7 +164,7 @@ The trailer hook (`itil-rfc-trailer-advisory.sh`) sits on top of this skill-side
164
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`:
165
165
 
166
166
  ```bash
167
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-rfc-references-section.sh" "$rfc_file" "Stories"
167
+ wr-itil-update-rfc-references-section "$rfc_file" "Stories"
168
168
  ```
169
169
 
170
170
  Idempotent + lazy-empty per the Slice 2a/2b contract. Run after the rename + frontmatter edit so the section reflects the post-transition `stories:` shape. Stage the RFC file (already staged for the lifecycle transition; the helper modifies the same file in-place).
@@ -197,7 +197,7 @@ Batch re-rank all RFCs and refresh `docs/rfcs/README.md`.
197
197
  ```bash
198
198
  # Aggregate the union of P<NNN> across all RFCs touched in this review.
199
199
  for problem_file in $(printf '%s\n' "${touched_problem_files[@]}" | sort -u); do
200
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-rfcs-section.sh" "$problem_file" docs/rfcs
200
+ wr-itil-update-problem-rfcs-section "$problem_file" docs/rfcs
201
201
  git add "$problem_file"
202
202
  done
203
203
  ```
@@ -138,7 +138,7 @@ for pid_token in $(awk '/^problems:/{gsub(/[][]/,""); gsub(/,/," "); for(i=2;i<=
138
138
  pid_num="${pid_token#P}"
139
139
  problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
140
140
  [ -z "$problem_file" ] && continue
141
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-references-section.sh" "$problem_file" "Stories"
141
+ wr-itil-update-problem-references-section "$problem_file" "Stories"
142
142
  git add "$problem_file"
143
143
  done
144
144
 
@@ -146,7 +146,7 @@ done
146
146
  for jid in $(awk '/^jtbd:/{gsub(/[][]/,""); gsub(/,/," "); for(i=2;i<=NF;i++)print $i; exit}' "$story_file"); do
147
147
  jtbd_file=$(ls docs/jtbd/*/${jid}-*.md 2>/dev/null | head -1)
148
148
  [ -z "$jtbd_file" ] && continue
149
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-jtbd-references-section.sh" "$jtbd_file" "Stories"
149
+ wr-itil-update-jtbd-references-section "$jtbd_file" "Stories"
150
150
  git add "$jtbd_file"
151
151
  done
152
152
 
@@ -154,7 +154,7 @@ done
154
154
  for rid in $(awk '/^rfcs:/{gsub(/[][]/,""); gsub(/,/," "); for(i=2;i<=NF;i++)print $i; exit}' "$story_file"); do
155
155
  rfc_file=$(ls docs/rfcs/${rid}-*.md 2>/dev/null | head -1)
156
156
  [ -z "$rfc_file" ] && continue
157
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-rfc-references-section.sh" "$rfc_file" "Stories"
157
+ wr-itil-update-rfc-references-section "$rfc_file" "Stories"
158
158
  git add "$rfc_file"
159
159
  done
160
160
 
@@ -104,7 +104,7 @@ for pid_token in $(grep -oE '<meta name="problems" content="[^"]*"' "$map_file"
104
104
  pid_num="${pid_token#P}"
105
105
  problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
106
106
  [ -z "$problem_file" ] && continue
107
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-references-section.sh" "$problem_file" "Story Maps"
107
+ wr-itil-update-problem-references-section "$problem_file" "Story Maps"
108
108
  git add "$problem_file"
109
109
  done
110
110
 
@@ -56,11 +56,11 @@ For reverse-trace drift entries — invoke the appropriate Slice 2a/2b helper fo
56
56
 
57
57
  ```bash
58
58
  # Problem parent
59
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-references-section.sh" "$problem_file" "Stories"
59
+ wr-itil-update-problem-references-section "$problem_file" "Stories"
60
60
  # RFC parent
61
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-rfc-references-section.sh" "$rfc_file" "Stories"
61
+ wr-itil-update-rfc-references-section "$rfc_file" "Stories"
62
62
  # JTBD parent
63
- bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-jtbd-references-section.sh" "$jtbd_file" "Stories"
63
+ wr-itil-update-jtbd-references-section "$jtbd_file" "Stories"
64
64
  ```
65
65
 
66
66
  ### 4. Verify + commit
@@ -127,10 +127,11 @@ This is a robustness layer ON TOP of P094 + P062, not a supersession — both pe
127
127
  After Step 0's fetch/divergence preflight and the README reconciliation block but **before** Step 1's backlog scan, source the shared shell migration routine and call the idempotent entrypoint:
128
128
 
129
129
  ```bash
130
- source packages/itil/lib/migrate-problems-layout.sh
131
- migrate_problems_to_per_state_layout "$PWD"
130
+ wr-itil-migrate-problems-layout "$PWD"
132
131
  ```
133
132
 
133
+ `wr-itil-migrate-problems-layout` is the ADR-049 `$PATH` shim (adopter-safe — resolves `lib/migrate-problems-layout.sh` relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline `source packages/itil/lib/migrate-problems-layout.sh; migrate_problems_to_per_state_layout "$PWD"`. NEVER `source packages/...` repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
134
+
134
135
  The routine is **idempotent and partial-migration-safe**. It no-ops when no flat-layout files (`docs/problems/*.<state>.md` at the top level of `docs/problems/`) are detected — the common case post-Slice-5 T5a in this monorepo and in freshly-migrated adopter repos.
135
136
 
136
137
  **Closes the Step 1 false-zero defect** (per ADR-031 § Backward Compatibility line 126 "Why both skills"): Step 1 enumerates BEFORE delegating to manage-problem. On a flat-layout adopter repo, the post-ADR-031 Step 1 glob would return zero matches at the per-state shape and stop-condition #1 would fire incorrectly — the orchestrator would exit with a false "nothing to do" signal, never reaching manage-problem's Step 0a auto-migrate. Wiring auto-migrate here at Step 0a is structurally required, not an optimisation.
@@ -154,10 +155,11 @@ After Step 0a's auto-migrate and before Step 1's backlog scan, check whether the
154
155
  **Mechanism:**
155
156
 
156
157
  ```bash
157
- source packages/itil/lib/check-upstream-cache-staleness.sh
158
- preflight_reason="$(should_promote_inbound_discovery_preflight "$PWD")"
158
+ preflight_reason="$(wr-itil-check-upstream-cache-staleness "$PWD")"
159
159
  ```
160
160
 
161
+ `wr-itil-check-upstream-cache-staleness` is the ADR-049 `$PATH` shim (adopter-safe — resolves `lib/check-upstream-cache-staleness.sh` relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline `source ...; should_promote_inbound_discovery_preflight "$PWD"` and echoes the result. NEVER `source packages/...` repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
162
+
161
163
  The helper returns one of five outcomes (contract documented at `packages/itil/lib/check-upstream-cache-staleness.sh` + asserted by `packages/itil/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats`):
162
164
 
163
165
  | `preflight_reason` | Action |