@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,642 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P165: itil-readme-refresh-discipline.sh PreToolUse:Bash hook must deny
4
- # `git commit` invocations whose staged set includes any
5
- # docs/problems/<state>/NNN-*.md (or legacy docs/problems/NNN-*.md) but
6
- # does NOT also stage docs/problems/README.md. Hook-level enforcement
7
- # closes the P094/P062 README-refresh enforcement gap — iter subprocess
8
- # commits could previously ship a `.verifying.md` rename or Status edit
9
- # without the corresponding Verification Queue / WSJF Rankings row in
10
- # the README.
11
- #
12
- # Detection logic (per ticket Fix Strategy + architect verdict):
13
- # On `git commit` invocations, run `git diff --staged --name-only`.
14
- # If any path matches docs/problems/(open|verifying|closed|known-error|parked)/NNN-*.md
15
- # OR docs/problems/NNN-*.<state>.md (legacy flat layout) AND
16
- # docs/problems/README.md is NOT staged, emit a deny with recovery
17
- # directive `git add docs/problems/README.md` and the P165 cite.
18
- # Allow when README is staged alongside, when no ticket file is
19
- # staged at all (README-only / retro-only / ADR-only / source-only
20
- # commits), or when BYPASS_README_REFRESH_GATE=1 is set.
21
- #
22
- # Per ADR-005 (plugin testing strategy) — hook bats live under
23
- # packages/<plugin>/hooks/test/ and assert behaviour on emitted JSON,
24
- # not source content. Per P081 — no source-grep on hook text. Simulate
25
- # the PreToolUse:Bash payload on stdin and assert on the emitted
26
- # permissionDecision.
27
- #
28
- # Per ADR-045 Pattern 1 (silent-on-pass) — allow paths emit 0 bytes.
29
- # Per ADR-045 deny-band — deny messages target ~245 bytes; cap at 300.
30
-
31
- setup() {
32
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
33
- HOOK="$SCRIPT_DIR/itil-readme-refresh-discipline.sh"
34
- ORIG_DIR="$PWD"
35
- TEST_DIR=$(mktemp -d)
36
- cd "$TEST_DIR"
37
- git init --quiet -b main
38
- git config user.email "test@example.com"
39
- git config user.name "Test"
40
- mkdir -p docs/problems/open docs/problems/verifying docs/problems/closed \
41
- docs/problems/known-error docs/problems/parked docs/retros \
42
- docs/decisions packages/itil/skills/foo .changeset
43
- echo "seed" > seed.txt
44
- git add seed.txt
45
- git -c commit.gpgsign=false commit --quiet -m "initial"
46
- # README must exist for the "stage it alongside" tests to work.
47
- echo "# Problem Backlog" > docs/problems/README.md
48
- git add docs/problems/README.md
49
- git -c commit.gpgsign=false commit --quiet -m "seed readme"
50
- unset BYPASS_README_REFRESH_GATE
51
- }
52
-
53
- teardown() {
54
- cd "$ORIG_DIR"
55
- rm -rf "$TEST_DIR"
56
- unset BYPASS_README_REFRESH_GATE
57
- }
58
-
59
- run_bash_hook() {
60
- local cmd="$1"
61
- local json
62
- json=$(printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$cmd")
63
- echo "$json" | bash "$HOOK"
64
- }
65
-
66
- # --- Trap detection: the canonical P165 shape ---
67
-
68
- @test "deny: staged docs/problems/open/NNN-*.md without README refresh triggers deny on git commit" {
69
- echo "# Problem 999" > docs/problems/open/999-some-new-ticket.md
70
- git add docs/problems/open/999-some-new-ticket.md
71
- run run_bash_hook "git commit -m 'feat'"
72
- [ "$status" -eq 0 ]
73
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
74
- [[ "$output" == *"P165"* ]]
75
- }
76
-
77
- @test "deny: staged docs/problems/verifying/NNN-*.md without README refresh triggers deny" {
78
- echo "# Problem 999 verifying" > docs/problems/verifying/999-some-ticket.md
79
- git add docs/problems/verifying/999-some-ticket.md
80
- run run_bash_hook "git commit -m 'fix'"
81
- [ "$status" -eq 0 ]
82
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
83
- [[ "$output" == *"P165"* ]]
84
- }
85
-
86
- @test "deny: staged docs/problems/closed/NNN-*.md without README refresh triggers deny" {
87
- echo "# Problem 999 closed" > docs/problems/closed/999-some-ticket.md
88
- git add docs/problems/closed/999-some-ticket.md
89
- run run_bash_hook "git commit -m 'close'"
90
- [ "$status" -eq 0 ]
91
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
92
- }
93
-
94
- @test "deny: staged docs/problems/known-error/NNN-*.md without README refresh triggers deny" {
95
- echo "# Problem 999 known error" > docs/problems/known-error/999-some-ticket.md
96
- git add docs/problems/known-error/999-some-ticket.md
97
- run run_bash_hook "git commit -m 'transition'"
98
- [ "$status" -eq 0 ]
99
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
100
- }
101
-
102
- @test "deny: staged docs/problems/parked/NNN-*.md without README refresh triggers deny" {
103
- echo "# Problem 999 parked" > docs/problems/parked/999-some-ticket.md
104
- git add docs/problems/parked/999-some-ticket.md
105
- run run_bash_hook "git commit -m 'park'"
106
- [ "$status" -eq 0 ]
107
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
108
- }
109
-
110
- @test "deny: staged legacy flat-layout docs/problems/NNN-*.<state>.md without README triggers deny" {
111
- echo "# Problem 999 flat" > docs/problems/999-some-legacy.open.md
112
- git add docs/problems/999-some-legacy.open.md
113
- run run_bash_hook "git commit -m 'feat'"
114
- [ "$status" -eq 0 ]
115
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
116
- }
117
-
118
- @test "deny message names offending ticket ID, recovery command, P165 cite" {
119
- echo "# Problem 999" > docs/problems/open/999-some-new-ticket.md
120
- git add docs/problems/open/999-some-new-ticket.md
121
- run run_bash_hook "git commit -m 'feat'"
122
- [ "$status" -eq 0 ]
123
- # Deny names the ticket as `P<NNN>` (not full path — see hook
124
- # comment: full descriptive ticket slugs exceed ADR-045 deny-band).
125
- [[ "$output" == *"P999"* ]]
126
- [[ "$output" == *"docs/problems/README.md"* ]]
127
- [[ "$output" == *"P165"* ]]
128
- }
129
-
130
- @test "deny message stays under ADR-045 deny-band (<300 bytes)" {
131
- echo "# Problem 999" > docs/problems/open/999-some-ticket.md
132
- git add docs/problems/open/999-some-ticket.md
133
- run run_bash_hook "git commit -m 'feat'"
134
- [ "$status" -eq 0 ]
135
- [ "${#output}" -lt 300 ]
136
- }
137
-
138
- # --- Allow paths: each non-trap shape must NOT deny ---
139
-
140
- @test "allow: staged ticket file WITH docs/problems/README.md allows the commit" {
141
- echo "# Problem 999" > docs/problems/open/999-new.md
142
- echo "# Problem Backlog updated" > docs/problems/README.md
143
- git add docs/problems/open/999-new.md docs/problems/README.md
144
- run run_bash_hook "git commit -m 'feat'"
145
- [ "$status" -eq 0 ]
146
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
147
- }
148
-
149
- @test "allow: README-only commit (reconcile-readme path) allows without ticket change" {
150
- echo "# Problem Backlog reconciled" > docs/problems/README.md
151
- git add docs/problems/README.md
152
- run run_bash_hook "git commit -m 'docs: reconcile readme'"
153
- [ "$status" -eq 0 ]
154
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
155
- }
156
-
157
- @test "allow: retro-only commit allows without ticket change or README refresh" {
158
- echo "# Retro 2026-05-11" > docs/retros/2026-05-11-iter.md
159
- git add docs/retros/2026-05-11-iter.md
160
- run run_bash_hook "git commit -m 'docs(retros): iter'"
161
- [ "$status" -eq 0 ]
162
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
163
- }
164
-
165
- @test "allow: ADR-only commit allows without ticket change or README refresh" {
166
- echo "# ADR 999" > docs/decisions/999-some-decision.proposed.md
167
- git add docs/decisions/999-some-decision.proposed.md
168
- run run_bash_hook "git commit -m 'docs(decisions): adr-999'"
169
- [ "$status" -eq 0 ]
170
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
171
- }
172
-
173
- @test "allow: source-only commit (packages/) allows without ticket change or README refresh" {
174
- echo "skill body" > packages/itil/skills/foo/SKILL.md
175
- git add packages/itil/skills/foo/SKILL.md
176
- run run_bash_hook "git commit -m 'feat'"
177
- [ "$status" -eq 0 ]
178
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
179
- }
180
-
181
- @test "allow: BYPASS_README_REFRESH_GATE=1 env var allows ticket commit without README refresh" {
182
- echo "# Problem 999" > docs/problems/open/999-bypass.md
183
- git add docs/problems/open/999-bypass.md
184
- BYPASS_README_REFRESH_GATE=1 run run_bash_hook "git commit -m 'feat'"
185
- [ "$status" -eq 0 ]
186
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
187
- }
188
-
189
- @test "allow: docs/problems/README-history.md edit alone does NOT trigger deny (not a ticket file)" {
190
- echo "# History" > docs/problems/README-history.md
191
- git add docs/problems/README-history.md
192
- run run_bash_hook "git commit -m 'docs: rotate history'"
193
- [ "$status" -eq 0 ]
194
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
195
- }
196
-
197
- # --- Allow path silence (ADR-045 Pattern 1) ---
198
-
199
- @test "allow path emits 0 bytes (ADR-045 Pattern 1 silent-on-pass)" {
200
- echo "# Retro" > docs/retros/2026-05-11-iter.md
201
- git add docs/retros/2026-05-11-iter.md
202
- run run_bash_hook "git commit -m 'docs'"
203
- [ "$status" -eq 0 ]
204
- [ "${#output}" -eq 0 ]
205
- }
206
-
207
- # --- Tool-name and command-shape filters ---
208
-
209
- @test "allow: non-Bash tool exits 0 without deny" {
210
- run bash -c "echo '{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"foo.md\"}}' | bash $HOOK"
211
- [ "$status" -eq 0 ]
212
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
213
- }
214
-
215
- @test "allow: Bash command that is NOT git commit (e.g., git status) bypasses detection" {
216
- echo "# Problem 999" > docs/problems/open/999-x.md
217
- git add docs/problems/open/999-x.md
218
- run run_bash_hook "git status"
219
- [ "$status" -eq 0 ]
220
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
221
- }
222
-
223
- # --- Mixed staged sets ---
224
-
225
- @test "deny: staged ticket + ADR (no README) still triggers deny (mixed surface dominance)" {
226
- echo "# Problem 999" > docs/problems/open/999-x.md
227
- echo "# ADR 999" > docs/decisions/999-x.proposed.md
228
- git add docs/problems/open/999-x.md docs/decisions/999-x.proposed.md
229
- run run_bash_hook "git commit -m 'feat'"
230
- [ "$status" -eq 0 ]
231
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
232
- }
233
-
234
- @test "allow: staged ticket + ADR + README allows (mixed set with README)" {
235
- echo "# Problem 999" > docs/problems/open/999-x.md
236
- echo "# ADR 999" > docs/decisions/999-x.proposed.md
237
- echo "# Problem Backlog updated" > docs/problems/README.md
238
- git add docs/problems/open/999-x.md docs/decisions/999-x.proposed.md docs/problems/README.md
239
- run run_bash_hook "git commit -m 'feat'"
240
- [ "$status" -eq 0 ]
241
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
242
- }
243
-
244
- # --- Parse / fail-open contracts ---
245
-
246
- @test "allow: empty JSON exits 0 without deny (fail-open on parse-incomplete)" {
247
- run bash -c "echo '{}' | bash $HOOK"
248
- [ "$status" -eq 0 ]
249
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
250
- }
251
-
252
- @test "allow: outside a git work tree exits 0 without deny (fail-open)" {
253
- cd "$ORIG_DIR"
254
- TEMP_NONGIT=$(mktemp -d)
255
- cd "$TEMP_NONGIT"
256
- run run_bash_hook "git commit -m 'feat'"
257
- cd "$TEST_DIR"
258
- rm -rf "$TEMP_NONGIT"
259
- [ "$status" -eq 0 ]
260
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
261
- }
262
-
263
- # --- P230: narrative-only short-circuit (reconcile-readme is authority) ---
264
- #
265
- # When all staged ticket edits are purely narrative (Change Log entries,
266
- # Investigation Task checkbox ticks, prose edits — no ranking-bearing
267
- # field change, no rename between state subdirs, no creation/deletion)
268
- # AND `packages/itil/scripts/reconcile-readme.sh` reports exit=0 against
269
- # the current README, the hook silently passes. Ranking-bearing edits
270
- # still fall through to existing detection per ADR-014 single-commit
271
- # grain (architect verdict: reconcile is robustness layer, not
272
- # supersession of per-operation refresh).
273
-
274
- seed_valid_readme_p999_open() {
275
- cat > docs/problems/README.md <<EOF
276
- # Problem Backlog
277
-
278
- ## WSJF Rankings
279
-
280
- | ID | Title | WSJF |
281
- |---|---|---|
282
- | P999 | Test ticket | 1.0 |
283
-
284
- ## Verification Queue
285
-
286
- (none)
287
-
288
- ## Closed
289
-
290
- (none)
291
- EOF
292
- }
293
-
294
- @test "P230 allow: narrative-only edit + reconcile-readme exit=0 → allow silently" {
295
- cat > docs/problems/open/999-narrative.md <<EOF
296
- # Problem 999: Test ticket
297
- **Status**: Open
298
- **Priority**: 1
299
- EOF
300
- seed_valid_readme_p999_open
301
- git add docs/problems/open/999-narrative.md docs/problems/README.md
302
- git -c commit.gpgsign=false commit --quiet -m "seed p999"
303
- # Narrative-only edit: append a Change Log line
304
- echo "- 2026-05-16 — narrative tweak" >> docs/problems/open/999-narrative.md
305
- git add docs/problems/open/999-narrative.md
306
- run run_bash_hook "git commit -m 'docs(problems): narrative tweak'"
307
- [ "$status" -eq 0 ]
308
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
309
- }
310
-
311
- @test "P230 allow: Investigation Task checkbox tick (narrative-only) + reconcile=0 → allow silently" {
312
- cat > docs/problems/open/999-checkbox.md <<EOF
313
- # Problem 999: Test ticket
314
- **Status**: Open
315
- **Priority**: 1
316
-
317
- ## Investigation Tasks
318
-
319
- - [ ] First task
320
- EOF
321
- seed_valid_readme_p999_open
322
- git add docs/problems/open/999-checkbox.md docs/problems/README.md
323
- git -c commit.gpgsign=false commit --quiet -m "seed p999"
324
- # Narrative-only edit: tick a checkbox
325
- sed -i.bak 's/- \[ \] First task/- [x] First task/' docs/problems/open/999-checkbox.md
326
- rm docs/problems/open/999-checkbox.md.bak
327
- git add docs/problems/open/999-checkbox.md
328
- run run_bash_hook "git commit -m 'docs(problems): tick task'"
329
- [ "$status" -eq 0 ]
330
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
331
- }
332
-
333
- @test "P230 deny: ranking-bearing Status field change + reconcile=0 → still deny per ADR-014 single-commit grain" {
334
- cat > docs/problems/open/999-ranking.md <<EOF
335
- # Problem 999: Test ticket
336
- **Status**: Open
337
- **Priority**: 1
338
- EOF
339
- seed_valid_readme_p999_open
340
- git add docs/problems/open/999-ranking.md docs/problems/README.md
341
- git -c commit.gpgsign=false commit --quiet -m "seed p999"
342
- # Ranking-bearing edit: change Status
343
- sed -i.bak 's/\*\*Status\*\*: Open/\*\*Status\*\*: Verifying/' docs/problems/open/999-ranking.md
344
- rm docs/problems/open/999-ranking.md.bak
345
- git add docs/problems/open/999-ranking.md
346
- run run_bash_hook "git commit -m 'transition'"
347
- [ "$status" -eq 0 ]
348
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
349
- }
350
-
351
- @test "P230 deny: ranking-bearing Priority field change + reconcile=0 → still deny" {
352
- cat > docs/problems/open/999-priority.md <<EOF
353
- # Problem 999: Test ticket
354
- **Status**: Open
355
- **Priority**: 1
356
- EOF
357
- seed_valid_readme_p999_open
358
- git add docs/problems/open/999-priority.md docs/problems/README.md
359
- git -c commit.gpgsign=false commit --quiet -m "seed p999"
360
- # Ranking-bearing edit: change Priority
361
- sed -i.bak 's/\*\*Priority\*\*: 1/\*\*Priority\*\*: 5/' docs/problems/open/999-priority.md
362
- rm docs/problems/open/999-priority.md.bak
363
- git add docs/problems/open/999-priority.md
364
- run run_bash_hook "git commit -m 're-rate'"
365
- [ "$status" -eq 0 ]
366
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
367
- }
368
-
369
- @test "P230 deny: git mv between state subdirs (open→verifying) + no README refresh → deny (canonical iter-subprocess case)" {
370
- cat > docs/problems/open/999-rename.md <<EOF
371
- # Problem 999: Test ticket
372
- **Status**: Open
373
- EOF
374
- seed_valid_readme_p999_open
375
- git add docs/problems/open/999-rename.md docs/problems/README.md
376
- git -c commit.gpgsign=false commit --quiet -m "seed p999"
377
- # Rename to verifying state subdir
378
- git mv docs/problems/open/999-rename.md docs/problems/verifying/999-rename.md
379
- run run_bash_hook "git commit -m 'transition'"
380
- [ "$status" -eq 0 ]
381
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
382
- }
383
-
384
- @test "P230 deny: narrative-only edit + reconcile-readme drift (README missing ticket) → still deny per existing logic" {
385
- cat > docs/problems/open/999-narrative-drift.md <<EOF
386
- # Problem 999: Test ticket
387
- **Status**: Open
388
- EOF
389
- # README does NOT list P999 → reconcile detects MISSING drift → exit=1
390
- cat > docs/problems/README.md <<EOF
391
- # Problem Backlog
392
-
393
- ## WSJF Rankings
394
-
395
- | ID | Title | WSJF |
396
- |---|---|---|
397
- EOF
398
- git add docs/problems/open/999-narrative-drift.md docs/problems/README.md
399
- git -c commit.gpgsign=false commit --quiet -m "seed p999"
400
- # Narrative-only edit
401
- echo "- 2026-05-16 — narrative line" >> docs/problems/open/999-narrative-drift.md
402
- git add docs/problems/open/999-narrative-drift.md
403
- run run_bash_hook "git commit -m 'docs(problems): narrative'"
404
- [ "$status" -eq 0 ]
405
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
406
- }
407
-
408
- # --- P231: deny message advertises correct bypass syntax (Option A) ---
409
- #
410
- # Deny message advertises `.claude/settings.json env` rather than inline
411
- # prefix (which doesn't propagate to PreToolUse hooks per P173).
412
-
413
- @test "P231 deny message advertises .claude/settings.json bypass path + P173 reference" {
414
- echo "# Problem 999" > docs/problems/open/999-bypass-msg.md
415
- git add docs/problems/open/999-bypass-msg.md
416
- run run_bash_hook "git commit -m 'feat'"
417
- [ "$status" -eq 0 ]
418
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
419
- [[ "$output" == *".claude/settings.json"* ]]
420
- [[ "$output" == *"P173"* ]]
421
- }
422
-
423
- # --- P268: substring-vs-invocation regression coverage ---
424
- #
425
- # Prior to P268, the hook used `case "$COMMAND" in *"git commit"*) ;;`
426
- # which fired on ANY Bash command whose text contained the literal
427
- # phrase "git commit" — including grep patterns, sed substitutions,
428
- # cat heredoc bodies, echo strings, and `git log --grep` queries.
429
- # Workaround was stage-README-first, observed ≥3 times per session.
430
- # P268 replaces that match with a leading-executable-token check via
431
- # `lib/command-detect.sh::command_invokes_git_commit`. The tests
432
- # below stage a ticket file (which would trigger deny if the gate
433
- # fired) and run various non-commit Bash commands whose argument
434
- # vectors mention `git commit`. The hook MUST pass silently.
435
-
436
- @test "P268 allow: grep with 'git commit' pattern does NOT trigger gate" {
437
- echo "# Problem 999" > docs/problems/open/999-x.md
438
- git add docs/problems/open/999-x.md
439
- run run_bash_hook "grep -n 'git commit' file.md"
440
- [ "$status" -eq 0 ]
441
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
442
- # Silent pass per ADR-045 Pattern 1.
443
- [ "${#output}" -eq 0 ]
444
- }
445
-
446
- @test "P268 allow: grep -rn 'git commit' packages/ (the recurring orchestrator surface)" {
447
- echo "# Problem 999" > docs/problems/open/999-x.md
448
- git add docs/problems/open/999-x.md
449
- run run_bash_hook "grep -rn 'git commit' packages/"
450
- [ "$status" -eq 0 ]
451
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
452
- [ "${#output}" -eq 0 ]
453
- }
454
-
455
- @test "P268 allow: sed -i 's/git commit/.../' substitution does NOT trigger gate" {
456
- echo "# Problem 999" > docs/problems/open/999-x.md
457
- git add docs/problems/open/999-x.md
458
- run run_bash_hook "sed -i 's/git commit/git push/' file.md"
459
- [ "$status" -eq 0 ]
460
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
461
- [ "${#output}" -eq 0 ]
462
- }
463
-
464
- @test "P268 allow: echo with 'git commit' inside string does NOT trigger gate" {
465
- echo "# Problem 999" > docs/problems/open/999-x.md
466
- git add docs/problems/open/999-x.md
467
- run run_bash_hook "echo 'the git commit gate fires here'"
468
- [ "$status" -eq 0 ]
469
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
470
- [ "${#output}" -eq 0 ]
471
- }
472
-
473
- @test "P268 allow: git log --grep 'git commit' does NOT trigger gate (git log is leading)" {
474
- echo "# Problem 999" > docs/problems/open/999-x.md
475
- git add docs/problems/open/999-x.md
476
- run run_bash_hook "git log --grep 'git commit'"
477
- [ "$status" -eq 0 ]
478
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
479
- [ "${#output}" -eq 0 ]
480
- }
481
-
482
- @test "P268 allow: cat heredoc whose body contains 'git commit' does NOT trigger gate (iter-1 retro write surface)" {
483
- echo "# Problem 999" > docs/problems/open/999-x.md
484
- git add docs/problems/open/999-x.md
485
- # Inline-build JSON with embedded newlines via printf to mimic the
486
- # Bash tool's multi-line command payload (the canonical retro-write
487
- # surface that misfired in P268).
488
- local payload
489
- 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'}}))")
490
- run bash -c "echo '$payload' | bash $HOOK"
491
- [ "$status" -eq 0 ]
492
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
493
- [ "${#output}" -eq 0 ]
494
- }
495
-
496
- @test "P268 deny: actual git commit invocation with staged ticket still triggers gate (positive regression)" {
497
- echo "# Problem 999" > docs/problems/open/999-x.md
498
- git add docs/problems/open/999-x.md
499
- run run_bash_hook "git commit -m 'feat'"
500
- [ "$status" -eq 0 ]
501
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
502
- [[ "$output" == *"P165"* ]]
503
- }
504
-
505
- @test "P268 deny: cd <path> && git commit (prefix-strip path) still triggers gate" {
506
- echo "# Problem 999" > docs/problems/open/999-x.md
507
- git add docs/problems/open/999-x.md
508
- run run_bash_hook "cd . && git commit -m 'feat'"
509
- [ "$status" -eq 0 ]
510
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
511
- }
512
-
513
- @test "P268 deny: GIT_AUTHOR_NAME=Test git commit (env-prefix path) still triggers gate" {
514
- echo "# Problem 999" > docs/problems/open/999-x.md
515
- git add docs/problems/open/999-x.md
516
- run run_bash_hook "GIT_AUTHOR_NAME=Test git commit -m 'feat'"
517
- [ "$status" -eq 0 ]
518
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
519
- }
520
-
521
- @test "P268 allow: git commit-tree (boundary check — commit-tree is a different plumbing command)" {
522
- echo "# Problem 999" > docs/problems/open/999-x.md
523
- git add docs/problems/open/999-x.md
524
- run run_bash_hook "git commit-tree HEAD^{tree}"
525
- [ "$status" -eq 0 ]
526
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
527
- [ "${#output}" -eq 0 ]
528
- }
529
-
530
- # --- P265: RISK_BYPASS commit-message trailer allow-list bypass ---
531
- #
532
- # The ADR-031 layout-migration commit (lib/migrate-problems-layout.sh)
533
- # is a pure rename (flat docs/problems/NNN-*.<state>.md → per-state
534
- # subdir docs/problems/<state>/NNN-*.md) that legitimately stages NO
535
- # README refresh — the rename does not change README content (the table
536
- # references tickets by ID, not path). Its `RISK_BYPASS: adr-031-migration`
537
- # trailer (written via sequential `-m` paragraphs, so the literal token
538
- # appears in the `git commit` command argv) carries the policy
539
- # authorisation (ADR-031 § Backward Compatibility + ADR-013 Rule 6).
540
- # The hook must allow such commits silently. The bypass is an allow-list:
541
- # only the registered token bypasses; an unregistered RISK_BYPASS token
542
- # still denies. The recognition grep is kept identical to the sibling
543
- # risk-score-commit-gate.sh (P170 T11 precedent) so both commit gates
544
- # recognise the token the same way.
545
-
546
- @test "P265 allow: migration rename + RISK_BYPASS: adr-031-migration trailer → allow silently" {
547
- echo "# Problem 999 flat" > docs/problems/999-mig.open.md
548
- git add docs/problems/999-mig.open.md
549
- git -c commit.gpgsign=false commit --quiet -m "seed flat ticket"
550
- git mv docs/problems/999-mig.open.md docs/problems/open/999-mig.md
551
- run run_bash_hook "git commit -m 'docs(problems): auto-migrate to per-state subdirectory layout (ADR-031)' -m 'RISK_BYPASS: adr-031-migration'"
552
- [ "$status" -eq 0 ]
553
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
554
- # Bypass is an allow path — silent per ADR-045 Pattern 1.
555
- [ "${#output}" -eq 0 ]
556
- }
557
-
558
- @test "P265 deny: same migration rename WITHOUT the trailer still denies (negative control)" {
559
- echo "# Problem 999 flat" > docs/problems/999-mig.open.md
560
- git add docs/problems/999-mig.open.md
561
- git -c commit.gpgsign=false commit --quiet -m "seed flat ticket"
562
- git mv docs/problems/999-mig.open.md docs/problems/open/999-mig.md
563
- run run_bash_hook "git commit -m 'docs(problems): auto-migrate'"
564
- [ "$status" -eq 0 ]
565
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
566
- }
567
-
568
- @test "P265 deny: unregistered RISK_BYPASS token does NOT bypass (allow-list scope)" {
569
- echo "# Problem 999" > docs/problems/open/999-x.md
570
- git add docs/problems/open/999-x.md
571
- run run_bash_hook "git commit -m 'feat' -m 'RISK_BYPASS: some-other-thing'"
572
- [ "$status" -eq 0 ]
573
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
574
- }
575
-
576
- @test "P265 allow: registered trailer bypasses a newly-staged ticket too (bypass is staged-shape-agnostic)" {
577
- echo "# Problem 999" > docs/problems/open/999-x.md
578
- git add docs/problems/open/999-x.md
579
- run run_bash_hook "git commit -m 'docs(problems): auto-migrate' -m 'RISK_BYPASS: adr-031-migration'"
580
- [ "$status" -eq 0 ]
581
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
582
- [ "${#output}" -eq 0 ]
583
- }
584
-
585
- # --- P262: capture-deferred-readme RISK_BYPASS trailer allow-list bypass ---
586
- #
587
- # /wr-itil:capture-problem SKILL.md Step 6 deliberately stages ONLY the new
588
- # ticket file and NOT docs/problems/README.md — the deferred-README-refresh
589
- # contract is the load-bearing capture-time-speed distinction from
590
- # /wr-itil:manage-problem (ADR-032). The P165 gate treats any new ticket
591
- # file (status A) as ranking-bearing, so a bare capture commit was denied
592
- # 4×/session. Architect verdict (P262): register a reason-named token
593
- # `capture-deferred-readme` into the same P265 allow-list mechanism; the
594
- # capture commit carries `RISK_BYPASS: capture-deferred-readme` in its
595
- # message body. The carve-out is scoped to the TOKEN, not the commit
596
- # subject — a `docs(problems): capture` subject WITHOUT the trailer still
597
- # denies (proves the bypass cannot be self-claimed by message prose, and
598
- # manage-problem's full-intake `docs(problems): open` path still enforces
599
- # the refresh).
600
-
601
- @test "P262 allow: capture commit with RISK_BYPASS: capture-deferred-readme trailer → allow silently" {
602
- echo "# Problem 999" > docs/problems/open/999-capture.md
603
- git add docs/problems/open/999-capture.md
604
- run run_bash_hook "git commit -m 'docs(problems): capture P999 some-thing' -m 'RISK_BYPASS: capture-deferred-readme'"
605
- [ "$status" -eq 0 ]
606
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
607
- # Bypass is an allow path — silent per ADR-045 Pattern 1.
608
- [ "${#output}" -eq 0 ]
609
- }
610
-
611
- @test "P262 deny: capture-subject commit WITHOUT the trailer still denies (token-scoped, not subject-scoped)" {
612
- echo "# Problem 999" > docs/problems/open/999-capture.md
613
- git add docs/problems/open/999-capture.md
614
- run run_bash_hook "git commit -m 'docs(problems): capture P999 some-thing'"
615
- [ "$status" -eq 0 ]
616
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
617
- [[ "$output" == *"P165"* ]]
618
- }
619
-
620
- @test "P262 deny: subject merely contains the word capture but no registered trailer still denies" {
621
- echo "# Problem 999" > docs/problems/open/999-x.md
622
- git add docs/problems/open/999-x.md
623
- run run_bash_hook "git commit -m 'fix(itil): handle the capture edge case'"
624
- [ "$status" -eq 0 ]
625
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
626
- }
627
-
628
- @test "P262 deny: docs(problems): open (manage-problem full intake) without README still denies" {
629
- echo "# Problem 999" > docs/problems/open/999-x.md
630
- git add docs/problems/open/999-x.md
631
- run run_bash_hook "git commit -m 'docs(problems): open P999 some-thing'"
632
- [ "$status" -eq 0 ]
633
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
634
- }
635
-
636
- @test "P262 deny: unregistered capture-ish token does NOT bypass (allow-list scope)" {
637
- echo "# Problem 999" > docs/problems/open/999-x.md
638
- git add docs/problems/open/999-x.md
639
- run run_bash_hook "git commit -m 'docs(problems): capture P999 x' -m 'RISK_BYPASS: capture-something-else'"
640
- [ "$status" -eq 0 ]
641
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
642
- }