@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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/wr-itil-check-ticket-jtbd-ratification +51 -0
- package/package.json +1 -1
- package/scripts/check-ticket-jtbd-ratification.sh +92 -0
- package/skills/work-problems/SKILL.md +29 -0
- package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +191 -0
|
@@ -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
|
@@ -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
|
+
}
|