@windyroad/itil 0.61.1 → 0.61.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 +7 -2
- package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
- package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
- package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
- package/agents/test/hang-off-check.bats +0 -225
- package/hooks/test/block-list.bats +0 -153
- package/hooks/test/command-detect.bats +0 -181
- package/hooks/test/itil-assistant-output-gate.bats +0 -114
- package/hooks/test/itil-assistant-output-review.bats +0 -224
- package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
- package/hooks/test/itil-changeset-discipline.bats +0 -603
- package/hooks/test/itil-claude-space-protection.bats +0 -294
- package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
- package/hooks/test/itil-correction-detect.bats +0 -189
- package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
- package/hooks/test/itil-fictional-defer-detect.bats +0 -292
- package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
- package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
- package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
- package/hooks/test/itil-pending-questions-surface.bats +0 -307
- package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
- package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
- package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
- package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
- package/hooks/test/manage-problem-enforce-create.bats +0 -424
- package/hooks/test/p057-staging-trap-detect.bats +0 -231
- package/hooks/test/pre-publish-intake-gate.bats +0 -144
- package/hooks/test/runtime-sid-marker.bats +0 -90
- package/hooks/test/session-id.bats +0 -370
- package/scripts/migrate-problems-add-type.sh +0 -116
- package/scripts/test/catchup-scan.bats +0 -274
- package/scripts/test/check-afk-accept-eligible.bats +0 -310
- package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
- package/scripts/test/check-fix-rfc-trace.bats +0 -164
- package/scripts/test/check-locale-discipline.bats +0 -291
- package/scripts/test/check-problems-readme-budget.bats +0 -192
- package/scripts/test/check-rfc-has-stories.bats +0 -69
- package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
- package/scripts/test/check-rfc-stories-ratified.bats +0 -82
- package/scripts/test/check-upstream-responses.bats +0 -503
- package/scripts/test/classify-readme-drift.bats +0 -392
- package/scripts/test/derive-first-dispatch.bats +0 -274
- package/scripts/test/derive-release-vehicle.bats +0 -454
- package/scripts/test/detect-unratified-stories-maps.bats +0 -60
- package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
- package/scripts/test/effort-tally.bats +0 -170
- package/scripts/test/evaluate-relevance.bats +0 -773
- package/scripts/test/kind-a-commands.bats +0 -46
- package/scripts/test/mark-create-gate.bats +0 -59
- package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
- package/scripts/test/migrate-problems-add-type.bats +0 -280
- package/scripts/test/migrate-story-status-mirror.bats +0 -167
- package/scripts/test/no-type-regression-guard.bats +0 -74
- package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
- package/scripts/test/plugin-exercise-index.bats +0 -386
- package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
- package/scripts/test/plugin-maturity-populate.bats +0 -631
- package/scripts/test/plugin-maturity-render.bats +0 -469
- package/scripts/test/plugin-validate-ci-gate.bats +0 -208
- package/scripts/test/reconcile-readme.bats +0 -1007
- package/scripts/test/reconcile-rfcs.bats +0 -496
- package/scripts/test/reconcile-stories.bats +0 -173
- package/scripts/test/reconcile-story-maps.bats +0 -74
- package/scripts/test/release-watch-poll-loop.bats +0 -180
- package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
- package/scripts/test/rfc-stories-extension.bats +0 -175
- package/scripts/test/skill-invocations.bats +0 -535
- package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
- package/scripts/test/story-oversight-lib.bats +0 -190
- package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
- package/scripts/test/update-problem-references-section.bats +0 -242
- package/scripts/test/update-problem-rfcs-section.bats +0 -242
- package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
- package/scripts/test/update-rfc-commits-section.bats +0 -77
- package/scripts/test/verify-iter-summary.bats +0 -196
- package/scripts/test/working-the-problem-traversal.bats +0 -116
- package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
- package/skills/capture-problem/test/capture-problem.bats +0 -389
- package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
- package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
- package/skills/close-incident/test/close-incident-contract.bats +0 -146
- package/skills/link-incident/test/link-incident-contract.bats +0 -125
- package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
- package/skills/list-problems/test/list-problems-contract.bats +0 -100
- package/skills/list-stories/test/list-stories-contract.bats +0 -127
- package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
- package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
- package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
- package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident.bats +0 -171
- package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
- package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
- package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
- package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
- package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
- package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
- package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
- package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
- package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
- package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
- package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
- package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
- package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
- package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
- package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
- package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
- package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
- package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
- package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
- package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
- package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
- package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
- package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
- package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
- package/skills/manage-story/test/manage-story-contract.bats +0 -174
- package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
- package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
- package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
- package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
- package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
- package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
- package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
- package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
- package/skills/review-problems/test/review-problems-contract.bats +0 -235
- package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
- package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
- package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
- package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
- package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
- package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
- package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
- package/skills/work-problem/test/work-problem-contract.bats +0 -294
- package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
- package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
- package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
- package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
- package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
- package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
- package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
- package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
- package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
- package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
- package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
- package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
- package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
- package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
- package/skills/work-problems/test/work-problems-preflight.bats +0 -69
- package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
- package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
- package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
- package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
- package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
- package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
- package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
- package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
- package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
- package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
- package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
- package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
- package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
- package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
- package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
- package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
- package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
- package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
- package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
- package/skills/work-problems/test/work-problems-stop-condition-questions.bats +0 -85
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P119: manage-problem-enforce-create.sh PreToolUse hook must block new-file
|
|
4
|
-
# Write to docs/problems/<NNN>-*.<status>.md until /wr-itil:manage-problem
|
|
5
|
-
# Step 2 (duplicate-check) has run for the current session.
|
|
6
|
-
#
|
|
7
|
-
# Marker: /tmp/manage-problem-grep-${SESSION_ID} (set by Step 2 grep
|
|
8
|
-
# completion). Per-session scope so a single skill invocation can write
|
|
9
|
-
# multiple tickets (Step 4b multi-concern split) without re-grep blocking.
|
|
10
|
-
#
|
|
11
|
-
# Per feedback_behavioural_tests.md (P081): behavioural assertions —
|
|
12
|
-
# simulate the hook's payload on stdin and assert on emitted JSON
|
|
13
|
-
# permissionDecision and exit status. No source-grep on hook content.
|
|
14
|
-
#
|
|
15
|
-
# ADR-031 forward-compat: matcher uses docs/problems/ prefix +
|
|
16
|
-
# numeric-prefix basename test, agnostic to current suffix-based
|
|
17
|
-
# layout vs future per-state subdirectory layout.
|
|
18
|
-
|
|
19
|
-
setup() {
|
|
20
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
21
|
-
HOOK="$SCRIPT_DIR/manage-problem-enforce-create.sh"
|
|
22
|
-
ORIG_DIR="$PWD"
|
|
23
|
-
TEST_DIR=$(mktemp -d)
|
|
24
|
-
cd "$TEST_DIR"
|
|
25
|
-
mkdir -p docs/problems docs/rfcs
|
|
26
|
-
SID="mp-create-test-$$-$RANDOM"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
teardown() {
|
|
30
|
-
cd "$ORIG_DIR"
|
|
31
|
-
rm -rf "$TEST_DIR"
|
|
32
|
-
rm -f "/tmp/manage-problem-grep-${SID}"
|
|
33
|
-
rm -f "/tmp/wr-itil-rfc-capture-grep-${SID}"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
set_rfc_marker() {
|
|
37
|
-
: > "/tmp/wr-itil-rfc-capture-grep-${SID}"
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
# Helper: run the hook with mock JSON for a Write tool call to file_path
|
|
41
|
-
run_write_hook() {
|
|
42
|
-
local file_path="$1"
|
|
43
|
-
local sid="$2"
|
|
44
|
-
local json
|
|
45
|
-
json=$(printf '{"tool_name":"Write","tool_input":{"file_path":"%s"},"session_id":"%s"}' "$file_path" "$sid")
|
|
46
|
-
echo "$json" | bash "$HOOK"
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# Helper: run the hook with mock JSON for an Edit tool call to file_path
|
|
50
|
-
run_edit_hook() {
|
|
51
|
-
local file_path="$1"
|
|
52
|
-
local sid="$2"
|
|
53
|
-
local json
|
|
54
|
-
json=$(printf '{"tool_name":"Edit","tool_input":{"file_path":"%s"},"session_id":"%s"}' "$file_path" "$sid")
|
|
55
|
-
echo "$json" | bash "$HOOK"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
set_marker() {
|
|
59
|
-
: > "/tmp/manage-problem-grep-${SID}"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# --- Core deny path: new-file Write without marker ---
|
|
63
|
-
|
|
64
|
-
@test "deny: Write to new docs/problems/999-foo.open.md without marker" {
|
|
65
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
66
|
-
[ "$status" -eq 0 ]
|
|
67
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
68
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
69
|
-
[[ "$output" == *"manage-problem"* ]]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@test "deny message names the skill the agent must invoke" {
|
|
73
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
74
|
-
[ "$status" -eq 0 ]
|
|
75
|
-
[[ "$output" == *"/wr-itil:manage-problem"* ]]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
# --- Allow paths ---
|
|
79
|
-
|
|
80
|
-
@test "allow: Write to new docs/problems/999-foo.open.md WITH marker" {
|
|
81
|
-
set_marker
|
|
82
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
83
|
-
[ "$status" -eq 0 ]
|
|
84
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
85
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@test "allow: marker permits multiple consecutive Writes (multi-concern split, Step 4b)" {
|
|
89
|
-
set_marker
|
|
90
|
-
run run_write_hook "$PWD/docs/problems/998-foo.open.md" "$SID"
|
|
91
|
-
[ "$status" -eq 0 ]
|
|
92
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
93
|
-
run run_write_hook "$PWD/docs/problems/999-bar.open.md" "$SID"
|
|
94
|
-
[ "$status" -eq 0 ]
|
|
95
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@test "allow: Write to docs/problems/README.md regardless of marker" {
|
|
99
|
-
# README is regenerated by Steps 5/6/7 — chicken-and-egg per ticket Out of Scope
|
|
100
|
-
run run_write_hook "$PWD/docs/problems/README.md" "$SID"
|
|
101
|
-
[ "$status" -eq 0 ]
|
|
102
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
@test "allow: Edit (not Write) to existing ticket regardless of marker" {
|
|
106
|
-
echo "stub" > docs/problems/042-existing.open.md
|
|
107
|
-
run run_edit_hook "$PWD/docs/problems/042-existing.open.md" "$SID"
|
|
108
|
-
[ "$status" -eq 0 ]
|
|
109
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
@test "allow: Write to existing ticket file (overwrite) regardless of marker" {
|
|
113
|
-
# Existing file = not a new ticket creation — Edit-flow / status transitions
|
|
114
|
-
# are governed by /wr-itil:transition-problem, not this hook.
|
|
115
|
-
echo "stub" > docs/problems/042-existing.open.md
|
|
116
|
-
run run_write_hook "$PWD/docs/problems/042-existing.open.md" "$SID"
|
|
117
|
-
[ "$status" -eq 0 ]
|
|
118
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
@test "allow: Write to non-docs/problems path regardless of marker" {
|
|
122
|
-
run run_write_hook "$PWD/src/component.tsx" "$SID"
|
|
123
|
-
[ "$status" -eq 0 ]
|
|
124
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@test "allow: Write to docs/problems/ non-numeric basename (e.g. NOTES.md)" {
|
|
128
|
-
# Only docs/problems/<NNN>-*.md ticket-shaped paths are gated. Other
|
|
129
|
-
# docs in this directory (e.g. NOTES.md, archive/index.md) are not
|
|
130
|
-
# tickets and don't need the duplicate-check.
|
|
131
|
-
run run_write_hook "$PWD/docs/problems/NOTES.md" "$SID"
|
|
132
|
-
[ "$status" -eq 0 ]
|
|
133
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
# --- Status-suffix coverage (ADR-022 lifecycle) ---
|
|
137
|
-
|
|
138
|
-
@test "deny: new .known-error.md without marker" {
|
|
139
|
-
run run_write_hook "$PWD/docs/problems/999-foo.known-error.md" "$SID"
|
|
140
|
-
[ "$status" -eq 0 ]
|
|
141
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@test "deny: new .verifying.md without marker" {
|
|
145
|
-
run run_write_hook "$PWD/docs/problems/999-foo.verifying.md" "$SID"
|
|
146
|
-
[ "$status" -eq 0 ]
|
|
147
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
@test "deny: new .parked.md without marker" {
|
|
151
|
-
run run_write_hook "$PWD/docs/problems/999-foo.parked.md" "$SID"
|
|
152
|
-
[ "$status" -eq 0 ]
|
|
153
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
# --- Tool-name and edge cases ---
|
|
157
|
-
|
|
158
|
-
@test "allow: empty session_id (fail-open for parse failure parity with sibling hooks)" {
|
|
159
|
-
# Mirror jtbd-enforce-edit.sh: empty session_id => exit 0 without deny.
|
|
160
|
-
run bash -c "echo '{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$PWD/docs/problems/999-foo.open.md\"}}' | bash $HOOK"
|
|
161
|
-
[ "$status" -eq 0 ]
|
|
162
|
-
# No deny — empty session is the parse-error / fallback shape
|
|
163
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]] || skip "fail-closed parse error is also acceptable; assert in parse-failure test"
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
@test "allow: empty file_path exits 0 without action" {
|
|
167
|
-
run bash -c "echo '{\"tool_name\":\"Write\",\"tool_input\":{},\"session_id\":\"$SID\"}' | bash $HOOK"
|
|
168
|
-
[ "$status" -eq 0 ]
|
|
169
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
# --- ADR-031 forward-compat: per-state subdirectory shape ---
|
|
173
|
-
|
|
174
|
-
@test "deny: hypothetical ADR-031 layout docs/problems/open/999-foo.md without marker" {
|
|
175
|
-
# If/when ADR-031 lands and tickets move to docs/problems/<state>/<NNN>-...md,
|
|
176
|
-
# the hook must still gate them. Matcher should be path-prefix +
|
|
177
|
-
# numeric-prefix basename, not suffix-based.
|
|
178
|
-
mkdir -p docs/problems/open
|
|
179
|
-
run run_write_hook "$PWD/docs/problems/open/999-foo.md" "$SID"
|
|
180
|
-
[ "$status" -eq 0 ]
|
|
181
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
# --- Marker hygiene ---
|
|
185
|
-
|
|
186
|
-
@test "marker file presence is the gate signal — touching marker after first deny unblocks subsequent Write" {
|
|
187
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
188
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
189
|
-
set_marker
|
|
190
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
191
|
-
[ "$status" -eq 0 ]
|
|
192
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
# --- P142 / ADR-050: deny-message simplicity post-supersession ---
|
|
196
|
-
#
|
|
197
|
-
# ADR-048 documented a two-tier recovery procedure for SID-mismatch denials
|
|
198
|
-
# (when the helper picked a stale subprocess SID while the runtime hook saw
|
|
199
|
-
# the orchestrator SID). The hook appended a "Helper succeeded but SID
|
|
200
|
-
# mismatch detected — see manage-problem SKILL.md Step 2 substep 7."
|
|
201
|
-
# pointer when SOME marker existed for SOME SID (the helper-bug signal).
|
|
202
|
-
#
|
|
203
|
-
# P142 / ADR-050 superseded ADR-048 by capturing the runtime stdin SID
|
|
204
|
-
# in a per-machine marker via a new PreToolUse hook
|
|
205
|
-
# (`itil-runtime-sid-marker.sh`). The helper reads the marker as
|
|
206
|
-
# authoritative; SID-mismatch is structurally impossible in routine flow.
|
|
207
|
-
# The conditional RECOVERY_HINT was removed; the deny message stays
|
|
208
|
-
# terse and skill-pointing regardless of marker presence.
|
|
209
|
-
#
|
|
210
|
-
# These tests pin that the deny message is INVARIANT of the
|
|
211
|
-
# /tmp/manage-problem-grep-* marker state (no recovery-hint branching).
|
|
212
|
-
|
|
213
|
-
setup_other_sid_marker() {
|
|
214
|
-
OTHER_SID="other-sid-$$-$RANDOM"
|
|
215
|
-
: > "/tmp/manage-problem-grep-${OTHER_SID}"
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
teardown_other_sid_marker() {
|
|
219
|
-
if [ -n "${OTHER_SID:-}" ]; then
|
|
220
|
-
rm -f "/tmp/manage-problem-grep-${OTHER_SID}"
|
|
221
|
-
fi
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
@test "deny without ANY /tmp/manage-problem-grep-* marker → deny is terse, no recovery prose" {
|
|
225
|
-
rm -f /tmp/manage-problem-grep-*
|
|
226
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
227
|
-
[ "$status" -eq 0 ]
|
|
228
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
229
|
-
[[ "$output" != *"SID mismatch"* ]]
|
|
230
|
-
[[ "$output" != *"Step 2 substep 7"* ]]
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
@test "deny with /tmp/manage-problem-grep-* marker for OTHER SID → deny is terse (post-ADR-050; no recovery hint)" {
|
|
234
|
-
# Pre-ADR-050 contract: an other-SID marker triggered the helper-bug
|
|
235
|
-
# recovery pointer. Post-ADR-050: the runtime-SID marker prevents
|
|
236
|
-
# SID-mismatch in routine flow, so the recovery pointer is removed.
|
|
237
|
-
# The deny message is identical regardless of marker presence — the
|
|
238
|
-
# only signal that matters is "this session has not run Step 2".
|
|
239
|
-
rm -f /tmp/manage-problem-grep-*
|
|
240
|
-
setup_other_sid_marker
|
|
241
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
242
|
-
status=$?
|
|
243
|
-
teardown_other_sid_marker
|
|
244
|
-
[ "$status" -eq 0 ]
|
|
245
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
246
|
-
[[ "$output" != *"SID mismatch"* ]]
|
|
247
|
-
[[ "$output" != *"Step 2 substep 7"* ]]
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
# --- P170 / ADR-060: RFC tier extension ---
|
|
251
|
-
#
|
|
252
|
-
# The hook gate covers both docs/problems/ and docs/rfcs/. Each tier has its
|
|
253
|
-
# own marker (problems: /tmp/manage-problem-grep-${SID};
|
|
254
|
-
# rfcs: /tmp/wr-itil-rfc-capture-grep-${SID}) and its own deny message
|
|
255
|
-
# pointing to the right skill (manage-problem vs capture-rfc).
|
|
256
|
-
|
|
257
|
-
@test "rfcs deny: Write to new docs/rfcs/RFC-001-foo.proposed.md without RFC marker" {
|
|
258
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
259
|
-
[ "$status" -eq 0 ]
|
|
260
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
261
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
@test "rfcs deny message names capture-rfc skill (not manage-problem)" {
|
|
265
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
266
|
-
[ "$status" -eq 0 ]
|
|
267
|
-
[[ "$output" == *"/wr-itil:capture-rfc"* ]]
|
|
268
|
-
[[ "$output" != *"/wr-itil:manage-problem"* ]]
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
@test "rfcs deny message names the I1 trace-to-problem invariant + ADR-060" {
|
|
272
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
273
|
-
[ "$status" -eq 0 ]
|
|
274
|
-
[[ "$output" == *"problem-trace"* ]]
|
|
275
|
-
[[ "$output" == *"ADR-060"* ]]
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
@test "rfcs allow: Write to new docs/rfcs/RFC-001-foo.proposed.md WITH RFC marker" {
|
|
279
|
-
set_rfc_marker
|
|
280
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
281
|
-
[ "$status" -eq 0 ]
|
|
282
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
283
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
@test "rfcs allow: Write to docs/rfcs/README.md regardless of marker (chicken-and-egg)" {
|
|
287
|
-
run run_write_hook "$PWD/docs/rfcs/README.md" "$SID"
|
|
288
|
-
[ "$status" -eq 0 ]
|
|
289
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
@test "rfcs allow: Write to docs/rfcs/ non-RFC basename (e.g. NOTES.md) regardless of marker" {
|
|
293
|
-
run run_write_hook "$PWD/docs/rfcs/NOTES.md" "$SID"
|
|
294
|
-
[ "$status" -eq 0 ]
|
|
295
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
@test "rfcs deny: Write across all RFC lifecycle suffixes without marker (proposed/accepted/in-progress/verifying/closed)" {
|
|
299
|
-
for suffix in proposed accepted in-progress verifying closed; do
|
|
300
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.${suffix}.md" "$SID"
|
|
301
|
-
[ "$status" -eq 0 ]
|
|
302
|
-
[[ "$output" == *"BLOCKED"* ]] || { echo "expected deny for suffix=$suffix"; return 1; }
|
|
303
|
-
done
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
@test "rfcs marker independence: problem marker does NOT unlock RFC writes" {
|
|
307
|
-
# The two markers are siblings, not interchangeable. Setting the
|
|
308
|
-
# problem-tier marker should NOT bypass the RFC-tier gate.
|
|
309
|
-
set_marker
|
|
310
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
311
|
-
[ "$status" -eq 0 ]
|
|
312
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
313
|
-
[[ "$output" == *"capture-rfc"* ]]
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
@test "problems marker independence: RFC marker does NOT unlock problem writes" {
|
|
317
|
-
# Inverse direction — preserves audit-trail per-surface granularity.
|
|
318
|
-
set_rfc_marker
|
|
319
|
-
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
320
|
-
[ "$status" -eq 0 ]
|
|
321
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
322
|
-
[[ "$output" == *"manage-problem"* ]]
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
@test "rfcs allow: existing RFC file (overwrite) regardless of marker" {
|
|
326
|
-
echo "stub" > docs/rfcs/RFC-001-foo.proposed.md
|
|
327
|
-
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
328
|
-
[ "$status" -eq 0 ]
|
|
329
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
@test "rfcs allow: Edit (not Write) regardless of marker" {
|
|
333
|
-
echo "stub" > docs/rfcs/RFC-001-foo.proposed.md
|
|
334
|
-
run run_edit_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
335
|
-
[ "$status" -eq 0 ]
|
|
336
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
@test "rfcs deny: bare numeric basename (no RFC- prefix) does NOT match the rfcs gate" {
|
|
340
|
-
# Defensive: a docs/rfcs/123-foo.proposed.md (no RFC- prefix) is not
|
|
341
|
-
# an RFC by naming convention; it should NOT trigger the gate.
|
|
342
|
-
# (If the user accidentally creates such a file, it's project
|
|
343
|
-
# housekeeping, not a ticket.)
|
|
344
|
-
run run_write_hook "$PWD/docs/rfcs/123-no-prefix.proposed.md" "$SID"
|
|
345
|
-
[ "$status" -eq 0 ]
|
|
346
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
# --- P260: concurrent orchestrator+subprocess create-gate marker race ---
|
|
350
|
-
#
|
|
351
|
-
# /wr-itil:work-problems Step 5 BACKGROUNDS the iter subprocess
|
|
352
|
-
# (`claude -p ... &`) and runs an idle-timeout poll loop in the
|
|
353
|
-
# orchestrator's main turn, so the orchestrator fires PreToolUse hooks
|
|
354
|
-
# CONCURRENTLY with the running subprocess. Both sessions write the same
|
|
355
|
-
# per-machine runtime-sid marker (same project ⇒ same proj_hash),
|
|
356
|
-
# last-writer-wins. When the orchestrator captures a ticket while the
|
|
357
|
-
# subprocess holds the runtime-sid, the single-SID Step-2 marker-write
|
|
358
|
-
# (`sid=$(get_current_session_id) && mark_step2_complete "$sid"`) lands the
|
|
359
|
-
# marker under the SUBPROCESS SID, but the orchestrator's Write fires the
|
|
360
|
-
# PreToolUse:Write hook whose stdin session_id is the ORCHESTRATOR SID ⇒
|
|
361
|
-
# marker mismatch ⇒ deny. This was P260 (the 2026-05-18 P254/P255 foreground
|
|
362
|
-
# captures). ADR-050 Option C (architect-resolved + human-confirmed
|
|
363
|
-
# 2026-05-26) is the fix: the candidate-set marker-write
|
|
364
|
-
# (`get_candidate_session_ids | mark_step2_complete_candidates`) writes the
|
|
365
|
-
# marker under EVERY recent candidate SID, so whichever SID the hook reads,
|
|
366
|
-
# a matching marker exists.
|
|
367
|
-
#
|
|
368
|
-
# These tests are end-to-end: they run the real agent-side marker-write
|
|
369
|
-
# helpers under the concurrent fixture, then fire the real hook with the
|
|
370
|
-
# orchestrator's stdin SID and assert the observable permit/deny outcome.
|
|
371
|
-
|
|
372
|
-
# Build the concurrent fixture in a sandbox marker dir: orchestrator
|
|
373
|
-
# announced first (older mtime), subprocess announced later (newer mtime),
|
|
374
|
-
# and the subprocess clobbered the runtime-sid marker last.
|
|
375
|
-
p260_setup_concurrent_fixture() {
|
|
376
|
-
P260_SANDBOX=$(mktemp -d)
|
|
377
|
-
P260_ORCH_SID="p260-orch-$$-$RANDOM"
|
|
378
|
-
P260_SUB_SID="p260-sub-$$-$RANDOM"
|
|
379
|
-
: > "$P260_SANDBOX/architect-announced-${P260_ORCH_SID}"
|
|
380
|
-
sleep 1
|
|
381
|
-
: > "$P260_SANDBOX/jtbd-announced-${P260_SUB_SID}"
|
|
382
|
-
printf '%s' "$P260_SUB_SID" > "$P260_SANDBOX/itil-runtime-sid.current"
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
p260_teardown_concurrent_fixture() {
|
|
386
|
-
rm -f "/tmp/manage-problem-grep-${P260_ORCH_SID}" \
|
|
387
|
-
"/tmp/manage-problem-grep-${P260_SUB_SID}"
|
|
388
|
-
rm -rf "$P260_SANDBOX"
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
@test "P260: candidate-set marker-write PERMITS the orchestrator Write despite runtime-sid clobbered to the subprocess SID" {
|
|
392
|
-
p260_setup_concurrent_fixture
|
|
393
|
-
# Agent-side Step 2 marker write under the FULL candidate set (Option C).
|
|
394
|
-
SESSION_MARKER_DIR="$P260_SANDBOX" bash -c "
|
|
395
|
-
source '$SCRIPT_DIR/lib/session-id.sh'
|
|
396
|
-
source '$SCRIPT_DIR/lib/create-gate.sh'
|
|
397
|
-
get_candidate_session_ids | mark_step2_complete_candidates
|
|
398
|
-
"
|
|
399
|
-
# The orchestrator's Write carries the orchestrator SID on its stdin.
|
|
400
|
-
run run_write_hook "$PWD/docs/problems/997-concurrent-capture.open.md" "$P260_ORCH_SID"
|
|
401
|
-
p260_teardown_concurrent_fixture
|
|
402
|
-
[ "$status" -eq 0 ]
|
|
403
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
404
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
@test "P260 negative control: single-SID marker-write (pre-Option-C behaviour) DENIES the orchestrator Write under the same race" {
|
|
408
|
-
# Pins WHY Option C is needed: the old single-SID write lands the marker
|
|
409
|
-
# under the subprocess SID (the clobbered runtime value), so the
|
|
410
|
-
# orchestrator's Write — stdin = orchestrator SID — is denied. This is
|
|
411
|
-
# the reproduced P260 failure; the candidate-set write above is what
|
|
412
|
-
# fixes it.
|
|
413
|
-
p260_setup_concurrent_fixture
|
|
414
|
-
SESSION_MARKER_DIR="$P260_SANDBOX" bash -c "
|
|
415
|
-
source '$SCRIPT_DIR/lib/session-id.sh'
|
|
416
|
-
source '$SCRIPT_DIR/lib/create-gate.sh'
|
|
417
|
-
sid=\$(get_current_session_id) && mark_step2_complete \"\$sid\"
|
|
418
|
-
"
|
|
419
|
-
run run_write_hook "$PWD/docs/problems/996-concurrent-capture.open.md" "$P260_ORCH_SID"
|
|
420
|
-
p260_teardown_concurrent_fixture
|
|
421
|
-
[ "$status" -eq 0 ]
|
|
422
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
423
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
424
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P125: p057-staging-trap-detect.sh PreToolUse:Bash hook must detect the
|
|
4
|
-
# rename-then-edit-without-re-stage pattern that drops post-rename edits
|
|
5
|
-
# into the next commit (P057 staging trap).
|
|
6
|
-
#
|
|
7
|
-
# Detection logic (per ticket Fix Strategy):
|
|
8
|
-
# On `git commit` invocations, run `git diff --staged --name-status`
|
|
9
|
-
# and `git diff --name-only`. If a file appears in --staged with an
|
|
10
|
-
# `R<num>` (rename) status AND in working-tree `git diff --name-only`
|
|
11
|
-
# as modified, the trap shape is present — emit a deny with recovery
|
|
12
|
-
# command `git add <new-path>` and the P057 cite.
|
|
13
|
-
#
|
|
14
|
-
# Per ADR-005 (plugin testing strategy) — hook bats live under
|
|
15
|
-
# packages/<plugin>/hooks/test/ and assert behaviour on emitted JSON,
|
|
16
|
-
# not source-content. ADR-037 partitions skill tests separately.
|
|
17
|
-
#
|
|
18
|
-
# Per feedback_behavioural_tests.md (P081) — no source-grep on hook
|
|
19
|
-
# text. Simulate the PreToolUse:Bash payload on stdin and assert on
|
|
20
|
-
# the emitted permissionDecision.
|
|
21
|
-
|
|
22
|
-
setup() {
|
|
23
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
24
|
-
HOOK="$SCRIPT_DIR/p057-staging-trap-detect.sh"
|
|
25
|
-
ORIG_DIR="$PWD"
|
|
26
|
-
TEST_DIR=$(mktemp -d)
|
|
27
|
-
cd "$TEST_DIR"
|
|
28
|
-
git init --quiet -b main
|
|
29
|
-
git config user.email "test@example.com"
|
|
30
|
-
git config user.name "Test"
|
|
31
|
-
echo "original" > foo.md
|
|
32
|
-
git add foo.md
|
|
33
|
-
git -c commit.gpgsign=false commit --quiet -m "initial"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
teardown() {
|
|
37
|
-
cd "$ORIG_DIR"
|
|
38
|
-
rm -rf "$TEST_DIR"
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Helper: simulate the PreToolUse:Bash payload on stdin.
|
|
42
|
-
run_bash_hook() {
|
|
43
|
-
local cmd="$1"
|
|
44
|
-
local json
|
|
45
|
-
json=$(printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$cmd")
|
|
46
|
-
echo "$json" | bash "$HOOK"
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# --- Trap detection: the canonical P057 shape ---
|
|
50
|
-
|
|
51
|
-
@test "deny: rename + post-rename edit without re-stage triggers deny on git commit" {
|
|
52
|
-
git mv foo.md bar.md
|
|
53
|
-
echo "modified content" > bar.md
|
|
54
|
-
run run_bash_hook "git commit -m 'test'"
|
|
55
|
-
[ "$status" -eq 0 ]
|
|
56
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
57
|
-
[[ "$output" == *"P057"* ]]
|
|
58
|
-
[[ "$output" == *"bar.md"* ]]
|
|
59
|
-
[[ "$output" == *"git add bar.md"* ]]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# --- Allow paths: each non-trap shape must NOT deny ---
|
|
63
|
-
|
|
64
|
-
@test "allow: rename + post-rename edit + re-stage allows the commit" {
|
|
65
|
-
git mv foo.md bar.md
|
|
66
|
-
echo "modified content" > bar.md
|
|
67
|
-
git add bar.md
|
|
68
|
-
run run_bash_hook "git commit -m 'test'"
|
|
69
|
-
[ "$status" -eq 0 ]
|
|
70
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@test "allow: pure rename without subsequent edit allows the commit" {
|
|
74
|
-
git mv foo.md bar.md
|
|
75
|
-
run run_bash_hook "git commit -m 'test'"
|
|
76
|
-
[ "$status" -eq 0 ]
|
|
77
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
@test "allow: modify-only batch (no rename) allows the commit" {
|
|
81
|
-
echo "modified" > foo.md
|
|
82
|
-
git add foo.md
|
|
83
|
-
run run_bash_hook "git commit -m 'test'"
|
|
84
|
-
[ "$status" -eq 0 ]
|
|
85
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@test "allow: empty batch (nothing staged, nothing modified) allows the commit" {
|
|
89
|
-
run run_bash_hook "git commit -m 'test'"
|
|
90
|
-
[ "$status" -eq 0 ]
|
|
91
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
# --- Tool-name and command-shape filters ---
|
|
95
|
-
|
|
96
|
-
@test "allow: non-Bash tool exits 0 without deny" {
|
|
97
|
-
run bash -c "echo '{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"foo.md\"}}' | bash $HOOK"
|
|
98
|
-
[ "$status" -eq 0 ]
|
|
99
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@test "allow: Bash command that is NOT git commit (e.g., git status) bypasses detection" {
|
|
103
|
-
# Trap shape IS present in the working tree, but the command isn't
|
|
104
|
-
# `git commit` — the hook only fires on commit invocations.
|
|
105
|
-
git mv foo.md bar.md
|
|
106
|
-
echo "modified" > bar.md
|
|
107
|
-
run run_bash_hook "git status"
|
|
108
|
-
[ "$status" -eq 0 ]
|
|
109
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# --- Parse / fail-open (mirror create-gate.sh's exit-0 on parse failure) ---
|
|
113
|
-
|
|
114
|
-
@test "allow: empty JSON exits 0 without deny (fail-open on parse-incomplete)" {
|
|
115
|
-
run bash -c "echo '{}' | bash $HOOK"
|
|
116
|
-
[ "$status" -eq 0 ]
|
|
117
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
# --- Deny message contract (ADR-038 progressive disclosure budget) ---
|
|
121
|
-
|
|
122
|
-
@test "deny message names the file, recovery command, and P057 cite" {
|
|
123
|
-
git mv foo.md bar.md
|
|
124
|
-
echo "modified" > bar.md
|
|
125
|
-
run run_bash_hook "git commit -m 'test'"
|
|
126
|
-
[ "$status" -eq 0 ]
|
|
127
|
-
[[ "$output" == *"P057"* ]]
|
|
128
|
-
[[ "$output" == *"git add bar.md"* ]]
|
|
129
|
-
[[ "$output" == *"bar.md"* ]]
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
@test "deny message stays under ADR-038 progressive-disclosure budget (<400 bytes)" {
|
|
133
|
-
# Voice-tone draft target is ~245 bytes; allow generous headroom for
|
|
134
|
-
# JSON envelope. Hard cap at 400 keeps the message terse per
|
|
135
|
-
# ADR-038 — fail loudly if the message bloats over time.
|
|
136
|
-
git mv foo.md bar.md
|
|
137
|
-
echo "modified" > bar.md
|
|
138
|
-
run run_bash_hook "git commit -m 'test'"
|
|
139
|
-
[ "$status" -eq 0 ]
|
|
140
|
-
[ "${#output}" -lt 400 ]
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
# --- P273 / P268: leading-executable command detection regression cases ---
|
|
144
|
-
#
|
|
145
|
-
# The hook must fire on ACTUAL `git commit` invocations, NOT on Bash that
|
|
146
|
-
# merely MENTIONS the phrase "git commit" in argument vectors or heredoc
|
|
147
|
-
# bodies. Mirrors the P268 regression fixtures landed in
|
|
148
|
-
# command-detect.bats and the P272 sibling fixtures in
|
|
149
|
-
# itil-changeset-discipline.bats.
|
|
150
|
-
|
|
151
|
-
@test "allow: grep with literal 'git commit' pattern in trap state does NOT deny" {
|
|
152
|
-
# Trap shape IS present in the working tree, but the Bash is a grep
|
|
153
|
-
# invocation whose pattern argument MENTIONS "git commit" — the hook
|
|
154
|
-
# must not fire (leading executable is `grep`, not `git`).
|
|
155
|
-
git mv foo.md bar.md
|
|
156
|
-
echo "modified" > bar.md
|
|
157
|
-
run run_bash_hook "grep -r 'git commit' ."
|
|
158
|
-
[ "$status" -eq 0 ]
|
|
159
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
@test "allow: sed pattern containing 'git commit' in trap state does NOT deny" {
|
|
163
|
-
git mv foo.md bar.md
|
|
164
|
-
echo "modified" > bar.md
|
|
165
|
-
run run_bash_hook "sed -n 's/git commit/X/p' bar.md"
|
|
166
|
-
[ "$status" -eq 0 ]
|
|
167
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
@test "allow: echo with literal 'git commit' string in trap state does NOT deny" {
|
|
171
|
-
git mv foo.md bar.md
|
|
172
|
-
echo "modified" > bar.md
|
|
173
|
-
run run_bash_hook "echo 'run git commit -m foo'"
|
|
174
|
-
[ "$status" -eq 0 ]
|
|
175
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@test "allow: cat heredoc body containing 'git commit' in trap state does NOT deny" {
|
|
179
|
-
git mv foo.md bar.md
|
|
180
|
-
echo "modified" > bar.md
|
|
181
|
-
run run_bash_hook "cat <<EOF
|
|
182
|
-
prose mentioning git commit invocations
|
|
183
|
-
EOF"
|
|
184
|
-
[ "$status" -eq 0 ]
|
|
185
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
@test "allow: git log --grep with 'git commit' search term does NOT deny" {
|
|
189
|
-
git mv foo.md bar.md
|
|
190
|
-
echo "modified" > bar.md
|
|
191
|
-
run run_bash_hook "git log --grep='git commit'"
|
|
192
|
-
[ "$status" -eq 0 ]
|
|
193
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
@test "allow: git commit-tree plumbing in trap state does NOT deny (boundary)" {
|
|
197
|
-
# `git commit-tree` is a different git subcommand — the helper must
|
|
198
|
-
# not match it on the `commit-*` boundary.
|
|
199
|
-
git mv foo.md bar.md
|
|
200
|
-
echo "modified" > bar.md
|
|
201
|
-
run run_bash_hook "git commit-tree HEAD^{tree} -m 'msg'"
|
|
202
|
-
[ "$status" -eq 0 ]
|
|
203
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
# --- P273 / P268: positive leading-executable cases still deny ---
|
|
207
|
-
|
|
208
|
-
@test "deny: env-var-prefixed git commit in trap state still triggers deny" {
|
|
209
|
-
git mv foo.md bar.md
|
|
210
|
-
echo "modified" > bar.md
|
|
211
|
-
run run_bash_hook "GIT_AUTHOR_NAME=foo git commit -m 'test'"
|
|
212
|
-
[ "$status" -eq 0 ]
|
|
213
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
214
|
-
[[ "$output" == *"P057"* ]]
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
@test "deny: cd-prefixed git commit in trap state still triggers deny" {
|
|
218
|
-
git mv foo.md bar.md
|
|
219
|
-
echo "modified" > bar.md
|
|
220
|
-
run run_bash_hook "cd . && git commit -m 'test'"
|
|
221
|
-
[ "$status" -eq 0 ]
|
|
222
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
@test "deny: leading-whitespace git commit in trap state still triggers deny" {
|
|
226
|
-
git mv foo.md bar.md
|
|
227
|
-
echo "modified" > bar.md
|
|
228
|
-
run run_bash_hook " git commit -m 'test'"
|
|
229
|
-
[ "$status" -eq 0 ]
|
|
230
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
231
|
-
}
|