@windyroad/retrospective 0.27.1 → 0.27.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/hooks/test/retrospective-deferral-census.bats +0 -112
- package/hooks/test/retrospective-readme-jtbd-currency.bats +0 -312
- package/scripts/test/check-ask-hygiene.bats +0 -262
- package/scripts/test/check-autocreate-rfc-scope.bats +0 -250
- package/scripts/test/check-briefing-budgets.bats +0 -349
- package/scripts/test/check-internal-id-leaks.bats +0 -286
- package/scripts/test/check-plugin-maturity-drift.bats +0 -269
- package/scripts/test/check-readme-jtbd-currency.bats +0 -165
- package/scripts/test/check-skill-md-budgets.bats +0 -336
- package/scripts/test/check-tarball-shipped-shims.bats +0 -284
- package/scripts/test/check-tickets-deferred-cause.bats +0 -330
- package/scripts/test/list-plugin-attribution.bats +0 -149
- package/scripts/test/measure-context-budget.bats +0 -298
- package/skills/analyze-context/test/analyze-context-skill-contract.bats +0 -142
- package/skills/migrate-briefing/test/migrate-briefing-contract.bats +0 -105
- package/skills/migrate-briefing/test/migrate-briefing-fixture.bats +0 -242
- package/skills/run-retro/test/run-retro-anti-pattern-clause.bats +0 -87
- package/skills/run-retro/test/run-retro-codification-candidates.bats +0 -168
- package/skills/run-retro/test/run-retro-context-usage-step-2c.bats +0 -156
- package/skills/run-retro/test/run-retro-pipeline-instability-scan.bats +0 -122
- package/skills/run-retro/test/run-retro-signal-vs-noise.bats +0 -137
- package/skills/run-retro/test/run-retro-skill-candidates.bats +0 -116
- package/skills/run-retro/test/run-retro-stage-1-fallback-gating.bats +0 -65
- package/skills/run-retro/test/run-retro-step-2d-r6-auto-flag.bats +0 -82
- package/skills/run-retro/test/run-retro-step-3-tier-3-branch-b-evidence-based.bats +0 -189
- package/skills/run-retro/test/run-retro-step-4a-cross-plugin-dispatch.bats +0 -114
- package/skills/run-retro/test/run-retro-step-4a-prior-session-evidence-drain.bats +0 -168
- package/skills/run-retro/test/run-retro-step-4a-recovery-path.bats +0 -84
- package/skills/run-retro/test/run-retro-step-4b-retro-auto-ticket-carveout.bats +0 -115
- package/skills/run-retro/test/run-retro-ticket-first-flow.bats +0 -161
- package/skills/run-retro/test/run-retro-verification-close-housekeeping.bats +0 -132
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windyroad/retrospective",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"description": "Session retrospectives that update briefings and create problem tickets",
|
|
5
5
|
"bin": {
|
|
6
6
|
"windyroad-retrospective": "./bin/install.mjs"
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"scripts/",
|
|
27
27
|
".claude-plugin/",
|
|
28
28
|
"lib/",
|
|
29
|
-
"!skills/*/eval/"
|
|
29
|
+
"!skills/*/eval/",
|
|
30
|
+
"!hooks/test/",
|
|
31
|
+
"!scripts/test/",
|
|
32
|
+
"!skills/*/test/"
|
|
30
33
|
]
|
|
31
34
|
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# ADR-040 / P375: retrospective-deferral-census.sh (SessionStart) emits a one-line
|
|
4
|
-
# census + bounded top-offenders list when deferred-work markers exist that lack a
|
|
5
|
-
# self-firing trigger, is silent at zero, self-suppresses under the AFK guard
|
|
6
|
-
# (WR_SUPPRESS_DEFERRAL_CENSUS=1), respects the ADR-040 Tier-1 ≤2KB budget, scans
|
|
7
|
-
# BOTH docs/ and packages/ (.md only — code-comment false-positive guard), and is
|
|
8
|
-
# fail-open (never aborts session startup). Behavioural — exercises the hook against
|
|
9
|
-
# fixture trees. Marker vocabulary is the single source of truth in
|
|
10
|
-
# lib/deferral-markers.sh (P375 "shared vocabulary").
|
|
11
|
-
|
|
12
|
-
setup() {
|
|
13
|
-
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
14
|
-
HOOK="$REPO_ROOT/packages/retrospective/hooks/retrospective-deferral-census.sh"
|
|
15
|
-
PLUGIN_ROOT="$REPO_ROOT/packages/retrospective"
|
|
16
|
-
DIR="$(mktemp -d)"
|
|
17
|
-
mkdir -p "$DIR/docs/problems" "$DIR/packages/itil/skills/foo"
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
teardown() { rm -rf "$DIR"; }
|
|
21
|
-
|
|
22
|
-
mk() { mkdir -p "$(dirname "$DIR/$1")"; printf '%s\n' "$2" > "$DIR/$1"; }
|
|
23
|
-
|
|
24
|
-
@test "emits a census line with the total marker count" {
|
|
25
|
-
mk "docs/problems/001-x.md" "Priority: 3 (deferred — re-rate at next /wr-itil:review-problems)"
|
|
26
|
-
mk "docs/problems/002-y.md" "**Severity**: (deferred to investigation)"
|
|
27
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
28
|
-
[ "$status" -eq 0 ]
|
|
29
|
-
[[ "$output" == *"deferred-work marker"* ]]
|
|
30
|
-
[[ "$output" == *"[wr-retrospective]"* ]]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@test "lists the worst-offender files" {
|
|
34
|
-
mk "docs/problems/001-x.md" "$(printf '(deferred to A)\n(deferred to B)\n(deferred to C)')"
|
|
35
|
-
mk "docs/problems/002-y.md" "(deferred to D)"
|
|
36
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
37
|
-
[[ "$output" == *"001-x.md"* ]]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@test "scans BOTH docs/ and packages/ .md files" {
|
|
41
|
-
mk "packages/itil/skills/foo/SKILL.md" "section deferred to /wr-itil:manage-rfc accepted transition"
|
|
42
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
43
|
-
[ "$status" -eq 0 ]
|
|
44
|
-
[[ "$output" == *"SKILL.md"* ]]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
@test "silent when there are no deferred-work markers" {
|
|
48
|
-
mk "docs/problems/001-x.md" "Everything here is rated and tracked."
|
|
49
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
50
|
-
[ "$status" -eq 0 ]
|
|
51
|
-
[ -z "$output" ]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@test "fail-open: silent + exit 0 when neither docs/ nor packages/ exists" {
|
|
55
|
-
EMPTY="$(mktemp -d)"
|
|
56
|
-
run env CLAUDE_PROJECT_DIR="$EMPTY" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
57
|
-
rm -rf "$EMPTY"
|
|
58
|
-
[ "$status" -eq 0 ]
|
|
59
|
-
[ -z "$output" ]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@test "fail-open: marker lib unreadable → exit 0, no output, no abort" {
|
|
63
|
-
mk "docs/problems/001-x.md" "(deferred to investigation)"
|
|
64
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="/nonexistent/plugin/root" bash "$HOOK"
|
|
65
|
-
[ "$status" -eq 0 ]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@test "AFK guard WR_SUPPRESS_DEFERRAL_CENSUS=1 suppresses entirely" {
|
|
69
|
-
mk "docs/problems/001-x.md" "(deferred to investigation)"
|
|
70
|
-
run env WR_SUPPRESS_DEFERRAL_CENSUS=1 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
71
|
-
[ "$status" -eq 0 ]
|
|
72
|
-
[ -z "$output" ]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
@test "guard value other than 1 does not suppress" {
|
|
76
|
-
mk "docs/problems/001-x.md" "(deferred to investigation)"
|
|
77
|
-
run env WR_SUPPRESS_DEFERRAL_CENSUS=yes CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
78
|
-
[[ "$output" == *"deferred-work marker"* ]]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@test "output respects the ADR-040 Tier-1 budget (<= 2048 bytes) even with many offenders" {
|
|
82
|
-
for i in $(seq 1 50); do
|
|
83
|
-
mk "docs/problems/$(printf '%03d' "$i")-ticket.md" "$(printf '(deferred to X)\n(deferred to Y)\n(deferred to Z)')"
|
|
84
|
-
done
|
|
85
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
86
|
-
[ "$status" -eq 0 ]
|
|
87
|
-
bytes=$(printf '%s' "$output" | wc -c | tr -d ' ')
|
|
88
|
-
[ "$bytes" -le 2048 ]
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@test "excludes archival records (CHANGELOG.md, *-history.md) — they are not rotting work" {
|
|
92
|
-
mk "packages/itil/CHANGELOG.md" "$(printf '(deferred to A)\n(deferred to B)')"
|
|
93
|
-
mk "docs/problems/README-history.md" "(deferred to investigation)"
|
|
94
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
95
|
-
[ "$status" -eq 0 ]
|
|
96
|
-
[ -z "$output" ]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@test "catches slice/phase-deferral phrasing (P378 fold) — 'lands in Slice N' etc." {
|
|
100
|
-
mk "packages/itil/skills/foo/SKILL.md" "the executor lands in Slice 3 task B5.T9"
|
|
101
|
-
mk "packages/itil/skills/bar/SKILL.md" "deferred to a hook-source slice"
|
|
102
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
103
|
-
[ "$status" -eq 0 ]
|
|
104
|
-
[[ "$output" == *"deferred-work marker"* ]]
|
|
105
|
-
[[ "$output" == *"SKILL.md"* ]]
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
@test "points at a drain path (run-retro / backlog) and cites P375" {
|
|
109
|
-
mk "docs/problems/001-x.md" "(deferred to investigation)"
|
|
110
|
-
run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
|
|
111
|
-
[[ "$output" == *"P375"* ]]
|
|
112
|
-
}
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# ADR-069 (P294): retrospective-readme-jtbd-currency.sh PreToolUse:Bash hook
|
|
4
|
-
# must deny `git commit` invocations whose post-commit working tree exhibits
|
|
5
|
-
# skill-inventory drift (a directory under packages/<plugin>/skills/ is not
|
|
6
|
-
# named in that plugin's README). Hook-level enforcement at commit time is
|
|
7
|
-
# retained per the carried-forward load-bearing-from-the-start-for-drift-class
|
|
8
|
-
# driver: the most-common drift class (contributor adds a skill and forgets
|
|
9
|
-
# the README) ships in a commit that doesn't touch README.md.
|
|
10
|
-
#
|
|
11
|
-
# HISTORY: under superseded ADR-051 (P159) this hook also gated on
|
|
12
|
-
# JTBD-ID-citation drift. ADR-069 superseded that rule — READMEs market the
|
|
13
|
-
# persona's problem derived FROM the JTBD but MUST NOT cite JTBD IDs. The
|
|
14
|
-
# JTBD-ID anchor + its docs/jtbd/ resolution (and the docs/jtbd/ activation
|
|
15
|
-
# guard) are removed; only skill-inventory-drift remains.
|
|
16
|
-
#
|
|
17
|
-
# Detection delegates to
|
|
18
|
-
# `packages/retrospective/scripts/check-readme-jtbd-currency.sh`, invoked
|
|
19
|
-
# against the project's working tree (`./packages/`). The hook reads the
|
|
20
|
-
# detector's `TOTAL packages=<N> drift_instances=<K>` summary and denies
|
|
21
|
-
# when `drift_instances > 0`.
|
|
22
|
-
#
|
|
23
|
-
# Per ADR-005 / ADR-052 / P081 — behavioural; assert on emitted JSON, no
|
|
24
|
-
# source greps. Per ADR-045 Pattern 1 — allow paths emit 0 bytes; deny-band
|
|
25
|
-
# ≤300 bytes. Per ADR-013 Rule 1 — deny redirects to mechanical recovery
|
|
26
|
-
# (here: "name the skill in the README"). Per ADR-013 Rule 6 — fail-open
|
|
27
|
-
# outside a git work tree, on parse errors, or in projects without
|
|
28
|
-
# `./packages/`.
|
|
29
|
-
|
|
30
|
-
setup() {
|
|
31
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
32
|
-
HOOK="$SCRIPT_DIR/retrospective-readme-jtbd-currency.sh"
|
|
33
|
-
ORIG_DIR="$PWD"
|
|
34
|
-
TEST_DIR=$(mktemp -d)
|
|
35
|
-
cd "$TEST_DIR"
|
|
36
|
-
git init --quiet -b main
|
|
37
|
-
git config user.email "test@example.com"
|
|
38
|
-
git config user.name "Test"
|
|
39
|
-
echo "seed" > seed.txt
|
|
40
|
-
git add seed.txt
|
|
41
|
-
git -c commit.gpgsign=false commit --quiet -m "initial"
|
|
42
|
-
unset BYPASS_JTBD_CURRENCY
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
teardown() {
|
|
46
|
-
cd "$ORIG_DIR"
|
|
47
|
-
rm -rf "$TEST_DIR"
|
|
48
|
-
unset BYPASS_JTBD_CURRENCY
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
run_bash_hook() {
|
|
52
|
-
local cmd="$1"
|
|
53
|
-
local json
|
|
54
|
-
json=$(printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$cmd")
|
|
55
|
-
echo "$json" | bash "$HOOK"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# Helper: stub project with skill-inventory drift —
|
|
59
|
-
# packages/stub/skills/orphan/ exists but the README doesn't name "orphan".
|
|
60
|
-
make_drifted_project() {
|
|
61
|
-
mkdir -p packages/stub/skills/orphan
|
|
62
|
-
printf '%s\n' "# @windyroad/stub" "Markets a problem in prose; names no skills." > packages/stub/README.md
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
# Helper: clean stub project — every skill directory is named in the README.
|
|
66
|
-
make_clean_project() {
|
|
67
|
-
mkdir -p packages/stub/skills/do-thing
|
|
68
|
-
printf '%s\n' "# @windyroad/stub" "Run /wr-stub:do-thing to solve the problem." > packages/stub/README.md
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# ── Trap detection: deny when drift detected ───────────────────────────────
|
|
72
|
-
|
|
73
|
-
@test "deny: skill-inventory drift on git commit triggers deny" {
|
|
74
|
-
make_drifted_project
|
|
75
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
76
|
-
[ "$status" -eq 0 ]
|
|
77
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
78
|
-
[[ "$output" == *"P294"* ]]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@test "deny message names the offending plugin slug" {
|
|
82
|
-
make_drifted_project
|
|
83
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
84
|
-
[ "$status" -eq 0 ]
|
|
85
|
-
[[ "$output" == *"stub"* ]]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@test "deny message names the mechanical recovery (name the skill in the README)" {
|
|
89
|
-
make_drifted_project
|
|
90
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
91
|
-
[ "$status" -eq 0 ]
|
|
92
|
-
[[ "$output" == *"name the skill in the README"* ]]
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
@test "deny message does NOT instruct citing a JTBD ID (ADR-069 regression guard)" {
|
|
96
|
-
make_drifted_project
|
|
97
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
98
|
-
[ "$status" -eq 0 ]
|
|
99
|
-
[[ "$output" != *"JTBD-NNN"* ]]
|
|
100
|
-
[[ "$output" != *"wr-jtbd"* ]]
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@test "deny message stays under ADR-045 deny-band (<300 bytes)" {
|
|
104
|
-
make_drifted_project
|
|
105
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
106
|
-
[ "$status" -eq 0 ]
|
|
107
|
-
[ "${#output}" -lt 300 ]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# P173: the deny must NOT advertise the env bypass as an in-flight escape.
|
|
111
|
-
# BYPASS_JTBD_CURRENCY only takes effect when set in Claude Code's process env
|
|
112
|
-
# before the session started; a mid-session Bash export never reaches the hook.
|
|
113
|
-
# The deny clarifies the bypass is pre-session.
|
|
114
|
-
@test "P173 deny message clarifies the env bypass is pre-session (not a mid-session action)" {
|
|
115
|
-
make_drifted_project
|
|
116
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
117
|
-
[ "$status" -eq 0 ]
|
|
118
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
119
|
-
[[ "$output" == *"pre-session"* ]]
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@test "deny: canonical release commit shape (chore: version packages) is subject to the gate" {
|
|
123
|
-
make_drifted_project
|
|
124
|
-
# Not a `git commit` invocation — the bare message must NOT deny.
|
|
125
|
-
run run_bash_hook "chore: version packages"
|
|
126
|
-
[ "$status" -eq 0 ]
|
|
127
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
128
|
-
# The canonical release commit shape IS gated:
|
|
129
|
-
run run_bash_hook "git commit -m 'chore: version packages'"
|
|
130
|
-
[ "$status" -eq 0 ]
|
|
131
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
@test "deny: git commit --amend on drifted tree also triggers deny" {
|
|
135
|
-
make_drifted_project
|
|
136
|
-
run run_bash_hook "git commit --amend --no-edit"
|
|
137
|
-
[ "$status" -eq 0 ]
|
|
138
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
@test "deny: skill drift with NO docs/jtbd present still denies (ADR-069 guard removed)" {
|
|
142
|
-
make_drifted_project
|
|
143
|
-
# deliberately no docs/jtbd/ — under ADR-051 the hook was a no-op here;
|
|
144
|
-
# ADR-069 removed that guard, so inventory drift is now evaluated.
|
|
145
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
146
|
-
[ "$status" -eq 0 ]
|
|
147
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
# ── Allow paths: each non-trap shape must NOT deny ─────────────────────────
|
|
151
|
-
|
|
152
|
-
@test "allow: clean README (all skills named) on git commit allows the commit" {
|
|
153
|
-
make_clean_project
|
|
154
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
155
|
-
[ "$status" -eq 0 ]
|
|
156
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
@test "allow: BYPASS_JTBD_CURRENCY=1 env var allows drifted commit" {
|
|
160
|
-
make_drifted_project
|
|
161
|
-
BYPASS_JTBD_CURRENCY=1 run run_bash_hook "git commit -m 'feat'"
|
|
162
|
-
[ "$status" -eq 0 ]
|
|
163
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
@test "allow: non-Bash tool exits 0 without deny" {
|
|
167
|
-
make_drifted_project
|
|
168
|
-
run bash -c "echo '{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"foo.md\"}}' | bash $HOOK"
|
|
169
|
-
[ "$status" -eq 0 ]
|
|
170
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
@test "allow: Bash command that is NOT git commit (e.g., git status) bypasses detection" {
|
|
174
|
-
make_drifted_project
|
|
175
|
-
run run_bash_hook "git status"
|
|
176
|
-
[ "$status" -eq 0 ]
|
|
177
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
# ── Fail-open contracts (ADR-013 Rule 6) ───────────────────────────────────
|
|
181
|
-
|
|
182
|
-
@test "allow: outside a git work tree exits 0 without deny (fail-open)" {
|
|
183
|
-
cd "$ORIG_DIR"
|
|
184
|
-
TEMP_NONGIT=$(mktemp -d)
|
|
185
|
-
cd "$TEMP_NONGIT"
|
|
186
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
187
|
-
cd "$TEST_DIR"
|
|
188
|
-
rm -rf "$TEMP_NONGIT"
|
|
189
|
-
[ "$status" -eq 0 ]
|
|
190
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
@test "allow: project without packages/ dir exits 0 without deny (fail-open)" {
|
|
194
|
-
# No packages/ — adopter project shape.
|
|
195
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
196
|
-
[ "$status" -eq 0 ]
|
|
197
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
@test "allow: project with packages/ and no docs/jtbd/, clean README, allows (docs/jtbd no longer required)" {
|
|
201
|
-
make_clean_project
|
|
202
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
203
|
-
[ "$status" -eq 0 ]
|
|
204
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
@test "allow: empty JSON exits 0 without deny (fail-open on parse-incomplete)" {
|
|
208
|
-
make_drifted_project
|
|
209
|
-
run bash -c "echo '{}' | bash $HOOK"
|
|
210
|
-
[ "$status" -eq 0 ]
|
|
211
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
@test "allow: malformed JSON exits 0 without deny (fail-open on parse error)" {
|
|
215
|
-
make_drifted_project
|
|
216
|
-
run bash -c "echo 'not-json' | bash $HOOK"
|
|
217
|
-
[ "$status" -eq 0 ]
|
|
218
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
# ── Allow path silence (ADR-045 Pattern 1) ─────────────────────────────────
|
|
222
|
-
|
|
223
|
-
@test "allow path on clean tree emits 0 bytes (ADR-045 Pattern 1 silent-on-pass)" {
|
|
224
|
-
make_clean_project
|
|
225
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
226
|
-
[ "$status" -eq 0 ]
|
|
227
|
-
[ "${#output}" -eq 0 ]
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
@test "allow path on non-Bash tool emits 0 bytes (silent-on-pass)" {
|
|
231
|
-
make_drifted_project
|
|
232
|
-
run bash -c "echo '{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"foo.md\"}}' | bash $HOOK"
|
|
233
|
-
[ "$status" -eq 0 ]
|
|
234
|
-
[ "${#output}" -eq 0 ]
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
@test "allow path on non-commit Bash emits 0 bytes (silent-on-pass)" {
|
|
238
|
-
make_drifted_project
|
|
239
|
-
run run_bash_hook "git status"
|
|
240
|
-
[ "$status" -eq 0 ]
|
|
241
|
-
[ "${#output}" -eq 0 ]
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
# ── P275 / P268 leading-executable regression cases ─────────────────────────
|
|
245
|
-
#
|
|
246
|
-
# The hook must fire on ACTUAL `git commit` invocations, NOT on Bash that
|
|
247
|
-
# merely MENTIONS the phrase "git commit" in argument vectors or heredoc
|
|
248
|
-
# bodies. Mirrors P268 regression fixtures in command-detect.bats.
|
|
249
|
-
|
|
250
|
-
@test "P275 allow: grep with literal 'git commit' pattern on drifted project does NOT deny" {
|
|
251
|
-
make_drifted_project
|
|
252
|
-
run run_bash_hook "grep -r 'git commit' ."
|
|
253
|
-
[ "$status" -eq 0 ]
|
|
254
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
255
|
-
[ "${#output}" -eq 0 ]
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
@test "P275 allow: sed pattern containing 'git commit' on drifted project does NOT deny" {
|
|
259
|
-
make_drifted_project
|
|
260
|
-
run run_bash_hook "sed -n 's/git commit/X/p' packages/stub/README.md"
|
|
261
|
-
[ "$status" -eq 0 ]
|
|
262
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
263
|
-
[ "${#output}" -eq 0 ]
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
@test "P275 allow: echo with literal 'git commit' string on drifted project does NOT deny" {
|
|
267
|
-
make_drifted_project
|
|
268
|
-
run run_bash_hook "echo 'run git commit -m foo'"
|
|
269
|
-
[ "$status" -eq 0 ]
|
|
270
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
271
|
-
[ "${#output}" -eq 0 ]
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
@test "P275 allow: git log --grep with 'git commit' search term on drifted project does NOT deny" {
|
|
275
|
-
make_drifted_project
|
|
276
|
-
run run_bash_hook "git log --grep='git commit'"
|
|
277
|
-
[ "$status" -eq 0 ]
|
|
278
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
279
|
-
[ "${#output}" -eq 0 ]
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
@test "P275 allow: git commit-tree plumbing on drifted project does NOT deny (boundary)" {
|
|
283
|
-
make_drifted_project
|
|
284
|
-
run run_bash_hook "git commit-tree HEAD^{tree} -m 'msg'"
|
|
285
|
-
[ "$status" -eq 0 ]
|
|
286
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
287
|
-
[ "${#output}" -eq 0 ]
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
# ── P275 positive leading-executable cases still deny ──────────────────────
|
|
291
|
-
|
|
292
|
-
@test "P275 deny: env-var-prefixed git commit on drifted project still triggers deny" {
|
|
293
|
-
make_drifted_project
|
|
294
|
-
run run_bash_hook "GIT_AUTHOR_NAME=foo git commit -m 'feat'"
|
|
295
|
-
[ "$status" -eq 0 ]
|
|
296
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
297
|
-
[[ "$output" == *"P294"* ]]
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
@test "P275 deny: cd-prefixed git commit on drifted project still triggers deny" {
|
|
301
|
-
make_drifted_project
|
|
302
|
-
run run_bash_hook "cd . && git commit -m 'feat'"
|
|
303
|
-
[ "$status" -eq 0 ]
|
|
304
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
@test "P275 deny: leading-whitespace git commit on drifted project still triggers deny" {
|
|
308
|
-
make_drifted_project
|
|
309
|
-
run run_bash_hook " git commit -m 'feat'"
|
|
310
|
-
[ "$status" -eq 0 ]
|
|
311
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
312
|
-
}
|