@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,603 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P141: itil-changeset-discipline.sh PreToolUse:Bash hook must deny
|
|
4
|
-
# `git commit` invocations whose staged set includes packages/<plugin>/
|
|
5
|
-
# source files but no .changeset/*.md is staged. Hook-level enforcement
|
|
6
|
-
# replaces unreliable iter-prompt-time changeset reminders (40% miss
|
|
7
|
-
# rate observed in 2026-04-28 AFK loop session).
|
|
8
|
-
#
|
|
9
|
-
# Detection logic (per ticket Fix Strategy):
|
|
10
|
-
# On `git commit` invocations, run `git diff --staged --name-only`.
|
|
11
|
-
# If any path matches packages/<plugin>/<non-allow-listed> AND no
|
|
12
|
-
# .changeset/*.md (excluding README.md / config.json) is staged,
|
|
13
|
-
# emit a deny with recovery directive `bun run changeset` and the
|
|
14
|
-
# P141 cite. Allow when at least one valid changeset is staged, when
|
|
15
|
-
# staged packages/<plugin>/ files are entirely allow-listed (test
|
|
16
|
-
# paths or doc paths), or when BYPASS_CHANGESET_GATE=1 is set.
|
|
17
|
-
#
|
|
18
|
-
# Per ADR-005 (plugin testing strategy) — hook bats live under
|
|
19
|
-
# packages/<plugin>/hooks/test/ and assert behaviour on emitted JSON,
|
|
20
|
-
# not source-content. Per P081 — no source-grep on hook text. Simulate
|
|
21
|
-
# the PreToolUse:Bash payload on stdin and assert on the emitted
|
|
22
|
-
# permissionDecision.
|
|
23
|
-
#
|
|
24
|
-
# Per ADR-045 Pattern 1 (silent-on-pass) — allow paths emit 0 bytes.
|
|
25
|
-
# Per ADR-045 deny-band — deny messages target ~245 bytes; cap at 300.
|
|
26
|
-
|
|
27
|
-
setup() {
|
|
28
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
29
|
-
HOOK="$SCRIPT_DIR/itil-changeset-discipline.sh"
|
|
30
|
-
ORIG_DIR="$PWD"
|
|
31
|
-
TEST_DIR=$(mktemp -d)
|
|
32
|
-
cd "$TEST_DIR"
|
|
33
|
-
git init --quiet -b main
|
|
34
|
-
git config user.email "test@example.com"
|
|
35
|
-
git config user.name "Test"
|
|
36
|
-
mkdir -p packages/itil/skills/foo packages/itil/hooks packages/itil/hooks/test \
|
|
37
|
-
packages/itil/hooks/lib packages/itil/scripts/test packages/itil/docs \
|
|
38
|
-
packages/itil/.claude-plugin .changeset .github/workflows docs
|
|
39
|
-
echo "seed" > seed.txt
|
|
40
|
-
git add seed.txt
|
|
41
|
-
git -c commit.gpgsign=false commit --quiet -m "initial"
|
|
42
|
-
unset BYPASS_CHANGESET_GATE
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
teardown() {
|
|
46
|
-
cd "$ORIG_DIR"
|
|
47
|
-
rm -rf "$TEST_DIR"
|
|
48
|
-
unset BYPASS_CHANGESET_GATE
|
|
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
|
-
# --- Trap detection: the canonical P141 shape ---
|
|
59
|
-
|
|
60
|
-
@test "deny: staged packages/<plugin>/ source without changeset triggers deny on git commit" {
|
|
61
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
62
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
63
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
64
|
-
[ "$status" -eq 0 ]
|
|
65
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
66
|
-
[[ "$output" == *"P141"* ]]
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@test "deny: staged packages/<plugin>/hooks/ shell source without changeset triggers deny" {
|
|
70
|
-
echo "#!/bin/bash" > packages/itil/hooks/new-hook.sh
|
|
71
|
-
git add packages/itil/hooks/new-hook.sh
|
|
72
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
73
|
-
[ "$status" -eq 0 ]
|
|
74
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
75
|
-
[[ "$output" == *"P141"* ]]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@test "deny: staged plugin.json without changeset triggers deny" {
|
|
79
|
-
echo "{}" > packages/itil/.claude-plugin/plugin.json
|
|
80
|
-
git add packages/itil/.claude-plugin/plugin.json
|
|
81
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
82
|
-
[ "$status" -eq 0 ]
|
|
83
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@test "deny message names plugin slug, recovery command, P141 cite" {
|
|
87
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
88
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
89
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
90
|
-
[ "$status" -eq 0 ]
|
|
91
|
-
[[ "$output" == *"itil"* ]]
|
|
92
|
-
[[ "$output" == *"changeset"* ]]
|
|
93
|
-
[[ "$output" == *"P141"* ]]
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@test "deny message stays under ADR-045 deny-band (<300 bytes)" {
|
|
97
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
98
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
99
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
100
|
-
[ "$status" -eq 0 ]
|
|
101
|
-
[ "${#output}" -lt 300 ]
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
# P173: the deny must NOT advertise the env bypass as an in-flight escape.
|
|
105
|
-
# BYPASS_CHANGESET_GATE only takes effect when set in Claude Code's process
|
|
106
|
-
# env before the session started; a mid-session Bash `export`/inline assignment
|
|
107
|
-
# never reaches the hook. The deny clarifies the bypass is pre-session.
|
|
108
|
-
@test "P173 deny message clarifies the env bypass is pre-session (not a mid-session action)" {
|
|
109
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
110
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
111
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
112
|
-
[ "$status" -eq 0 ]
|
|
113
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
114
|
-
[[ "$output" == *"pre-session"* ]]
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
# --- Allow paths: each non-trap shape must NOT deny ---
|
|
118
|
-
|
|
119
|
-
@test "allow: staged packages/<plugin>/ source WITH a staged changeset allows the commit" {
|
|
120
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
121
|
-
echo "---" > .changeset/wr-itil-p141.md
|
|
122
|
-
echo '"@windyroad/itil": patch' >> .changeset/wr-itil-p141.md
|
|
123
|
-
echo "---" >> .changeset/wr-itil-p141.md
|
|
124
|
-
echo "fix the thing" >> .changeset/wr-itil-p141.md
|
|
125
|
-
git add packages/itil/skills/foo/SKILL.md .changeset/wr-itil-p141.md
|
|
126
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
127
|
-
[ "$status" -eq 0 ]
|
|
128
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
@test "allow: staged test-only changes under packages/<plugin>/hooks/test/ allow without changeset" {
|
|
132
|
-
echo "#!/usr/bin/env bats" > packages/itil/hooks/test/new-test.bats
|
|
133
|
-
git add packages/itil/hooks/test/new-test.bats
|
|
134
|
-
run run_bash_hook "git commit -m 'test'"
|
|
135
|
-
[ "$status" -eq 0 ]
|
|
136
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
@test "allow: staged test-only changes under packages/<plugin>/scripts/test/ allow without changeset" {
|
|
140
|
-
echo "#!/usr/bin/env bats" > packages/itil/scripts/test/new-script-test.bats
|
|
141
|
-
git add packages/itil/scripts/test/new-script-test.bats
|
|
142
|
-
run run_bash_hook "git commit -m 'test'"
|
|
143
|
-
[ "$status" -eq 0 ]
|
|
144
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
@test "allow: staged packages/<plugin>/README.md alone allows without changeset (doc-only)" {
|
|
148
|
-
echo "# itil" > packages/itil/README.md
|
|
149
|
-
git add packages/itil/README.md
|
|
150
|
-
run run_bash_hook "git commit -m 'docs'"
|
|
151
|
-
[ "$status" -eq 0 ]
|
|
152
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@test "allow: staged docs under packages/<plugin>/docs/ allow without changeset (doc-only)" {
|
|
156
|
-
echo "# guide" > packages/itil/docs/guide.md
|
|
157
|
-
git add packages/itil/docs/guide.md
|
|
158
|
-
run run_bash_hook "git commit -m 'docs'"
|
|
159
|
-
[ "$status" -eq 0 ]
|
|
160
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
@test "deny: staged SKILL.md (NOT in allow-list per architect amendment) triggers deny" {
|
|
164
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
165
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
166
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
167
|
-
[ "$status" -eq 0 ]
|
|
168
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@test "allow: staged .github/ workflow change without changeset (non-publishable path)" {
|
|
172
|
-
echo "name: ci" > .github/workflows/ci.yml
|
|
173
|
-
git add .github/workflows/ci.yml
|
|
174
|
-
run run_bash_hook "git commit -m 'ci'"
|
|
175
|
-
[ "$status" -eq 0 ]
|
|
176
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@test "allow: staged top-level docs/ change without changeset (non-publishable path)" {
|
|
180
|
-
echo "# briefing" > docs/BRIEFING.md
|
|
181
|
-
git add docs/BRIEFING.md
|
|
182
|
-
run run_bash_hook "git commit -m 'docs'"
|
|
183
|
-
[ "$status" -eq 0 ]
|
|
184
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
@test "allow: BYPASS_CHANGESET_GATE=1 env var allows packages/<plugin>/ commit without changeset" {
|
|
188
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
189
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
190
|
-
BYPASS_CHANGESET_GATE=1 run run_bash_hook "git commit -m 'feat'"
|
|
191
|
-
[ "$status" -eq 0 ]
|
|
192
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
# --- Allow path silence (ADR-045 Pattern 1) ---
|
|
196
|
-
|
|
197
|
-
@test "allow path emits 0 bytes (ADR-045 Pattern 1 silent-on-pass)" {
|
|
198
|
-
echo "#!/usr/bin/env bats" > packages/itil/hooks/test/new-test.bats
|
|
199
|
-
git add packages/itil/hooks/test/new-test.bats
|
|
200
|
-
run run_bash_hook "git commit -m 'test'"
|
|
201
|
-
[ "$status" -eq 0 ]
|
|
202
|
-
[ "${#output}" -eq 0 ]
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# --- Tool-name and command-shape filters ---
|
|
206
|
-
|
|
207
|
-
@test "allow: non-Bash tool exits 0 without deny" {
|
|
208
|
-
run bash -c "echo '{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"foo.md\"}}' | bash $HOOK"
|
|
209
|
-
[ "$status" -eq 0 ]
|
|
210
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
@test "allow: Bash command that is NOT git commit (e.g., git status) bypasses detection" {
|
|
214
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
215
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
216
|
-
run run_bash_hook "git status"
|
|
217
|
-
[ "$status" -eq 0 ]
|
|
218
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
# --- Changeset variants: README.md and config.json don't count as a real changeset ---
|
|
222
|
-
|
|
223
|
-
@test "deny: staged .changeset/README.md alone does NOT count as a valid changeset" {
|
|
224
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
225
|
-
echo "# changesets" > .changeset/README.md
|
|
226
|
-
git add packages/itil/skills/foo/SKILL.md .changeset/README.md
|
|
227
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
228
|
-
[ "$status" -eq 0 ]
|
|
229
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
@test "ADR-099 deny: a staged legacy holding entry does NOT satisfy changeset discipline" {
|
|
233
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
234
|
-
mkdir -p docs/changesets-holding
|
|
235
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nlegacy metadata\n' > docs/changesets-holding/legacy.md
|
|
236
|
-
git add packages/itil/skills/foo/SKILL.md docs/changesets-holding/legacy.md
|
|
237
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
238
|
-
[ "$status" -eq 0 ]
|
|
239
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
240
|
-
[[ "$output" == *"P141"* ]]
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
# --- Mixed staged sets ---
|
|
244
|
-
|
|
245
|
-
@test "deny: staged source + test in same commit still requires changeset (mixed set)" {
|
|
246
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
247
|
-
echo "#!/usr/bin/env bats" > packages/itil/hooks/test/new-test.bats
|
|
248
|
-
git add packages/itil/skills/foo/SKILL.md packages/itil/hooks/test/new-test.bats
|
|
249
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
250
|
-
[ "$status" -eq 0 ]
|
|
251
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
# --- Parse / fail-open contracts ---
|
|
255
|
-
|
|
256
|
-
@test "allow: empty JSON exits 0 without deny (fail-open on parse-incomplete)" {
|
|
257
|
-
run bash -c "echo '{}' | bash $HOOK"
|
|
258
|
-
[ "$status" -eq 0 ]
|
|
259
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
@test "allow: outside a git work tree exits 0 without deny (fail-open)" {
|
|
263
|
-
cd "$ORIG_DIR"
|
|
264
|
-
TEMP_NONGIT=$(mktemp -d)
|
|
265
|
-
cd "$TEMP_NONGIT"
|
|
266
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
267
|
-
cd "$TEST_DIR"
|
|
268
|
-
rm -rf "$TEMP_NONGIT"
|
|
269
|
-
[ "$status" -eq 0 ]
|
|
270
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
# --- P272: substring-vs-invocation regression coverage ---
|
|
274
|
-
#
|
|
275
|
-
# Prior to P272, the hook used `case "$COMMAND" in *"git commit"*) ;;`
|
|
276
|
-
# which fired on ANY Bash command whose text contained the literal
|
|
277
|
-
# phrase "git commit" — including grep patterns, sed substitutions,
|
|
278
|
-
# cat heredoc bodies, echo strings, and `git log --grep` queries.
|
|
279
|
-
# Workaround was stage-changeset-first-or-different-shell, observed
|
|
280
|
-
# ≥3 events per session in the P268 sibling-hook class.
|
|
281
|
-
# P272 replaces that match with a leading-executable-token check via
|
|
282
|
-
# `lib/command-detect.sh::command_invokes_git_commit` (the shared
|
|
283
|
-
# helper landed by P268). The tests below stage `@windyroad/itil/`
|
|
284
|
-
# source (which would trigger deny if the gate fired) and run various
|
|
285
|
-
# non-commit Bash commands whose argument vectors mention `git commit`.
|
|
286
|
-
# The hook MUST pass silently.
|
|
287
|
-
|
|
288
|
-
@test "P272 allow: grep with 'git commit' pattern does NOT trigger gate" {
|
|
289
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
290
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
291
|
-
run run_bash_hook "grep -n 'git commit' file.md"
|
|
292
|
-
[ "$status" -eq 0 ]
|
|
293
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
294
|
-
# Silent pass per ADR-045 Pattern 1.
|
|
295
|
-
[ "${#output}" -eq 0 ]
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
@test "P272 allow: grep -rn 'git commit' packages/ (the recurring orchestrator surface)" {
|
|
299
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
300
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
301
|
-
run run_bash_hook "grep -rn 'git commit' packages/"
|
|
302
|
-
[ "$status" -eq 0 ]
|
|
303
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
304
|
-
[ "${#output}" -eq 0 ]
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
@test "P272 allow: sed -i 's/git commit/.../' substitution does NOT trigger gate" {
|
|
308
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
309
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
310
|
-
run run_bash_hook "sed -i 's/git commit/git push/' file.md"
|
|
311
|
-
[ "$status" -eq 0 ]
|
|
312
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
313
|
-
[ "${#output}" -eq 0 ]
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
@test "P272 allow: echo with 'git commit' inside string does NOT trigger gate" {
|
|
317
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
318
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
319
|
-
run run_bash_hook "echo 'the git commit gate fires here'"
|
|
320
|
-
[ "$status" -eq 0 ]
|
|
321
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
322
|
-
[ "${#output}" -eq 0 ]
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
@test "P272 allow: git log --grep 'git commit' does NOT trigger gate (git log is leading)" {
|
|
326
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
327
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
328
|
-
run run_bash_hook "git log --grep 'git commit'"
|
|
329
|
-
[ "$status" -eq 0 ]
|
|
330
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
331
|
-
[ "${#output}" -eq 0 ]
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
@test "P272 allow: cat heredoc whose body contains 'git commit' does NOT trigger gate (retro-write surface)" {
|
|
335
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
336
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
337
|
-
# Inline-build JSON with embedded newlines via python3 to mimic the
|
|
338
|
-
# Bash tool's multi-line command payload (the canonical retro-write
|
|
339
|
-
# surface that misfired in the P268 sibling).
|
|
340
|
-
local payload
|
|
341
|
-
payload=$(python3 -c "import json,sys; print(json.dumps({'tool_name':'Bash','tool_input':{'command':'cat >> docs/problems/README-history.md <<EOF\nFlow note: the git commit gate fires here.\nEOF'}}))")
|
|
342
|
-
run bash -c "echo '$payload' | bash $HOOK"
|
|
343
|
-
[ "$status" -eq 0 ]
|
|
344
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
345
|
-
[ "${#output}" -eq 0 ]
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
@test "P272 allow: git commit-tree (boundary check — commit-tree is a different plumbing command)" {
|
|
349
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
350
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
351
|
-
run run_bash_hook "git commit-tree HEAD^{tree}"
|
|
352
|
-
[ "$status" -eq 0 ]
|
|
353
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
354
|
-
[ "${#output}" -eq 0 ]
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
@test "P272 deny: actual git commit invocation with staged packages/<plugin>/ source still triggers gate (positive regression)" {
|
|
358
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
359
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
360
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
361
|
-
[ "$status" -eq 0 ]
|
|
362
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
363
|
-
[[ "$output" == *"P141"* ]]
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
@test "P272 deny: cd <path> && git commit (prefix-strip path) still triggers gate" {
|
|
367
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
368
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
369
|
-
run run_bash_hook "cd . && git commit -m 'feat'"
|
|
370
|
-
[ "$status" -eq 0 ]
|
|
371
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
@test "P272 deny: GIT_AUTHOR_NAME=Test git commit (env-prefix path) still triggers gate" {
|
|
375
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
376
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
377
|
-
run run_bash_hook "GIT_AUTHOR_NAME=Test git commit -m 'feat'"
|
|
378
|
-
[ "$status" -eq 0 ]
|
|
379
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
# --- P141 Phase 2: in-scope-changeset coverage for multi-commit slices ---
|
|
383
|
-
#
|
|
384
|
-
# Phase 2 amendment (2026-05-31) widens the allow path: a `.changeset/*.md`
|
|
385
|
-
# already in the unpushed slice scope (committed in a prior unpushed commit,
|
|
386
|
-
# untracked in the working tree, or modified-not-staged) that targets the
|
|
387
|
-
# plugin via its YAML frontmatter `"@windyroad/<plugin>": <any-bump>` line
|
|
388
|
-
# also satisfies the gate. Phase 1 strict-deny behaviour preserved for the
|
|
389
|
-
# no-coverage case.
|
|
390
|
-
#
|
|
391
|
-
# Scope boundary: `<base>..HEAD` where base = `@{u}` upstream tracking branch
|
|
392
|
-
# with fallback to `origin/main`. Once a changeset is on `origin/<base>`
|
|
393
|
-
# (drained by changesets-action), it no longer counts — Phase 2 boundary
|
|
394
|
-
# fixture below proves this.
|
|
395
|
-
#
|
|
396
|
-
# Per-plugin granularity: an `@windyroad/itil` changeset does NOT cover a
|
|
397
|
-
# `packages/voice-tone/` commit — wrong-plugin negative fixture below.
|
|
398
|
-
|
|
399
|
-
# Helper: mark the current HEAD as `origin/main` so subsequent commits
|
|
400
|
-
# fall into the unpushed-range scope `origin/main..HEAD`. The bats setup
|
|
401
|
-
# creates a local repo with no remote; this synthesises an origin/main
|
|
402
|
-
# ref via `git update-ref` for behavioural testing.
|
|
403
|
-
mark_origin_at_head() {
|
|
404
|
-
git update-ref refs/remotes/origin/main HEAD
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
@test "P141 Phase 2 allow: in-range committed changeset for plugin covers subsequent same-plugin commit" {
|
|
408
|
-
mark_origin_at_head
|
|
409
|
-
# Commit 1: ship the changeset + initial source together (Phase 1 case).
|
|
410
|
-
echo "skill body 1" > packages/itil/skills/foo/SKILL.md
|
|
411
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nfix the thing\n' > .changeset/wr-itil-p347.md
|
|
412
|
-
git add packages/itil/skills/foo/SKILL.md .changeset/wr-itil-p347.md
|
|
413
|
-
git -c commit.gpgsign=false commit --quiet -m "feat 1"
|
|
414
|
-
# Commit 2: stage more itil source — no new changeset, but the in-range
|
|
415
|
-
# changeset from commit 1 covers @windyroad/itil. Gate must allow.
|
|
416
|
-
echo "more skill" > packages/itil/skills/foo/SKILL.md
|
|
417
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
418
|
-
run run_bash_hook "git commit -m 'feat 2'"
|
|
419
|
-
[ "$status" -eq 0 ]
|
|
420
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
421
|
-
# Silent pass per ADR-045 Pattern 1.
|
|
422
|
-
[ "${#output}" -eq 0 ]
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
@test "P141 Phase 2 deny boundary: changeset consumed onto origin no longer counts; fresh required" {
|
|
426
|
-
# Commit 1: ship the changeset onto the base.
|
|
427
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nfix the thing\n' > .changeset/wr-itil-p347.md
|
|
428
|
-
git add .changeset/wr-itil-p347.md
|
|
429
|
-
git -c commit.gpgsign=false commit --quiet -m "changeset on base"
|
|
430
|
-
# Mark as drained-to-origin — changesets-action consumed it at release.
|
|
431
|
-
mark_origin_at_head
|
|
432
|
-
# Remove the file as changesets-action would on consumption.
|
|
433
|
-
git rm --quiet .changeset/wr-itil-p347.md
|
|
434
|
-
git -c commit.gpgsign=false commit --quiet -m "changeset consumed on origin"
|
|
435
|
-
mark_origin_at_head
|
|
436
|
-
# Now stage fresh itil source — no in-range changeset, no staged changeset.
|
|
437
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
438
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
439
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
440
|
-
[ "$status" -eq 0 ]
|
|
441
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
442
|
-
[[ "$output" == *"P141"* ]]
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
@test "P141 Phase 2 deny: in-range changeset for DIFFERENT plugin does not cover this plugin's source (wrong-plugin)" {
|
|
446
|
-
mark_origin_at_head
|
|
447
|
-
# Commit 1: ship an @windyroad/itil changeset.
|
|
448
|
-
echo "itil source" > packages/itil/skills/foo/SKILL.md
|
|
449
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nfix itil\n' > .changeset/wr-itil-p347.md
|
|
450
|
-
git add packages/itil/skills/foo/SKILL.md .changeset/wr-itil-p347.md
|
|
451
|
-
git -c commit.gpgsign=false commit --quiet -m "feat itil"
|
|
452
|
-
# Commit 2: stage voice-tone source. The in-range itil changeset must
|
|
453
|
-
# NOT satisfy the gate for a different plugin (per-plugin granularity).
|
|
454
|
-
mkdir -p packages/voice-tone/src
|
|
455
|
-
echo "voice source" > packages/voice-tone/src/x.ts
|
|
456
|
-
git add packages/voice-tone/src/x.ts
|
|
457
|
-
run run_bash_hook "git commit -m 'feat voice'"
|
|
458
|
-
[ "$status" -eq 0 ]
|
|
459
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
460
|
-
# Deny message names the offending plugin slug — voice-tone, not itil.
|
|
461
|
-
[[ "$output" == *"voice-tone"* ]]
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
@test "P141 Phase 2 allow: untracked .changeset/*.md targeting plugin covers staged source" {
|
|
465
|
-
mark_origin_at_head
|
|
466
|
-
# Author the changeset to disk but DO NOT stage. Gate must still
|
|
467
|
-
# recognise it via `git ls-files --others --exclude-standard`.
|
|
468
|
-
printf -- '---\n"@windyroad/itil": minor\n---\nfeature\n' > .changeset/wr-itil-p347.md
|
|
469
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
470
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
471
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
472
|
-
[ "$status" -eq 0 ]
|
|
473
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
474
|
-
[ "${#output}" -eq 0 ]
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
@test "P141 Phase 2 allow: in-range changeset that was modified-not-staged still covers" {
|
|
478
|
-
mark_origin_at_head
|
|
479
|
-
# Commit 1: ship the changeset.
|
|
480
|
-
printf -- '---\n"@windyroad/itil": patch\n---\noriginal\n' > .changeset/wr-itil-p347.md
|
|
481
|
-
git add .changeset/wr-itil-p347.md
|
|
482
|
-
git -c commit.gpgsign=false commit --quiet -m "changeset"
|
|
483
|
-
# Modify the prose body (frontmatter preserved); do NOT stage the edit.
|
|
484
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nedited prose\n' > .changeset/wr-itil-p347.md
|
|
485
|
-
# Stage source.
|
|
486
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
487
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
488
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
489
|
-
[ "$status" -eq 0 ]
|
|
490
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
@test "P141 Phase 2 deny: in-range changeset for plugin exists but its frontmatter targets a different plugin slug" {
|
|
494
|
-
mark_origin_at_head
|
|
495
|
-
# A changeset committed in-range whose frontmatter declares ONLY
|
|
496
|
-
# @windyroad/voice-tone, not @windyroad/itil. Staged source is itil.
|
|
497
|
-
# Check 2b must NOT match (frontmatter scan is per-plugin-slug).
|
|
498
|
-
printf -- '---\n"@windyroad/voice-tone": patch\n---\nfix voice\n' > .changeset/wr-voice-p999.md
|
|
499
|
-
git add .changeset/wr-voice-p999.md
|
|
500
|
-
git -c commit.gpgsign=false commit --quiet -m "voice changeset"
|
|
501
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
502
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
503
|
-
run run_bash_hook "git commit -m 'feat itil'"
|
|
504
|
-
[ "$status" -eq 0 ]
|
|
505
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
506
|
-
[[ "$output" == *"itil"* ]]
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
@test "P141 Phase 2: when no upstream and no origin/main ref exists, Check 2b skips silently and Phase 1 strict-deny is preserved" {
|
|
510
|
-
# No mark_origin_at_head — refs/remotes/origin/main is absent.
|
|
511
|
-
# Stage source without any changeset. Phase 1 strict-deny must fire
|
|
512
|
-
# (Check 2b returns 1 on missing base, Check 2a returns 0).
|
|
513
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
514
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
515
|
-
run run_bash_hook "git commit -m 'feat'"
|
|
516
|
-
[ "$status" -eq 0 ]
|
|
517
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
518
|
-
[[ "$output" == *"P141"* ]]
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
# --- P387: Check 2b is change-scoped, not merely plugin-scoped ---
|
|
522
|
-
#
|
|
523
|
-
# P141 Phase 2's Check 2b passed a plugin-source commit if ANY in-scope
|
|
524
|
-
# changeset targeted the plugin — even one authored for a DIFFERENT change.
|
|
525
|
-
# So a change shipped to npm with no CHANGELOG record of its own, riding a
|
|
526
|
-
# sibling changeset's coattails (witnessed: P164's octal fix shipped
|
|
527
|
-
# undocumented under P374's changeset, @windyroad/risk-scorer 0.13.5/0.14.0).
|
|
528
|
-
#
|
|
529
|
-
# P387 tightens Check 2b to change-scoped via conservative work-item-ID
|
|
530
|
-
# (ticket) keying. The commit's work-item IDs (P<NNN> / RFC-<NNN> /
|
|
531
|
-
# STORY-<NNN>, extracted from the git-commit COMMAND string passed into the
|
|
532
|
-
# helper) are compared against each in-scope covering changeset's work-item
|
|
533
|
-
# IDs (extracted from its filename + body). Check 2b DENIES only on positive
|
|
534
|
-
# evidence of an unrelated sibling: the commit cites work-item ID(s), EVERY
|
|
535
|
-
# covering changeset cites work-item ID(s), and none overlap. Any ambiguity
|
|
536
|
-
# allows — a ticket-less commit, a prose-only changeset, or an ID overlap.
|
|
537
|
-
# This preserves the ADR-014 batch-grain (same-slice commits share a ticket,
|
|
538
|
-
# so the slice's changeset still covers them) and never over-fires on
|
|
539
|
-
# adopter/prose-only changesets that carry no ticket ref.
|
|
540
|
-
|
|
541
|
-
@test "P387 deny: plugin-source commit citing a DIFFERENT ticket than the only in-scope changeset (unrelated sibling) denies" {
|
|
542
|
-
mark_origin_at_head
|
|
543
|
-
# Commit 1: P374's changeset + its source.
|
|
544
|
-
echo "p374 work" > packages/itil/skills/foo/SKILL.md
|
|
545
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nP374 work. Refs: P374.\n' > .changeset/wr-itil-p374.md
|
|
546
|
-
git add packages/itil/skills/foo/SKILL.md .changeset/wr-itil-p374.md
|
|
547
|
-
git -c commit.gpgsign=false commit --quiet -m "fix(itil): P374 work (P374)"
|
|
548
|
-
# Commit 2: P164's UNRELATED fix — stages itil source, authors NO changeset.
|
|
549
|
-
# The only in-scope changeset is P374's. Change-scoped Check 2b must DENY.
|
|
550
|
-
echo "octal fix" > packages/itil/scripts/extract.sh
|
|
551
|
-
git add packages/itil/scripts/extract.sh
|
|
552
|
-
run run_bash_hook "git commit -m 'fix(itil): octal eval (P164)'"
|
|
553
|
-
[ "$status" -eq 0 ]
|
|
554
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
555
|
-
[[ "$output" == *"P141"* ]]
|
|
556
|
-
[[ "$output" == *"itil"* ]]
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
@test "P387 allow: plugin-source commit citing the SAME ticket as the in-scope changeset (same change) allows" {
|
|
560
|
-
mark_origin_at_head
|
|
561
|
-
# Commit 1: P347's changeset + slice-1 source.
|
|
562
|
-
echo "p347 slice 1" > packages/itil/skills/foo/SKILL.md
|
|
563
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nP347 slice. Refs: P347.\n' > .changeset/wr-itil-p347.md
|
|
564
|
-
git add packages/itil/skills/foo/SKILL.md .changeset/wr-itil-p347.md
|
|
565
|
-
git -c commit.gpgsign=false commit --quiet -m "feat(itil): P347 slice 1 (P347)"
|
|
566
|
-
# Commit 2: same P347 slice — no new changeset; the in-range P347 changeset
|
|
567
|
-
# shares the commit's ticket, so the ADR-014 batch is preserved. Allow.
|
|
568
|
-
echo "p347 slice 2" > packages/itil/skills/foo/SKILL.md
|
|
569
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
570
|
-
run run_bash_hook "git commit -m 'feat(itil): P347 slice 2 (P347)'"
|
|
571
|
-
[ "$status" -eq 0 ]
|
|
572
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
573
|
-
[ "${#output}" -eq 0 ]
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
@test "P387 allow: plugin-source commit with NO work-item ID falls back to plugin-scoped allow (conservative)" {
|
|
577
|
-
mark_origin_at_head
|
|
578
|
-
# In-range changeset cites P374; the new commit cites no ticket at all —
|
|
579
|
-
# we cannot prove it is a DIFFERENT change, so we must not over-fire.
|
|
580
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nsome fix. Refs: P374.\n' > .changeset/wr-itil-p374.md
|
|
581
|
-
git add .changeset/wr-itil-p374.md
|
|
582
|
-
git -c commit.gpgsign=false commit --quiet -m "chore: add changeset"
|
|
583
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
584
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
585
|
-
run run_bash_hook "git commit -m 'feat without a ticket ref'"
|
|
586
|
-
[ "$status" -eq 0 ]
|
|
587
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
@test "P387 allow: prose-only in-scope changeset (no work-item ID) does not over-fire even when the commit cites a ticket" {
|
|
591
|
-
mark_origin_at_head
|
|
592
|
-
# An adopter (or a plain) changeset with no ticket ref in filename or body.
|
|
593
|
-
# The commit cites P164, but we cannot prove the prose-only changeset is for
|
|
594
|
-
# a different change, so Check 2b allows (no over-fire on prose changesets).
|
|
595
|
-
printf -- '---\n"@windyroad/itil": patch\n---\nfix the thing\n' > .changeset/wr-itil-feature.md
|
|
596
|
-
git add .changeset/wr-itil-feature.md
|
|
597
|
-
git -c commit.gpgsign=false commit --quiet -m "chore: add changeset"
|
|
598
|
-
echo "skill body" > packages/itil/skills/foo/SKILL.md
|
|
599
|
-
git add packages/itil/skills/foo/SKILL.md
|
|
600
|
-
run run_bash_hook "git commit -m 'fix(itil): a thing (P164)'"
|
|
601
|
-
[ "$status" -eq 0 ]
|
|
602
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
603
|
-
}
|