@windyroad/jtbd 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Tests for slide_marker_on_subprocess_return helper (P111).
4
- #
5
- # Behavioural contract:
6
- # - Slides an existing marker forward (touch) on PostToolUse:Agent|Bash
7
- # completion, treating subprocess wall-clock as continuous parent-session
8
- # work for TTL purposes.
9
- # - Never CREATES a marker (creating requires a real gate review).
10
- # - Skips slide on subprocess error (tool_response.is_error=true) so a failed
11
- # subprocess does NOT extend the parent's trust window (ADR-009 amendment).
12
- # - No-op when no marker exists or session_id is empty (fail-safe).
13
-
14
- setup() {
15
- HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
16
- source "$HOOKS_DIR/lib/gate-helpers.sh"
17
-
18
- TEST_SESSION="bats-slide-$$-${BATS_TEST_NUMBER}"
19
- MARKER="/tmp/architect-reviewed-${TEST_SESSION}"
20
- rm -f "$MARKER"
21
- }
22
-
23
- teardown() {
24
- rm -f "$MARKER"
25
- }
26
-
27
- # Helper: backdate file mtime by N seconds (portable between macOS and Linux)
28
- _backdate() {
29
- local file="$1" seconds="$2"
30
- local stamp
31
- stamp=$(date -v-${seconds}S +%Y%m%d%H%M.%S 2>/dev/null \
32
- || date -d "${seconds} seconds ago" +%Y%m%d%H%M.%S 2>/dev/null)
33
- touch -t "$stamp" "$file"
34
- }
35
-
36
- @test "slide: existing marker is touched on success response" {
37
- touch "$MARKER"
38
- _backdate "$MARKER" 60
39
- BEFORE=$(_mtime "$MARKER")
40
- _HOOK_INPUT='{"tool_response":{"content":[]}}'
41
- slide_marker_on_subprocess_return "$MARKER"
42
- AFTER=$(_mtime "$MARKER")
43
- [ "$AFTER" -gt "$BEFORE" ]
44
- }
45
-
46
- @test "slide: long-running subprocess does NOT cause parent marker expiry on return (P111 reproduction)" {
47
- # Simulate the P111 failure mode: parent's marker is set, then a long
48
- # subprocess runs (we backdate the marker to simulate elapsed wall-clock),
49
- # then PostToolUse fires with a successful tool_response. The marker mtime
50
- # must be refreshed so the parent's NEXT PreToolUse gate check (which
51
- # compares NOW - mtime against TTL) sees a fresh marker.
52
- touch "$MARKER"
53
- # Marker is 50 minutes old — under default 60-min TTL but close to expiry.
54
- # Without the slide on subprocess return, a subsequent 15-min subprocess
55
- # would push the mtime past TTL and the next PreToolUse would deny.
56
- _backdate "$MARKER" 3000
57
- BEFORE=$(_mtime "$MARKER")
58
- _HOOK_INPUT='{"tool_response":{"content":[{"type":"text","text":"OK"}]}}'
59
- slide_marker_on_subprocess_return "$MARKER"
60
- AFTER=$(_mtime "$MARKER")
61
- NOW=$(date +%s)
62
- [ "$AFTER" -gt "$BEFORE" ]
63
- # And the new mtime is approximately NOW (within 5 seconds of slide call)
64
- AGE=$((NOW - AFTER))
65
- [ "$AGE" -lt 5 ]
66
- }
67
-
68
- @test "slide: does NOT touch marker when tool_response.is_error=true" {
69
- touch "$MARKER"
70
- _backdate "$MARKER" 60
71
- BEFORE=$(_mtime "$MARKER")
72
- _HOOK_INPUT='{"tool_response":{"is_error":true,"content":[]}}'
73
- slide_marker_on_subprocess_return "$MARKER"
74
- AFTER=$(_mtime "$MARKER")
75
- [ "$BEFORE" = "$AFTER" ]
76
- }
77
-
78
- @test "slide: no-op when marker does not exist (never creates)" {
79
- [ ! -f "$MARKER" ]
80
- _HOOK_INPUT='{"tool_response":{"content":[]}}'
81
- slide_marker_on_subprocess_return "$MARKER"
82
- [ ! -f "$MARKER" ]
83
- }
84
-
85
- @test "slide: no-op when marker path argument is empty" {
86
- _HOOK_INPUT='{"tool_response":{"content":[]}}'
87
- run slide_marker_on_subprocess_return ""
88
- [ "$status" -eq 0 ]
89
- }
90
-
91
- @test "slide: malformed hook input is fail-safe (no slide)" {
92
- touch "$MARKER"
93
- _backdate "$MARKER" 60
94
- BEFORE=$(_mtime "$MARKER")
95
- _HOOK_INPUT='not valid json'
96
- slide_marker_on_subprocess_return "$MARKER"
97
- AFTER=$(_mtime "$MARKER")
98
- # Fail-safe: when the hook input cannot be parsed, treat as error and skip
99
- [ "$BEFORE" = "$AFTER" ]
100
- }
101
-
102
- @test "slide: triggers correctly on Skill tool_response shape (P213, ADR-009 2026-06-08 amendment)" {
103
- # hooks.json matcher expansion Agent|Bash → Agent|Bash|Skill (P213 Option D)
104
- # widens slide-marker coverage to PostToolUse:Skill completions (e.g. the
105
- # /wr-risk-scorer:assess-* sibling assessor SKILLs run as long subprocesses
106
- # by the AFK orchestrator). The Skill tool_response shape is identical to
107
- # Agent|Bash (Claude Code's uniform PostToolUse contract), so the matcher-
108
- # agnostic helper composes without code changes. This test documents that
109
- # contract explicitly so a future hook_input shape divergence regression
110
- # surfaces here rather than at the gate-denial site.
111
- touch "$MARKER"
112
- _backdate "$MARKER" 60
113
- BEFORE=$(_mtime "$MARKER")
114
- _HOOK_INPUT='{"tool_name":"Skill","tool_response":{"content":[{"type":"text","text":"OK"}]}}'
115
- slide_marker_on_subprocess_return "$MARKER"
116
- AFTER=$(_mtime "$MARKER")
117
- [ "$AFTER" -gt "$BEFORE" ]
118
- }
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Behavioural tests for ADR-009 amendment 2026-06-06: substance-aware drift +
4
- # atomic verdict-write — JTBD gate. Closes P353 (hash-marker brittleness
5
- # umbrella) for the review-gate.sh code path shared by JTBD / voice-tone /
6
- # style-guide.
7
- #
8
- # Cases ratified 2026-06-06: see substance-aware-drift.bats in the architect
9
- # package for the full contract. JTBD's tests cover the same four cases
10
- # through `check_review_gate` + `store_review_hash`.
11
-
12
- setup() {
13
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
14
- source "$SCRIPT_DIR/lib/review-gate.sh"
15
-
16
- TEST_SESSION="bats-jtbd-substance-$$-${BATS_TEST_NUMBER}"
17
- SYSTEM="jtbd"
18
- TEST_DIR=$(mktemp -d -t substance-aware-drift-jtbd.XXXXXX)
19
- mkdir -p "$TEST_DIR/docs/jtbd"
20
- POLICY_DIR="$TEST_DIR/docs/jtbd"
21
-
22
- MARKER="/tmp/${SYSTEM}-reviewed-${TEST_SESSION}"
23
- HASH_FILE="${MARKER}.hash"
24
- rm -f "$MARKER" "$HASH_FILE"
25
- }
26
-
27
- teardown() {
28
- rm -f "$MARKER" "$HASH_FILE"
29
- rm -rf "$TEST_DIR"
30
- }
31
-
32
- _install_and_mark() {
33
- local body="$1"
34
- printf '%s' "$body" > "$POLICY_DIR/JTBD-001.proposed.md"
35
- touch "$MARKER"
36
- store_review_hash "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
37
- }
38
-
39
- # ---------------------------------------------------------------------------
40
- # Case (a) — trivial edits do NOT re-trigger
41
- # ---------------------------------------------------------------------------
42
-
43
- @test "jtbd substance-aware: trailing-whitespace edit does NOT re-trigger drift" {
44
- _install_and_mark "# JTBD-001
45
-
46
- A job-to-be-done description.
47
- "
48
- printf '%s' "# JTBD-001
49
-
50
- A job-to-be-done description.
51
- " > "$POLICY_DIR/JTBD-001.proposed.md"
52
-
53
- run check_review_gate "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
54
- [ "$status" -eq 0 ]
55
- [ -f "$MARKER" ]
56
- }
57
-
58
- @test "jtbd substance-aware: CRLF→LF edit does NOT re-trigger drift" {
59
- _install_and_mark "# JTBD-001
60
-
61
- Body.
62
- "
63
- printf '# JTBD-001\r\n\r\nBody.\r\n' > "$POLICY_DIR/JTBD-001.proposed.md"
64
-
65
- run check_review_gate "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
66
- [ "$status" -eq 0 ]
67
- }
68
-
69
- # ---------------------------------------------------------------------------
70
- # Case (b) — substantive edits DO re-trigger
71
- # ---------------------------------------------------------------------------
72
-
73
- @test "jtbd substance-aware: new-word edit DOES re-trigger drift" {
74
- _install_and_mark "# JTBD-001
75
-
76
- A job-to-be-done description.
77
- "
78
- printf '%s' "# JTBD-001
79
-
80
- A different job-to-be-done description.
81
- " > "$POLICY_DIR/JTBD-001.proposed.md"
82
-
83
- run check_review_gate "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
84
- [ "$status" -ne 0 ]
85
- [ ! -f "$MARKER" ]
86
- [ ! -f "$HASH_FILE" ]
87
- }
88
-
89
- @test "jtbd substance-aware: new JTBD file DOES re-trigger drift" {
90
- _install_and_mark "# JTBD-001
91
-
92
- Body.
93
- "
94
- printf '%s' "# JTBD-002
95
-
96
- New job.
97
- " > "$POLICY_DIR/JTBD-002.proposed.md"
98
-
99
- run check_review_gate "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
100
- [ "$status" -ne 0 ]
101
- }
102
-
103
- # ---------------------------------------------------------------------------
104
- # Case (c) — atomic write persists reliably
105
- # ---------------------------------------------------------------------------
106
-
107
- @test "jtbd atomic-write: store_review_hash lands marker + hash together" {
108
- printf '%s' "# JTBD-001
109
-
110
- Body.
111
- " > "$POLICY_DIR/JTBD-001.proposed.md"
112
- touch "$MARKER"
113
- run store_review_hash "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
114
- [ "$status" -eq 0 ]
115
- [ -f "$MARKER" ]
116
- [ -f "$HASH_FILE" ]
117
- # Stored hash matches the substance hash of the policy content.
118
- local expected
119
- expected=$(_substance_hash_path "$POLICY_DIR")
120
- [ "$(cat "$HASH_FILE")" = "$expected" ]
121
- }
122
-
123
- # ---------------------------------------------------------------------------
124
- # Case (d) — conservative boundary holds
125
- # ---------------------------------------------------------------------------
126
-
127
- @test "jtbd conservative: single-numeral change DOES re-trigger drift" {
128
- _install_and_mark "# JTBD-001
129
-
130
- Threshold is 5 minutes.
131
- "
132
- printf '%s' "# JTBD-001
133
-
134
- Threshold is 6 minutes.
135
- " > "$POLICY_DIR/JTBD-001.proposed.md"
136
-
137
- run check_review_gate "$TEST_SESSION" "$SYSTEM" "$POLICY_DIR"
138
- [ "$status" -ne 0 ]
139
- }
@@ -1,113 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # ADR-068: detect-unoversighted.sh prints jobs/personas whose frontmatter lacks the
4
- # `human-oversight: confirmed` marker. Behavioural — exercises the script against
5
- # fixture docs/jtbd/<persona>/ trees and asserts on stdout.
6
-
7
- setup() {
8
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
9
- SCRIPT="$REPO_ROOT/packages/jtbd/scripts/detect-unoversighted.sh"
10
- DIR="$(mktemp -d)"
11
- mkdir -p "$DIR/docs/jtbd/solo-developer"
12
- }
13
-
14
- teardown() { rm -rf "$DIR"; }
15
-
16
- mk() { # mk <relpath under docs/jtbd> <extra frontmatter lines...>
17
- local rel="$1"; shift
18
- mkdir -p "$(dirname "$DIR/docs/jtbd/$rel")"
19
- {
20
- echo "---"
21
- echo "status: proposed"
22
- echo "date-created: 2026-05-25"
23
- for line in "$@"; do echo "$line"; done
24
- echo "---"
25
- echo "# $rel"
26
- } > "$DIR/docs/jtbd/$rel"
27
- }
28
-
29
- @test "a job without the marker is reported" {
30
- mk "solo-developer/JTBD-001-foo.proposed.md"
31
- run bash "$SCRIPT" "$DIR/docs/jtbd"
32
- [ "$status" -eq 0 ]
33
- [[ "$output" == *"JTBD-001-foo.proposed.md"* ]]
34
- }
35
-
36
- @test "a persona file without the marker is reported" {
37
- mk "solo-developer/persona.md"
38
- run bash "$SCRIPT" "$DIR/docs/jtbd"
39
- [[ "$output" == *"solo-developer/persona.md"* ]]
40
- }
41
-
42
- @test "a job carrying human-oversight: confirmed is NOT reported" {
43
- mk "solo-developer/JTBD-002-bar.proposed.md" "human-oversight: confirmed" "oversight-date: 2026-05-25"
44
- run bash "$SCRIPT" "$DIR/docs/jtbd"
45
- [[ "$output" != *"JTBD-002-bar.proposed.md"* ]]
46
- }
47
-
48
- @test "the top-level docs/jtbd/README.md is never reported" {
49
- echo "# JTBD index" > "$DIR/docs/jtbd/README.md"
50
- run bash "$SCRIPT" "$DIR/docs/jtbd"
51
- [[ "$output" != *"README.md"* ]]
52
- }
53
-
54
- @test "a per-persona README is also excluded" {
55
- echo "# persona index" > "$DIR/docs/jtbd/solo-developer/README.md"
56
- run bash "$SCRIPT" "$DIR/docs/jtbd"
57
- [[ "$output" != *"README.md"* ]]
58
- }
59
-
60
- @test "a file with no frontmatter counts as unoversighted" {
61
- mkdir -p "$DIR/docs/jtbd/tech-lead"
62
- echo "# bare persona, no frontmatter" > "$DIR/docs/jtbd/tech-lead/persona.md"
63
- run bash "$SCRIPT" "$DIR/docs/jtbd"
64
- [[ "$output" == *"tech-lead/persona.md"* ]]
65
- }
66
-
67
- @test "marker match is case-insensitive and tolerant of trailing space" {
68
- mk "solo-developer/JTBD-003-baz.proposed.md" "human-oversight: confirmed "
69
- run bash "$SCRIPT" "$DIR/docs/jtbd"
70
- [[ "$output" != *"JTBD-003-baz.proposed.md"* ]]
71
- }
72
-
73
- @test "missing jtbd dir exits 0 with no output" {
74
- run bash "$SCRIPT" "$DIR/docs/nonexistent"
75
- [ "$status" -eq 0 ]
76
- [ -z "$output" ]
77
- }
78
-
79
- @test "fully-confirmed corpus produces empty output" {
80
- mk "solo-developer/persona.md" "human-oversight: confirmed"
81
- mk "solo-developer/JTBD-001-foo.proposed.md" "human-oversight: confirmed"
82
- run bash "$SCRIPT" "$DIR/docs/jtbd"
83
- [ "$status" -eq 0 ]
84
- [ -z "$output" ]
85
- }
86
-
87
- # ADR-068 amendment (P316): mirror the architect `rejected-pending-supersede`
88
- # exclusion onto the JTBD detector. Exclusion requires BOTH the marker AND
89
- # a supersede-ticket: P<NNN> scalar; a marker without the ticket is
90
- # malformed and still surfaces (defensive).
91
-
92
- @test "JTBD with rejected-pending-supersede + supersede-ticket is excluded" {
93
- mk "solo-developer/JTBD-020-rejected.proposed.md" \
94
- "human-oversight: rejected-pending-supersede" \
95
- "supersede-ticket: P297"
96
- run bash "$SCRIPT" "$DIR/docs/jtbd"
97
- [[ "$output" != *"JTBD-020-rejected.proposed.md"* ]]
98
- }
99
-
100
- @test "JTBD with rejected-pending-supersede WITHOUT supersede-ticket still surfaces (defensive)" {
101
- mk "solo-developer/JTBD-021-untracked.proposed.md" \
102
- "human-oversight: rejected-pending-supersede"
103
- run bash "$SCRIPT" "$DIR/docs/jtbd"
104
- [[ "$output" == *"JTBD-021-untracked.proposed.md"* ]]
105
- }
106
-
107
- @test "persona with rejected-pending-supersede + ticket is also excluded" {
108
- mk "rejected-persona/persona.md" \
109
- "human-oversight: rejected-pending-supersede" \
110
- "supersede-ticket: P299"
111
- run bash "$SCRIPT" "$DIR/docs/jtbd"
112
- [[ "$output" != *"rejected-persona/persona.md"* ]]
113
- }
@@ -1,198 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # ADR-068 surface 3 / RFC-011 / P323: is-job-or-persona-unconfirmed.sh is the
4
- # single-artifact predicate for the JTBD build-upon guard (the wr-jtbd:agent
5
- # [Unratified Dependency] verdict). It answers "is this referenced persona or
6
- # job unconfirmed?" via its EXIT CODE, where "unconfirmed" mirrors
7
- # detect-unoversighted.sh EXACTLY (frontmatter lacks `human-oversight:
8
- # confirmed`, and the artifact is not superseded). The JTBD twin of the
9
- # architect side's is-decision-unconfirmed.sh (ADR-074).
10
- #
11
- # Behavioural — exercises the script against fixture trees and asserts on its
12
- # exit code + stdout, not its source text (ADR-052).
13
-
14
- setup() {
15
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
16
- SCRIPT="$REPO_ROOT/packages/jtbd/scripts/is-job-or-persona-unconfirmed.sh"
17
- DETECT="$REPO_ROOT/packages/jtbd/scripts/detect-unoversighted.sh"
18
- DIR="$(mktemp -d)"
19
- mkdir -p "$DIR/docs/jtbd"
20
- }
21
-
22
- teardown() {
23
- rm -rf "$DIR"
24
- }
25
-
26
- # mk_persona <persona-name> <confirmed?yes|no>
27
- mk_persona() {
28
- local name="$1"; local confirmed="$2"
29
- mkdir -p "$DIR/docs/jtbd/$name"
30
- {
31
- echo "---"
32
- echo "name: $name"
33
- echo "description: test persona $name"
34
- [ "$confirmed" = "yes" ] && { echo "human-oversight: confirmed"; echo "oversight-date: 2026-05-27"; }
35
- echo "---"
36
- echo "# $name"
37
- } > "$DIR/docs/jtbd/$name/persona.md"
38
- }
39
-
40
- # mk_job <persona-name> <NNN> <slug> <confirmed?yes|no> [state]
41
- mk_job() {
42
- local persona="$1"; local num="$2"; local slug="$3"; local confirmed="$4"; local state="${5:-proposed}"
43
- mkdir -p "$DIR/docs/jtbd/$persona"
44
- {
45
- echo "---"
46
- echo "status: $state"
47
- echo "job-id: $slug"
48
- echo "persona: $persona"
49
- [ "$confirmed" = "yes" ] && { echo "human-oversight: confirmed"; echo "oversight-date: 2026-05-27"; }
50
- echo "---"
51
- echo "# $slug"
52
- } > "$DIR/docs/jtbd/$persona/JTBD-$num-$slug.md"
53
- }
54
-
55
- @test "persona without the marker is unconfirmed (exit 0, prints path)" {
56
- mk_persona "solo-developer" "no"
57
- run bash "$SCRIPT" "solo-developer" "$DIR/docs/jtbd"
58
- [ "$status" -eq 0 ]
59
- [[ "$output" == *"solo-developer/persona.md"* ]]
60
- }
61
-
62
- @test "persona carrying human-oversight: confirmed is confirmed (exit 1, no stdout)" {
63
- mk_persona "tech-lead" "yes"
64
- run bash "$SCRIPT" "tech-lead" "$DIR/docs/jtbd"
65
- [ "$status" -eq 1 ]
66
- [ -z "$output" ]
67
- }
68
-
69
- @test "job (JTBD-NNN) without the marker is unconfirmed (exit 0, prints path)" {
70
- mk_job "solo-developer" "001" "enforce-governance" "no"
71
- run bash "$SCRIPT" "JTBD-001" "$DIR/docs/jtbd"
72
- [ "$status" -eq 0 ]
73
- [[ "$output" == *"JTBD-001-enforce-governance.md"* ]]
74
- }
75
-
76
- @test "job carrying the marker is confirmed (exit 1)" {
77
- mk_job "tech-lead" "201" "restore-service-fast" "yes"
78
- run bash "$SCRIPT" "JTBD-201" "$DIR/docs/jtbd"
79
- [ "$status" -eq 1 ]
80
- }
81
-
82
- @test "superseded job (even without marker) does NOT fire the guard (exit 1)" {
83
- mkdir -p "$DIR/docs/jtbd/solo-developer"
84
- {
85
- echo "---"; echo "status: superseded"; echo "persona: solo-developer"; echo "---"; echo "# retired"
86
- } > "$DIR/docs/jtbd/solo-developer/JTBD-009-retired.superseded.md"
87
- run bash "$SCRIPT" "JTBD-009" "$DIR/docs/jtbd"
88
- [ "$status" -eq 1 ]
89
- }
90
-
91
- @test "a bare numeric job ref resolves" {
92
- mk_job "solo-developer" "002" "ship-with-confidence" "no"
93
- run bash "$SCRIPT" "002" "$DIR/docs/jtbd"
94
- [ "$status" -eq 0 ]
95
- [[ "$output" == *"JTBD-002-ship-with-confidence.md"* ]]
96
- }
97
-
98
- # P383: maintainer JTBD-M-NNN alpha-infix scheme. The old number-only
99
- # extraction dropped the `M` and globbed JTBD-NNN-*.md (wrong / no match).
100
- @test "a maintainer JTBD-M-NNN job ref resolves (exit 0, preserves M infix)" {
101
- mk_job "maintainer" "M-101" "release-plugins" "no"
102
- run bash "$SCRIPT" "JTBD-M-101" "$DIR/docs/jtbd"
103
- [ "$status" -eq 0 ]
104
- [[ "$output" == *"JTBD-M-101-release-plugins.md"* ]]
105
- }
106
-
107
- @test "a confirmed maintainer JTBD-M-NNN job is confirmed (exit 1)" {
108
- mk_job "maintainer" "M-202" "audit-suite" "yes"
109
- run bash "$SCRIPT" "JTBD-M-202" "$DIR/docs/jtbd"
110
- [ "$status" -eq 1 ]
111
- }
112
-
113
- @test "a bare M-NNN job ref variant resolves" {
114
- mk_job "maintainer" "M-303" "scaffold-intake" "no"
115
- run bash "$SCRIPT" "M-303" "$DIR/docs/jtbd"
116
- [ "$status" -eq 0 ]
117
- [[ "$output" == *"JTBD-M-303-scaffold-intake.md"* ]]
118
- }
119
-
120
- # Boundary lock (architect P383 review): a hyphenated persona NAME must still
121
- # route to the persona branch, not the job branch — it has no trailing -NNN.
122
- @test "a hyphenated persona name (smb-owner) routes to the persona branch" {
123
- mk_persona "smb-owner" "no"
124
- run bash "$SCRIPT" "smb-owner" "$DIR/docs/jtbd"
125
- [ "$status" -eq 0 ]
126
- [[ "$output" == *"smb-owner/persona.md"* ]]
127
- }
128
-
129
- @test "a direct path ref resolves" {
130
- mk_persona "plugin-user" "no"
131
- run bash "$SCRIPT" "$DIR/docs/jtbd/plugin-user/persona.md"
132
- [ "$status" -eq 0 ]
133
- }
134
-
135
- @test "an unknown persona name exits 2 (not found)" {
136
- run bash "$SCRIPT" "ghost-persona" "$DIR/docs/jtbd"
137
- [ "$status" -eq 2 ]
138
- }
139
-
140
- @test "an unknown job ref exits 2 (not found)" {
141
- run bash "$SCRIPT" "JTBD-999" "$DIR/docs/jtbd"
142
- [ "$status" -eq 2 ]
143
- }
144
-
145
- @test "agrees with detect-unoversighted on the same fixture (sync guard)" {
146
- # The two scripts share the frontmatter/marker/superseded shape. This guard
147
- # fails if a future edit drifts one from the other.
148
- mk_persona "solo-developer" "no"
149
- mk_persona "tech-lead" "yes"
150
- detect_out="$(bash "$DETECT" "$DIR/docs/jtbd")"
151
- # solo-developer is in the detector's unoversighted list AND the predicate exits 0.
152
- [[ "$detect_out" == *"solo-developer/persona.md"* ]]
153
- run bash "$SCRIPT" "solo-developer" "$DIR/docs/jtbd"; [ "$status" -eq 0 ]
154
- # tech-lead is NOT in the detector's list AND the predicate exits 1.
155
- [[ "$detect_out" != *"tech-lead/persona.md"* ]]
156
- run bash "$SCRIPT" "tech-lead" "$DIR/docs/jtbd"; [ "$status" -eq 1 ]
157
- }
158
-
159
- # ADR-068 amendment (P316): mirror the architect `rejected-pending-supersede`
160
- # exclusion onto the JTBD predicate.
161
-
162
- # mk_rejected_persona <name> [supersede-ticket-or-empty]
163
- mk_rejected_persona() {
164
- local name="$1"; local ticket="${2:-}"
165
- mkdir -p "$DIR/docs/jtbd/$name"
166
- {
167
- echo "---"
168
- echo "name: $name"
169
- echo "human-oversight: rejected-pending-supersede"
170
- [ -n "$ticket" ] && echo "supersede-ticket: $ticket"
171
- echo "---"
172
- echo "# $name"
173
- } > "$DIR/docs/jtbd/$name/persona.md"
174
- }
175
-
176
- @test "persona with rejected-pending-supersede + supersede-ticket does NOT fire the guard (exit 1)" {
177
- mk_rejected_persona "scrapped-persona" "P299"
178
- run bash "$SCRIPT" "scrapped-persona" "$DIR/docs/jtbd"
179
- [ "$status" -eq 1 ]
180
- [ -z "$output" ]
181
- }
182
-
183
- @test "persona with rejected-pending-supersede WITHOUT supersede-ticket DOES fire the guard (exit 0)" {
184
- mk_rejected_persona "untracked-persona" ""
185
- run bash "$SCRIPT" "untracked-persona" "$DIR/docs/jtbd"
186
- [ "$status" -eq 0 ]
187
- [[ "$output" == *"untracked-persona/persona.md"* ]]
188
- }
189
-
190
- @test "agrees with detect-unoversighted on rejected-pending-supersede (sync guard)" {
191
- mk_rejected_persona "alpha-tracked" "P297"
192
- mk_rejected_persona "beta-untracked" ""
193
- detect_out="$(bash "$DETECT" "$DIR/docs/jtbd")"
194
- [[ "$detect_out" != *"alpha-tracked/persona.md"* ]]
195
- run bash "$SCRIPT" "alpha-tracked" "$DIR/docs/jtbd"; [ "$status" -eq 1 ]
196
- [[ "$detect_out" == *"beta-untracked/persona.md"* ]]
197
- run bash "$SCRIPT" "beta-untracked" "$DIR/docs/jtbd"; [ "$status" -eq 0 ]
198
- }