@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,181 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P268: lib/command-detect.sh — `command_invokes_git_commit` helper.
4
- #
5
- # Behavioural contract:
6
- # `command_invokes_git_commit "$cmd"` returns 0 iff `$cmd`, when
7
- # executed by bash, would actually invoke `git commit` as its
8
- # leading-effective command (after stripping common prefix shapes:
9
- # leading whitespace, env-var assignments, `cd <path> &&` prefix).
10
- # Returns 1 for any other command, including commands whose
11
- # argument vectors or heredoc bodies merely mention the literal
12
- # string "git commit".
13
- #
14
- # Replaces the substring-match `case "$COMMAND" in *"git commit"*) ;;`
15
- # pattern that 5 sibling PreToolUse:Bash hooks previously shared. The
16
- # substring match misfired on `grep -n 'git commit' file.md`,
17
- # `cat >> file <<EOF ... git commit ... EOF`, `echo "git commit ..."`,
18
- # `sed -i 's/git commit/.../' file`, `git log --grep 'git commit'`,
19
- # and similar non-commit Bash invocations whose arguments contained
20
- # the literal phrase (P268 ticket Description).
21
- #
22
- # Per P081 (behavioural tests): tests assert helper return code against
23
- # realistic command strings — NOT grep-against-source-content.
24
-
25
- setup() {
26
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
27
- HELPER="$SCRIPT_DIR/lib/command-detect.sh"
28
- # shellcheck source=../lib/command-detect.sh
29
- source "$HELPER"
30
- }
31
-
32
- # --- Positive cases (helper returns 0 — command IS a git commit) ---
33
-
34
- @test "positive: bare git commit -m" {
35
- run command_invokes_git_commit "git commit -m 'feat'"
36
- [ "$status" -eq 0 ]
37
- }
38
-
39
- @test "positive: git commit --amend" {
40
- run command_invokes_git_commit "git commit --amend"
41
- [ "$status" -eq 0 ]
42
- }
43
-
44
- @test "positive: git commit with no args" {
45
- run command_invokes_git_commit "git commit"
46
- [ "$status" -eq 0 ]
47
- }
48
-
49
- @test "positive: leading whitespace before git commit" {
50
- run command_invokes_git_commit " git commit -m 'feat'"
51
- [ "$status" -eq 0 ]
52
- }
53
-
54
- @test "positive: env-var prefix BYPASS_X=1 git commit" {
55
- run command_invokes_git_commit "BYPASS_README_REFRESH_GATE=1 git commit -m 'feat'"
56
- [ "$status" -eq 0 ]
57
- }
58
-
59
- @test "positive: multiple env-var prefixes git commit" {
60
- run command_invokes_git_commit "FOO=1 BAR=baz git commit -m 'feat'"
61
- [ "$status" -eq 0 ]
62
- }
63
-
64
- @test "positive: env-var with double-quoted value git commit" {
65
- run command_invokes_git_commit "GIT_AUTHOR_NAME=\"Test User\" git commit -m 'feat'"
66
- [ "$status" -eq 0 ]
67
- }
68
-
69
- @test "positive: env-var with single-quoted value git commit" {
70
- run command_invokes_git_commit "GIT_AUTHOR_NAME='Test User' git commit -m 'feat'"
71
- [ "$status" -eq 0 ]
72
- }
73
-
74
- @test "positive: cd <path> && git commit" {
75
- run command_invokes_git_commit "cd /tmp && git commit -m 'feat'"
76
- [ "$status" -eq 0 ]
77
- }
78
-
79
- @test "positive: cd <quoted path> && git commit" {
80
- run command_invokes_git_commit "cd \"/tmp/with spaces\" && git commit -m 'feat'"
81
- [ "$status" -eq 0 ]
82
- }
83
-
84
- @test "positive: env-var then cd then git commit (combined prefixes)" {
85
- run command_invokes_git_commit "BYPASS=1 cd /tmp && git commit -m 'feat'"
86
- [ "$status" -eq 0 ]
87
- }
88
-
89
- @test "positive: cd then env-var then git commit (reversed prefix order)" {
90
- run command_invokes_git_commit "cd /tmp && BYPASS=1 git commit -m 'feat'"
91
- [ "$status" -eq 0 ]
92
- }
93
-
94
- @test "positive: tab-indented git commit" {
95
- run command_invokes_git_commit $'\tgit commit -m feat'
96
- [ "$status" -eq 0 ]
97
- }
98
-
99
- # --- Negative cases (helper returns 1 — command is NOT a git commit) ---
100
-
101
- @test "negative: grep with 'git commit' as pattern argument" {
102
- run command_invokes_git_commit "grep -n 'git commit' file.md"
103
- [ "$status" -eq 1 ]
104
- }
105
-
106
- @test "negative: grep -rn 'git commit' (the recurring orchestrator surface)" {
107
- run command_invokes_git_commit "grep -rn 'git commit' packages/"
108
- [ "$status" -eq 1 ]
109
- }
110
-
111
- @test "negative: cat heredoc whose body contains the phrase git commit" {
112
- run command_invokes_git_commit $'cat >> docs/problems/README-history.md <<EOF\nWe added a git commit gate.\nEOF'
113
- [ "$status" -eq 1 ]
114
- }
115
-
116
- @test "negative: echo string containing git commit" {
117
- run command_invokes_git_commit "echo 'the git commit gate fires here'"
118
- [ "$status" -eq 1 ]
119
- }
120
-
121
- @test "negative: sed substitution mentioning git commit" {
122
- run command_invokes_git_commit "sed -i 's/git commit/git push/' file.md"
123
- [ "$status" -eq 1 ]
124
- }
125
-
126
- @test "negative: git log --grep 'git commit'" {
127
- run command_invokes_git_commit "git log --grep 'git commit'"
128
- [ "$status" -eq 1 ]
129
- }
130
-
131
- @test "negative: git status (other git subcommand)" {
132
- run command_invokes_git_commit "git status"
133
- [ "$status" -eq 1 ]
134
- }
135
-
136
- @test "negative: git push (other git subcommand)" {
137
- run command_invokes_git_commit "git push origin main"
138
- [ "$status" -eq 1 ]
139
- }
140
-
141
- @test "negative: git commit-tree (plumbing — boundary check)" {
142
- run command_invokes_git_commit "git commit-tree HEAD^{tree}"
143
- [ "$status" -eq 1 ]
144
- }
145
-
146
- @test "negative: empty command" {
147
- run command_invokes_git_commit ""
148
- [ "$status" -eq 1 ]
149
- }
150
-
151
- @test "negative: whitespace-only command" {
152
- run command_invokes_git_commit " "
153
- [ "$status" -eq 1 ]
154
- }
155
-
156
- @test "negative: cat with single-quoted body containing git commit phrase" {
157
- run command_invokes_git_commit "printf '%s\n' 'the git commit gate is here' > /tmp/out"
158
- [ "$status" -eq 1 ]
159
- }
160
-
161
- @test "negative: a different command chained before git commit (e.g. git add foo && git commit) — leading is git add, helper passes" {
162
- # Per Fix shape B narrow scope: helper only checks the leading-
163
- # effective command after prefix-strip. Mid-chain `git commit`
164
- # after a non-prefix-shape leading command (here `git add`) is a
165
- # documented false negative — standalone re-commit would re-trigger
166
- # detection. Acceptable per P268 ticket Description.
167
- run command_invokes_git_commit "git add foo && git commit -m 'feat'"
168
- [ "$status" -eq 1 ]
169
- }
170
-
171
- # --- Silence contract (ADR-045 Pattern 1) ---
172
-
173
- @test "helper emits zero bytes on stdout (pure exit-code contract)" {
174
- run command_invokes_git_commit "git commit -m 'feat'"
175
- [ "${#output}" -eq 0 ]
176
- }
177
-
178
- @test "helper emits zero bytes on stdout for negative case too" {
179
- run command_invokes_git_commit "grep -n 'git commit' file.md"
180
- [ "${#output}" -eq 0 ]
181
- }
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P085: itil-assistant-output-gate.sh UserPromptSubmit hook must detect
4
- # when the user's incoming prompt pins a direction / confirms a prior
5
- # ask / issues an act-verb, and inject a once-per-session MANDATORY
6
- # reminder instructing the assistant to act without asking — or use
7
- # AskUserQuestion for genuine ambiguity — and NEVER prose-ask.
8
- #
9
- # Per ADR-038: full block emits once per session; subsequent prompts
10
- # emit a terse reminder (<250 bytes) that keeps the MANDATORY signal,
11
- # the gate name, and the AskUserQuestion affordance.
12
- #
13
- # Per feedback_behavioural_tests.md (P081): these are behavioural
14
- # assertions — they simulate the hook's payload on stdin and assert
15
- # on what the hook emits, not on the source text of the hook file.
16
-
17
- setup() {
18
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
19
- HOOK="$REPO_ROOT/packages/itil/hooks/itil-assistant-output-gate.sh"
20
- SID="itil-gate-test-$$-$RANDOM"
21
- }
22
-
23
- teardown() {
24
- rm -f "/tmp/itil-assistant-gate-announced-${SID}"
25
- rm -f "/tmp/itil-assistant-gate-announced-${SID}-alt"
26
- }
27
-
28
- run_hook() {
29
- local sid="$1"
30
- local prompt="$2"
31
- echo "{\"session_id\":\"$sid\",\"prompt\":$(printf '%s' "$prompt" | jq -Rs .)}" | bash "$HOOK"
32
- }
33
-
34
- @test "gate: emits full MANDATORY block on first direction-pin prompt" {
35
- run run_hook "$SID" "yes, update P084 and verify the subagent tools thing"
36
- [ "$status" -eq 0 ]
37
- [ "${#output}" -gt 400 ]
38
- [[ "$output" == *"MANDATORY"* ]]
39
- [[ "$output" == *"AskUserQuestion"* ]]
40
- [[ "$output" == *"obvious"* ]] || [[ "$output" == *"act"* ]]
41
- }
42
-
43
- @test "gate: writes the announcement marker on first emission" {
44
- run run_hook "$SID" "go ahead and do it"
45
- [ "$status" -eq 0 ]
46
- [ -f "/tmp/itil-assistant-gate-announced-${SID}" ]
47
- }
48
-
49
- @test "gate: second direction-pin prompt in same session emits terse reminder only" {
50
- run_hook "$SID" "yes, please proceed" >/dev/null
51
- # Second prompt also pins direction (required for the gate to fire).
52
- run run_hook "$SID" "yes, go ahead"
53
- [ "$status" -eq 0 ]
54
- [ "${#output}" -lt 250 ]
55
- [[ "$output" == *"AskUserQuestion"* ]]
56
- # Full block is NOT re-emitted
57
- [[ "$output" != *"Canonical prose-ask phrasings"* ]]
58
- }
59
-
60
- @test "gate: terse reminder preserves MANDATORY / REQUIRED signal word" {
61
- run_hook "$SID" "yes" >/dev/null
62
- run run_hook "$SID" "act on this"
63
- [ "$status" -eq 0 ]
64
- [[ "$output" == *"MANDATORY"* ]] || [[ "$output" == *"REQUIRED"* ]] || [[ "$output" == *"NON-OPTIONAL"* ]]
65
- }
66
-
67
- @test "gate: different session_id re-emits the full block" {
68
- run_hook "$SID" "yes" >/dev/null
69
- local SID2="${SID}-alt"
70
- run run_hook "$SID2" "yes"
71
- [ "$status" -eq 0 ]
72
- [ "${#output}" -gt 400 ]
73
- rm -f "/tmp/itil-assistant-gate-announced-${SID2}"
74
- }
75
-
76
- @test "gate: empty session_id emits the full block and writes no marker" {
77
- run run_hook "" "yes, go"
78
- [ "$status" -eq 0 ]
79
- [ "${#output}" -gt 400 ]
80
- [ ! -f "/tmp/itil-assistant-gate-announced-" ]
81
- }
82
-
83
- @test "gate: non-direction-pinning prompt does not emit a block" {
84
- # A conversational prompt with no direction/act-verb/yes signal
85
- # should not burn the session-marker budget.
86
- run run_hook "$SID" "what does ADR-013 say about ambiguous decisions?"
87
- [ "$status" -eq 0 ]
88
- # May emit a short neutral note OR nothing, but must not emit the
89
- # full MANDATORY block on a non-direction prompt.
90
- [[ "$output" != *"MANDATORY: act"* ]] || [ "${#output}" -lt 250 ]
91
- # Must not have written the announcement marker either.
92
- [ ! -f "/tmp/itil-assistant-gate-announced-${SID}" ]
93
- }
94
-
95
- @test "gate: direction-pin via 'act now' verb triggers the block" {
96
- run run_hook "$SID" "act now and close the ticket"
97
- [ "$status" -eq 0 ]
98
- [ "${#output}" -gt 400 ]
99
- [[ "$output" == *"MANDATORY"* ]]
100
- }
101
-
102
- @test "gate: direction-pin via 'just do it' triggers the block" {
103
- run run_hook "$SID" "just do it"
104
- [ "$status" -eq 0 ]
105
- [ "${#output}" -gt 400 ]
106
- [[ "$output" == *"MANDATORY"* ]]
107
- }
108
-
109
- @test "gate: terse reminder references AskUserQuestion" {
110
- run_hook "$SID" "yes" >/dev/null
111
- run run_hook "$SID" "proceed"
112
- [ "$status" -eq 0 ]
113
- [[ "$output" == *"AskUserQuestion"* ]]
114
- }
@@ -1,224 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P085: itil-assistant-output-review.sh Stop hook reads the last
4
- # assistant turn from the transcript at `transcript_path` on stdin and
5
- # scans for canonical prose-ask phrasings ("Want me to", "Should I",
6
- # "Option A or Option B", etc.). When a prose-ask is detected, the
7
- # hook emits a stopReason JSON object with a nudge instructing the
8
- # assistant to re-emit via AskUserQuestion (or to act, if the decision
9
- # was obvious). Clean turns pass silently (no stopReason field).
10
- #
11
- # The Stop hook cannot rewrite the emitted turn — only nudge the next
12
- # turn — so its job is post-hoc detection + durable signal, per the
13
- # architect verdict for P085.
14
-
15
- setup() {
16
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
17
- HOOK="$REPO_ROOT/packages/itil/hooks/itil-assistant-output-review.sh"
18
- TMPDIR_="$(mktemp -d)"
19
- TRANSCRIPT="$TMPDIR_/transcript.jsonl"
20
- }
21
-
22
- teardown() {
23
- rm -rf "$TMPDIR_"
24
- }
25
-
26
- # Writes a JSONL transcript with a user message followed by an
27
- # assistant message. Claude Code transcript format: each line is a
28
- # JSON object with `type: user|assistant` and `message.content` being
29
- # either a string or an array of content blocks (text/tool_use).
30
- write_transcript() {
31
- local user_text="$1"
32
- local assistant_text="$2"
33
- {
34
- printf '{"type":"user","message":{"role":"user","content":%s}}\n' "$(printf '%s' "$user_text" | jq -Rs .)"
35
- printf '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":%s}]}}\n' "$(printf '%s' "$assistant_text" | jq -Rs .)"
36
- } > "$TRANSCRIPT"
37
- }
38
-
39
- run_hook() {
40
- echo "{\"session_id\":\"stop-test\",\"transcript_path\":$(printf '%s' "$TRANSCRIPT" | jq -Rs .)}" | bash "$HOOK"
41
- }
42
-
43
- @test "review: 'Want me to' in assistant text triggers stopReason nudge" {
44
- write_transcript "update the ticket" "Done. Want me to also commit now or wait?"
45
- run run_hook
46
- [ "$status" -eq 0 ]
47
- [[ "$output" == *"stopReason"* ]]
48
- [[ "$output" == *"AskUserQuestion"* ]]
49
- }
50
-
51
- @test "review: 'Should I' in assistant text triggers stopReason nudge" {
52
- write_transcript "look at this" "I see the issue. Should I fix it now or wait for review?"
53
- run run_hook
54
- [ "$status" -eq 0 ]
55
- [[ "$output" == *"stopReason"* ]]
56
- }
57
-
58
- @test "review: 'Would you like me to' triggers stopReason nudge" {
59
- write_transcript "ok" "Ticket updated. Would you like me to open a PR next?"
60
- run run_hook
61
- [ "$status" -eq 0 ]
62
- [[ "$output" == *"stopReason"* ]]
63
- }
64
-
65
- @test "review: 'Option A or Option B' triggers stopReason nudge" {
66
- write_transcript "plan this" "Two paths: Option A: do it inline. Or Option B: split into two commits. Which do you prefer?"
67
- run run_hook
68
- [ "$status" -eq 0 ]
69
- [[ "$output" == *"stopReason"* ]]
70
- }
71
-
72
- @test "review: '(a) / (b) / (c)?' triggers stopReason nudge" {
73
- write_transcript "go" "Three choices: (a) ship now, (b) wait for review, or (c) add tests first?"
74
- run run_hook
75
- [ "$status" -eq 0 ]
76
- [[ "$output" == *"stopReason"* ]]
77
- }
78
-
79
- @test "review: clean informative turn does not trigger stopReason" {
80
- write_transcript "what changed" "The last commit added three files: detectors.sh, gate.sh, and review.sh. No new decisions were introduced."
81
- run run_hook
82
- [ "$status" -eq 0 ]
83
- # Clean output: either empty or a JSON object WITHOUT stopReason.
84
- [[ "$output" != *"stopReason"* ]]
85
- }
86
-
87
- @test "review: assistant turn containing AskUserQuestion tool_use does NOT flag" {
88
- # If the assistant used the AskUserQuestion tool, that's the compliant
89
- # path — we must not nudge them for using prose inside the tool's
90
- # rendered question text (the tool surface is structured).
91
- {
92
- printf '{"type":"user","message":{"role":"user","content":"plan this"}}\n'
93
- cat <<'JSON'
94
- {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Here is the choice:"},{"type":"tool_use","name":"AskUserQuestion","input":{"question":"Which option?","options":[{"label":"A"},{"label":"B"}]}}]}}
95
- JSON
96
- } > "$TRANSCRIPT"
97
- run run_hook
98
- [ "$status" -eq 0 ]
99
- [[ "$output" != *"stopReason"* ]]
100
- }
101
-
102
- @test "review: missing transcript_path exits cleanly without error" {
103
- run bash -c 'echo "{\"session_id\":\"sid\"}" | bash "$1"' -- "$HOOK"
104
- [ "$status" -eq 0 ]
105
- [[ "$output" != *"stopReason"* ]]
106
- }
107
-
108
- @test "review: non-existent transcript file exits cleanly without error" {
109
- run bash -c "echo '{\"session_id\":\"sid\",\"transcript_path\":\"/tmp/does-not-exist-$RANDOM\"}' | bash '$HOOK'"
110
- [ "$status" -eq 0 ]
111
- [[ "$output" != *"stopReason"* ]]
112
- }
113
-
114
- @test "review: ask-when-obvious pattern triggers stopReason" {
115
- # Prior user message pins direction ("yes, update the ticket"), next
116
- # assistant turn ends with a question mark on an obvious-next-step.
117
- # This is Facet A of P085 — asking when the answer is obvious.
118
- write_transcript "yes, update the ticket with the findings" "Updated. Want me to commit and close the ticket now, or leave it open for review?"
119
- run run_hook
120
- [ "$status" -eq 0 ]
121
- [[ "$output" == *"stopReason"* ]]
122
- }
123
-
124
- @test "review: 'Shall we' triggers stopReason nudge" {
125
- write_transcript "look at options" "That's one route. Shall we go with it?"
126
- run run_hook
127
- [ "$status" -eq 0 ]
128
- [[ "$output" == *"stopReason"* ]]
129
- }
130
-
131
- @test "review: 'Do you want to' triggers stopReason nudge" {
132
- write_transcript "plan" "I can split into two PRs. Do you want to go that route?"
133
- run run_hook
134
- [ "$status" -eq 0 ]
135
- [[ "$output" == *"stopReason"* ]]
136
- }
137
-
138
- # 2026-04-28 regression evidence (P085 reopen, Citation 1).
139
- # Orchestrator main turn emitted a halt-summary ending with "Awaiting your
140
- # direction on whether to add it + resume on P123, or end the session."
141
- # The Stop hook should have caught this binary-choice prose-ask but the
142
- # pattern list did not match. Detector extension closes the gap.
143
- @test "review: 'Awaiting your direction on whether ... or ...' (Citation 1 shape) triggers stopReason" {
144
- write_transcript "ok" "Loop is still halted. Remaining open item: missing changeset for b9da37e. Awaiting your direction on whether to add it + resume on P123, or end the session."
145
- run run_hook
146
- [ "$status" -eq 0 ]
147
- [[ "$output" == *"stopReason"* ]]
148
- }
149
-
150
- @test "review: 'Awaiting your input' triggers stopReason nudge" {
151
- write_transcript "ok" "Plan staged. Awaiting your input on the next step."
152
- run run_hook
153
- [ "$status" -eq 0 ]
154
- [[ "$output" == *"stopReason"* ]]
155
- }
156
-
157
- @test "review: 'Pending your decision' triggers stopReason nudge" {
158
- write_transcript "review" "Refactor scoped to three files. Pending your decision before I continue."
159
- run run_hook
160
- [ "$status" -eq 0 ]
161
- [[ "$output" == *"stopReason"* ]]
162
- }
163
-
164
- @test "review: 'Once you confirm' triggers stopReason nudge" {
165
- write_transcript "look" "Rename ready. Once you confirm, I will proceed with the rename."
166
- run run_hook
167
- [ "$status" -eq 0 ]
168
- [[ "$output" == *"stopReason"* ]]
169
- }
170
-
171
- @test "review: 'Awaiting your response' triggers stopReason nudge" {
172
- write_transcript "go" "Two paths identified. Awaiting your response so I know which to take."
173
- run run_hook
174
- [ "$status" -eq 0 ]
175
- [[ "$output" == *"stopReason"* ]]
176
- }
177
-
178
- # P403: mechanical-step-framed-as-user-optional. When a skill contract
179
- # mandates a mechanical step, the agent must not re-surface it as a user
180
- # decision (or a budget-caution skip) in end-of-turn prose. The detector
181
- # fires only when a step-skip signal, a step/mechanical-pass reference,
182
- # AND an offloaded-justification closer all co-occur (the AND-discriminator).
183
-
184
- @test "review: P403 evidence 1 — 'Step 7 auto-release skipped — your call' triggers stopReason" {
185
- write_transcript "review the backlog" "Done. Step 7 auto-release skipped — your call whether to drain via /wr-risk-scorer:assess-release."
186
- run run_hook
187
- [ "$status" -eq 0 ]
188
- [[ "$output" == *"stopReason"* ]]
189
- [[ "$output" == *"mechanical"* ]]
190
- }
191
-
192
- @test "review: P403 evidence 2 — 'Skipped the full re-rank (Step 2) ... to stay within context budget' triggers stopReason" {
193
- write_transcript "review problems" "Skipped the full 40-ticket re-rank (Step 2), inbound-discovery pipeline (Step 4.5), and relevance-close pass (Step 4.6) to stay within context budget."
194
- run run_hook
195
- [ "$status" -eq 0 ]
196
- [[ "$output" == *"stopReason"* ]]
197
- }
198
-
199
- @test "review: AND-discriminator — optional closer WITHOUT a step-skip does not trigger stopReason" {
200
- # "up to you" is a legitimately user-owned decision with no skipped
201
- # mechanical step in context — must NOT fire (the inverse-P078 guard).
202
- write_transcript "which theme" "Both palettes meet contrast AA. It is up to you which one reads better for your brand."
203
- run run_hook
204
- [ "$status" -eq 0 ]
205
- [[ "$output" != *"stopReason"* ]]
206
- }
207
-
208
- @test "review: AND-discriminator — skipped step with a real reason (no offload) does not trigger stopReason" {
209
- # A skipped step reported with a substantive reason (not offloaded to the
210
- # user, not budget-caution) is a legitimate report, not the P403 defect.
211
- write_transcript "run it" "Step 2 skipped because its precondition is already satisfied by Step 1. Proceeding to Step 3."
212
- run run_hook
213
- [ "$status" -eq 0 ]
214
- [[ "$output" != *"stopReason"* ]]
215
- }
216
-
217
- @test "review: P403 nudge stays within the ADR-045 hook injection budget" {
218
- write_transcript "review the backlog" "Done. Step 7 auto-release skipped — your call whether to drain."
219
- run run_hook
220
- [ "$status" -eq 0 ]
221
- [[ "$output" == *"stopReason"* ]]
222
- # ADR-045 advisory band: keep the injected nudge well under 1000 bytes.
223
- [ "${#output}" -lt 1000 ]
224
- }
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P232: itil-bash-polling-antipattern-detect.sh PreToolUse:Bash hook
4
- # must deny bash polling loops that self-reference via `pgrep -f`
5
- # (parent class) or `pkill -0` (sibling), advising `wait $bg_pid` or
6
- # Bash-tool `BashOutput` polling instead.
7
- #
8
- # Detection shape: a loop construct (`until` / `while`) combined with a
9
- # polling mechanism (`pgrep -f` / `pkill -0`). One-shot `pgrep -f` (no
10
- # surrounding loop) is allowed — the polling shape is the antipattern,
11
- # not pgrep itself.
12
- #
13
- # Per ADR-005 / ADR-052 — bats live under packages/<plugin>/hooks/test/
14
- # and assert behaviour on emitted JSON, not source-content. Per
15
- # feedback_behavioural_tests.md (P081) — no source-grep on hook text.
16
-
17
- setup() {
18
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
19
- HOOK="$SCRIPT_DIR/itil-bash-polling-antipattern-detect.sh"
20
- }
21
-
22
- # Helper: simulate the PreToolUse:Bash payload on stdin.
23
- # Uses python to build the JSON so we don't escape-hell with bash.
24
- run_bash_hook() {
25
- local cmd="$1"
26
- python3 -c "
27
- import json, sys
28
- print(json.dumps({'tool_name': 'Bash', 'tool_input': {'command': sys.argv[1]}}))
29
- " "$cmd" | bash "$HOOK"
30
- }
31
-
32
- # --- Antipattern detection: positive cases (deny) ---
33
-
34
- @test "deny: until ! pgrep -f loop" {
35
- run run_bash_hook "until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done"
36
- [ "$status" -eq 0 ]
37
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
38
- [[ "$output" == *"P232"* ]]
39
- }
40
-
41
- @test "deny: while pgrep -f loop (positive form, no negation)" {
42
- run run_bash_hook "while pgrep -f 'long-running-job'; do sleep 2; done"
43
- [ "$status" -eq 0 ]
44
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
45
- [[ "$output" == *"P232"* ]]
46
- }
47
-
48
- @test "deny: until ! pkill -0 signal-0 poll" {
49
- run run_bash_hook "until ! pkill -0 -f 'worker'; do sleep 3; done"
50
- [ "$status" -eq 0 ]
51
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
52
- }
53
-
54
- @test "deny: while pkill -0 signal-0 poll" {
55
- run run_bash_hook "while pkill -0 12345; do sleep 1; done"
56
- [ "$status" -eq 0 ]
57
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
58
- }
59
-
60
- @test "deny: pgrep poll embedded in heredoc body" {
61
- # Heredoc body lands in the same tool_input.command string.
62
- run run_bash_hook "bash <<'EOF'
63
- until ! pgrep -f 'bats'; do sleep 5; done
64
- EOF"
65
- [ "$status" -eq 0 ]
66
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
67
- }
68
-
69
- @test "deny: P232 deadlock witness — multi-line shape with trailing tail" {
70
- run run_bash_hook "until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done; echo done; tail -30 /tmp/bats-out.log"
71
- [ "$status" -eq 0 ]
72
- [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
73
- }
74
-
75
- # --- Allow paths: legitimate non-polling uses ---
76
-
77
- @test "allow: one-shot pgrep -f without surrounding loop" {
78
- run run_bash_hook "pgrep -f 'nginx' && echo running"
79
- [ "$status" -eq 0 ]
80
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
81
- }
82
-
83
- @test "allow: one-shot pkill (no -0, real signal — not a poll)" {
84
- run run_bash_hook "pkill -TERM -f 'stale-worker'"
85
- [ "$status" -eq 0 ]
86
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
87
- }
88
-
89
- @test "allow: wait \$bg_pid (the canonical recovery shape)" {
90
- run run_bash_hook "bats --recursive packages/itil/hooks/test/ & wait \$!"
91
- [ "$status" -eq 0 ]
92
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
93
- }
94
-
95
- @test "allow: while loop without pgrep/pkill (unrelated)" {
96
- run run_bash_hook "while read line; do echo \$line; done < input.txt"
97
- [ "$status" -eq 0 ]
98
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
99
- }
100
-
101
- @test "allow: until loop without pgrep/pkill (unrelated)" {
102
- run run_bash_hook "until [ -f /tmp/sentinel ]; do sleep 1; done"
103
- [ "$status" -eq 0 ]
104
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
105
- }
106
-
107
- @test "allow: commit message text mentioning pgrep does not deny" {
108
- # The literal pair is in the commit message body, not a poll shape.
109
- # The hook should not over-match on commit prose.
110
- run run_bash_hook "git commit -m 'document pgrep antipattern in P232'"
111
- [ "$status" -eq 0 ]
112
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
113
- }
114
-
115
- # --- Tool-name filters ---
116
-
117
- @test "allow: non-Bash tool exits 0 without deny" {
118
- run bash -c "echo '{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"foo.md\"}}' | bash $HOOK"
119
- [ "$status" -eq 0 ]
120
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
121
- }
122
-
123
- # --- Parse / fail-open ---
124
-
125
- @test "allow: empty JSON fails open" {
126
- run bash -c "echo '{}' | bash $HOOK"
127
- [ "$status" -eq 0 ]
128
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
129
- }
130
-
131
- @test "allow: empty command field fails open" {
132
- run bash -c "echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"\"}}' | bash $HOOK"
133
- [ "$status" -eq 0 ]
134
- [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
135
- }
136
-
137
- # --- Deny message contract (ADR-038 progressive disclosure / ADR-045 budget) ---
138
-
139
- @test "deny message cites P232 + names BOTH recovery alternatives" {
140
- run run_bash_hook "until ! pgrep -f 'bats'; do sleep 5; done"
141
- [ "$status" -eq 0 ]
142
- [[ "$output" == *"P232"* ]]
143
- [[ "$output" == *"wait"* ]]
144
- [[ "$output" == *"BashOutput"* ]]
145
- }
146
-
147
- @test "deny message stays under ADR-045 deny-path budget (<700 bytes)" {
148
- # Voice-tone target ~245 bytes (sibling p057-staging-trap-detect.sh
149
- # precedent). ADR-045 deny-path band hard cap at 700 bytes keeps the
150
- # message terse — fail loudly if it bloats.
151
- run run_bash_hook "until ! pgrep -f 'bats'; do sleep 5; done"
152
- [ "$status" -eq 0 ]
153
- [ "${#output}" -lt 700 ]
154
- }