@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,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
|
-
}
|