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