@windyroad/jtbd 0.4.0-preview.62 → 0.4.0-preview.68
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,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bats
|
|
2
2
|
|
|
3
|
-
# Tests for jtbd-enforce-edit.sh — verifies broadened scope with exclusions
|
|
4
|
-
#
|
|
3
|
+
# Tests for jtbd-enforce-edit.sh — verifies broadened scope with exclusions.
|
|
4
|
+
# All tests are functional: they execute the hook with mock JSON input
|
|
5
|
+
# and assert on exit status and BLOCKED output. Source-grep assertions
|
|
6
|
+
# were removed (P011) — they over-specified the implementation and
|
|
7
|
+
# false-positived on legitimate refactors.
|
|
5
8
|
|
|
6
9
|
setup() {
|
|
7
10
|
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
@@ -16,12 +19,6 @@ teardown() {
|
|
|
16
19
|
rm -rf "$TEST_DIR"
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
# Helper: check if a pattern is in the exclusion list by grepping the hook
|
|
20
|
-
file_is_excluded() {
|
|
21
|
-
local pattern="$1"
|
|
22
|
-
grep -q "$pattern" "$HOOK"
|
|
23
|
-
}
|
|
24
|
-
|
|
25
22
|
# Helper: run the hook with a mock JSON input for a given file path
|
|
26
23
|
run_hook_with_file() {
|
|
27
24
|
local file_path="$1"
|
|
@@ -29,49 +26,75 @@ run_hook_with_file() {
|
|
|
29
26
|
echo "$json" | bash "$HOOK"
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
#
|
|
29
|
+
# Helper: assert the hook exits 0 and does NOT emit BLOCKED for the given path
|
|
30
|
+
assert_path_allowed() {
|
|
31
|
+
local file_path="$1"
|
|
32
|
+
run run_hook_with_file "$file_path"
|
|
33
|
+
[ "$status" -eq 0 ]
|
|
34
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Helper: assert the hook BLOCKS the given path
|
|
38
|
+
assert_path_blocked() {
|
|
39
|
+
local file_path="$1"
|
|
40
|
+
run run_hook_with_file "$file_path"
|
|
41
|
+
[ "$status" -eq 0 ]
|
|
42
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# --- Exclusion tests (functional) ---
|
|
46
|
+
|
|
47
|
+
# Claude Code passes absolute file paths in tool_input.file_path, so tests
|
|
48
|
+
# use $PWD-prefixed paths to match the real shape (after the P004 root check).
|
|
33
49
|
|
|
34
50
|
@test "enforce: excludes CSS files" {
|
|
35
|
-
|
|
51
|
+
assert_path_allowed "$PWD/src/styles.css"
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
@test "enforce: excludes image files" {
|
|
39
|
-
|
|
55
|
+
assert_path_allowed "$PWD/public/logo.png"
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
@test "enforce: excludes font files" {
|
|
43
|
-
|
|
59
|
+
assert_path_allowed "$PWD/public/fonts/regular.woff"
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
@test "enforce: excludes lockfiles" {
|
|
47
|
-
|
|
63
|
+
assert_path_allowed "$PWD/package-lock.json"
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
@test "enforce: excludes changeset files" {
|
|
51
|
-
|
|
67
|
+
assert_path_allowed "$PWD/.changeset/some-change.md"
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
@test "enforce: excludes memory files" {
|
|
55
|
-
|
|
71
|
+
assert_path_allowed "$PWD/MEMORY.md"
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
@test "enforce: excludes plan files" {
|
|
59
|
-
|
|
75
|
+
assert_path_allowed "$PWD/.claude/plans/2026-01-01-plan.md"
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
@test "enforce: excludes risk reports" {
|
|
63
|
-
|
|
79
|
+
assert_path_allowed "$PWD/.risk-reports/2026-01-01.md"
|
|
64
80
|
}
|
|
65
81
|
|
|
66
82
|
@test "enforce: excludes RISK-POLICY.md" {
|
|
67
|
-
|
|
83
|
+
assert_path_allowed "$PWD/RISK-POLICY.md"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@test "enforce: does NOT exempt by UI-only extension (ADR-007/008)" {
|
|
87
|
+
# ADR-007/008 broadened scope: the hook must gate UI files like any
|
|
88
|
+
# other source file, not silently allow them.
|
|
89
|
+
assert_path_blocked "$PWD/src/Component.tsx"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@test "enforce: does NOT exempt .html files (ADR-007/008)" {
|
|
93
|
+
assert_path_blocked "$PWD/public/index.html"
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
@test "enforce: does NOT
|
|
71
|
-
|
|
72
|
-
# by UI file extension. NOTE: `*) exit 0 ;;` is a legitimate pattern
|
|
73
|
-
# for the project-root check (P004) — see jtbd-project-root.bats.
|
|
74
|
-
! grep -qE '\.html\||tsx\|jsx\|html\|vue\|svelte' "$HOOK"
|
|
96
|
+
@test "enforce: does NOT exempt .vue files (ADR-007/008)" {
|
|
97
|
+
assert_path_blocked "$PWD/src/App.vue"
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
# --- Functional tests (execute hook with mock JSON) ---
|
|
@@ -1,19 +1,163 @@
|
|
|
1
1
|
#!/usr/bin/env bats
|
|
2
2
|
|
|
3
|
-
# Tests for
|
|
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.
|
|
4
12
|
|
|
5
|
-
|
|
13
|
+
setup() {
|
|
6
14
|
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
7
|
-
|
|
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"
|
|
8
25
|
}
|
|
9
26
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
teardown() {
|
|
28
|
+
cd "$ORIG_DIR"
|
|
29
|
+
rm -rf "$TEST_DIR"
|
|
30
|
+
rm -f "$MARKER" "$PLAN_MARKER" "$HASH_FILE" "$VERDICT_FILE"
|
|
13
31
|
}
|
|
14
32
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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/JOBS_TO_BE_DONE.md fallback (legacy) ---
|
|
74
|
+
|
|
75
|
+
@test "uses docs/JOBS_TO_BE_DONE.md when docs/jtbd does not exist" {
|
|
76
|
+
mkdir -p docs
|
|
77
|
+
echo "# Jobs" > docs/JOBS_TO_BE_DONE.md
|
|
78
|
+
echo "PASS" > "$VERDICT_FILE"
|
|
79
|
+
|
|
80
|
+
run_hook "wr-jtbd:agent"
|
|
81
|
+
|
|
82
|
+
[ -f "$MARKER" ]
|
|
83
|
+
[ -f "$HASH_FILE" ]
|
|
84
|
+
[ "$(cat "$HASH_FILE")" != "missing" ]
|
|
85
|
+
|
|
86
|
+
# Hash should match the file's content hash. _hashcmd in
|
|
87
|
+
# gate-helpers.sh prefers md5sum, falls back to md5 -r, then shasum.
|
|
88
|
+
EXPECTED=$(cat docs/JOBS_TO_BE_DONE.md \
|
|
89
|
+
| (md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null) \
|
|
90
|
+
| cut -d' ' -f1)
|
|
91
|
+
[ "$(cat "$HASH_FILE")" = "$EXPECTED" ]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@test "prefers docs/jtbd over docs/JOBS_TO_BE_DONE.md when both exist" {
|
|
95
|
+
mkdir -p docs/jtbd
|
|
96
|
+
echo "# job" > docs/jtbd/job.md
|
|
97
|
+
echo "# legacy jobs" > docs/JOBS_TO_BE_DONE.md
|
|
98
|
+
echo "PASS" > "$VERDICT_FILE"
|
|
99
|
+
|
|
100
|
+
run_hook "wr-jtbd:agent"
|
|
101
|
+
|
|
102
|
+
# The directory-derived hash must NOT equal the standalone-file hash.
|
|
103
|
+
DIR_HASH="$(cat "$HASH_FILE")"
|
|
104
|
+
FILE_HASH=$(cat docs/JOBS_TO_BE_DONE.md \
|
|
105
|
+
| (md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null) \
|
|
106
|
+
| cut -d' ' -f1)
|
|
107
|
+
[ "$DIR_HASH" != "$FILE_HASH" ]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# --- Verdict handling ---
|
|
111
|
+
|
|
112
|
+
@test "FAIL verdict does NOT create review marker (but plan marker still set)" {
|
|
113
|
+
mkdir -p docs/jtbd
|
|
114
|
+
echo "# job" > docs/jtbd/job.md
|
|
115
|
+
echo "FAIL" > "$VERDICT_FILE"
|
|
116
|
+
|
|
117
|
+
run_hook "wr-jtbd:agent"
|
|
118
|
+
|
|
119
|
+
[ ! -f "$MARKER" ]
|
|
120
|
+
[ -f "$PLAN_MARKER" ]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@test "missing verdict file allows marker (backward compat)" {
|
|
124
|
+
mkdir -p docs/jtbd
|
|
125
|
+
echo "# job" > docs/jtbd/job.md
|
|
126
|
+
|
|
127
|
+
run_hook "wr-jtbd:agent"
|
|
128
|
+
|
|
129
|
+
[ -f "$MARKER" ]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@test "verdict file is consumed (removed) after hook runs" {
|
|
133
|
+
mkdir -p docs/jtbd
|
|
134
|
+
echo "# job" > docs/jtbd/job.md
|
|
135
|
+
echo "PASS" > "$VERDICT_FILE"
|
|
136
|
+
|
|
137
|
+
run_hook "wr-jtbd:agent"
|
|
138
|
+
|
|
139
|
+
[ ! -f "$VERDICT_FILE" ]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# --- Subagent routing ---
|
|
143
|
+
|
|
144
|
+
@test "ignores unrelated subagent (no marker created)" {
|
|
145
|
+
mkdir -p docs/jtbd
|
|
146
|
+
echo "# job" > docs/jtbd/job.md
|
|
147
|
+
echo "PASS" > "$VERDICT_FILE"
|
|
148
|
+
|
|
149
|
+
run_hook "wr-architect:agent"
|
|
150
|
+
|
|
151
|
+
[ ! -f "$MARKER" ]
|
|
152
|
+
[ ! -f "$PLAN_MARKER" ]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@test "matches legacy jtbd-lead subagent name" {
|
|
156
|
+
mkdir -p docs/jtbd
|
|
157
|
+
echo "# job" > docs/jtbd/job.md
|
|
158
|
+
echo "PASS" > "$VERDICT_FILE"
|
|
159
|
+
|
|
160
|
+
run_hook "jtbd-lead"
|
|
161
|
+
|
|
162
|
+
[ -f "$MARKER" ]
|
|
19
163
|
}
|