@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.
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +5 -2
- package/agents/test/jtbd-output-formatting.bats +0 -26
- package/agents/test/jtbd-pre-edit-review-mode.bats +0 -48
- package/agents/test/jtbd-unratified-dependency-verdict.bats +0 -62
- package/agents/test/jtbd-verdict-contract.bats +0 -57
- package/hooks/test/jtbd-enforce-scope.bats +0 -199
- package/hooks/test/jtbd-eval-once-per-session.bats +0 -87
- package/hooks/test/jtbd-eval-scope.bats +0 -45
- package/hooks/test/jtbd-eval.bats +0 -69
- package/hooks/test/jtbd-mark-reviewed.bats +0 -160
- package/hooks/test/jtbd-marker-only-exempt.bats +0 -140
- package/hooks/test/jtbd-no-stop-hook.bats +0 -15
- package/hooks/test/jtbd-oversight-marker-discipline.bats +0 -216
- package/hooks/test/jtbd-oversight-nudge.bats +0 -74
- package/hooks/test/jtbd-project-root.bats +0 -60
- package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
- package/hooks/test/substance-aware-drift.bats +0 -139
- package/scripts/test/detect-unoversighted.bats +0 -113
- package/scripts/test/is-job-or-persona-unconfirmed.bats +0 -198
|
@@ -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
|
-
}
|