@mmerterden/multi-agent-pipeline 12.7.0 → 12.9.0

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 (247) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/install/_common.mjs +48 -0
  3. package/install/_dev-only-files.mjs +125 -5
  4. package/install/claude.mjs +14 -8
  5. package/install/copilot.mjs +5 -8
  6. package/package.json +17 -2
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  8. package/pipeline/lib/credential-store.sh +20 -0
  9. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  10. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  11. package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
  12. package/pipeline/multi-agent-refs/features/review-multi-repo.md +65 -0
  13. package/pipeline/multi-agent-refs/features/url-enrichment.md +93 -0
  14. package/pipeline/multi-agent-refs/phases/operations.md +28 -0
  15. package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -83
  16. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
  17. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
  18. package/pipeline/multi-agent-refs/phases/phase-4-review.md +50 -62
  19. package/pipeline/schemas/prefs.schema.json +6 -0
  20. package/pipeline/scripts/_smoke-root.sh +61 -0
  21. package/pipeline/scripts/agent-guard.py +57 -3
  22. package/pipeline/scripts/audit-log.sh +25 -0
  23. package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
  24. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
  25. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
  26. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
  27. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
  28. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
  29. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
  30. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
  31. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
  32. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
  33. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
  34. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
  35. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
  36. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
  37. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
  38. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
  39. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
  40. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
  41. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
  42. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
  43. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
  44. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
  45. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
  46. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
  47. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
  48. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
  49. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
  50. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
  51. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
  52. package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
  53. package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
  54. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
  55. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
  56. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
  57. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
  58. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
  59. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
  60. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
  61. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
  62. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
  63. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
  64. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
  65. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
  66. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
  67. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
  68. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
  69. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
  70. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
  71. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
  72. package/pipeline/eval/golden-tasks/README.md +0 -65
  73. package/pipeline/eval/intent-cases.json +0 -40
  74. package/pipeline/eval/run-metrics-fixture.json +0 -93
  75. package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
  76. package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
  77. package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
  78. package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
  79. package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
  80. package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
  81. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
  82. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
  83. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
  84. package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
  85. package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
  86. package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
  87. package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
  88. package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
  89. package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
  90. package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
  91. package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
  92. package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
  93. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
  94. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
  95. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
  96. package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
  97. package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
  98. package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
  99. package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
  100. package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
  101. package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
  102. package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
  103. package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
  104. package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
  105. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
  106. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
  107. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
  108. package/pipeline/eval/triage/README.md +0 -54
  109. package/pipeline/scripts/benchmark-phase-0.sh +0 -128
  110. package/pipeline/scripts/check-md-links.mjs +0 -88
  111. package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
  112. package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
  113. package/pipeline/scripts/eval-intent.mjs +0 -107
  114. package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
  115. package/pipeline/scripts/eval-triage.mjs +0 -171
  116. package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
  117. package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
  118. package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
  119. package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
  120. package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
  121. package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
  122. package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
  123. package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
  124. package/pipeline/scripts/lint-skills.mjs +0 -154
  125. package/pipeline/scripts/run-smokes.mjs +0 -130
  126. package/pipeline/scripts/scorecard.mjs +0 -258
  127. package/pipeline/scripts/smoke-add-detail.sh +0 -137
  128. package/pipeline/scripts/smoke-agent-guard.sh +0 -74
  129. package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
  130. package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
  131. package/pipeline/scripts/smoke-ask-choice.sh +0 -42
  132. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
  133. package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
  134. package/pipeline/scripts/smoke-changelog-version.sh +0 -47
  135. package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
  136. package/pipeline/scripts/smoke-channels-flow.sh +0 -130
  137. package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
  138. package/pipeline/scripts/smoke-clarify.sh +0 -148
  139. package/pipeline/scripts/smoke-command-inventory.sh +0 -81
  140. package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
  141. package/pipeline/scripts/smoke-community-gates.sh +0 -75
  142. package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
  143. package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
  144. package/pipeline/scripts/smoke-context-budget.sh +0 -72
  145. package/pipeline/scripts/smoke-cost-budget.sh +0 -70
  146. package/pipeline/scripts/smoke-cost-summary.sh +0 -139
  147. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
  148. package/pipeline/scripts/smoke-description-tr.sh +0 -82
  149. package/pipeline/scripts/smoke-dev-critic.sh +0 -144
  150. package/pipeline/scripts/smoke-diff-explain.sh +0 -147
  151. package/pipeline/scripts/smoke-diff-risk.sh +0 -190
  152. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
  153. package/pipeline/scripts/smoke-eval-live.sh +0 -136
  154. package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
  155. package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
  156. package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
  157. package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
  158. package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
  159. package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
  160. package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
  161. package/pipeline/scripts/smoke-generate-issue.sh +0 -120
  162. package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
  163. package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
  164. package/pipeline/scripts/smoke-install-layout.sh +0 -248
  165. package/pipeline/scripts/smoke-intent-guard.sh +0 -86
  166. package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
  167. package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
  168. package/pipeline/scripts/smoke-keychain.sh +0 -158
  169. package/pipeline/scripts/smoke-language-axis.sh +0 -109
  170. package/pipeline/scripts/smoke-learning-curve.sh +0 -61
  171. package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
  172. package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
  173. package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
  174. package/pipeline/scripts/smoke-md-links.sh +0 -8
  175. package/pipeline/scripts/smoke-md2confluence.sh +0 -126
  176. package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
  177. package/pipeline/scripts/smoke-migrate-state.sh +0 -102
  178. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
  179. package/pipeline/scripts/smoke-model-fallback.sh +0 -89
  180. package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
  181. package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
  182. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
  183. package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
  184. package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
  185. package/pipeline/scripts/smoke-pack-contents.sh +0 -140
  186. package/pipeline/scripts/smoke-pat-audit.sh +0 -128
  187. package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
  188. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
  189. package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
  190. package/pipeline/scripts/smoke-phase-banner.sh +0 -101
  191. package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
  192. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
  193. package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
  194. package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
  195. package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
  196. package/pipeline/scripts/smoke-plan-safety.sh +0 -139
  197. package/pipeline/scripts/smoke-plan-todos.sh +0 -196
  198. package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
  199. package/pipeline/scripts/smoke-pre-commit.sh +0 -170
  200. package/pipeline/scripts/smoke-pref-migration.sh +0 -226
  201. package/pipeline/scripts/smoke-prefs-language.sh +0 -134
  202. package/pipeline/scripts/smoke-progress-contract.sh +0 -127
  203. package/pipeline/scripts/smoke-prune-logs.sh +0 -137
  204. package/pipeline/scripts/smoke-purge.sh +0 -138
  205. package/pipeline/scripts/smoke-push-retry.sh +0 -75
  206. package/pipeline/scripts/smoke-repo-map.sh +0 -300
  207. package/pipeline/scripts/smoke-review-readiness.sh +0 -92
  208. package/pipeline/scripts/smoke-review-watch.sh +0 -146
  209. package/pipeline/scripts/smoke-routines.sh +0 -84
  210. package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
  211. package/pipeline/scripts/smoke-run-metrics.sh +0 -50
  212. package/pipeline/scripts/smoke-search.sh +0 -187
  213. package/pipeline/scripts/smoke-shadow-git.sh +0 -224
  214. package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
  215. package/pipeline/scripts/smoke-skill-language.sh +0 -83
  216. package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
  217. package/pipeline/scripts/smoke-skill-scan.sh +0 -198
  218. package/pipeline/scripts/smoke-source-parity.sh +0 -85
  219. package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
  220. package/pipeline/scripts/smoke-sync-parity.sh +0 -92
  221. package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
  222. package/pipeline/scripts/smoke-telemetry.sh +0 -147
  223. package/pipeline/scripts/smoke-test-gap.sh +0 -183
  224. package/pipeline/scripts/smoke-token-budget.sh +0 -67
  225. package/pipeline/scripts/smoke-token-preflight.sh +0 -82
  226. package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
  227. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
  228. package/pipeline/scripts/smoke-triage-memory.sh +0 -174
  229. package/pipeline/scripts/smoke-update-check.sh +0 -135
  230. package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
  231. package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
  232. package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
  233. package/pipeline/scripts/smoke-validator-gates.sh +0 -164
  234. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
  235. package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
  236. package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
  237. package/pipeline/scripts/smoke-work-summary.sh +0 -163
  238. package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
  239. package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
  240. package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
  241. package/pipeline/scripts/smoke-write-state.sh +0 -159
  242. package/pipeline/scripts/sync-parity-check.sh +0 -135
  243. package/pipeline/scripts/test-gap-rules/android.json +0 -25
  244. package/pipeline/scripts/test-gap-rules/ios.json +0 -34
  245. package/pipeline/scripts/test-gap-rules/node.json +0 -29
  246. package/pipeline/scripts/test-gap-rules/python.json +0 -25
  247. package/pipeline/scripts/validate-schemas.mjs +0 -88
@@ -1,163 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-work-summary.sh - v7.1.0
3
- #
4
- # Verifies the Phase 7 Work Summary renderer contract:
5
- # 1. Missing state + no flags → exit 2 (caller should skip section)
6
- # 2. Full state fixture → renders header, scope, review, phases
7
- # 3. Scope section marks ✅ for done tasks and ⏳ for pending
8
- # 4. Review outcome suppressed when all buckets empty
9
- # 5. Phase tick strip uses correct marks (completed ✅ · in_progress ▶ · skipped ⏭)
10
- # 6. Post-hoc invocation with --branch + --base-branch (no state file) still renders header
11
- # 7. prefs schema exposes reportContent.workSummary toggle
12
- # 8. channels.md documents the Work Summary option
13
- #
14
- # Exit 0 all pass, 1 any failure.
15
-
16
- set -euo pipefail
17
-
18
- ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
19
- RENDER="$ROOT/pipeline/scripts/render-work-summary.sh"
20
- SCHEMA="$ROOT/pipeline/schemas/prefs.schema.json"
21
- CHANNELS_DOC="$ROOT/pipeline/commands/multi-agent/channels/SKILL.md"
22
-
23
- pass=0; fail=0; failures=()
24
- record_pass() { pass=$((pass + 1)); printf ' \033[0;32mPASS\033[0m %s\n' "$1"; }
25
- record_fail() { fail=$((fail + 1)); failures+=("$1"); printf ' \033[0;31mFAIL\033[0m %s\n' "$1"; }
26
-
27
- printf '→ smoke-work-summary (v7.1.0): Phase 7 Work Summary renderer contract\n'
28
-
29
- # --- 1: missing state → exit 2 ---
30
- tmp=$(mktemp -d); pushd "$tmp" >/dev/null
31
- set +e
32
- bash "$RENDER" GHOST-1 >/dev/null 2>&1
33
- rc=$?
34
- set -e
35
- if [ "$rc" = "2" ]; then
36
- record_pass "exit 2 when no state + no flags"
37
- else
38
- record_fail "expected exit 2 for empty state (got $rc)"
39
- fi
40
- popd >/dev/null; rm -rf "$tmp"
41
-
42
- # --- 2-5: full fixture ---
43
- tmp=$(mktemp -d); pushd "$tmp" >/dev/null
44
- mkdir -p .worktrees/PROJ-2001
45
- cat > .worktrees/PROJ-2001/agent-state.json <<'JSON'
46
- {"taskId":"PROJ-2001","branch":"feature/PROJ-2001-checkout-v2","baseBranch":"develop","prNumber":"#256",
47
- "planTodos":[
48
- {"id":"T1","title":"Add checkout acceptance tests","status":"done"},
49
- {"id":"T2","title":"Wire CheckoutViewModel","status":"done"},
50
- {"id":"T3","title":"Optimize cart recomposition","status":"deferred"}
51
- ],
52
- "reviewConsensus":{
53
- "accepted":[{"severity":"blocking","file":"ViewModel.swift","line":42,"issue":"race","fix":"lock","reviewer":"opus"}],
54
- "deferred":[],
55
- "rejected":[{"finding":{"severity":"suggestion","file":"x.swift","line":0,"issue":"style","reviewer":"sonnet"},"reason":"style-only"}],
56
- "approved":false
57
- }}
58
- JSON
59
- cat > .worktrees/PROJ-2001/phase-tracker.json <<'JSON'
60
- {"taskId":"PROJ-2001","phases":{
61
- "0":{"status":"completed"},"1":{"status":"completed"},"2":{"status":"completed"},
62
- "3":{"status":"completed"},"4":{"status":"completed"},"5":{"status":"skipped"},
63
- "6":{"status":"completed"},"7":{"status":"in_progress"}
64
- }}
65
- JSON
66
- out=$(bash "$RENDER" PROJ-2001)
67
- popd >/dev/null; rm -rf "$tmp"
68
-
69
- if grep -q '^### Work Summary' <<< "$out" && \
70
- grep -q 'PROJ-2001' <<< "$out" && \
71
- grep -q 'feature/PROJ-2001-checkout-v2' <<< "$out" && \
72
- grep -q '#256' <<< "$out"; then
73
- record_pass "renders task header with jira+branch+base+PR"
74
- else
75
- record_fail "task header missing fields"
76
- fi
77
-
78
- if grep -q '✅ T1' <<< "$out" && grep -q '✅ T2' <<< "$out"; then
79
- record_pass "scope marks done tasks with ✅"
80
- else
81
- record_fail "done tasks not marked ✅ (output: $(echo "$out" | head -20))"
82
- fi
83
-
84
- # deferred is not "done" - should get ⏳
85
- if grep -qE '⏳ T3' <<< "$out"; then
86
- record_pass "scope marks non-done tasks with ⏳"
87
- else
88
- record_fail "non-done tasks not marked ⏳"
89
- fi
90
-
91
- if grep -q '1 accepted · 0 deferred · 1 rejected · approved=false' <<< "$out"; then
92
- record_pass "review outcome line matches triage counts"
93
- else
94
- record_fail "review outcome line wrong (expected '1 accepted · 0 deferred · 1 rejected · approved=false')"
95
- fi
96
-
97
- if grep -q '0 Init ✅' <<< "$out" && \
98
- grep -q '5 Test ⏭' <<< "$out" && \
99
- grep -q '7 Report ▶' <<< "$out"; then
100
- record_pass "phase tick strip renders correct marks"
101
- else
102
- record_fail "phase tick marks incorrect"
103
- fi
104
-
105
- # --- 4b: review outcome suppressed when empty ---
106
- tmp=$(mktemp -d); pushd "$tmp" >/dev/null
107
- mkdir -p .worktrees/PROJ-2002
108
- cat > .worktrees/PROJ-2002/agent-state.json <<'JSON'
109
- {"taskId":"PROJ-2002","branch":"fix/PROJ-2002","baseBranch":"main",
110
- "planTodos":[{"id":"T1","title":"tiny fix","status":"done"}],
111
- "reviewConsensus":{"accepted":[],"deferred":[],"rejected":[],"approved":true}}
112
- JSON
113
- out=$(bash "$RENDER" PROJ-2002)
114
- popd >/dev/null; rm -rf "$tmp"
115
-
116
- if ! grep -q '#### Review outcome' <<< "$out"; then
117
- record_pass "review outcome hidden when all buckets empty"
118
- else
119
- record_fail "review outcome should be hidden when empty"
120
- fi
121
-
122
- # --- 6: post-hoc flags, no state ---
123
- tmp=$(mktemp -d); pushd "$tmp" >/dev/null
124
- out=$(bash "$RENDER" POST-HOC-1 --branch "bugfix/post-hoc" --base-branch develop 2>&1 || true)
125
- popd >/dev/null; rm -rf "$tmp"
126
- if grep -q '^### Work Summary' <<< "$out" && grep -q 'POST-HOC-1' <<< "$out"; then
127
- record_pass "post-hoc flags render header without state"
128
- else
129
- record_fail "post-hoc invocation should still render header"
130
- fi
131
-
132
- # --- 7: prefs schema ---
133
- if jq -e '.properties.global.properties.reportContent.properties.workSummary.default == false' "$SCHEMA" >/dev/null 2>&1; then
134
- record_pass "prefs schema exposes reportContent.workSummary (default false)"
135
- else
136
- record_fail "prefs schema missing reportContent.workSummary"
137
- fi
138
-
139
- # --- 8: channels.md docs ---
140
- if grep -q '### Work Summary' "$CHANNELS_DOC" && \
141
- grep -q 'Yapılan iş özeti' "$CHANNELS_DOC" && \
142
- grep -q 'Work summary generation' "$CHANNELS_DOC"; then
143
- record_pass "channels.md documents Work Summary content option + menu row + generation spec"
144
- else
145
- record_fail "channels.md missing Work Summary docs"
146
- fi
147
-
148
- # --- 9: v7.1.1 preview-first flow documented ---
149
- if grep -q 'Step 3a' "$CHANNELS_DOC" && \
150
- grep -q 'summary-first' "$CHANNELS_DOC" && \
151
- grep -q 'auto-tick' "$CHANNELS_DOC"; then
152
- record_pass "channels.md documents v7.1.1 preview-before-select flow"
153
- else
154
- record_fail "channels.md missing Step 3a summary-first flow docs"
155
- fi
156
-
157
- printf '\n══ work-summary smoke: %d passed, %d failed ══\n' "$pass" "$fail"
158
- if [ "$fail" -gt 0 ]; then
159
- printf '\nFailures:\n'
160
- for m in "${failures[@]}"; do printf ' - %s\n' "$m"; done
161
- exit 1
162
- fi
163
- exit 0
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # smoke-workflow-audit.sh - security + correctness audit of
4
- # `.github/workflows/` with the two dedicated external linters:
5
- #
6
- # - zizmor GitHub Actions security auditor (template injection,
7
- # credential persistence, cache poisoning, unpinned actions,
8
- # excessive permissions, ...). Runs offline, no network.
9
- # - actionlint workflow correctness linter (expression syntax, matrix
10
- # typos, shellcheck of run: blocks, ...).
11
- #
12
- # Any finding from either tool fails the smoke.
13
- #
14
- # WHY THIS IS STRICT BY DEFAULT
15
- #
16
- # Until now a missing binary produced a SKIP and the smoke exited 0, so it
17
- # reported "0 passed, 0 failed, 2 skipped" on the dev machine AND on the CI
18
- # runner - neither had the tools. ci-lite.yml justified not installing them by
19
- # saying to run the smoke locally "where the tools exist", but they did not
20
- # exist there either. The audit had therefore never run anywhere, while
21
- # appearing as a green step. When it was finally run it found nine real issues,
22
- # one of them high severity (a dependency cache restored into the job that
23
- # signs and publishes the release tarball).
24
- #
25
- # So the failure mode is inverted: absent tooling is a failure, and skipping is
26
- # something you opt into deliberately and locally.
27
- #
28
- # brew install zizmor actionlint # macOS
29
- # pipx install zizmor && go install github.com/rhysd/actionlint/cmd/actionlint@latest
30
- #
31
- # WORKFLOW_AUDIT_ALLOW_MISSING=1 downgrades a missing binary back to a SKIP for
32
- # a local run. It is ignored when CI is set: CI installs both, pinned, and a
33
- # runner that cannot must fail loudly rather than pass quietly.
34
- #
35
- # Structural baseline checks that need no external binary live in
36
- # smoke-ci-workflows.sh - this smoke is the deeper, tool-backed layer.
37
- #
38
- # Exit codes: 0=pass, 1=findings or missing tooling, 2=workflows missing
39
-
40
- set -uo pipefail
41
-
42
- REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
43
- WF_DIR="$REPO_ROOT/.github/workflows"
44
-
45
- [ -d "$WF_DIR" ] || { echo "FAIL: .github/workflows/ missing"; exit 2; }
46
-
47
- # In CI the escape hatch does not apply, whatever the environment says.
48
- ALLOW_MISSING="${WORKFLOW_AUDIT_ALLOW_MISSING:-0}"
49
- [ -n "${CI:-}" ] && ALLOW_MISSING=0
50
-
51
- PASS=0
52
- FAIL=0
53
- SKIPPED=0
54
- pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
55
- fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
56
- skip() { SKIPPED=$((SKIPPED + 1)); echo " ↷ SKIP: $1"; }
57
-
58
- # A tool that is not installed is a failure unless a local run opted out.
59
- missing_tool() {
60
- local tool="$1" install="$2"
61
- if [ "$ALLOW_MISSING" = "1" ]; then
62
- skip "$tool not on PATH, WORKFLOW_AUDIT_ALLOW_MISSING=1 ($install)"
63
- else
64
- fail "$tool not on PATH - the audit cannot run ($install)"
65
- fi
66
- }
67
-
68
- # ──────────────────────────────────────────────────────────────────────────
69
- echo "→ 1. zizmor (security audit, offline)"
70
- if command -v zizmor >/dev/null 2>&1; then
71
- # Flag names shifted across zizmor releases (--offline vs --no-online-audits);
72
- # probe --help so the smoke works with whichever is installed.
73
- HELP=$(zizmor --help 2>&1 || true)
74
- OFFLINE_FLAG="--offline"
75
- echo "$HELP" | grep -q -- '--offline' || OFFLINE_FLAG="--no-online-audits"
76
- if OUT=$(zizmor "$WF_DIR" --min-severity medium "$OFFLINE_FLAG" 2>&1); then
77
- pass "zizmor: no findings at medium+ severity"
78
- else
79
- fail "zizmor reported findings (medium+ severity)"
80
- echo "$OUT" | sed 's/^/ /'
81
- fi
82
- else
83
- missing_tool zizmor "brew install zizmor"
84
- fi
85
-
86
- # ──────────────────────────────────────────────────────────────────────────
87
- echo "→ 2. actionlint (workflow correctness)"
88
- if command -v actionlint >/dev/null 2>&1; then
89
- if OUT=$(cd "$REPO_ROOT" && actionlint 2>&1); then
90
- pass "actionlint: no findings"
91
- else
92
- fail "actionlint reported findings"
93
- echo "$OUT" | sed 's/^/ /'
94
- fi
95
- else
96
- missing_tool actionlint "brew install actionlint"
97
- fi
98
-
99
- echo ""
100
- echo "══ smoke-workflow-audit: $PASS passed, $FAIL failed, $SKIPPED skipped ══"
101
- [ "$FAIL" -gt 0 ] && exit 1 || exit 0
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # smoke-worktree-path-convention.sh
4
- #
5
- # Asserts the pipeline never creates worktrees outside `{projectRoot}/{worktreeBasePath}/{taskId}`.
6
- # Per `feedback_worktree_path_convention.md`:
7
- # - Worktree is ALWAYS under the project root, NEVER under $HOME directly.
8
- # - Default basePath is `.worktrees`, override via `prefs.global.worktreeBasePath`.
9
- # - Multi-repo tasks: each repo gets its own independent worktree under that repo's root.
10
- #
11
- # This is a static-analysis lint, not a runtime probe. It greps the pipeline
12
- # source for forbidden worktree paths in shell scripts, markdown contracts,
13
- # and orchestrator command files. A behavioural check (live `git worktree list`)
14
- # is out of scope - that would require a real run.
15
-
16
- set -euo pipefail
17
-
18
- SELF_DIR=$(cd "$(dirname "$0")" && pwd)
19
- PIPELINE_ROOT=$(cd "$SELF_DIR/.." && pwd)
20
-
21
- cd "$PIPELINE_ROOT"
22
-
23
- FAIL=0
24
-
25
- # Forbidden patterns: any worktree path that lives under $HOME directly,
26
- # or uses repo-name--taskId style outside a project root.
27
- declare -a FORBIDDEN=(
28
- '\$HOME/\.worktrees' # $HOME/.worktrees/foo - wrong (must be under projectRoot)
29
- '~/\.worktrees' # ~/.worktrees/foo - same
30
- '\$HOME/[a-zA-Z0-9_-]+--[a-zA-Z0-9_-]+' # $HOME/repo--taskid pattern
31
- '~/[a-zA-Z0-9_-]+--[a-zA-Z0-9_-]+' # ~/repo--taskid pattern
32
- 'git worktree add.*\$HOME/[^.]' # `git worktree add $HOME/something`
33
- 'git worktree add.*~/[^/]' # `git worktree add ~/something`
34
- )
35
-
36
- # In-scope files: shell + markdown phase contracts + orchestrator skills.
37
- TARGETS=(
38
- "scripts/*.sh"
39
- "scripts/*.mjs"
40
- "lib/*.sh"
41
- "commands/multi-agent/SKILL.md"
42
- "commands/multi-agent/*.md"
43
- "multi-agent-refs/*.md"
44
- "multi-agent-refs/phases/*.md"
45
- "skills/shared/core/multi-agent/SKILL.md"
46
- )
47
-
48
- # Allowlist file paths - reference docs that mention the wrong pattern only
49
- # to teach the rule are tagged with `# allow: worktree-bad-example` annotation
50
- # on the offending line.
51
- for pattern in "${FORBIDDEN[@]}"; do
52
- for glob in "${TARGETS[@]}"; do
53
- while IFS= read -r match; do
54
- [ -z "$match" ] && continue
55
- file="${match%%:*}"
56
- # Allow lines that are rule-explanation examples (annotated).
57
- if echo "$match" | grep -q '# allow: worktree-bad-example'; then continue; fi
58
- # Allow this very smoke gate's own forbidden-pattern array.
59
- case "$file" in
60
- */smoke-worktree-path-convention.sh) continue ;;
61
- */feedback_worktree_path_convention.md) continue ;;
62
- esac
63
- echo "FAIL [smoke-worktree-path-convention]: forbidden worktree path in $file"
64
- echo " $match"
65
- FAIL=$((FAIL + 1))
66
- done < <(grep -rEn "$pattern" "pipeline/$glob" 2>/dev/null || true)
67
- done
68
- done
69
-
70
- # Positive assertion: phase-0-init.md MUST mention the convention text.
71
- PHASE0="multi-agent-refs/phases/phase-0-init.md"
72
- if [ -f "pipeline/$PHASE0" ]; then
73
- if ! grep -qE 'projectRoot.*worktreeBasePath|\.worktrees/.*taskId' "pipeline/$PHASE0"; then
74
- echo "FAIL [smoke-worktree-path-convention]: phase-0-init.md must document {projectRoot}/{worktreeBasePath}/{taskId} convention"
75
- FAIL=$((FAIL + 1))
76
- fi
77
- fi
78
-
79
- if [ "$FAIL" -gt 0 ]; then
80
- echo ""
81
- echo "smoke-worktree-path-convention: $FAIL violation(s)."
82
- echo "Worktree path contract: {projectRoot}/{worktreeBasePath default .worktrees}/{taskId}. Never under \$HOME directly. See feedback_worktree_path_convention.md."
83
- exit 1
84
- fi
85
-
86
- echo "smoke-worktree-path-convention: ok"
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-wrapper-preservation.sh - local-only alias wrappers must survive install/update.
3
- #
4
- # Local-only wrappers (frontmatter `local-only: true`) live ONLY under
5
- # ~/.claude/commands/multi-agent/ (never in pipeline/, never synced). install
6
- # wipes that namespace before copying from pipeline; without the snapshot+restore
7
- # carve-out in install/claude.mjs installCommands, wrappers are destroyed on every
8
- # run. This asserts they survive a second install.
9
-
10
- set -uo pipefail
11
- HERE="$(cd "$(dirname "$0")" && pwd)"
12
- ROOT="$(cd "$HERE/../.." && pwd)"
13
- INSTALL_JS="$ROOT/install.js"
14
-
15
- PASS=0; FAIL=0
16
- pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
17
- fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
18
-
19
- TMP="$(mktemp -d)"
20
- trap 'rm -rf "$TMP"' EXIT
21
-
22
- echo "→ 1. first install lays down the namespace"
23
- if HOME="$TMP" node "$INSTALL_JS" --claude >/dev/null 2>&1; then
24
- pass "install --claude (1st) exited 0"
25
- else
26
- fail "install --claude (1st) failed"
27
- echo "══ wrapper-preservation smoke: $PASS passed, $FAIL failed ══"; exit 1
28
- fi
29
-
30
- WRAP_DIR="$TMP/.claude/commands/multi-agent/zz-wrapper-probe"
31
- mkdir -p "$WRAP_DIR"
32
- cat > "$WRAP_DIR/SKILL.md" <<'EOF'
33
- ---
34
- description: "probe local-only wrapper"
35
- allowed-tools: Skill
36
- local-only: true
37
- ---
38
- Use the Skill tool to invoke `some-plugin:some-skill`.
39
- EOF
40
- [ -f "$WRAP_DIR/SKILL.md" ] && pass "seeded a local-only wrapper" || fail "could not seed wrapper"
41
-
42
- echo ""
43
- echo "→ 2. second install must PRESERVE the wrapper"
44
- if HOME="$TMP" node "$INSTALL_JS" --claude >/dev/null 2>&1; then
45
- pass "install --claude (2nd) exited 0"
46
- else
47
- fail "install --claude (2nd) failed"
48
- fi
49
- if [ -f "$WRAP_DIR/SKILL.md" ] && grep -q '^local-only: true' "$WRAP_DIR/SKILL.md"; then
50
- pass "local-only wrapper survived re-install"
51
- else
52
- fail "local-only wrapper was DESTROYED by re-install (carve-out regressed)"
53
- fi
54
-
55
- echo ""
56
- echo "→ 3. a normal (non-local-only) stray dir is still wiped"
57
- STRAY="$TMP/.claude/commands/multi-agent/zz-stray-probe"
58
- mkdir -p "$STRAY"; printf -- '---\ndescription: "stray"\n---\n' > "$STRAY/SKILL.md"
59
- HOME="$TMP" node "$INSTALL_JS" --claude >/dev/null 2>&1
60
- if [ -f "$STRAY/SKILL.md" ]; then
61
- fail "non-local-only stray survived (wipe too weak)"
62
- else
63
- pass "non-local-only stray correctly wiped"
64
- fi
65
-
66
- echo ""
67
- echo "══ wrapper-preservation smoke: $PASS passed, $FAIL failed ══"
68
- [ "$FAIL" -eq 0 ]
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-write-state.sh - verify write-state.mjs atomic write + merge + lock behavior.
3
-
4
- set -uo pipefail
5
-
6
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
- WRITER="$SCRIPT_DIR/write-state.mjs"
8
-
9
- if [ ! -f "$WRITER" ]; then
10
- echo "FAIL: write-state.mjs not found" >&2
11
- exit 1
12
- fi
13
-
14
- PASS=0
15
- FAIL=0
16
- pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
17
- fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
18
-
19
- TMPDIR=$(mktemp -d) || { echo "FAIL: mktemp" >&2; exit 1; }
20
- trap 'rm -rf "$TMPDIR"' EXIT
21
-
22
- STATE="$TMPDIR/state.json"
23
-
24
- # 1. Initial write creates file
25
- echo '{"currentPhase":0,"status":"in_progress"}' | node "$WRITER" "$STATE"
26
- if [ -f "$STATE" ] && grep -q '"currentPhase": 0' "$STATE"; then
27
- pass "initial write creates file"
28
- else
29
- fail "initial write missing/malformed"
30
- fi
31
-
32
- # 2. Merge preserves existing fields
33
- echo '{"currentPhase":3}' | node "$WRITER" "$STATE"
34
- if grep -q '"currentPhase": 3' "$STATE" && grep -q '"status": "in_progress"' "$STATE"; then
35
- pass "merge preserves untouched fields"
36
- else
37
- fail "merge dropped fields"
38
- fi
39
-
40
- # 3. Nested merge
41
- echo '{"phases":{"3":{"status":"done"}}}' | node "$WRITER" "$STATE"
42
- echo '{"phases":{"4":{"status":"in_progress"}}}' | node "$WRITER" "$STATE"
43
- if grep -q '"3"' "$STATE" && grep -q '"4"' "$STATE"; then
44
- pass "nested merge preserves sibling keys"
45
- else
46
- fail "nested merge overwrote sibling keys"
47
- fi
48
-
49
- # 4. --replace overwrites completely
50
- echo '{"currentPhase":7}' | node "$WRITER" --replace "$STATE"
51
- if ! grep -q '"status"' "$STATE" && grep -q '"currentPhase": 7' "$STATE"; then
52
- pass "--replace overwrites full state"
53
- else
54
- fail "--replace did not fully overwrite"
55
- fi
56
-
57
- # 5. Invalid JSON exits 1
58
- echo 'not-json' | node "$WRITER" "$TMPDIR/other.json" 2>/dev/null
59
- if [ $? -eq 1 ]; then
60
- pass "invalid JSON exits 1"
61
- else
62
- fail "invalid JSON did not exit 1"
63
- fi
64
-
65
- # 6. Empty stdin exits 1
66
- : | node "$WRITER" "$TMPDIR/empty.json" 2>/dev/null
67
- if [ $? -eq 1 ]; then
68
- pass "empty stdin exits 1"
69
- else
70
- fail "empty stdin did not exit 1"
71
- fi
72
-
73
- # 7. Missing path arg exits 3
74
- echo '{}' | node "$WRITER" 2>/dev/null
75
- if [ $? -eq 3 ]; then
76
- pass "missing path exits 3"
77
- else
78
- fail "missing path did not exit 3"
79
- fi
80
-
81
- # 8. Atomic - no .tmp leftover after success
82
- if ls "$TMPDIR"/.state.json.*.tmp 2>/dev/null | head -1 | grep -q .; then
83
- fail "leftover .tmp file after success"
84
- else
85
- pass "no .tmp leftover after successful write"
86
- fi
87
-
88
- # 9. No .lock leftover after success
89
- if [ -f "$STATE.lock" ]; then
90
- fail "leftover .lock file after success"
91
- else
92
- pass "no .lock leftover after successful write"
93
- fi
94
-
95
- # 10. Concurrent writers don't corrupt state
96
- #
97
- # Was 5 writers, which lost an update roughly one run in five on a loaded
98
- # machine and never on an idle one, so it read as a flaky test rather than the
99
- # data-loss bug it was. 20 writers puts enough writers in the acquire window to
100
- # make a regression show up on an idle machine too.
101
- STATE2="$TMPDIR/concurrent.json"
102
- WRITERS=20
103
- echo '{"counter":0}' | node "$WRITER" "$STATE2"
104
- for i in $(seq 1 "$WRITERS"); do
105
- echo "{\"w$i\":$i}" | node "$WRITER" "$STATE2" &
106
- done
107
- wait
108
- MISSING=0
109
- for i in $(seq 1 "$WRITERS"); do
110
- grep -q "\"w$i\": $i" "$STATE2" || MISSING=$((MISSING+1))
111
- done
112
- if [ "$MISSING" -eq 0 ]; then
113
- pass "$WRITERS concurrent writers all persisted"
114
- else
115
- fail "$MISSING of $WRITERS concurrent writes lost"
116
- fi
117
-
118
- # 11. A lock with no readable PID must not be treated as stale
119
- #
120
- # This is the exact shape of the bug above. The lock used to be created empty
121
- # and filled on the next line, so a writer that arrived in between read an
122
- # unparseable PID, called the lock stale, and deleted it while its owner was
123
- # still working. Both then wrote and one update vanished.
124
- #
125
- # An empty lock now has to block until the staleness window elapses, so a
126
- # writer given a 1s acquire budget must give up with exit 2 rather than help
127
- # itself to the lock.
128
- STATE3="$TMPDIR/emptylock.json"
129
- echo '{"seed":1}' | node "$WRITER" "$STATE3"
130
- : > "$STATE3.lock"
131
- LOCK_RC=0
132
- echo '{"stolen":true}' | WRITE_STATE_LOCK_TIMEOUT_MS=1000 WRITE_STATE_LOCK_STALE_MS=30000 \
133
- node "$WRITER" "$STATE3" >/dev/null 2>&1 || LOCK_RC=$?
134
- if [ "$LOCK_RC" -eq 2 ] && ! grep -q '"stolen"' "$STATE3"; then
135
- pass "empty lock is not stolen (exit 2, state untouched)"
136
- else
137
- fail "empty lock was treated as stale (exit $LOCK_RC) - the live-lock race is back"
138
- fi
139
- rm -f "$STATE3.lock"
140
-
141
- # 12. A lock older than the staleness window is still reclaimed
142
- #
143
- # The counterpart to 11: refusing to reclaim an unreadable PID must not mean a
144
- # genuinely abandoned lock blocks forever.
145
- STATE4="$TMPDIR/stalelock.json"
146
- echo '{"seed":1}' | node "$WRITER" "$STATE4"
147
- : > "$STATE4.lock"
148
- STALE_RC=0
149
- echo '{"recovered":true}' | WRITE_STATE_LOCK_TIMEOUT_MS=3000 WRITE_STATE_LOCK_STALE_MS=1 \
150
- node "$WRITER" "$STATE4" >/dev/null 2>&1 || STALE_RC=$?
151
- if [ "$STALE_RC" -eq 0 ] && grep -q '"recovered"' "$STATE4"; then
152
- pass "abandoned lock past the staleness window is reclaimed"
153
- else
154
- fail "stale lock not reclaimed (exit $STALE_RC) - writers would wedge"
155
- fi
156
-
157
- echo ""
158
- echo "══ write-state smoke: $PASS passed, $FAIL failed ══"
159
- [ "$FAIL" -eq 0 ] || exit 1