session-orchestrator 5.12.0 → 5.13.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 (385) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/agents/explorer.toml +1 -1
  4. package/.codex-plugin/agents/session-reviewer.toml +2 -2
  5. package/.codex-plugin/agents/wave-worker.toml +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/.orchestrator/policy/blocked-commands.json +8 -0
  9. package/.orchestrator/policy/public-export.json +30 -0
  10. package/CHANGELOG.md +162 -4
  11. package/README.md +126 -127
  12. package/agents/analyst.md +7 -2
  13. package/agents/architect-reviewer.md +7 -2
  14. package/agents/code-implementer.md +4 -2
  15. package/agents/db-specialist.md +9 -2
  16. package/agents/dialectic-deriver.md +9 -3
  17. package/agents/docs-writer.md +7 -2
  18. package/agents/eval-judge.md +7 -1
  19. package/agents/qa-strategist.md +7 -2
  20. package/agents/security-reviewer.md +9 -2
  21. package/agents/session-reviewer.md +18 -2
  22. package/agents/skill-applied-judge.md +7 -1
  23. package/agents/test-writer.md +9 -2
  24. package/agents/ui-developer.md +9 -2
  25. package/agents/ux-evaluator.md +6 -1
  26. package/commands/session.md +1 -1
  27. package/commands/templates-ack.md +89 -18
  28. package/docs/agent-authoring.md +30 -11
  29. package/docs/ci-setup.md +38 -2
  30. package/docs/codex-setup.md +33 -9
  31. package/docs/components.md +13 -2
  32. package/docs/cursor-setup.md +3 -3
  33. package/docs/events-schema.md +5 -4
  34. package/docs/evidence-candidates.md +63 -0
  35. package/docs/fleet-checkin-audit.md +7 -4
  36. package/docs/github-mirror-protection.md +150 -3
  37. package/docs/install.md +90 -17
  38. package/docs/instruction-delivery.md +1 -1
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/mods.md +118 -0
  41. package/docs/owner-config-schema.md +1 -0
  42. package/docs/plugin-architecture-v3.md +1 -1
  43. package/docs/pm-skills-marketplace.md +4 -3
  44. package/docs/rule-authoring.md +24 -13
  45. package/docs/scope-collision-guard.md +2 -1
  46. package/docs/session-config-reference.md +49 -15
  47. package/docs/session-config-template.md +30 -22
  48. package/docs/sessions-ledger-repair.md +137 -0
  49. package/docs/vault-docs-architecture.md +1 -1
  50. package/hooks/_lib/hook-import-set.json +406 -73
  51. package/hooks/_lib/hook-root.mjs +301 -0
  52. package/hooks/_lib/lock-bootstrap.mjs +434 -4
  53. package/hooks/_lib/orchestrator-ignore.mjs +3 -0
  54. package/hooks/_lib/subagent-transcript.mjs +2 -2
  55. package/hooks/_lib/vcs-create-matcher.mjs +13 -17
  56. package/hooks/cwd-change-restore.mjs +5 -1
  57. package/hooks/enforce-commands.mjs +4 -3
  58. package/hooks/enforce-scope.mjs +11 -1
  59. package/hooks/hooks-codex.json +48 -2
  60. package/hooks/hooks.json +31 -31
  61. package/hooks/on-session-end.mjs +38 -15
  62. package/hooks/on-session-start.mjs +139 -49
  63. package/hooks/on-stop.mjs +11 -6
  64. package/hooks/post-bash-issue-budget-refund.mjs +6 -1
  65. package/hooks/post-bash-write-verify.mjs +8 -4
  66. package/hooks/post-edit-validate.mjs +45 -52
  67. package/hooks/post-subagent-discovery-validator.mjs +2 -2
  68. package/hooks/post-tool-batch-wave-signal.mjs +8 -4
  69. package/hooks/post-tool-failure-corrective-context.mjs +5 -1
  70. package/hooks/pre-bash-destructive-guard.mjs +220 -8
  71. package/hooks/pre-bash-issue-budget.mjs +6 -1
  72. package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
  73. package/hooks/pre-bash-staging-fence.mjs +258 -14
  74. package/hooks/pre-bash-templates-first.mjs +28 -27
  75. package/hooks/run-node.sh +1 -1
  76. package/hooks/skill-invocation-telemetry.mjs +15 -8
  77. package/hooks/subagent-telemetry.mjs +77 -14
  78. package/hooks/wave-scope-commit-guard.mjs +143 -14
  79. package/mods/so-hud/.claude-plugin/plugin.json +11 -0
  80. package/mods/so-hud/README.md +57 -0
  81. package/mods/so-hud/hooks/beacon-io.mjs +101 -0
  82. package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
  83. package/mods/so-hud/hooks/hooks.json +1 -0
  84. package/mods/so-hud/hooks/logic.ts +182 -0
  85. package/mods/so-hud/hooks/register.tsx +670 -0
  86. package/mods/so-hud/hooks/rules.ts +2998 -0
  87. package/mods/so-hud/tsconfig.json +8 -0
  88. package/mods/so-hud/types/index.d.ts +36 -0
  89. package/package.json +4 -2
  90. package/rules/_index.md +1 -1
  91. package/rules/always-on/ask-via-tool.md +1 -1
  92. package/rules/always-on/loop-and-monitor.md +17 -194
  93. package/rules/always-on/parallel-sessions.md +3 -1
  94. package/rules/opt-in-stack/backend-data.md +1 -1
  95. package/rules/opt-in-stack/backend.md +1 -1
  96. package/rules/opt-in-stack/security-web.md +5 -1
  97. package/scripts/agent-usage-census.mjs +79 -0
  98. package/scripts/apply-session-learnings.mjs +34 -6
  99. package/scripts/archive-closed-prds.mjs +21 -5
  100. package/scripts/autopilot.mjs +11 -5
  101. package/scripts/backfill-abandoned-sessions.mjs +13 -3
  102. package/scripts/backfill-evidence-digest.mjs +2 -1
  103. package/scripts/backfill-learnings-from-vault.mjs +61 -6
  104. package/scripts/backfill-sessions.mjs +16 -2
  105. package/scripts/check-package-manager.mjs +26 -8
  106. package/scripts/check-sessions-integrity.mjs +4 -1
  107. package/scripts/ci/assert-vitest-green.mjs +3 -0
  108. package/scripts/ci/windows-smoke.mjs +201 -0
  109. package/scripts/close-precheck.mjs +55 -0
  110. package/scripts/dialectic-deriver.mjs +20 -12
  111. package/scripts/emit-event.mjs +2 -2
  112. package/scripts/emit-session.mjs +239 -20
  113. package/scripts/eval-session.mjs +17 -3
  114. package/scripts/export-hw-learnings.mjs +29 -2
  115. package/scripts/fleet-checkin-audit.mjs +8 -4
  116. package/scripts/fleet-checkin.mjs +39 -27
  117. package/scripts/gc-stale-worktrees.mjs +6 -1
  118. package/scripts/github-export.mjs +128 -0
  119. package/scripts/github-protection-audit.mjs +3 -1
  120. package/scripts/handover-queue-check.mjs +209 -0
  121. package/scripts/harness-audit.mjs +6 -4
  122. package/scripts/lib/agent-frontmatter.mjs +57 -10
  123. package/scripts/lib/agent-status.mjs +125 -7
  124. package/scripts/lib/agent-usage-census.mjs +394 -0
  125. package/scripts/lib/auto-dialectic.mjs +17 -10
  126. package/scripts/lib/auto-dream.mjs +22 -14
  127. package/scripts/lib/autopilot/loop.mjs +3 -1
  128. package/scripts/lib/autopilot/recent-runs.mjs +4 -2
  129. package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
  130. package/scripts/lib/autopilot/telemetry.mjs +5 -1
  131. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  132. package/scripts/lib/baseline-archetypes.mjs +10 -2
  133. package/scripts/lib/blocked-commands-policy.mjs +4 -3
  134. package/scripts/lib/build-live-signals.mjs +13 -9
  135. package/scripts/lib/ci-status-banner.mjs +329 -24
  136. package/scripts/lib/codex/plugin-contract.mjs +8 -0
  137. package/scripts/lib/cold-start-detector.mjs +5 -2
  138. package/scripts/lib/command-blocker.mjs +603 -43
  139. package/scripts/lib/common.mjs +3 -2
  140. package/scripts/lib/config/cross-repo.mjs +74 -7
  141. package/scripts/lib/config/host-paths.mjs +6 -2
  142. package/scripts/lib/config/section-extractor.mjs +65 -11
  143. package/scripts/lib/config/template-keys.mjs +153 -0
  144. package/scripts/lib/config/vault-integration.mjs +1 -0
  145. package/scripts/lib/config.mjs +2 -2
  146. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  147. package/scripts/lib/discovery/triage-state.mjs +2 -1
  148. package/scripts/lib/dispatcher/rank.mjs +5 -7
  149. package/scripts/lib/ecosystem-health.mjs +2 -2
  150. package/scripts/lib/eval/engine.mjs +3 -2
  151. package/scripts/lib/eval/session-resolve.mjs +6 -2
  152. package/scripts/lib/eval/sink.mjs +4 -3
  153. package/scripts/lib/events-retention-banner.mjs +3 -2
  154. package/scripts/lib/events-schema.mjs +10 -0
  155. package/scripts/lib/events.mjs +23 -12
  156. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  157. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  158. package/scripts/lib/fleet-protocol.mjs +4 -0
  159. package/scripts/lib/gates/gate-full.mjs +172 -11
  160. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  161. package/scripts/lib/git-config-drift.mjs +141 -7
  162. package/scripts/lib/github-export.mjs +447 -0
  163. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  164. package/scripts/lib/handover-queue-marks.mjs +159 -0
  165. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  166. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  167. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  168. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  169. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  170. package/scripts/lib/host-identity.mjs +9 -5
  171. package/scripts/lib/http-request.mjs +64 -0
  172. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  173. package/scripts/lib/io.mjs +6 -3
  174. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  175. package/scripts/lib/issue-budget.mjs +36 -7
  176. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  177. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  178. package/scripts/lib/learnings/io.mjs +289 -15
  179. package/scripts/lib/learnings/select.mjs +53 -4
  180. package/scripts/lib/learnings/store-role.mjs +209 -0
  181. package/scripts/lib/learnings/surface.mjs +46 -7
  182. package/scripts/lib/ledger-path.mjs +113 -0
  183. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  184. package/scripts/lib/memory-banner.mjs +5 -6
  185. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  186. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  187. package/scripts/lib/memory-proposals/store.mjs +22 -5
  188. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  189. package/scripts/lib/no-follow-open.mjs +57 -0
  190. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  191. package/scripts/lib/own-lock-root.mjs +95 -0
  192. package/scripts/lib/owner-interview.mjs +2 -1
  193. package/scripts/lib/owner-yaml.mjs +70 -7
  194. package/scripts/lib/platform.mjs +161 -9
  195. package/scripts/lib/plugin-update-banner.mjs +15 -11
  196. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  197. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  198. package/scripts/lib/process-group.mjs +270 -34
  199. package/scripts/lib/project-hygiene.mjs +28 -0
  200. package/scripts/lib/quality-gate.mjs +33 -9
  201. package/scripts/lib/reconcile/backlog.mjs +29 -15
  202. package/scripts/lib/reconcile/engine.mjs +26 -2
  203. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  204. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  205. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  206. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  207. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  208. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  209. package/scripts/lib/rule-loader.mjs +20 -10
  210. package/scripts/lib/scope-baseline.mjs +123 -8
  211. package/scripts/lib/scope-echo.mjs +5 -2
  212. package/scripts/lib/scope-gate.mjs +55 -20
  213. package/scripts/lib/session-close-backfill.mjs +75 -7
  214. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  215. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  216. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  217. package/scripts/lib/session-id.mjs +9 -1
  218. package/scripts/lib/session-identity/own-session.mjs +63 -0
  219. package/scripts/lib/session-lock.mjs +114 -0
  220. package/scripts/lib/session-record-repair.mjs +245 -35
  221. package/scripts/lib/session-registry.mjs +72 -9
  222. package/scripts/lib/session-schema/constants.mjs +9 -0
  223. package/scripts/lib/session-schema/filters.mjs +56 -1
  224. package/scripts/lib/session-schema/validator.mjs +15 -5
  225. package/scripts/lib/session-start-probes.mjs +149 -22
  226. package/scripts/lib/session-token-rollup.mjs +2 -1
  227. package/scripts/lib/session-work-trees.mjs +166 -0
  228. package/scripts/lib/sessions-canonical.mjs +61 -3
  229. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  230. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  231. package/scripts/lib/sh-quote.mjs +1 -0
  232. package/scripts/lib/skill-evidence-window.mjs +5 -1
  233. package/scripts/lib/skill-health/join.mjs +10 -9
  234. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  235. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  236. package/scripts/lib/snapshot-probe.mjs +414 -0
  237. package/scripts/lib/so-hud/collect.mjs +49 -5
  238. package/scripts/lib/so-hud/runtime.mjs +15 -4
  239. package/scripts/lib/spiral-carryover.mjs +8 -1
  240. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  241. package/scripts/lib/state-md/idle-reset.mjs +14 -5
  242. package/scripts/lib/state-md/mission-status.mjs +5 -1
  243. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  244. package/scripts/lib/state-md.mjs +31 -8
  245. package/scripts/lib/sunset/walker.mjs +4 -3
  246. package/scripts/lib/telemetry/consent.mjs +4 -2
  247. package/scripts/lib/telemetry/queue.mjs +3 -3
  248. package/scripts/lib/telemetry/sync.mjs +45 -9
  249. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  250. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  251. package/scripts/lib/ux-grill/paths.mjs +5 -1
  252. package/scripts/lib/validate/check-agents.mjs +37 -6
  253. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  254. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  255. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  256. package/scripts/lib/validate/check-json-files.mjs +3 -1
  257. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  258. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  259. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  260. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  261. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  262. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  263. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  264. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  265. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  266. package/scripts/lib/validate/confidential-names.mjs +3 -1
  267. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  268. package/scripts/lib/vault-mirror/namespace.mjs +19 -7
  269. package/scripts/lib/vault-mirror/process.mjs +56 -27
  270. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  271. package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
  272. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  273. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  274. package/scripts/lib/vault-mirror/utils.mjs +79 -0
  275. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  276. package/scripts/lib/vault-status/board-writer.mjs +2 -1
  277. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  278. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  279. package/scripts/lib/win-process-tree.mjs +137 -0
  280. package/scripts/lib/workspace.mjs +1 -1
  281. package/scripts/lib/worktree/constants.mjs +1 -1
  282. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  283. package/scripts/lib/worktree/listing.mjs +1 -1
  284. package/scripts/lib/zx.mjs +50 -0
  285. package/scripts/mcp-server.mjs +16 -7
  286. package/scripts/mcp-server.sh +13 -83
  287. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  288. package/scripts/memory-propose.mjs +10 -3
  289. package/scripts/migrate-cold-start-seed.mjs +7 -0
  290. package/scripts/migrate-legacy-learnings.sh +22 -0
  291. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  292. package/scripts/migrate-vault-paths.mjs +29 -13
  293. package/scripts/parse-config.mjs +71 -14
  294. package/scripts/print-learnings-index.mjs +25 -2
  295. package/scripts/release-session-lock.mjs +94 -7
  296. package/scripts/release.mjs +451 -24
  297. package/scripts/relocate-vault-corpus.mjs +11 -7
  298. package/scripts/repair-invalid-sessions.mjs +68 -3
  299. package/scripts/resolve-session-invocation.mjs +10 -17
  300. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  301. package/scripts/run-quality-gate.mjs +33 -7
  302. package/scripts/self-update.mjs +56 -7
  303. package/scripts/session-commit-files.mjs +133 -0
  304. package/scripts/site-numbers.mjs +52 -9
  305. package/scripts/snapshot-probe.mjs +156 -0
  306. package/scripts/so-hud.mjs +29 -6
  307. package/scripts/state-md-idle-reset.mjs +2 -1
  308. package/scripts/sweep-expired-learnings.mjs +54 -6
  309. package/scripts/sweep-expired-rules.mjs +4 -1
  310. package/scripts/validate-plugin.mjs +22 -0
  311. package/scripts/vault-integration-watcher.mjs +54 -6
  312. package/scripts/vault-mirror.mjs +9 -0
  313. package/skills/_shared/fleet-protocol.md +142 -1
  314. package/skills/_shared/instruction-file-resolution.md +1 -1
  315. package/skills/_shared/monitor-patterns.md +44 -25
  316. package/skills/_shared/platform-tools.md +7 -0
  317. package/skills/_shared/state-ownership.md +3 -2
  318. package/skills/architecture/references/domain-model.md +1 -1
  319. package/skills/bootstrap/SKILL.md +3 -1
  320. package/skills/bootstrap/_shared-template.md +4 -4
  321. package/skills/bootstrap/fast-template.md +1 -1
  322. package/skills/bootstrap/private-contract.md +1 -1
  323. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  324. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  325. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  326. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  327. package/skills/claude-md-drift-check/SKILL.md +3 -3
  328. package/skills/claude-md-drift-check/checker.mjs +61 -101
  329. package/skills/close/SKILL.md +20 -7
  330. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  331. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  332. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  333. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  334. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  335. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  336. package/skills/discovery/probes-session.md +3 -0
  337. package/skills/discovery/probes-vault.md +2 -0
  338. package/skills/dispatcher/SKILL.md +1 -1
  339. package/skills/eval/SKILL.md +3 -1
  340. package/skills/evolve/SKILL.md +2 -0
  341. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  342. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  343. package/skills/gitlab-ops/SKILL.md +7 -6
  344. package/skills/gitlab-portfolio/SKILL.md +2 -2
  345. package/skills/go/SKILL.md +13 -1
  346. package/skills/npm-publish/SKILL.md +5 -2
  347. package/skills/plan/SKILL.md +23 -10
  348. package/skills/plan/mode-feature.md +13 -8
  349. package/skills/plan/mode-new.md +6 -14
  350. package/skills/plan/prd-feature-template.md +19 -0
  351. package/skills/plan/prd-full-template.md +12 -5
  352. package/skills/plan/prd-premortem-panel.md +48 -0
  353. package/skills/plan/prd-reviewer-prompt.md +11 -3
  354. package/skills/plan/soul.md +1 -1
  355. package/skills/portfolio/SKILL.md +5 -6
  356. package/skills/quality-gates/SKILL.md +2 -4
  357. package/skills/reconcile/SKILL.md +27 -4
  358. package/skills/release/SKILL.md +16 -8
  359. package/skills/session-end/SKILL.md +94 -11
  360. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  361. package/skills/session-end/phase-3-6-tail.md +11 -7
  362. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  363. package/skills/session-end/plan-verification.md +16 -1
  364. package/skills/session-end/references/phase-3-documentation-updates.md +14 -12
  365. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  366. package/skills/session-end/references/session-summary-template.md +4 -0
  367. package/skills/session-end/session-metrics-write.md +7 -4
  368. package/skills/session-plan/SKILL.md +5 -3
  369. package/skills/session-start/SKILL.md +9 -3
  370. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  371. package/skills/session-start/phase-8-5-express-path.md +4 -3
  372. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  373. package/skills/session-start/references/phase-1-5-session-continuity.md +2 -2
  374. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  375. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  376. package/skills/tmux-layout/SKILL.md +2 -2
  377. package/skills/vault-sync/validator.mjs +3 -2
  378. package/skills/wave-executor/SKILL.md +5 -4
  379. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  380. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  381. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  382. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  383. package/assets/og-card.png +0 -0
  384. package/assets/og-card.svg +0 -47
  385. package/assets/wave-lifecycle.svg +0 -98
@@ -2,7 +2,7 @@
2
2
  * events.mjs — JSONL event emission + optional webhook POST for session-orchestrator.
3
3
  *
4
4
  * Replaces scripts/lib/events.sh. Windows-safe (no hardcoded path separators).
5
- * Uses native fetch (Node 20+) and fs.promises — no external dependencies.
5
+ * Uses Stdlib HTTP and fs.promises — no external dependencies.
6
6
  *
7
7
  * Part of v3.0.0 migration (Epic #124, issue #133).
8
8
  * Issue #228: removed hardcoded personal-domain default URL. Clank Event Bus URL
@@ -39,6 +39,7 @@
39
39
  * 0.0961 ms/call.
40
40
  */
41
41
 
42
+ import { httpRequest } from './http-request.mjs';
42
43
  import {
43
44
  promises as fs,
44
45
  closeSync,
@@ -54,7 +55,7 @@ import { tmpdir } from 'node:os';
54
55
  import path from 'node:path';
55
56
  import { getProjectDir, resolveEventsLedgerRoot, SO_SHARED_DIR } from './platform.mjs';
56
57
  import { readLock } from './session-lock.mjs';
57
- import { resolveStateMdPath } from './state-md/frontmatter-mutators.mjs';
58
+ import { resolveStateMdWriteTarget } from './state-md/frontmatter-mutators.mjs';
58
59
  import {
59
60
  classifyManifestSession,
60
61
  readProcessLocalSessionIds,
@@ -282,9 +283,12 @@ export function attributionForRecord(root = getProjectDir()) {
282
283
  /**
283
284
  * Absolute path of the active `wave-scope.json`, or `null` when none exists.
284
285
  *
285
- * The active platform's state dir is tried first (via `resolveStateMdPath()`,
286
- * the repo's existing resolver), then the remaining candidates — so a Codex or
287
- * Cursor run finds its own manifest rather than a stale `.claude/` one.
286
+ * This session's state dir is tried first (the dir of
287
+ * `resolveStateMdWriteTarget()`'s path), then the remaining candidates — so a
288
+ * Codex or Cursor run finds its own manifest rather than a stale `.claude/`
289
+ * one. #1636: the write target, not the first existing STATE.md, because a
290
+ * foreign STATE.md in another platform's dir would put that dir's (foreign)
291
+ * manifest first and hide this session's own wave.
288
292
  *
289
293
  * @param {string} root
290
294
  * @returns {string|null}
@@ -292,7 +296,7 @@ export function attributionForRecord(root = getProjectDir()) {
292
296
  function waveScopePath(root) {
293
297
  const dirs = [];
294
298
  try {
295
- dirs.push(path.dirname(resolveStateMdPath(root)));
299
+ dirs.push(path.dirname(resolveStateMdWriteTarget(root).path));
296
300
  } catch {
297
301
  /* fall through to the fixed candidate list */
298
302
  }
@@ -364,8 +368,9 @@ function waveForRecord(root, attribution) {
364
368
  *
365
369
  * Writes `{timestamp, event, schema_version, ...payload}` as a single JSON line
366
370
  * to `.orchestrator/metrics/events.jsonl` (creates parent directory if needed).
367
- * If both `CLANK_EVENT_SECRET` and `CLANK_EVENT_URL` are set, fires an async
368
- * fire-and-forget POST to `CLANK_EVENT_URL` with a 3-second timeout. Network
371
+ * If both `CLANK_EVENT_SECRET` and `CLANK_EVENT_URL` are set, fires a
372
+ * fire-and-forget POST (not awaited, #1612 R1-03) to `CLANK_EVENT_URL` with a
373
+ * 3-second timeout over node:https (`scripts/lib/http-request.mjs`). Network
369
374
  * errors are swallowed. Write errors propagate to the caller. No personal-domain
370
375
  * default URL exists — both vars must be set explicitly (#228).
371
376
  *
@@ -476,7 +481,7 @@ export async function emitEvent(type, payload = {}, opts = {}) {
476
481
  await fs.mkdir(path.dirname(filePath), { recursive: true });
477
482
  await fs.appendFile(filePath, line, 'utf8');
478
483
 
479
- // Fire-and-forget webhook POST — only when secret AND URL are configured.
484
+ // R1-03: do not await: multiple events per hook × 3s exceed the 5s hook budget.
480
485
  // No personal-domain default: CLANK_EVENT_URL must be set explicitly (#228).
481
486
  if (process.env.CLANK_EVENT_SECRET && process.env.CLANK_EVENT_URL) {
482
487
  const url = process.env.CLANK_EVENT_URL;
@@ -486,14 +491,15 @@ export async function emitEvent(type, payload = {}, opts = {}) {
486
491
  payload,
487
492
  });
488
493
 
489
- fetch(`${url}/api/webhooks/events`, {
494
+ void httpRequest(`${url}/api/webhooks/events`, {
490
495
  method: 'POST',
491
496
  headers: {
492
497
  'Content-Type': 'application/json',
493
498
  'Authorization': `Bearer ${process.env.CLANK_EVENT_SECRET}`,
494
499
  },
495
500
  body,
496
- signal: AbortSignal.timeout(3000),
501
+ timeoutMs: 3000,
502
+ maxBytes: 64 * 1024,
497
503
  }).catch(() => {});
498
504
  }
499
505
  }
@@ -948,7 +954,12 @@ export function scanEventsBackwards(opts = {}) {
948
954
  let fd;
949
955
  try {
950
956
  fd = openSync(source.path, 'r');
951
- let pos = fstatSync(fd).size;
957
+ const stats = fstatSync(fd);
958
+ // Windows opens a directory "as a file" with size 0; without this it
959
+ // would read as an EMPTY source instead of an unreadable one (POSIX
960
+ // throws EISDIR on the first read).
961
+ if (!stats.isFile()) throw new Error(`not a regular file: ${source.path}`);
962
+ let pos = stats.size;
952
963
  /** Partial line at the FRONT of everything read from THIS source so far. */
953
964
  let carry = Buffer.alloc(0);
954
965
  while (pos > 0) {
@@ -223,15 +223,10 @@ function readinessConfidence(autopilotSummary, judgmentSummary, score) {
223
223
  /**
224
224
  * Summarize autopilot run history plus type-8 mode effectiveness rollups.
225
225
  *
226
- * Abandoned-session filtering (#834) AND duplicate-identity collapse (#1167):
227
- * `sessions` is passed straight through to `groupByMode()`, which canonicalizes
228
- * the array (one record per physical session) and then filters phantom
229
- * `status: 'abandoned'` stubs before bucketing — this function inherits BOTH
230
- * guarantees transitively and duplicates neither. The abandoned filter alone
231
- * was not enough: a `supersedes` pair and an exact same-id duplicate are two
232
- * records of one session that both survive it. See
233
- * `autopilot-effectiveness.mjs` `groupByMode()` (which canonicalizes via
234
- * `canonicalizeSessions(sessions, { keepUnidentified: true })` before filtering).
226
+ * Measured-work filtering (#1584) AND duplicate-identity collapse (#1167):
227
+ * sessions pass through groupByMode(), which canonicalizes physical identities
228
+ * (keeping ID-less legacy rows) and applies isMeasuredSession before bucketing.
229
+ * This function inherits both guarantees without a separate population filter.
235
230
  *
236
231
  * @param {Array} autopilotRuns
237
232
  * @param {Array} sessions
@@ -26,8 +26,7 @@
26
26
 
27
27
  import { randomUUID } from 'node:crypto';
28
28
 
29
- import { filterRealSessions } from '../session-schema.mjs';
30
- import { canonicalizeSessions } from '../sessions-canonical.mjs';
29
+ import { canonicalizeSessions, isMeasuredSession } from '../sessions-canonical.mjs';
31
30
 
32
31
  // ---------------------------------------------------------------------------
33
32
  // Constants
@@ -160,11 +159,9 @@ function mean(values) {
160
159
  * whose autopilot_run_id matches a known run). For the skeleton it is used
161
160
  * only as a non-emptiness signal.
162
161
  *
163
- * Abandoned-session filtering (#834): `sessions` is filtered to REAL (non-
164
- * phantom) records via `filterRealSessions()` before bucketing. Phantom
165
- * `status: 'abandoned'` stubs are legitimate DATA but not legitimate SIGNAL —
166
- * they must not inflate `n_manual`/`n_autopilot` or dilute the
167
- * completion/carryover averages. See `scripts/lib/session-schema/filters.mjs`.
162
+ * Measured-session filtering (#1584): canonical identities are counted only
163
+ * when the shared isMeasuredSession predicate sees real work. Synthetic rows
164
+ * cannot inflate counts or dilute averages, even with completed status.
168
165
  *
169
166
  * Mode resolution (#834): `mode` is a LEGACY alias for the canonical
170
167
  * `session_type` field (`session-schema/constants.mjs` `SESSION_KEY_ALIASES`).
@@ -182,23 +179,16 @@ export function groupByMode(autopilotRuns, sessions) {
182
179
  const out = new Map();
183
180
  if (!Array.isArray(sessions) || sessions.length === 0) return out;
184
181
 
185
- // Canonicalize BEFORE filtering (#1167). `filterRealSessions` drops phantom
186
- // `abandoned` stubs, but two records of ONE physical session that are both
187
- // real survive it — a `supersedes` pair (the backfilled stub plus the
188
- // authoritative `completed` record that refutes it) and an exact same-id
189
- // duplicate line both do. Each of those inflates `n_manual` / `n_autopilot`
190
- // and skews every mean computed from the bucket, so the identity collapse
191
- // has to happen first; the two compose in exactly this order (see
192
- // `sessions-canonical.mjs` § "What this module does not do").
182
+ // Canonicalize BEFORE the shared measured-work filter (#1167, #1584):
183
+ // duplicate identities and supersedes pairs must not inflate bucket counts.
193
184
  //
194
185
  // `keepUnidentified: true` because effectiveness analysis must not LOSE the
195
186
  // id-less rows `canonicalizeSessions` drops by default: legacy ledger rows
196
187
  // and every in-repo fixture of the pre-id era carry `session_type` and
197
188
  // metrics but no id, and dropping them would silently shrink `n_manual`
198
189
  // instead of de-duplicating it.
199
- const realSessions = filterRealSessions(
200
- canonicalizeSessions(sessions, { keepUnidentified: true }),
201
- );
190
+ const realSessions = canonicalizeSessions(sessions, { keepUnidentified: true })
191
+ .filter(isMeasuredSession);
202
192
  if (realSessions.length === 0) return out;
203
193
 
204
194
  // Optional: known autopilot_run_id set for stricter pairing. Empty set means
@@ -26,6 +26,10 @@
26
26
  * `repo` field resolves to the reader's own git common dir ({@link readAuflagen},
27
27
  * #1520 / navigator#190).
28
28
  *
29
+ * There is no Rücklauf claim marker (removed in #1611): the owner inbox only lets
30
+ * a session rest when it reported in the turn of its last answer, so every
31
+ * waiting turn reports and nothing here suppresses a repeat.
32
+ *
29
33
  * Session ids become file names, so {@link isSafeSessionId} is the security
30
34
  * boundary between check-in input and the filesystem: every path builder that
31
35
  * takes a session id refuses an unsafe one with a TypeError.
@@ -5,11 +5,15 @@
5
5
  // Required env: TYPECHECK_CMD, TEST_CMD, LINT_CMD (each may equal "skip")
6
6
  // Optional env: SESSION_START_REF
7
7
 
8
+ import { mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import path from 'node:path';
8
11
  import {
9
12
  runCheck,
10
13
  extractCount,
11
14
  extractTestCounts,
12
15
  extractFailedTestFiles,
16
+ extractTimeoutOnlyFailedFiles,
13
17
  collectDebugArtifacts,
14
18
  commandKillFields,
15
19
  } from './gate-helpers.mjs';
@@ -109,6 +113,147 @@ const failedTestFiles = testResult.status === 'fail'
109
113
  ? extractFailedTestFiles(testResult.fullOutput ?? testResult.output ?? '')
110
114
  : [];
111
115
 
116
+ // --- Serial re-run of timeout-only failures (#1673) ---
117
+ // Under host load the parallel suite loses tests to their timeout while the
118
+ // code is fine: four pre-push runs on m5 blocked with four different red sets,
119
+ // none in the pushed diff, and the pushes then went out with the bypass. When
120
+ // EVERY failure of the run is a timeout, exactly those files run once more,
121
+ // one file at a time; only a clean re-run turns the test step green. An
122
+ // assertion failure is never re-run (it may be a genuine flake), a killed
123
+ // test command is never re-run (no complete error list), and the CI pipeline
124
+ // keeps running without this re-run, so it stays the strict instance.
125
+ // NAMED CEILING (BV-004): 10 files. More timeouts than that is not a few
126
+ // load victims but a broken run, and a serial re-run would double the wait.
127
+ // Revisit when a blocked push shows 11+ timeout-only files under load.
128
+ const RERUN_MAX_FILES = 10;
129
+ // The names go into a shell command line, so they must be plain paths.
130
+ const RERUN_FILE_NAME = /^[\w.@+][\w./@+-]*$/;
131
+ // Paths placed inside double quotes on that line: nothing the shell expands.
132
+ // NAMED CEILING (BV-004): a backslash is refused, so on Windows (`C:\…`
133
+ // temp paths) no re-run happens and the gate stays red as before. Revisit with
134
+ // the Windows gate work (#1612); cmd.exe also expands `%VAR%` inside quotes.
135
+ const QUOTABLE_PATH = /^[^"$`\\%\n\r]+$/;
136
+
137
+ /**
138
+ * The re-run command: the gate's OWN test command with the file filters and
139
+ * reporters appended, so its config, project selection, environment prefix and
140
+ * binary are exactly those of the first run (Codex review round 2, H2). Only
141
+ * shapes whose trailing arguments demonstrably reach vitest are accepted;
142
+ * anything else returns null and nothing is re-run.
143
+ * NAMED CEILING (BV-004): `npm test` / `npm run test` and a direct
144
+ * `[VAR=… ][exec|npx ][path/]vitest run|--run` without shell operators. pnpm/yarn pass
145
+ * `--` differently by version and are not accepted until measured. Revisit
146
+ * when a consumer repo with another test command hits #1673.
147
+ */
148
+ function rerunCommand(cmd, args) {
149
+ const trimmed = (cmd ?? '').trim();
150
+ if (/^npm (?:run )?test$/.test(trimmed)) return `${trimmed} -- ${args}`;
151
+ // `[VAR=value …] [exec|npx] [path/]vitest run` — vitest itself is the program,
152
+ // so the appended arguments are its own; no wrapper that might drop them.
153
+ const direct = /^(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|`$()<>]*\s+)*(?:(?:exec|npx)\s+)?(?:[^\s;&|`$()<>]*\/)?vitest (?:run|--run)$/;
154
+ if (direct.test(trimmed)) return `${trimmed} ${args}`;
155
+ return null;
156
+ }
157
+
158
+ const realOrSelf = (p) => {
159
+ try {
160
+ return realpathSync(p);
161
+ } catch {
162
+ return p;
163
+ }
164
+ };
165
+
166
+ /**
167
+ * Judge the re-run from vitest's JSON report, never from its exit code or its
168
+ * summary counts alone: the default reporter does not name passing files, so
169
+ * only the report proves that EXACTLY the requested files ran and passed (a
170
+ * filter matching another file, or a config excluding the file, would
171
+ * otherwise read as green). Returns `{ reason, passed, failed }`, `reason`
172
+ * null when clean.
173
+ */
174
+ function judgeRerunReport(reportPath, files, cwd) {
175
+ let report;
176
+ try {
177
+ report = JSON.parse(readFileSync(reportPath, 'utf8'));
178
+ } catch {
179
+ return { reason: 'report-unreadable', passed: null, failed: null };
180
+ }
181
+ const counts = { passed: report?.numPassedTests ?? null, failed: report?.numFailedTests ?? null };
182
+ if (report?.success !== true || report.numFailedTests !== 0 || !Array.isArray(report.testResults)) {
183
+ return { reason: 'report-not-clean', ...counts };
184
+ }
185
+ const root = realOrSelf(cwd);
186
+ const ran = report.testResults.map((t) => [
187
+ path.relative(root, realOrSelf(String(t?.name ?? ''))).split(path.sep).join('/'),
188
+ t?.status,
189
+ ]);
190
+ const wanted = new Set(files);
191
+ const exact =
192
+ ran.length === files.length && ran.every(([file, status]) => status === 'passed' && wanted.delete(file));
193
+ return { reason: exact && wanted.size === 0 ? null : 'file-set-mismatch', ...counts };
194
+ }
195
+
196
+ let rerun = null;
197
+ let rerunResult = null;
198
+ // Only a run that ENDED with exit 1 — not by a signal (`exitSignal`, which
199
+ // `exitCode` alone reports as 1), not by the gate's own kill — and printed
200
+ // vitest's closing `Test Files` summary (`testCounts.files`). A signal that hits
201
+ // vitest under `npm test` surfaces as npm exit 143 (measured, npm 11.19.1), so
202
+ // both routes of an externally ended run stay out (Codex review rounds 2+3, M3).
203
+ if (
204
+ testResult.status === 'fail' &&
205
+ testResult.timedOut !== true &&
206
+ testResult.exitCode === 1 &&
207
+ !testResult.exitSignal &&
208
+ (testResult.killSignals ?? []).length === 0 &&
209
+ testCounts.files
210
+ ) {
211
+ const timeouts = extractTimeoutOnlyFailedFiles(testResult.fullOutput ?? testResult.output ?? '');
212
+ if (
213
+ timeouts.eligible &&
214
+ timeouts.files.length <= RERUN_MAX_FILES &&
215
+ timeouts.files.every((f) => RERUN_FILE_NAME.test(f))
216
+ ) {
217
+ let reportDir = null;
218
+ try {
219
+ reportDir = mkdtempSync(path.join(tmpdir(), 'gate-rerun-'));
220
+ const reportPath = path.join(reportDir, 'report.json');
221
+ const command = QUOTABLE_PATH.test(reportPath)
222
+ ? rerunCommand(
223
+ testCmd,
224
+ // `default` keeps a readable failure on stderr; `json` is the proof.
225
+ `--no-file-parallelism --reporter=default --reporter=json --outputFile.json="${reportPath}" ${timeouts.files.join(' ')}`,
226
+ )
227
+ : null;
228
+ if (command !== null) {
229
+ rerunResult = await runCheck(command, CHECK_OPTS);
230
+ const judged = judgeRerunReport(reportPath, timeouts.files, process.cwd());
231
+ const verdict = rerunResult.status === 'pass' ? judged : { ...judged, reason: 'exit-non-zero' };
232
+ rerun = {
233
+ files: timeouts.files,
234
+ status: verdict.reason === null ? 'pass' : 'fail',
235
+ ...(verdict.reason ? { reason: verdict.reason } : {}),
236
+ passed: verdict.passed,
237
+ failed: verdict.failed,
238
+ ...commandKillFields(rerunResult),
239
+ };
240
+ }
241
+ } catch (err) {
242
+ // Any failure around the re-run keeps the step red and still names the files.
243
+ rerun = { files: timeouts.files, status: 'fail', reason: `rerun-error: ${err?.code ?? err?.message ?? 'unknown'}`, passed: null, failed: null };
244
+ } finally {
245
+ if (reportDir !== null) {
246
+ try {
247
+ rmSync(reportDir, { recursive: true, force: true });
248
+ } catch {
249
+ /* a leftover temp dir must not crash the gate or flip its verdict */
250
+ }
251
+ }
252
+ }
253
+ }
254
+ }
255
+ const testStatus = rerun?.status === 'pass' ? 'pass' : testResult.status;
256
+
112
257
  const fileFields = testCounts.files
113
258
  ? {
114
259
  files_total: testCounts.files.total,
@@ -119,7 +264,9 @@ const fileFields = testCounts.files
119
264
  // test-case `failed: 0` that a file-level failure explains. Greppable —
120
265
  // a consumer no longer has to recompute the contradiction by hand.
121
266
  suite_died:
122
- testResult.status === 'fail' && testCounts.failed === 0 && testCounts.files.failed > 0,
267
+ // `testStatus`: a suite whose only failure was a hook timeout and that
268
+ // re-ran clean (#1673) did not die.
269
+ testStatus === 'fail' && testCounts.failed === 0 && testCounts.files.failed > 0,
123
270
  }
124
271
  : {};
125
272
 
@@ -154,8 +301,10 @@ const output = {
154
301
  // re-derived as `total - passed` downstream: the derivation and the parse can
155
302
  // disagree, and only an explicit third number lets a consumer check
156
303
  // `passed + failed === total` as a real producer/consumer drift guard.
304
+ // `status` is the step's verdict after a re-run; the counts below stay the
305
+ // first, parallel run's measurement, and `rerun` says what turned it green.
157
306
  test: {
158
- status: testResult.status,
307
+ status: testStatus,
159
308
  total: testCounts.total,
160
309
  passed: testCounts.passed,
161
310
  failed: testCounts.failed,
@@ -166,6 +315,7 @@ const output = {
166
315
  // document and the test-case triple keeps `total === passed + failed`.
167
316
  ...fileFields,
168
317
  ...commandKillFields(testResult),
318
+ ...(rerun ? { rerun } : {}),
169
319
  },
170
320
  lint: { status: lintResult.status, warnings: lintWarnings, ...commandKillFields(lintResult) },
171
321
  debug_artifacts: debugArtifacts,
@@ -175,10 +325,19 @@ const output = {
175
325
  process.stdout.write(JSON.stringify(output) + '\n');
176
326
 
177
327
  // Exit 2 if any check failed; skipped counts as pass.
178
- const failed = [tcResult, testResult, lintResult].some(
179
- (r) => r.status === 'fail',
328
+ const failed = [tcResult.status, testStatus, lintResult.status].some(
329
+ (status) => status === 'fail',
180
330
  );
181
331
 
332
+ // Every re-run is named, green or not, so a load-sensitive file stays visible
333
+ // even when the push goes through.
334
+ if (rerun) {
335
+ process.stderr.write(
336
+ `\n──── test re-run: ${rerun.files.length} file(s) failed only by timeout, re-ran serially → ${rerun.status} ────\n` +
337
+ rerun.files.map((f) => ` ${f}\n`).join(''),
338
+ );
339
+ }
340
+
182
341
  // --- Failure disclosure ---
183
342
  // A gate that BLOCKS and says nothing is not concise, it is unusable. Measured
184
343
  // 2026-08-23: a pre-push run emitted exactly
@@ -207,20 +366,22 @@ if (failed) {
207
366
  // Names FIRST, raw output after. Under the pre-push hook the raw block is
208
367
  // hundreds of lines; the operator reads the top of it, and the one fact he
209
368
  // needs to act (`npx vitest run <file>`) must not sit at the bottom.
210
- if (failedTestFiles.length > 0) {
369
+ if (testStatus === 'fail' && failedTestFiles.length > 0) {
211
370
  process.stderr.write(
212
371
  `\n──── failing test files (${failedTestFiles.length}) ────\n` +
213
372
  failedTestFiles.map((f) => ` ${f}\n`).join('') +
214
373
  ` reproduce: npx vitest run ${failedTestFiles.join(' ')}\n`,
215
374
  );
216
375
  }
217
- for (const [name, result] of [
218
- ['typecheck', tcResult],
219
- ['test', testResult],
220
- ['lint', lintResult],
376
+ for (const [name, result, status] of [
377
+ ['typecheck', tcResult, tcResult.status],
378
+ ['test', testResult, testStatus],
379
+ ['test re-run', rerunResult, rerun?.status],
380
+ ['lint', lintResult, lintResult.status],
221
381
  ]) {
222
- if (result.status !== 'fail') continue;
223
- const raw = result.fullOutput ?? result.output ?? '';
382
+ if (status !== 'fail') continue;
383
+ // A re-run that never started (temp dir, spawn) has no result, only a reason.
384
+ const raw = result ? (result.fullOutput ?? result.output ?? '') : `(not run: ${rerun?.reason ?? 'unknown'})\n`;
224
385
  process.stderr.write(`\n──── ${name} FAILED — raw output ────\n`);
225
386
  process.stderr.write(raw.length > 0 ? raw : '(the gate captured no output)\n');
226
387
  process.stderr.write(`──── end ${name} ────\n`);