@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,160 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Tests for jtbd-mark-reviewed.sh — verifies the PostToolUse:Agent hook
4
- # creates session markers and stores the right policy-path hash when
5
- # wr-jtbd:agent (or legacy jtbd-lead) returns a PASS verdict.
6
- #
7
- # Per ADR-005 (P011): assertions are functional — execute the hook with
8
- # mock JSON, assert on side-effects (marker files, hash file contents).
9
- # Source-grep assertions for "the script mentions docs/jtbd" were
10
- # removed because they passed even when the surrounding code path was
11
- # unreachable.
12
-
13
- setup() {
14
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
15
- HOOK="$SCRIPT_DIR/jtbd-mark-reviewed.sh"
16
- ORIG_DIR="$PWD"
17
- TEST_DIR=$(mktemp -d)
18
- cd "$TEST_DIR"
19
- SESSION_ID="test-session-$$"
20
- MARKER="/tmp/jtbd-reviewed-${SESSION_ID}"
21
- PLAN_MARKER="/tmp/jtbd-plan-reviewed-${SESSION_ID}"
22
- HASH_FILE="/tmp/jtbd-reviewed-${SESSION_ID}.hash"
23
- VERDICT_FILE="/tmp/jtbd-verdict"
24
- rm -f "$MARKER" "$PLAN_MARKER" "$HASH_FILE" "$VERDICT_FILE"
25
- }
26
-
27
- teardown() {
28
- cd "$ORIG_DIR"
29
- rm -rf "$TEST_DIR"
30
- rm -f "$MARKER" "$PLAN_MARKER" "$HASH_FILE" "$VERDICT_FILE"
31
- }
32
-
33
- # Helper: pipe a PostToolUse:Agent JSON to the hook for the given subagent.
34
- run_hook() {
35
- local subagent="$1"
36
- local json="{\"tool_input\":{\"subagent_type\":\"${subagent}\"},\"session_id\":\"${SESSION_ID}\"}"
37
- echo "$json" | bash "$HOOK"
38
- }
39
-
40
- # --- Path support: docs/jtbd directory (preferred) ---
41
-
42
- @test "uses docs/jtbd directory when present (creates marker + hash)" {
43
- mkdir -p docs/jtbd/solo-developer
44
- echo "# Persona" > docs/jtbd/solo-developer/persona.md
45
- echo "# Index" > docs/jtbd/README.md
46
- echo "PASS" > "$VERDICT_FILE"
47
-
48
- run_hook "wr-jtbd:agent"
49
-
50
- [ -f "$MARKER" ]
51
- [ -f "$HASH_FILE" ]
52
- [ "$(cat "$HASH_FILE")" != "missing" ]
53
- [ -n "$(cat "$HASH_FILE")" ]
54
- }
55
-
56
- @test "directory hash excludes README.md (only persona/job files contribute)" {
57
- mkdir -p docs/jtbd
58
- echo "# Index" > docs/jtbd/README.md
59
- echo "PASS" > "$VERDICT_FILE"
60
- run_hook "wr-jtbd:agent"
61
- HASH_README_ONLY="$(cat "$HASH_FILE")"
62
-
63
- rm -f "$HASH_FILE" "$MARKER"
64
- echo "different content" >> docs/jtbd/README.md
65
- echo "PASS" > "$VERDICT_FILE"
66
- run_hook "wr-jtbd:agent"
67
- HASH_README_CHANGED="$(cat "$HASH_FILE")"
68
-
69
- # Changing README.md alone must not change the hash — README is excluded.
70
- [ "$HASH_README_ONLY" = "$HASH_README_CHANGED" ]
71
- }
72
-
73
- # --- Path support: docs/jtbd/ is the sole canonical layout (ADR-008 Option 3, P019) ---
74
-
75
- @test "does NOT store a review hash when only legacy docs/JOBS_TO_BE_DONE.md exists (P019)" {
76
- # Legacy single-file layout — the mark-reviewed hook must exit early
77
- # without storing a hash, because the enforce hook cannot gate the
78
- # project until it runs /wr-jtbd:update-guide to migrate to docs/jtbd/.
79
- mkdir -p docs
80
- echo "# Jobs" > docs/JOBS_TO_BE_DONE.md
81
- echo "PASS" > "$VERDICT_FILE"
82
-
83
- run_hook "wr-jtbd:agent"
84
-
85
- # No marker, no hash file — the gate is inactive on legacy-layout projects.
86
- [ ! -f "$MARKER" ]
87
- [ ! -f "$HASH_FILE" ]
88
- }
89
-
90
- @test "hashes docs/jtbd/ when both layouts coexist (legacy single-file is ignored, P019)" {
91
- mkdir -p docs/jtbd
92
- echo "# job" > docs/jtbd/job.md
93
- echo "# legacy jobs" > docs/JOBS_TO_BE_DONE.md
94
- echo "PASS" > "$VERDICT_FILE"
95
-
96
- run_hook "wr-jtbd:agent"
97
-
98
- # The directory-derived hash must NOT equal the standalone-file hash —
99
- # proves the hook did not consult the legacy file.
100
- DIR_HASH="$(cat "$HASH_FILE")"
101
- FILE_HASH=$(cat docs/JOBS_TO_BE_DONE.md \
102
- | (md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null) \
103
- | cut -d' ' -f1)
104
- [ "$DIR_HASH" != "$FILE_HASH" ]
105
- }
106
-
107
- # --- Verdict handling ---
108
-
109
- @test "FAIL verdict does NOT create review marker (but plan marker still set)" {
110
- mkdir -p docs/jtbd
111
- echo "# job" > docs/jtbd/job.md
112
- echo "FAIL" > "$VERDICT_FILE"
113
-
114
- run_hook "wr-jtbd:agent"
115
-
116
- [ ! -f "$MARKER" ]
117
- [ -f "$PLAN_MARKER" ]
118
- }
119
-
120
- @test "missing verdict file allows marker (backward compat)" {
121
- mkdir -p docs/jtbd
122
- echo "# job" > docs/jtbd/job.md
123
-
124
- run_hook "wr-jtbd:agent"
125
-
126
- [ -f "$MARKER" ]
127
- }
128
-
129
- @test "verdict file is consumed (removed) after hook runs" {
130
- mkdir -p docs/jtbd
131
- echo "# job" > docs/jtbd/job.md
132
- echo "PASS" > "$VERDICT_FILE"
133
-
134
- run_hook "wr-jtbd:agent"
135
-
136
- [ ! -f "$VERDICT_FILE" ]
137
- }
138
-
139
- # --- Subagent routing ---
140
-
141
- @test "ignores unrelated subagent (no marker created)" {
142
- mkdir -p docs/jtbd
143
- echo "# job" > docs/jtbd/job.md
144
- echo "PASS" > "$VERDICT_FILE"
145
-
146
- run_hook "wr-architect:agent"
147
-
148
- [ ! -f "$MARKER" ]
149
- [ ! -f "$PLAN_MARKER" ]
150
- }
151
-
152
- @test "matches legacy jtbd-lead subagent name" {
153
- mkdir -p docs/jtbd
154
- echo "# job" > docs/jtbd/job.md
155
- echo "PASS" > "$VERDICT_FILE"
156
-
157
- run_hook "jtbd-lead"
158
-
159
- [ -f "$MARKER" ]
160
- }
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P301: jtbd-enforce-edit.sh exempts marker-only frontmatter diffs to
4
- # docs/decisions/*.md ADRs from the JTBD review gate. The JTBD gate currently
5
- # fires on docs/decisions/ writes (decisions are not in its exclusion list);
6
- # the ticket's symptom "Batch 8 (ADR-020): blocked on architect review
7
- # (`jtbd policy file changed since last review`)" is that JTBD round-trip.
8
- #
9
- # Behavioural — exercises the full hook with constructed PreToolUse stdin
10
- # JSON payloads under a sandbox project dir; asserts on stdout+exit.
11
-
12
- setup() {
13
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
14
- HOOK="$SCRIPT_DIR/jtbd-enforce-edit.sh"
15
- ORIG_DIR="$PWD"
16
- TEST_DIR=$(mktemp -d)
17
- cd "$TEST_DIR"
18
- # JTBD docs must exist for the gate to engage (otherwise it denies
19
- # with "no JTBD documentation" — out of scope of this exemption test).
20
- mkdir -p docs/jtbd/persona docs/decisions
21
- echo "# stub job" > docs/jtbd/persona/JTBD-001-stub.proposed.md
22
- echo "# stub adr" > docs/decisions/001-stub.proposed.md
23
- SID="jtbd-marker-only-$$-$BATS_TEST_NUMBER"
24
- }
25
-
26
- teardown() {
27
- cd "$ORIG_DIR"
28
- rm -rf "$TEST_DIR"
29
- rm -f "/tmp/jtbd-reviewed-${SID}" "/tmp/jtbd-reviewed-${SID}.hash"
30
- }
31
-
32
- run_hook_json() {
33
- local json_file="$1"
34
- bash "$HOOK" < "$json_file"
35
- }
36
-
37
- # ── Marker-only ADD: should exempt ───────────────────────────────────────
38
-
39
- @test "P301: Edit adding only human-oversight + oversight-date to ADR is exempt (no JTBD marker required)" {
40
- adr="$PWD/docs/decisions/100-some-adr.proposed.md"
41
- cat > "$adr" <<'EOF'
42
- ---
43
- status: "proposed"
44
- ---
45
-
46
- # 100 some adr
47
- EOF
48
- old=$'---\nstatus: "proposed"\n---'
49
- new=$'---\nstatus: "proposed"\nhuman-oversight: unconfirmed\noversight-date: 2026-06-08\n---'
50
- json_file=$(mktemp)
51
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
52
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
53
- run run_hook_json "$json_file"
54
- rm -f "$json_file"
55
- [ "$status" -eq 0 ]
56
- [[ "$output" != *"BLOCKED"* ]]
57
- }
58
-
59
- @test "P301: Edit updating human-oversight value (unconfirmed → confirmed) on ADR is exempt" {
60
- adr="$PWD/docs/decisions/101-promote.proposed.md"
61
- cat > "$adr" <<'EOF'
62
- ---
63
- status: "proposed"
64
- human-oversight: unconfirmed
65
- ---
66
-
67
- # 101
68
- EOF
69
- old=$'human-oversight: unconfirmed'
70
- new=$'human-oversight: confirmed\noversight-date: 2026-06-08'
71
- json_file=$(mktemp)
72
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
73
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
74
- run run_hook_json "$json_file"
75
- rm -f "$json_file"
76
- [ "$status" -eq 0 ]
77
- [[ "$output" != *"BLOCKED"* ]]
78
- }
79
-
80
- # ── Mixed marker + body: must NOT exempt ─────────────────────────────────
81
-
82
- @test "P301: Edit changing body content along with markers still gates" {
83
- adr="$PWD/docs/decisions/110-mixed.proposed.md"
84
- cat > "$adr" <<'EOF'
85
- ---
86
- status: "proposed"
87
- ---
88
-
89
- # 110
90
-
91
- Original body.
92
- EOF
93
- old=$'---\nstatus: "proposed"\n---\n\n# 110\n\nOriginal body.'
94
- new=$'---\nstatus: "proposed"\nhuman-oversight: confirmed\noversight-date: 2026-06-08\n---\n\n# 110\n\nNew policy claim added.'
95
- json_file=$(mktemp)
96
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
97
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
98
- run run_hook_json "$json_file"
99
- rm -f "$json_file"
100
- [[ "$output" == *"BLOCKED"* ]]
101
- }
102
-
103
- # ── Pure body change: must still gate ────────────────────────────────────
104
-
105
- @test "P301: Edit changing only body content with no marker lines still gates" {
106
- adr="$PWD/docs/decisions/120-body.proposed.md"
107
- cat > "$adr" <<'EOF'
108
- ---
109
- status: "proposed"
110
- ---
111
-
112
- # 120
113
-
114
- Some text.
115
- EOF
116
- old='Some text.'
117
- new='Some text. And more.'
118
- json_file=$(mktemp)
119
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
120
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
121
- run run_hook_json "$json_file"
122
- rm -f "$json_file"
123
- [[ "$output" == *"BLOCKED"* ]]
124
- }
125
-
126
- # ── Scope: exemption is narrow to docs/decisions/*.md ────────────────────
127
-
128
- @test "P301: marker-only diff to a NON docs/decisions/ path still gates (no path exemption)" {
129
- mkdir -p "$PWD/src"
130
- echo "// stub" > "$PWD/src/x.ts"
131
- src="$PWD/src/x.ts"
132
- old='// stub'
133
- new=$'// stub\nhuman-oversight: confirmed'
134
- json_file=$(mktemp)
135
- jq -nc --arg p "$src" --arg s "$SID" --arg o "$old" --arg n "$new" \
136
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
137
- run run_hook_json "$json_file"
138
- rm -f "$json_file"
139
- [[ "$output" == *"BLOCKED"* ]]
140
- }
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P001 / ADR-009: Stop-hook marker reset removed.
4
-
5
- setup() {
6
- PLUGIN_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)"
7
- }
8
-
9
- @test "jtbd: hooks.json has no Stop hook entry (ADR-009)" {
10
- ! grep -q '"Stop"' "$PLUGIN_DIR/hooks/hooks.json"
11
- }
12
-
13
- @test "jtbd: jtbd-reset-marker.sh has been removed" {
14
- [ ! -f "$PLUGIN_DIR/hooks/jtbd-reset-marker.sh" ]
15
- }
@@ -1,216 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P348 / ADR-068 amendment 2026-06-02: jtbd-oversight-marker-discipline.sh
4
- # is the JTBD-side sibling of architect-oversight-marker-discipline.sh.
5
- # Denies Edit/Write that introduces `human-oversight: confirmed` into a
6
- # docs/jtbd/ artefact's frontmatter unless a session-scoped evidence marker
7
- # proves the user has substance-confirmed THAT artefact.
8
- #
9
- # Behavioural — exercises the hook with constructed PreToolUse stdin JSON
10
- # payloads under SESSION_MARKER_DIR sandboxing.
11
-
12
- setup() {
13
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
14
- HOOK="$REPO_ROOT/packages/jtbd/hooks/jtbd-oversight-marker-discipline.sh"
15
- MARK_SCRIPT="$REPO_ROOT/packages/jtbd/scripts/mark-oversight-confirmed.sh"
16
-
17
- DIR="$(mktemp -d)"
18
- mkdir -p "$DIR/docs/jtbd/developer"
19
- MARK_DIR="$(mktemp -d)"
20
- export SESSION_MARKER_DIR="$MARK_DIR"
21
-
22
- ORIG_DIR="$PWD"
23
- cd "$DIR"
24
-
25
- SID="jtbd-discipline-test-$$"
26
- }
27
-
28
- teardown() {
29
- cd "$ORIG_DIR"
30
- rm -rf "$DIR" "$MARK_DIR"
31
- unset SESSION_MARKER_DIR
32
- }
33
-
34
- expected_marker() {
35
- local f="$1"
36
- local abs_dir abs path_hash
37
- abs_dir="$(cd "$(dirname "$f")" && pwd)"
38
- abs="$abs_dir/$(basename "$f")"
39
- if command -v sha256sum >/dev/null 2>&1; then
40
- path_hash=$(printf '%s' "$abs" | sha256sum | cut -d' ' -f1 | cut -c1-16)
41
- else
42
- path_hash=$(printf '%s' "$abs" | shasum -a 256 | cut -d' ' -f1 | cut -c1-16)
43
- fi
44
- printf '%s/oversight-confirmed-%s-%s\n' "$MARK_DIR" "$path_hash" "$SID"
45
- }
46
-
47
- mk_existing_artefact() {
48
- local subpath="$1"; shift
49
- local dir
50
- dir="$(dirname "$DIR/docs/jtbd/$subpath")"
51
- mkdir -p "$dir"
52
- {
53
- echo "---"
54
- echo "status: \"proposed\""
55
- echo "date: 2026-06-02"
56
- for line in "$@"; do echo "$line"; done
57
- echo "---"
58
- echo "# $(basename "$subpath" .md)"
59
- } > "$DIR/docs/jtbd/$subpath"
60
- }
61
-
62
- # ── Positive paths ───────────────────────────────────────────────────────
63
-
64
- @test "Write introducing 'human-oversight: confirmed' to a JTBD with marker present is allowed" {
65
- mk_existing_artefact "developer/JTBD-300-test.proposed.md"
66
- art="$DIR/docs/jtbd/developer/JTBD-300-test.proposed.md"
67
- : > "$(expected_marker "$art")"
68
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# JTBD-300\n'
69
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
70
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
71
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
72
- [ "$status" -eq 0 ]
73
- [[ "$output" != *"BLOCKED"* ]]
74
- }
75
-
76
- @test "Edit to a persona introducing the marker with evidence is allowed" {
77
- mk_existing_artefact "developer/persona.md"
78
- art="$DIR/docs/jtbd/developer/persona.md"
79
- : > "$(expected_marker "$art")"
80
- old='date: 2026-06-02'
81
- new=$'date: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02'
82
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg o "$old" --arg n "$new" \
83
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}')
84
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
85
- [ "$status" -eq 0 ]
86
- [[ "$output" != *"BLOCKED"* ]]
87
- }
88
-
89
- # ── Negative paths ───────────────────────────────────────────────────────
90
-
91
- @test "Write introducing 'human-oversight: confirmed' to a JTBD WITHOUT marker is denied" {
92
- mk_existing_artefact "developer/JTBD-310-orphan.proposed.md"
93
- art="$DIR/docs/jtbd/developer/JTBD-310-orphan.proposed.md"
94
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# JTBD-310\n'
95
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
96
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
97
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
98
- [ "$status" -eq 0 ]
99
- [[ "$output" == *'"permissionDecision": "deny"'* ]]
100
- [[ "$output" == *"BLOCKED"* ]]
101
- [[ "$output" == *"wr-jtbd-mark-oversight-confirmed"* ]]
102
- [[ "$output" == *"/wr-jtbd:confirm-jobs-and-personas"* ]]
103
- }
104
-
105
- @test "Edit introducing the marker to persona WITHOUT marker is denied" {
106
- mk_existing_artefact "tech-lead/persona.md"
107
- art="$DIR/docs/jtbd/tech-lead/persona.md"
108
- old='date: 2026-06-02'
109
- new=$'date: 2026-06-02\nhuman-oversight: confirmed'
110
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg o "$old" --arg n "$new" \
111
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}')
112
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
113
- [[ "$output" == *'"permissionDecision": "deny"'* ]]
114
- }
115
-
116
- # ── AFK-unconfirmed-write ────────────────────────────────────────────────
117
-
118
- @test "Write introducing 'human-oversight: unconfirmed' is allowed without marker (AFK path)" {
119
- mk_existing_artefact "developer/JTBD-320-afk.proposed.md"
120
- art="$DIR/docs/jtbd/developer/JTBD-320-afk.proposed.md"
121
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: unconfirmed\n---\n\n# JTBD-320-afk\n'
122
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
123
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
124
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
125
- [ "$status" -eq 0 ]
126
- [[ "$output" != *"BLOCKED"* ]]
127
- }
128
-
129
- # ── Scope / non-fire paths ───────────────────────────────────────────────
130
-
131
- @test "Edit to a non-JTBD path exits 0 silently even with the marker line" {
132
- mkdir -p "$DIR/src"
133
- echo "// stub" > "$DIR/src/foo.ts"
134
- json=$(jq -nc --arg p "$DIR/src/foo.ts" --arg s "$SID" \
135
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"// stub",new_string:"// human-oversight: confirmed"}}')
136
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
137
- [ "$status" -eq 0 ]
138
- [ -z "$output" ]
139
- }
140
-
141
- @test "Edit to docs/jtbd/README.md exits 0 silently" {
142
- echo "# index" > "$DIR/docs/jtbd/README.md"
143
- art="$DIR/docs/jtbd/README.md"
144
- new=$'# index\nhuman-oversight: confirmed'
145
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg n "$new" \
146
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"# index",new_string:$n}}')
147
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
148
- [ "$status" -eq 0 ]
149
- [ -z "$output" ]
150
- }
151
-
152
- @test "Edit whose new content lacks the marker exits 0 silently" {
153
- mk_existing_artefact "developer/JTBD-330-rename.proposed.md"
154
- art="$DIR/docs/jtbd/developer/JTBD-330-rename.proposed.md"
155
- json=$(jq -nc --arg p "$art" --arg s "$SID" \
156
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"# JTBD-330-rename",new_string:"# JTBD-330 renamed"}}')
157
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
158
- [ "$status" -eq 0 ]
159
- [ -z "$output" ]
160
- }
161
-
162
- # ── End-to-end ───────────────────────────────────────────────────────────
163
-
164
- @test "mark-oversight-confirmed.sh writes a marker that satisfies the JTBD hook" {
165
- mk_existing_artefact "developer/JTBD-340-e2e.proposed.md"
166
- art="$DIR/docs/jtbd/developer/JTBD-340-e2e.proposed.md"
167
- : > "$MARK_DIR/jtbd-announced-$SID"
168
- bash "$MARK_SCRIPT" "$art"
169
- [ -f "$(expected_marker "$art")" ]
170
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# JTBD-340-e2e\n'
171
- json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
172
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
173
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
174
- [ "$status" -eq 0 ]
175
- [[ "$output" != *"BLOCKED"* ]]
176
- }
177
-
178
- # P380: on macOS SESSION_MARKER_DIR defaults to /tmp, a symlink to /private/tmp.
179
- # `find <symlink> -maxdepth 1` in default (-P) mode refuses to descend the
180
- # start-point symlink, so candidate enumeration returns empty and the script
181
- # writes ZERO markers (silent cold-path exit 0). The `-L` flag follows it. This
182
- # test points SESSION_MARKER_DIR at a SYMLINK to the marker dir (reproducing the
183
- # macOS /tmp shape on any platform); RED without `-L`, GREEN with it.
184
- @test "mark-oversight-confirmed.sh enumerates candidates when SESSION_MARKER_DIR is a symlink (P380)" {
185
- mk_existing_artefact "developer/JTBD-341-symlink.proposed.md"
186
- art="$DIR/docs/jtbd/developer/JTBD-341-symlink.proposed.md"
187
- : > "$MARK_DIR/jtbd-announced-$SID"
188
- link_dir="${MARK_DIR}.link"
189
- ln -s "$MARK_DIR" "$link_dir"
190
- SESSION_MARKER_DIR="$link_dir" bash "$MARK_SCRIPT" "$art"
191
- rm -f "$link_dir"
192
- [ -f "$(expected_marker "$art")" ]
193
- }
194
-
195
- # P368: genuine cold path — no CLAUDE_SESSION_ID and no announce markers at all.
196
- # The shim correctly writes zero markers but must NOT do so SILENTLY: a loud
197
- # stderr diagnostic explains why the subsequent discipline-hook deny happens,
198
- # breaking the confusing "you didn't run the shim" loop. Exit stays 0 (contract:
199
- # do not crash SKILL flows before any hook has fired this session).
200
- @test "mark-oversight-confirmed.sh emits a loud stderr diagnostic on the no-candidate cold path (P368)" {
201
- mk_existing_artefact "developer/JTBD-342-coldpath.proposed.md"
202
- art="$DIR/docs/jtbd/developer/JTBD-342-coldpath.proposed.md"
203
- run env -u CLAUDE_SESSION_ID bash "$MARK_SCRIPT" "$art"
204
- [ "$status" -eq 0 ] # contract: cold path still exits 0
205
- [ ! -f "$(expected_marker "$art")" ] # no marker written
206
- [[ "$output" == *"no candidate session id"* ]] # loud, not silent
207
- [[ "$output" == *"P368"* ]]
208
- }
209
-
210
- @test "tool_name=Bash exits 0 silently regardless of file path" {
211
- json=$(jq -nc --arg s "$SID" \
212
- '{tool_name:"Bash",session_id:$s,tool_input:{command:"echo human-oversight: confirmed"}}')
213
- run bash -c "echo '$json' | bash '$HOOK'"
214
- [ "$status" -eq 0 ]
215
- [ -z "$output" ]
216
- }
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # ADR-068: jtbd-oversight-nudge.sh (SessionStart) emits a one-line nudge when
4
- # jobs/personas lack the human-oversight marker, is silent when none do, and
5
- # self-suppresses under the shared AFK guard (WR_SUPPRESS_OVERSIGHT_NUDGE=1).
6
- # Behavioural — exercises the hook against fixture docs/jtbd/ trees.
7
-
8
- setup() {
9
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
10
- HOOK="$REPO_ROOT/packages/jtbd/hooks/jtbd-oversight-nudge.sh"
11
- PLUGIN_ROOT="$REPO_ROOT/packages/jtbd"
12
- DIR="$(mktemp -d)"
13
- mkdir -p "$DIR/docs/jtbd/solo-developer"
14
- # Hermeticity (P391): the AFK work-problems orchestrator exports
15
- # WR_SUPPRESS_OVERSIGHT_NUDGE=1, which the hook self-suppresses on. Unset it so
16
- # count-emitting tests assert real behaviour; guard-specific tests set it
17
- # per-invocation via `run env WR_SUPPRESS_OVERSIGHT_NUDGE=...`.
18
- unset WR_SUPPRESS_OVERSIGHT_NUDGE
19
- }
20
-
21
- teardown() { rm -rf "$DIR"; }
22
-
23
- mk_unmarked() {
24
- mkdir -p "$(dirname "$DIR/docs/jtbd/$1")"
25
- { echo "---"; echo "status: proposed"; echo "date-created: 2026-05-25"; echo "---"; echo "# $1"; } \
26
- > "$DIR/docs/jtbd/$1"
27
- }
28
- mk_marked() {
29
- mkdir -p "$(dirname "$DIR/docs/jtbd/$1")"
30
- { echo "---"; echo "status: proposed"; echo "human-oversight: confirmed"; echo "---"; echo "# $1"; } \
31
- > "$DIR/docs/jtbd/$1"
32
- }
33
-
34
- @test "emits a count line when there are unoversighted jobs/personas" {
35
- mk_unmarked "solo-developer/persona.md"
36
- mk_unmarked "solo-developer/JTBD-001-foo.proposed.md"
37
- run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
38
- [ "$status" -eq 0 ]
39
- [[ "$output" == *"2 jobs/personas lack human oversight"* ]]
40
- [[ "$output" == *"/wr-jtbd:confirm-jobs-and-personas"* ]]
41
- }
42
-
43
- @test "uses singular wording for exactly one unoversighted artifact" {
44
- mk_unmarked "solo-developer/persona.md"
45
- run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
46
- [[ "$output" == *"1 job/persona lacks human oversight"* ]]
47
- }
48
-
49
- @test "silent when every job/persona is confirmed" {
50
- mk_marked "solo-developer/persona.md"
51
- run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
52
- [ "$status" -eq 0 ]
53
- [ -z "$output" ]
54
- }
55
-
56
- @test "shared AFK guard suppresses the nudge entirely" {
57
- mk_unmarked "solo-developer/persona.md"
58
- run env WR_SUPPRESS_OVERSIGHT_NUDGE=1 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
59
- [ "$status" -eq 0 ]
60
- [ -z "$output" ]
61
- }
62
-
63
- @test "guard value other than 1 does not suppress" {
64
- mk_unmarked "solo-developer/persona.md"
65
- mk_unmarked "solo-developer/JTBD-001-foo.proposed.md"
66
- run env WR_SUPPRESS_OVERSIGHT_NUDGE=0 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
67
- [[ "$output" == *"lack human oversight"* ]]
68
- }
69
-
70
- @test "silent when project has no docs/jtbd dir" {
71
- run env CLAUDE_PROJECT_DIR="$DIR/empty" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
72
- [ "$status" -eq 0 ]
73
- [ -z "$output" ]
74
- }
@@ -1,60 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P004: jtbd-enforce-edit.sh project-root check.
4
-
5
- setup() {
6
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
7
- HOOK="$SCRIPT_DIR/jtbd-enforce-edit.sh"
8
- }
9
-
10
- run_hook_with_file() {
11
- local file_path="$1"
12
- local json="{\"tool_input\":{\"file_path\":\"${file_path}\"},\"session_id\":\"test-$$\"}"
13
- echo "$json" | bash "$HOOK"
14
- }
15
-
16
- @test "jtbd project-root: absolute path outside project exits 0" {
17
- run run_hook_with_file "/Users/other/somewhere/file.json"
18
- [ "$status" -eq 0 ]
19
- [[ "$output" != *"BLOCKED"* ]]
20
- }
21
-
22
- @test "jtbd project-root: home-dir config path exits 0" {
23
- run run_hook_with_file "/Users/somebody/.claude/channels/discord/access.json"
24
- [ "$status" -eq 0 ]
25
- [[ "$output" != *"BLOCKED"* ]]
26
- }
27
-
28
- # P191: the gate must resolve docs/jtbd from the project root
29
- # (CLAUDE_PROJECT_DIR), NOT the hook's actual runtime CWD. Claude Code can
30
- # launch the hook with a working directory that differs from the session/
31
- # project dir; a relative `[ -d "docs/jtbd" ]` then false-negatives and the
32
- # fail-closed "no JTBD documentation" branch blocks legitimate edits even
33
- # though docs/jtbd is present.
34
- @test "jtbd project-root: detects docs/jtbd via CLAUDE_PROJECT_DIR when hook CWD differs (P191)" {
35
- local proj other json
36
- proj="$(mktemp -d)"
37
- other="$(mktemp -d)" # a CWD that does NOT contain docs/jtbd
38
- mkdir -p "$proj/docs/jtbd"
39
- echo "# job" > "$proj/docs/jtbd/JTBD-001-x.md"
40
- json="{\"tool_input\":{\"file_path\":\"${proj}/packages/x/foo.sh\"},\"session_id\":\"test-$$\"}"
41
- # Fire the hook from `other` (wrong CWD) but with CLAUDE_PROJECT_DIR set to
42
- # the real project. Pre-fix this emitted "no JTBD documentation exists";
43
- # post-fix the gate is ACTIVE and denies for the missing review marker.
44
- run env CLAUDE_PROJECT_DIR="$proj" bash -c "cd '$other' && printf '%s' '$json' | bash '$HOOK'"
45
- rm -rf "$proj" "$other"
46
- [[ "$output" != *"no JTBD documentation exists"* ]]
47
- [[ "$output" == *"without JTBD review"* ]]
48
- }
49
-
50
- # P191 regression guard: when docs/jtbd genuinely does not exist under the
51
- # project root, the fail-closed "no JTBD documentation" deny is preserved
52
- # (the fix narrows the false-negative; it must not silence true-absence).
53
- @test "jtbd project-root: genuinely-absent docs/jtbd still denies (fail-closed preserved, P191)" {
54
- local proj json
55
- proj="$(mktemp -d)" # no docs/jtbd created
56
- json="{\"tool_input\":{\"file_path\":\"${proj}/foo.sh\"},\"session_id\":\"test-$$\"}"
57
- run env CLAUDE_PROJECT_DIR="$proj" bash -c "printf '%s' '$json' | bash '$HOOK'"
58
- rm -rf "$proj"
59
- [[ "$output" == *"no JTBD documentation exists"* ]]
60
- }