@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.
Files changed (171) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +7 -2
  3. package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
  4. package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
  5. package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
  6. package/agents/test/hang-off-check.bats +0 -225
  7. package/hooks/test/block-list.bats +0 -153
  8. package/hooks/test/command-detect.bats +0 -181
  9. package/hooks/test/itil-assistant-output-gate.bats +0 -114
  10. package/hooks/test/itil-assistant-output-review.bats +0 -224
  11. package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
  12. package/hooks/test/itil-changeset-discipline.bats +0 -603
  13. package/hooks/test/itil-claude-space-protection.bats +0 -294
  14. package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
  15. package/hooks/test/itil-correction-detect.bats +0 -189
  16. package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
  17. package/hooks/test/itil-fictional-defer-detect.bats +0 -292
  18. package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
  19. package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
  20. package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
  21. package/hooks/test/itil-pending-questions-surface.bats +0 -307
  22. package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
  23. package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
  24. package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
  25. package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
  26. package/hooks/test/manage-problem-enforce-create.bats +0 -424
  27. package/hooks/test/p057-staging-trap-detect.bats +0 -231
  28. package/hooks/test/pre-publish-intake-gate.bats +0 -144
  29. package/hooks/test/runtime-sid-marker.bats +0 -90
  30. package/hooks/test/session-id.bats +0 -370
  31. package/scripts/migrate-problems-add-type.sh +0 -116
  32. package/scripts/test/catchup-scan.bats +0 -274
  33. package/scripts/test/check-afk-accept-eligible.bats +0 -310
  34. package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
  35. package/scripts/test/check-fix-rfc-trace.bats +0 -164
  36. package/scripts/test/check-locale-discipline.bats +0 -291
  37. package/scripts/test/check-problems-readme-budget.bats +0 -192
  38. package/scripts/test/check-rfc-has-stories.bats +0 -69
  39. package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
  40. package/scripts/test/check-rfc-stories-ratified.bats +0 -82
  41. package/scripts/test/check-upstream-responses.bats +0 -503
  42. package/scripts/test/classify-readme-drift.bats +0 -392
  43. package/scripts/test/derive-first-dispatch.bats +0 -274
  44. package/scripts/test/derive-release-vehicle.bats +0 -454
  45. package/scripts/test/detect-unratified-stories-maps.bats +0 -60
  46. package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
  47. package/scripts/test/effort-tally.bats +0 -170
  48. package/scripts/test/evaluate-relevance.bats +0 -773
  49. package/scripts/test/kind-a-commands.bats +0 -46
  50. package/scripts/test/mark-create-gate.bats +0 -59
  51. package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
  52. package/scripts/test/migrate-problems-add-type.bats +0 -280
  53. package/scripts/test/migrate-story-status-mirror.bats +0 -167
  54. package/scripts/test/no-type-regression-guard.bats +0 -74
  55. package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
  56. package/scripts/test/plugin-exercise-index.bats +0 -386
  57. package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
  58. package/scripts/test/plugin-maturity-populate.bats +0 -631
  59. package/scripts/test/plugin-maturity-render.bats +0 -469
  60. package/scripts/test/plugin-validate-ci-gate.bats +0 -208
  61. package/scripts/test/reconcile-readme.bats +0 -1007
  62. package/scripts/test/reconcile-rfcs.bats +0 -496
  63. package/scripts/test/reconcile-stories.bats +0 -173
  64. package/scripts/test/reconcile-story-maps.bats +0 -74
  65. package/scripts/test/release-watch-poll-loop.bats +0 -180
  66. package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
  67. package/scripts/test/rfc-stories-extension.bats +0 -175
  68. package/scripts/test/skill-invocations.bats +0 -535
  69. package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
  70. package/scripts/test/story-oversight-lib.bats +0 -190
  71. package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
  72. package/scripts/test/update-problem-references-section.bats +0 -242
  73. package/scripts/test/update-problem-rfcs-section.bats +0 -242
  74. package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
  75. package/scripts/test/update-rfc-commits-section.bats +0 -77
  76. package/scripts/test/verify-iter-summary.bats +0 -196
  77. package/scripts/test/working-the-problem-traversal.bats +0 -116
  78. package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
  79. package/skills/capture-problem/test/capture-problem.bats +0 -389
  80. package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
  81. package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
  82. package/skills/close-incident/test/close-incident-contract.bats +0 -146
  83. package/skills/link-incident/test/link-incident-contract.bats +0 -125
  84. package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
  85. package/skills/list-problems/test/list-problems-contract.bats +0 -100
  86. package/skills/list-stories/test/list-stories-contract.bats +0 -127
  87. package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
  88. package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
  89. package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
  90. package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
  91. package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
  92. package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
  93. package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
  94. package/skills/manage-incident/test/manage-incident.bats +0 -171
  95. package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
  96. package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
  97. package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
  98. package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
  99. package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
  100. package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
  101. package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
  102. package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
  103. package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
  104. package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
  105. package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
  106. package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
  107. package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
  108. package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
  109. package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
  110. package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
  111. package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
  112. package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
  113. package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
  114. package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
  115. package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
  116. package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
  117. package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
  118. package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
  119. package/skills/manage-story/test/manage-story-contract.bats +0 -174
  120. package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
  121. package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
  122. package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
  123. package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
  124. package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
  125. package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
  126. package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
  127. package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
  128. package/skills/review-problems/test/review-problems-contract.bats +0 -235
  129. package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
  130. package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
  131. package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
  132. package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
  133. package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
  134. package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
  135. package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
  136. package/skills/work-problem/test/work-problem-contract.bats +0 -294
  137. package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
  138. package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
  139. package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
  140. package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
  141. package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
  142. package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
  143. package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
  144. package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
  145. package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
  146. package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
  147. package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
  148. package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
  149. package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
  150. package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
  151. package/skills/work-problems/test/work-problems-preflight.bats +0 -69
  152. package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
  153. package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
  154. package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
  155. package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
  156. package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
  157. package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
  158. package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
  159. package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
  160. package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
  161. package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
  162. package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
  163. package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
  164. package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
  165. package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
  166. package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
  167. package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
  168. package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
  169. package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
  170. package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
  171. 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
- }