@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,421 +0,0 @@
1
- #!/usr/bin/env bats
2
- # Behavioural test: work-problems Step 5 backgrounded-poll-loop dispatch fires
3
- # SIGTERM on the iteration subprocess when LAST_ACTIVITY_MARK has been stale
4
- # longer than WORK_PROBLEMS_IDLE_TIMEOUT_S (default 3600s = 60 min). The SIGTERM
5
- # empirically produces a clean JSON exit-flush per the 2026-04-25 P118 iter 5
6
- # evidence captured in P121 (and in docs/briefing/afk-subprocess.md).
7
- #
8
- # This is the second-source the architect addendum required: the SIGTERM-flushes-
9
- # JSON evidence is otherwise single-source from one production observation. The
10
- # fake-claude shim here re-creates the stuck-subprocess shape (emit JSON to stdout,
11
- # then sleep past the threshold while remaining killable by SIGTERM) and asserts
12
- # the orchestrator-shape harness's poll-and-sigterm behaviour matches the
13
- # contract documented in SKILL.md Step 5.
14
- #
15
- # @problem P121
16
- # @jtbd JTBD-006
17
- # @jtbd JTBD-001
18
- #
19
- # Cross-reference:
20
- # P121 (orchestrator should SIGTERM stuck claude -p subprocesses after idle-
21
- # timeout — and SIGTERM appears to flush a clean JSON) — driver ticket
22
- # ADR-032 (governance skill invocation patterns — subprocess-boundary variant
23
- # amended 2026-04-26 with the backgrounded-poll-loop refinement)
24
- # ADR-037 (skill testing strategy — behavioural is the default; doc-lint
25
- # contract assertions are the Permitted Exception)
26
- # docs/briefing/afk-subprocess.md (P121 entry — the cross-session knowledge
27
- # index entry this fixture provides empirical second-source for)
28
-
29
- setup() {
30
- TEST_TMP="$(mktemp -d)"
31
- FAKE_BIN="${TEST_TMP}/bin"
32
- mkdir -p "$FAKE_BIN"
33
-
34
- # Fake `claude` binary that simulates a stuck iteration subprocess: emits a
35
- # valid `claude -p --output-format json` envelope to stdout, then sleeps for
36
- # FAKE_SLEEP_AFTER seconds (default 30s) while trapping SIGTERM. This matches
37
- # the 2026-04-25 P118 iter 5 shape: subprocess completes its semantic work,
38
- # then sits in an idle-wait state until SIGTERM unblocks it. The trap exits 0
39
- # with the JSON already flushed to stdout — same observable as the production
40
- # CLI behaviour that motivated P121.
41
- cat > "$FAKE_BIN/claude" <<'FAKE_EOF'
42
- #!/usr/bin/env bash
43
- # Test fake for work-problems Step 5 idle-timeout SIGTERM bats fixture.
44
- # Emits a JSON envelope then sleeps; SIGTERM exits cleanly (JSON already flushed).
45
- trap 'exit 0' TERM
46
- printf '%s\n' '{"is_error":false,"result":"ITERATION_SUMMARY\nticket_id: P000\nticket_title: fake\naction: worked\noutcome: investigated\ncommitted: false\nreason: test fixture\nremaining_backlog_count: 0\nnotes: stuck-subprocess simulation","total_cost_usd":0.01,"duration_ms":100,"usage":{"input_tokens":10,"output_tokens":20,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}'
47
- sleep "${FAKE_SLEEP_AFTER:-30}"
48
- FAKE_EOF
49
- chmod +x "$FAKE_BIN/claude"
50
- export PATH="$FAKE_BIN:$PATH"
51
-
52
- SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
53
- SKILL_FILE="${SKILL_DIR}/SKILL.md"
54
- }
55
-
56
- teardown() {
57
- if [ -n "${TEST_TMP:-}" ] && [ -d "$TEST_TMP" ]; then
58
- rm -rf "$TEST_TMP"
59
- fi
60
- }
61
-
62
- # Faithful re-implementation of SKILL.md Step 5's backgrounded-poll-loop dispatch.
63
- # Adopters who copy-paste the SKILL.md Step 5 block into their orchestrator
64
- # should observe the same outcomes this harness does — that's the contract this
65
- # fixture pins. The harness uses sleep 1 instead of the SKILL.md's sleep 60 so
66
- # the test wall-clock stays bounded; the LAST_ACTIVITY_MARK math, SIGTERM action,
67
- # and JSON-after-SIGTERM read are otherwise identical.
68
- dispatch_with_poll() {
69
- local json_file="${TEST_TMP}/iter.json"
70
- local idle_timeout_s="${WORK_PROBLEMS_IDLE_TIMEOUT_S:-3600}"
71
- local dispatch_start_epoch
72
- dispatch_start_epoch=$(date +%s)
73
- local sigterm_sent=0
74
-
75
- : > "$json_file"
76
- claude -p --permission-mode bypassPermissions --output-format json "TEST" \
77
- < /dev/null > "$json_file" 2>&1 &
78
- local iter_pid=$!
79
-
80
- while kill -0 "$iter_pid" 2>/dev/null; do
81
- sleep 1
82
- local now
83
- now=$(date +%s)
84
- # LAST_ACTIVITY_MARK = max(DISPATCH_START, last commit timestamp).
85
- # In this test there is no git repo and no commits, so the max is just
86
- # DISPATCH_START — same shape as a real skip-iteration that produces no
87
- # commit during its run.
88
- local last_activity_mark=$dispatch_start_epoch
89
- local idle_seconds=$(( now - last_activity_mark ))
90
- if (( idle_seconds > idle_timeout_s )) && (( sigterm_sent == 0 )); then
91
- kill -TERM "$iter_pid" 2>/dev/null || true
92
- sigterm_sent=1
93
- fi
94
- done
95
-
96
- wait "$iter_pid" 2>/dev/null || true
97
-
98
- printf 'SIGTERM_SENT=%d\n' "$sigterm_sent"
99
- printf '%s\n' '---JSON---'
100
- cat "$json_file"
101
- }
102
-
103
- # (a) SIGTERM was sent within the threshold.
104
- @test "P121: SIGTERM fires when subprocess idle exceeds WORK_PROBLEMS_IDLE_TIMEOUT_S" {
105
- export FAKE_SLEEP_AFTER=10
106
- export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
107
- run dispatch_with_poll
108
- [ "$status" -eq 0 ]
109
- [[ "$output" == *"SIGTERM_SENT=1"* ]]
110
- }
111
-
112
- # (b) JSON arrives after SIGTERM (clean exit-flush).
113
- @test "P121: JSON arrives after SIGTERM and parses cleanly (clean exit-flush per 2026-04-25 P118 iter 5)" {
114
- export FAKE_SLEEP_AFTER=10
115
- export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
116
- run dispatch_with_poll
117
- [ "$status" -eq 0 ]
118
- [[ "$output" == *"SIGTERM_SENT=1"* ]]
119
- # Extract the JSON portion after the ---JSON--- marker and validate.
120
- json_payload=$(printf '%s\n' "$output" | sed -n '/^---JSON---$/,$p' | tail -n +2)
121
- printf '%s' "$json_payload" | python3 -c '
122
- import json, sys
123
- j = json.loads(sys.stdin.read().strip())
124
- assert not j.get("is_error"), "is_error should be false"
125
- assert "ITERATION_SUMMARY" in j["result"], "result must carry ITERATION_SUMMARY"
126
- assert "total_cost_usd" in j, "cost metadata must survive SIGTERM exit-flush"
127
- '
128
- }
129
-
130
- # (c) Env-var override is honoured (default 3600s; override to 2s).
131
- @test "P121: WORK_PROBLEMS_IDLE_TIMEOUT_S env-var override is honoured" {
132
- # Without an override, the default 3600s would never fire in test wall-clock,
133
- # so no SIGTERM. With WORK_PROBLEMS_IDLE_TIMEOUT_S=2, SIGTERM fires within
134
- # seconds. Confirms the override is consulted by the harness, matching the
135
- # SKILL.md contract that adopters can tune the threshold per-environment.
136
- export FAKE_SLEEP_AFTER=10
137
- export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
138
- run dispatch_with_poll
139
- [ "$status" -eq 0 ]
140
- [[ "$output" == *"SIGTERM_SENT=1"* ]]
141
- }
142
-
143
- # (d) Within-threshold runs are NOT SIGTERMed (negative case).
144
- @test "P121: within-threshold runs are NOT SIGTERMed (subprocess exits before idle threshold)" {
145
- # Subprocess exits naturally in 1 second; idle timeout is 60s. Loop must
146
- # observe the natural exit and NOT send SIGTERM. Guards against an over-eager
147
- # poll loop that would interrupt every iteration regardless of state.
148
- export FAKE_SLEEP_AFTER=1
149
- export WORK_PROBLEMS_IDLE_TIMEOUT_S=60
150
- run dispatch_with_poll
151
- [ "$status" -eq 0 ]
152
- [[ "$output" == *"SIGTERM_SENT=0"* ]]
153
- }
154
-
155
- # Doc-lint contract assertions — pin SKILL.md prose to the contract this fixture
156
- # exercises behaviourally. Permitted Exception under ADR-037 (the SKILL.md is
157
- # the contract document; these assertions guard against silent prose drift away
158
- # from the behavioural expectation above).
159
-
160
- @test "P121: SKILL.md Step 5 names WORK_PROBLEMS_IDLE_TIMEOUT_S env var" {
161
- run grep -nE "WORK_PROBLEMS_IDLE_TIMEOUT_S" "$SKILL_FILE"
162
- [ "$status" -eq 0 ]
163
- }
164
-
165
- @test "P121: SKILL.md Step 5 documents SIGTERM-on-idle action" {
166
- run grep -niE "SIGTERM.{0,80}idle|idle.{0,80}SIGTERM|kill[[:space:]]+-TERM" "$SKILL_FILE"
167
- [ "$status" -eq 0 ]
168
- }
169
-
170
- @test "P121: SKILL.md Step 5 names LAST_ACTIVITY_MARK signal" {
171
- run grep -nE "LAST_ACTIVITY_MARK|last activity mark" "$SKILL_FILE"
172
- [ "$status" -eq 0 ]
173
- }
174
-
175
- @test "P121: SKILL.md Step 5 cites P121 (idle-timeout SIGTERM driver)" {
176
- run grep -nE "P121" "$SKILL_FILE"
177
- [ "$status" -eq 0 ]
178
- }
179
-
180
- @test "P121: SKILL.md Step 5 documents the LAST_ACTIVITY signal trade-off (skip-iteration case)" {
181
- # Per architect amendment 3: signal trade-off must be explicit so future
182
- # contributors don't silently re-rate it. The SKILL.md prose names the
183
- # max(dispatch_start, last commit) shape so adopters know skip iterations
184
- # are bounded by IDLE_TIMEOUT_S since dispatch start, not a stale commit
185
- # timestamp.
186
- run grep -niE "max.{0,40}(dispatch.?start|DISPATCH_START).{0,80}(commit|git log)|skip.?iteration.{0,80}(timeout|threshold|bounded)|dispatch.?start.{0,80}upper.?bound" "$SKILL_FILE"
187
- [ "$status" -eq 0 ]
188
- }
189
-
190
- @test "P121: SKILL.md Step 5 dispatch backgrounds the subprocess (PID capture for poll)" {
191
- # The dispatch command shape must show the backgrounded form (& + $!) so the
192
- # poll loop has a PID to kill -0 / kill -TERM. Foreground synchronous
193
- # dispatch (current pre-P121 shape) cannot support idle-timeout SIGTERM.
194
- run grep -nE 'ITER_PID=\$!|& *\n*ITER_PID|claude -p.{0,200}&[[:space:]]*$' "$SKILL_FILE"
195
- [ "$status" -eq 0 ]
196
- }
197
-
198
- # ---------------------------------------------------------------------------
199
- # P147 stuck-before-emit subclass: P121's "SIGTERM produces clean JSON exit-
200
- # flush" claim was empirically grounded only against subprocesses that had
201
- # ALREADY emitted ITERATION_SUMMARY before going idle (the P118 evidence). The
202
- # 2026-04-29 P146 incident falsified the generalisation: a subprocess that
203
- # deadlocked BEFORE ITERATION_SUMMARY emission produced exit 143 + a 0-byte
204
- # JSON file when SIGTERMed. `claude -p --output-format json` writes the entire
205
- # response as a single blob ON normal exit; SIGTERM-before-blob-write means no
206
- # JSON is ever written.
207
- #
208
- # The fixture below exercises the stuck-before-emit shape with a fake `claude`
209
- # that traps SIGTERM and exits WITHOUT writing any stdout. The orchestrator-
210
- # shape harness then SIGTERMs after the idle threshold, and the assertions
211
- # pin: (a) the JSON file is 0 bytes (the metadata-loss-event indicator the
212
- # SKILL.md prose now warns adopters to watch for), and (b) SIGTERM was sent
213
- # (the recovery primitive still fires — the bug is in the prose claim about
214
- # what flushes, not in the SIGTERM action itself).
215
- #
216
- # @problem P147
217
-
218
- dispatch_with_poll_no_emit() {
219
- local json_file="${TEST_TMP}/iter.json"
220
- local idle_timeout_s="${WORK_PROBLEMS_IDLE_TIMEOUT_S:-3600}"
221
- local dispatch_start_epoch
222
- dispatch_start_epoch=$(date +%s)
223
- local sigterm_sent=0
224
-
225
- : > "$json_file"
226
- claude_no_emit -p --permission-mode bypassPermissions --output-format json "TEST" \
227
- < /dev/null > "$json_file" 2>&1 &
228
- local iter_pid=$!
229
-
230
- while kill -0 "$iter_pid" 2>/dev/null; do
231
- sleep 1
232
- local now
233
- now=$(date +%s)
234
- local last_activity_mark=$dispatch_start_epoch
235
- local idle_seconds=$(( now - last_activity_mark ))
236
- if (( idle_seconds > idle_timeout_s )) && (( sigterm_sent == 0 )); then
237
- kill -TERM "$iter_pid" 2>/dev/null || true
238
- sigterm_sent=1
239
- fi
240
- done
241
-
242
- wait "$iter_pid" 2>/dev/null || true
243
-
244
- local json_bytes
245
- json_bytes=$(wc -c < "$json_file" | tr -d ' ')
246
-
247
- printf 'SIGTERM_SENT=%d\n' "$sigterm_sent"
248
- printf 'JSON_BYTES=%s\n' "$json_bytes"
249
- }
250
-
251
- setup_no_emit_shim() {
252
- cat > "$FAKE_BIN/claude_no_emit" <<'FAKE_EOF'
253
- #!/usr/bin/env bash
254
- # Test fake for work-problems Step 5 P147 stuck-before-emit fixture.
255
- # Traps SIGTERM and exits 0 WITHOUT emitting any stdout. Mirrors the 2026-
256
- # 04-29 P146 incident shape: subprocess deadlocked before ITERATION_SUMMARY
257
- # emission; SIGTERM cannot flush a JSON blob the CLI never produced.
258
- trap 'exit 0' TERM
259
- sleep "${FAKE_SLEEP_AFTER:-30}"
260
- FAKE_EOF
261
- chmod +x "$FAKE_BIN/claude_no_emit"
262
- }
263
-
264
- @test "P147: SIGTERM-before-emit produces 0-byte JSON (stuck-before-emit subclass)" {
265
- setup_no_emit_shim
266
- export FAKE_SLEEP_AFTER=10
267
- export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
268
- run dispatch_with_poll_no_emit
269
- [ "$status" -eq 0 ]
270
- [[ "$output" == *"SIGTERM_SENT=1"* ]]
271
- [[ "$output" == *"JSON_BYTES=0"* ]]
272
- }
273
-
274
- @test "P147: SKILL.md Step 5 names the conditional caveat for SIGTERM exit-flush" {
275
- # The prose claim must NOT generalise the P118 clean-flush observation
276
- # universally; it must explicitly condition on ITERATION_SUMMARY having been
277
- # emitted before SIGTERM. Adopters reading the SKILL.md need to know that
278
- # SIGTERM-before-emit produces a 0-byte JSON, not a clean flush. Require
279
- # the failure-mode language explicitly so a future drift that removes the
280
- # caveat but happens to mention ITERATION_SUMMARY in a different sentence
281
- # cannot keep this assertion green.
282
- run grep -niE "stuck.?before.?emit|before ITERATION_SUMMARY|ITERATION_SUMMARY.{0,80}not.{0,40}(yet|been).{0,40}emit|conditional.{0,40}(caveat|on)" "$SKILL_FILE"
283
- [ "$status" -eq 0 ]
284
- }
285
-
286
- @test "P147: SKILL.md Step 5 documents metadata-loss-event handling (git-evidence + halt + billing-dashboard)" {
287
- # When SIGTERM-before-emit is observed (exit 143 + 0-byte JSON), the
288
- # orchestrator must verify work integrity from independent evidence (git
289
- # log + git status), halt the AFK loop per exit-code semantics, and
290
- # reconstruct cost from the Anthropic billing dashboard. This guards
291
- # against orchestrators silently treating exit-143-no-JSON as a normal
292
- # iteration completion.
293
- run grep -niE "metadata.?loss|git log.{0,80}git status|billing dashboard|reconstruct cost" "$SKILL_FILE"
294
- [ "$status" -eq 0 ]
295
- }
296
-
297
- @test "P147: SKILL.md Step 5 cites P147 (conditional-caveat ticket)" {
298
- run grep -nE "P147" "$SKILL_FILE"
299
- [ "$status" -eq 0 ]
300
- }
301
-
302
- # ---------------------------------------------------------------------------
303
- # P307 machine-sleep false-kill subclass: P121's IDLE_SECONDS = NOW -
304
- # LAST_ACTIVITY_MARK computation is wall-clock time. When the host machine
305
- # suspends/sleeps between the 60s polls, wall-clock advances while the iter
306
- # subprocess is itself suspended (no actual idle work). On resume, IDLE_SECONDS
307
- # jumps past the threshold and SIGTERM fires on a subprocess that was
308
- # genuinely making progress, not stuck. The 2026-05-26 evidence: poll log
309
- # idle jumped non-linearly 481s -> 1016s -> 5544s across suspend gaps,
310
- # SIGTERM at idle=5544s > 3600s threshold, exit 143 + 0-byte JSON (the
311
- # P147 stuck-before-emit metadata-loss class).
312
- #
313
- # Fix: detect large wall-clock jumps between consecutive polls (>> 60s
314
- # expected) as suspend events and shift LAST_ACTIVITY_MARK forward by the
315
- # gap-minus-expected so IDLE_SECONDS approximates active-elapsed rather
316
- # than wall-clock-elapsed. Pure-bash heuristic — no monotonic-clock
317
- # dependency.
318
- #
319
- # @problem P307
320
-
321
- # Pure-bash helper mirroring SKILL.md Step 5 suspend-detect math. Tests
322
- # below pin the algorithm against parameter combinations exercising
323
- # (a) normal poll cadence (no shift), (b) within-jitter delay (no shift),
324
- # (c) detected suspend (shift forward by actual-minus-expected),
325
- # (d) reproduction of the 2026-05-26 5544s evidence (large shift absorbs
326
- # the gap). The shape returned is the EFFECTIVE LAST_ACTIVITY_MARK such
327
- # that IDLE_SECONDS = NOW - effective_mark yields active-elapsed.
328
- compute_effective_mark() {
329
- local prev_mark="$1"
330
- local prev_poll="$2"
331
- local now="$3"
332
- local expected_delta="${4:-60}"
333
- local jitter="${5:-120}"
334
-
335
- local actual_delta=$(( now - prev_poll ))
336
- local threshold=$(( expected_delta + jitter ))
337
- if (( actual_delta > threshold )); then
338
- printf '%d\n' $(( prev_mark + actual_delta - expected_delta ))
339
- else
340
- printf '%d\n' "$prev_mark"
341
- fi
342
- }
343
-
344
- @test "P307: normal poll cadence (60s actual delta) does NOT shift LAST_ACTIVITY_MARK" {
345
- # 60s between polls is the expected `sleep 60` cadence; no suspend; mark
346
- # unchanged. Guards against an over-eager heuristic that would shift on
347
- # every normal poll.
348
- run compute_effective_mark 1000 0 60 60 120
349
- [ "$status" -eq 0 ]
350
- [ "$output" = "1000" ]
351
- }
352
-
353
- @test "P307: within-jitter delay (90s actual delta) does NOT shift LAST_ACTIVITY_MARK" {
354
- # 90s between polls is mild jitter (slow hook, GC pause, brief load
355
- # spike); below the 60+120=180s suspend threshold; mark unchanged.
356
- # Bounded noise must not trigger a shift.
357
- run compute_effective_mark 1000 0 90 60 120
358
- [ "$status" -eq 0 ]
359
- [ "$output" = "1000" ]
360
- }
361
-
362
- @test "P307: at-threshold delay (180s actual delta) does NOT shift LAST_ACTIVITY_MARK" {
363
- # Exactly at expected+jitter is the boundary; strict-greater-than test
364
- # means no shift at the boundary. Adopters tuning the jitter window
365
- # know 180s == EXPECTED_POLL_DELTA_S + SUSPEND_JITTER_S is the inclusive
366
- # ceiling of the no-shift band.
367
- run compute_effective_mark 1000 0 180 60 120
368
- [ "$status" -eq 0 ]
369
- [ "$output" = "1000" ]
370
- }
371
-
372
- @test "P307: detected suspend (300s actual delta) shifts mark forward by actual-minus-expected" {
373
- # 300s between polls vastly exceeds the 180s threshold; treat as suspend
374
- # event and shift mark forward by 300-60=240s. Effect: IDLE_SECONDS
375
- # (NOW - effective_mark) reads 60s instead of 300s, preserving the
376
- # subprocess from a wall-clock false-kill.
377
- run compute_effective_mark 1000 0 300 60 120
378
- [ "$status" -eq 0 ]
379
- [ "$output" = "1240" ]
380
- }
381
-
382
- @test "P307: reproduces 2026-05-26 iter 1 evidence (5544s suspend gap shifts mark to absorb)" {
383
- # Concrete reproduction of the production observation: poll saw idle
384
- # jump to 5544s after a multi-hour laptop suspend. Without suspend-detect,
385
- # SIGTERM fires at 5544s > 3600s threshold. With suspend-detect, mark
386
- # shifts forward by 5544-60=5484s; IDLE_SECONDS = 5544 - 5484 = 60s,
387
- # below threshold; iter survives.
388
- run compute_effective_mark 0 0 5544 60 120
389
- [ "$status" -eq 0 ]
390
- [ "$output" = "5484" ]
391
- }
392
-
393
- @test "P307: SKILL.md Step 5 documents the suspend-detect heuristic" {
394
- # Prose must name the heuristic so adopters reading the SKILL.md know
395
- # how the poll loop survives machine-sleep without inventing one.
396
- # Accept any of: "suspend-detect", "wall-clock jump", "machine sleep",
397
- # "machine-sleep", or the constants EXPECTED_POLL_DELTA_S /
398
- # SUSPEND_JITTER_S / SUSPEND_OFFSET_S that name the construct.
399
- run grep -niE "suspend.?detect|wall.?clock jump|machine.?sleep|EXPECTED_POLL_DELTA_S|SUSPEND_JITTER_S|SUSPEND_OFFSET_S" "$SKILL_FILE"
400
- [ "$status" -eq 0 ]
401
- }
402
-
403
- @test "P307: SKILL.md Step 5 cites P307 (machine-sleep false-kill driver)" {
404
- run grep -nE "P307" "$SKILL_FILE"
405
- [ "$status" -eq 0 ]
406
- }
407
-
408
- @test "P307: SKILL.md Step 5 trade-off paragraph names suspend-detect alongside skip-iter trade-off" {
409
- # The LAST_ACTIVITY_MARK signal trade-off paragraph (existing at L410)
410
- # enumerates alternatives considered and rejected (mtime, RSS). The
411
- # suspend-detect addition belongs in the same locus per architect
412
- # review — keeps the rationale chain (P121 -> P147 -> trade-off ->
413
- # P307 suspend-detect) reading linearly rather than fragmenting into
414
- # a separate section. Assert the trade-off paragraph names both
415
- # SUSPEND_OFFSET_S (the accumulator) AND the EXPECTED_POLL_DELTA_S +
416
- # SUSPEND_JITTER_S threshold so the rationale chain is complete.
417
- run grep -nE "LAST_ACTIVITY_MARK signal trade-off" "$SKILL_FILE"
418
- [ "$status" -eq 0 ]
419
- run grep -niE "SUSPEND_OFFSET_S|suspend.?offset" "$SKILL_FILE"
420
- [ "$status" -eq 0 ]
421
- }