session-orchestrator 5.2.0 → 5.4.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 (427) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +5 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +5 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +5 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
  9. package/.agents/skills/close/SKILL.md +5 -1
  10. package/.agents/skills/close/agents/openai.yaml +5 -0
  11. package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
  12. package/.agents/skills/debug/SKILL.md +5 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +5 -1
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +5 -1
  17. package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
  18. package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
  19. package/.agents/skills/ecosystem-health/SKILL.md +3 -1
  20. package/.agents/skills/eli5/SKILL.md +5 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +6 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +6 -2
  25. package/.agents/skills/evolve/agents/openai.yaml +5 -0
  26. package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
  27. package/.agents/skills/gitlab-ops/SKILL.md +3 -1
  28. package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
  29. package/.agents/skills/go/SKILL.md +5 -1
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +5 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +5 -1
  34. package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
  35. package/.agents/skills/hook-development/SKILL.md +3 -1
  36. package/.agents/skills/mcp-builder/SKILL.md +3 -1
  37. package/.agents/skills/memory-cleanup/SKILL.md +5 -1
  38. package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
  39. package/.agents/skills/mode-selector/SKILL.md +3 -1
  40. package/.agents/skills/navigator/SKILL.md +22 -0
  41. package/.agents/skills/navigator/agents/openai.yaml +5 -0
  42. package/.agents/skills/npm-publish/SKILL.md +4 -2
  43. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  44. package/.agents/skills/persona-panel/SKILL.md +5 -1
  45. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  46. package/.agents/skills/plan/SKILL.md +5 -1
  47. package/.agents/skills/plan/agents/openai.yaml +5 -0
  48. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  49. package/.agents/skills/portfolio/SKILL.md +5 -1
  50. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  51. package/.agents/skills/quality-gates/SKILL.md +3 -1
  52. package/.agents/skills/reconcile/SKILL.md +5 -1
  53. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  54. package/.agents/skills/release/SKILL.md +5 -1
  55. package/.agents/skills/release/agents/openai.yaml +5 -0
  56. package/.agents/skills/remote-offload/SKILL.md +3 -1
  57. package/.agents/skills/repo-audit/SKILL.md +5 -1
  58. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  59. package/.agents/skills/session/SKILL.md +21 -0
  60. package/.agents/skills/session/agents/openai.yaml +5 -0
  61. package/.agents/skills/session-end/SKILL.md +3 -1
  62. package/.agents/skills/session-plan/SKILL.md +3 -1
  63. package/.agents/skills/session-start/SKILL.md +3 -1
  64. package/.agents/skills/spinout/SKILL.md +5 -1
  65. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  66. package/.agents/skills/sunset-review/SKILL.md +5 -1
  67. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  68. package/.agents/skills/templates-ack/SKILL.md +21 -0
  69. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  70. package/.agents/skills/test/SKILL.md +5 -1
  71. package/.agents/skills/test/agents/openai.yaml +5 -0
  72. package/.agents/skills/test-audit/SKILL.md +21 -0
  73. package/.agents/skills/test-audit/agents/openai.yaml +5 -0
  74. package/.agents/skills/test-runner/SKILL.md +3 -1
  75. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  76. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  77. package/.agents/skills/ux-grill/SKILL.md +5 -1
  78. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  79. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  80. package/.agents/skills/vault-sync/SKILL.md +3 -1
  81. package/.agents/skills/wave-executor/SKILL.md +3 -1
  82. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  83. package/.claude-plugin/marketplace.json +1 -1
  84. package/.claude-plugin/plugin.json +1 -1
  85. package/.codex-plugin/plugin.json +4 -4
  86. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
  87. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
  88. package/.codex-plugin/skills/eval/SKILL.md +1 -1
  89. package/.codex-plugin/skills/evolve/SKILL.md +1 -1
  90. package/.codex-plugin/skills/navigator/SKILL.md +22 -0
  91. package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
  92. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
  93. package/.codex-plugin/skills/session/SKILL.md +1 -1
  94. package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
  95. package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
  96. package/.cursor/commands/eval.md +1 -1
  97. package/.cursor/commands/navigator.md +15 -0
  98. package/.cursor/commands/session.md +1 -1
  99. package/.cursor/commands/test-audit.md +14 -0
  100. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  101. package/.cursor/rules/050-plan.mdc +1 -1
  102. package/.cursor/rules/060-evolve.mdc +27 -12
  103. package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
  104. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  105. package/.cursor/skills/eval/SKILL.md +1 -1
  106. package/.cursor/skills/navigator/SKILL.md +13 -0
  107. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  108. package/.cursor/skills/test-audit/SKILL.md +12 -0
  109. package/.cursor-plugin/plugin.json +1 -1
  110. package/.orchestrator/policy/blocked-commands.json +12 -3
  111. package/AGENTS.md +9 -30
  112. package/CHANGELOG.md +206 -0
  113. package/README.md +13 -9
  114. package/SECURITY.md +12 -0
  115. package/agents/dialectic-deriver.md +13 -10
  116. package/agents/eval-judge.md +67 -45
  117. package/agents/skill-applied-judge.md +34 -19
  118. package/commands/session.md +7 -3
  119. package/docs/agent-authoring.md +2 -2
  120. package/docs/baseline.md +22 -7
  121. package/docs/ci-setup.md +57 -4
  122. package/docs/codex-setup.md +14 -2
  123. package/docs/components.md +12 -10
  124. package/docs/events-schema.md +65 -15
  125. package/docs/persona-panel.md +5 -4
  126. package/docs/rule-authoring.md +84 -17
  127. package/docs/scope-collision-guard.md +26 -4
  128. package/docs/session-config-reference.md +154 -12
  129. package/docs/session-config-template.md +41 -3
  130. package/docs/telemetry.md +2 -0
  131. package/hooks/_lib/hook-import-set.json +87 -6
  132. package/hooks/_lib/subagent-transcript.mjs +582 -31
  133. package/hooks/config-protection.mjs +11 -3
  134. package/hooks/cwd-change-restore.mjs +11 -3
  135. package/hooks/enforce-commands.mjs +70 -23
  136. package/hooks/enforce-scope.mjs +143 -33
  137. package/hooks/hooks-codex.json +1 -1
  138. package/hooks/hooks-cursor.json +5 -0
  139. package/hooks/hooks-pi.json +5 -0
  140. package/hooks/hooks.json +6 -1
  141. package/hooks/loop-guard.mjs +11 -3
  142. package/hooks/on-session-end.mjs +58 -23
  143. package/hooks/on-session-start.mjs +101 -11
  144. package/hooks/on-stop.mjs +170 -22
  145. package/hooks/operator-steer.mjs +11 -3
  146. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  147. package/hooks/post-bash-write-verify.mjs +3 -2
  148. package/hooks/post-edit-import-probe.mjs +17 -9
  149. package/hooks/post-edit-validate.mjs +13 -5
  150. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  151. package/hooks/post-tool-batch-wave-signal.mjs +202 -38
  152. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  153. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  154. package/hooks/pre-auq-clarity.mjs +15 -2
  155. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  156. package/hooks/pre-bash-issue-budget.mjs +16 -11
  157. package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
  158. package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
  159. package/hooks/pre-bash-staging-fence.mjs +335 -31
  160. package/hooks/pre-bash-templates-first.mjs +19 -14
  161. package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
  162. package/hooks/pre-task-scope-disjoint.mjs +792 -74
  163. package/hooks/subagent-telemetry.mjs +111 -38
  164. package/hooks/wave-scope-commit-guard.mjs +197 -100
  165. package/monitors/monitors.json +1 -1
  166. package/output-styles/wave-summary.md +1 -1
  167. package/package.json +3 -3
  168. package/pi/prompts/eval.md +1 -1
  169. package/pi/prompts/navigator.md +12 -0
  170. package/pi/prompts/session.md +1 -1
  171. package/pi/prompts/test-audit.md +12 -0
  172. package/rules/README.md +7 -2
  173. package/rules/_index.md +3 -3
  174. package/rules/always-on/bash-harness-pitfalls.md +7 -1
  175. package/rules/opt-in-domain/prompt-caching.md +6 -2
  176. package/rules/opt-in-stack/backend-data.md +10 -2
  177. package/rules/opt-in-stack/backend.md +10 -4
  178. package/rules/opt-in-stack/frontend.md +7 -2
  179. package/rules/opt-in-stack/security-web.md +10 -4
  180. package/rules/opt-in-stack/swift.md +6 -2
  181. package/scripts/apply-session-learnings.mjs +492 -0
  182. package/scripts/archive-closed-prds.mjs +2 -1
  183. package/scripts/autopilot.mjs +70 -53
  184. package/scripts/backfill-abandoned-sessions.mjs +118 -16
  185. package/scripts/baseline-archetypes.mjs +1 -1
  186. package/scripts/check-package-manager.mjs +62 -5
  187. package/scripts/check-sessions-integrity.mjs +300 -0
  188. package/scripts/cursor-install.mjs +1 -4
  189. package/scripts/dialectic-deriver.mjs +50 -13
  190. package/scripts/emit-session.mjs +371 -31
  191. package/scripts/eval-session.mjs +65 -3
  192. package/scripts/export-hw-learnings.mjs +22 -3
  193. package/scripts/fleet-checkin.mjs +147 -0
  194. package/scripts/generate-agents-skills.mjs +102 -29
  195. package/scripts/generate-cursor-adapter.mjs +61 -16
  196. package/scripts/lib/agent-frontmatter.mjs +3 -3
  197. package/scripts/lib/agent-status.mjs +2 -31
  198. package/scripts/lib/auq/clarity.mjs +10 -2
  199. package/scripts/lib/auq/parse.mjs +12 -31
  200. package/scripts/lib/auq/schema.mjs +56 -41
  201. package/scripts/lib/auto-dialectic.mjs +304 -15
  202. package/scripts/lib/auto-dream.mjs +7 -6
  203. package/scripts/lib/autopilot/flags.mjs +18 -2
  204. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  205. package/scripts/lib/autopilot/loop.mjs +19 -2
  206. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  207. package/scripts/lib/baseline-archetypes.mjs +41 -22
  208. package/scripts/lib/ci-status-banner.mjs +376 -16
  209. package/scripts/lib/command-blocker.mjs +275 -28
  210. package/scripts/lib/config/dialectic.mjs +12 -3
  211. package/scripts/lib/config/gate.mjs +74 -0
  212. package/scripts/lib/config/host-paths.mjs +79 -0
  213. package/scripts/lib/config/persona-gate-wave.mjs +3 -3
  214. package/scripts/lib/config/reaper.mjs +163 -0
  215. package/scripts/lib/config-schema.mjs +13 -0
  216. package/scripts/lib/config.mjs +85 -10
  217. package/scripts/lib/convergence-monitor.mjs +74 -11
  218. package/scripts/lib/ecosystem-health.mjs +11 -0
  219. package/scripts/lib/eval/engine.mjs +421 -53
  220. package/scripts/lib/eval/judge.mjs +463 -40
  221. package/scripts/lib/eval/schema.mjs +10 -1
  222. package/scripts/lib/events-rotation.mjs +221 -25
  223. package/scripts/lib/events-schema.mjs +114 -0
  224. package/scripts/lib/events.mjs +545 -7
  225. package/scripts/lib/fleet-protocol.mjs +270 -0
  226. package/scripts/lib/frontmatter-guard.mjs +21 -10
  227. package/scripts/lib/gates/gate-baseline.mjs +40 -5
  228. package/scripts/lib/gates/gate-full.mjs +35 -5
  229. package/scripts/lib/gates/gate-helpers.mjs +336 -23
  230. package/scripts/lib/gates/gate-incremental.mjs +49 -23
  231. package/scripts/lib/gates/gate-per-file.mjs +42 -8
  232. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  233. package/scripts/lib/instruction-budget-guard.mjs +208 -8
  234. package/scripts/lib/io.mjs +42 -8
  235. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  236. package/scripts/lib/js-mask.mjs +197 -0
  237. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  238. package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
  239. package/scripts/lib/learnings/io.mjs +154 -26
  240. package/scripts/lib/learnings/schema.mjs +163 -15
  241. package/scripts/lib/learnings/sizing-subject.mjs +2 -2
  242. package/scripts/lib/maintenance-due-banner.mjs +53 -88
  243. package/scripts/lib/memory-proposals/schema.mjs +11 -5
  244. package/scripts/lib/named-baseline-resolver.mjs +53 -28
  245. package/scripts/lib/named-vault-resolver.mjs +104 -5
  246. package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
  247. package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
  248. package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
  249. package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
  250. package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
  251. package/scripts/lib/orphan-reaper/scan.mjs +506 -0
  252. package/scripts/lib/orphan-reaper.mjs +316 -0
  253. package/scripts/lib/owner-yaml.mjs +104 -6
  254. package/scripts/lib/peer-cards/merger.mjs +48 -10
  255. package/scripts/lib/peer-cards/reader.mjs +78 -2
  256. package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
  257. package/scripts/lib/process-group.mjs +950 -0
  258. package/scripts/lib/project-hygiene.mjs +21 -1
  259. package/scripts/lib/quality-gate.mjs +152 -33
  260. package/scripts/lib/reconcile/backlog.mjs +368 -0
  261. package/scripts/lib/reconcile/engine.mjs +55 -188
  262. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
  263. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  264. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  265. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  266. package/scripts/lib/resource-probe.mjs +1 -1
  267. package/scripts/lib/rule-loader.mjs +141 -74
  268. package/scripts/lib/rules-sync.mjs +27 -12
  269. package/scripts/lib/scope-baseline.mjs +3 -4
  270. package/scripts/lib/scope-echo.mjs +39 -2
  271. package/scripts/lib/scope-gate.mjs +605 -1
  272. package/scripts/lib/session-close-backfill.mjs +33 -6
  273. package/scripts/lib/session-id.mjs +9 -20
  274. package/scripts/lib/session-invocation.mjs +20 -0
  275. package/scripts/lib/session-record-repair.mjs +183 -26
  276. package/scripts/lib/session-registry.mjs +27 -2
  277. package/scripts/lib/session-schema/constants.mjs +56 -2
  278. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  279. package/scripts/lib/session-schema/validator.mjs +28 -1
  280. package/scripts/lib/session-schema.mjs +8 -3
  281. package/scripts/lib/session-start-probes.mjs +95 -10
  282. package/scripts/lib/session-token-rollup.mjs +245 -52
  283. package/scripts/lib/sessions-canonical.mjs +118 -4
  284. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  285. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  286. package/scripts/lib/sh-quote.mjs +29 -0
  287. package/scripts/lib/skill-evidence-window.mjs +891 -0
  288. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  289. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  290. package/scripts/lib/skill-judge.mjs +45 -3
  291. package/scripts/lib/subagents-schema.mjs +30 -3
  292. package/scripts/lib/tail-window.mjs +56 -0
  293. package/scripts/lib/telemetry/pricing.mjs +118 -71
  294. package/scripts/lib/telemetry/schema.mjs +30 -0
  295. package/scripts/lib/telemetry/sync.mjs +61 -6
  296. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  297. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  298. package/scripts/lib/tests-src-ratio.mjs +140 -23
  299. package/scripts/lib/tmux-layout/layouts.mjs +1 -11
  300. package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
  301. package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
  302. package/scripts/lib/user-invocable-skills.mjs +23 -3
  303. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  304. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  305. package/scripts/lib/validate/check-agents.mjs +1 -1
  306. package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
  307. package/scripts/lib/validate/check-entry-guard.mjs +13 -50
  308. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  309. package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
  310. package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
  311. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  312. package/scripts/lib/validate/check-rules.mjs +7 -5
  313. package/scripts/lib/validate/check-skill-links.mjs +9 -1
  314. package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
  315. package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
  316. package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
  317. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  318. package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
  319. package/scripts/lib/validate/check-unwired-features.mjs +130 -27
  320. package/scripts/lib/validate/check-validator-registration.mjs +34 -10
  321. package/scripts/lib/validate/confidential-names.mjs +10 -0
  322. package/scripts/lib/validate-vendored-rules.mjs +210 -84
  323. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  324. package/scripts/lib/vault-mirror/process.mjs +10 -3
  325. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  326. package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
  327. package/scripts/lib/vault-status/board-writer.mjs +48 -11
  328. package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
  329. package/scripts/lib/vault-yaml.mjs +118 -0
  330. package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
  331. package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
  332. package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
  333. package/scripts/lib/wave-resource-gate.mjs +34 -8
  334. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  335. package/scripts/memory-propose.mjs +18 -5
  336. package/scripts/release-session-lock.mjs +306 -0
  337. package/scripts/release.mjs +40 -8
  338. package/scripts/repair-invalid-sessions.mjs +37 -4
  339. package/scripts/resolve-session-invocation.mjs +59 -0
  340. package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
  341. package/scripts/run-quality-gate.mjs +357 -197
  342. package/scripts/site-numbers.mjs +163 -5
  343. package/scripts/sweep-expired-learnings.mjs +56 -5
  344. package/scripts/sweep-expired-rules.mjs +16 -4
  345. package/scripts/validate-plugin.mjs +19 -0
  346. package/scripts/validate-wave-scope.mjs +32 -105
  347. package/scripts/vault-integration-watcher.mjs +2 -1
  348. package/scripts/vault-mirror.mjs +81 -47
  349. package/scripts/wave-scope-binding.mjs +2 -1
  350. package/skills/_shared/config-reading.md +3 -3
  351. package/skills/_shared/fleet-protocol.md +131 -0
  352. package/skills/_shared/platform-tools.md +23 -11
  353. package/skills/_shared/state-ownership.md +5 -0
  354. package/skills/autopilot/SKILL.md +47 -18
  355. package/skills/bootstrap/SKILL.md +1 -1
  356. package/skills/bootstrap/_shared-template.md +1 -1
  357. package/skills/bootstrap/fast-template.md +1 -1
  358. package/skills/bootstrap/private-contract.md +2 -1
  359. package/skills/bootstrap/public-fallback.md +6 -3
  360. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
  361. package/skills/claude-md-drift-check/SKILL.md +10 -10
  362. package/skills/claude-md-drift-check/checker.mjs +26 -18
  363. package/skills/convergence-monitoring/README.md +8 -1
  364. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  365. package/skills/convergence-monitoring/SKILL.md +15 -6
  366. package/skills/eval/SKILL.md +39 -24
  367. package/skills/eval/rubric-v1.md +1 -0
  368. package/skills/eval/rubric-v2.md +457 -0
  369. package/skills/evolve/SKILL.md +3 -3
  370. package/skills/evolve/references/evolve-analyze-mode.md +10 -6
  371. package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
  372. package/skills/gitlab-ops/SKILL.md +3 -2
  373. package/skills/memory-cleanup/SKILL.md +1 -1
  374. package/skills/navigator/SKILL.md +193 -0
  375. package/skills/navigator/references/account-check.sh +87 -0
  376. package/skills/navigator/references/fleet-decisions.mjs +231 -0
  377. package/skills/navigator/references/slot-check.sh +114 -0
  378. package/skills/npm-publish/SKILL.md +1 -1
  379. package/skills/persona-panel/SKILL.md +2 -2
  380. package/skills/persona-panel/persona-format.md +2 -2
  381. package/skills/persona-panel/presets/designer-lens.md +1 -1
  382. package/skills/persona-panel/presets/engineer-lens.md +1 -1
  383. package/skills/persona-panel/presets/pm-lens.md +1 -1
  384. package/skills/plan/mode-retro.md +19 -4
  385. package/skills/quality-gates/SKILL.md +6 -4
  386. package/skills/reconcile/SKILL.md +11 -0
  387. package/skills/remote-offload/SKILL.md +4 -2
  388. package/skills/session-end/SKILL.md +13 -16
  389. package/skills/session-end/discovery-scan.md +1 -1
  390. package/skills/session-end/learning-patterns.md +22 -12
  391. package/skills/session-end/metrics-collection.md +5 -6
  392. package/skills/session-end/phase-3-6-tail.md +55 -9
  393. package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
  394. package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
  395. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  396. package/skills/session-end/references/session-summary-template.md +1 -1
  397. package/skills/session-end/session-metrics-write.md +32 -60
  398. package/skills/session-plan/SKILL.md +17 -8
  399. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  400. package/skills/session-start/SKILL.md +6 -0
  401. package/skills/session-start/references/operations-contract.md +4 -0
  402. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  403. package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
  404. package/skills/test-audit/SKILL.md +152 -0
  405. package/skills/test-audit/references/campaign.md +111 -0
  406. package/skills/test-audit/references/mutate.sh +50 -0
  407. package/skills/ux-grill/SKILL.md +1 -1
  408. package/skills/wave-executor/SKILL.md +9 -5
  409. package/skills/wave-executor/circuit-breaker.md +2 -0
  410. package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
  411. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  412. package/skills/wave-executor/references/wave-loop-review.md +3 -5
  413. package/templates/personas/README.md +5 -5
  414. package/templates/personas/accounting-compliance.v1.md +1 -1
  415. package/templates/personas/accounting-tax-advisor.v1.md +1 -1
  416. package/templates/personas/buyer-p1-cto.v1.md +1 -1
  417. package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
  418. package/templates/personas/buyer-p3-build.v1.md +1 -1
  419. package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
  420. package/templates/personas/buyer-p5-solo.v1.md +1 -1
  421. package/templates/personas/buyer-p6-ld.v1.md +1 -1
  422. package/templates/personas/klima-ai-expert.v1.md +1 -1
  423. package/templates/personas/klima-physicist.v1.md +1 -1
  424. package/.cursor/commands/convergence-monitoring.md +0 -13
  425. package/.cursor/commands/npm-publish.md +0 -13
  426. package/pi/prompts/convergence-monitoring.md +0 -11
  427. package/pi/prompts/npm-publish.md +0 -11
@@ -0,0 +1,270 @@
1
+ /**
2
+ * fleet-protocol.mjs — Fleet protocol v1: file-based navigator lease + session
3
+ * check-ins under `~/.config/navigator/` (#1462, PRD !53 § 3).
4
+ *
5
+ * The protocol state lives in plain files so every platform (Claude Code, Codex,
6
+ * a headless run) can read and write it without a server:
7
+ *
8
+ * <navigatorDir>/leases/navigator.json — the navigator lease
9
+ * <navigatorDir>/checkin/<session_id>.json — one check-in per session
10
+ * <navigatorDir>/auflagen/<session_id>.json — conditions issued to a session
11
+ *
12
+ * `<navigatorDir>` is `NAVIGATOR_CONFIG_DIR` (trimmed; empty/whitespace counts as
13
+ * unset) or `<home>/.config/navigator`. Files are 0600, directories 0700,
14
+ * timestamps `date -u +%FT%TZ` (`YYYY-MM-DDTHH:MM:SSZ`).
15
+ *
16
+ * "A navigator is active" means exactly one thing: a readable, well-formed lease
17
+ * whose `laeuft_ab` lies in the future. Everything else is fail-closed — an
18
+ * expired lease is `none`, a lease that cannot be read or validated is
19
+ * `unreadable`, and neither is ever `active`.
20
+ *
21
+ * Session ids become file names, so {@link isSafeSessionId} is the security
22
+ * boundary between check-in input and the filesystem: every path builder that
23
+ * takes a session id refuses an unsafe one with a TypeError.
24
+ */
25
+
26
+ import { promises as fs } from 'node:fs';
27
+ import os from 'node:os';
28
+ import path from 'node:path';
29
+
30
+ /** Env var that overrides the navigator directory. */
31
+ export const NAVIGATOR_CONFIG_DIR_ENV = 'NAVIGATOR_CONFIG_DIR';
32
+
33
+ /** Minutes a session waits for a navigator before it falls back (PRD !53 § 3). */
34
+ export const CHECKIN_FALLBACK_MIN = 10;
35
+
36
+ const SESSION_ID_RE = /^[A-Za-z0-9._-]+$/;
37
+ const SESSION_ID_MAX = 128;
38
+ const UTC_SECONDS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
39
+
40
+ /** Lease timestamps: UTC with `Z`, seconds or milliseconds — never a local-time reading. */
41
+ const LEASE_TS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
42
+
43
+ const PLATTFORMEN = ['claude', 'codex', 'kopflos'];
44
+ const MODI = ['housekeeping', 'feature', 'deep', 'operations'];
45
+
46
+ /**
47
+ * Resolve the navigator directory.
48
+ * @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
49
+ * @returns {string} absolute path
50
+ */
51
+ export function navigatorDir({ env = process.env, home = os.homedir() } = {}) {
52
+ const raw = env[NAVIGATOR_CONFIG_DIR_ENV];
53
+ const override = typeof raw === 'string' ? raw.trim() : '';
54
+ if (override) return path.resolve(override);
55
+ return path.join(home, '.config', 'navigator');
56
+ }
57
+
58
+ /**
59
+ * Path of the navigator lease file.
60
+ * @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
61
+ * @returns {string}
62
+ */
63
+ export function leasePath(opts) {
64
+ return path.join(navigatorDir(opts), 'leases', 'navigator.json');
65
+ }
66
+
67
+ /**
68
+ * True when `id` is safe to use as a single file-name component: only
69
+ * `[A-Za-z0-9._-]`, 1..128 chars, and not `.` / `..`. This excludes `/`, `\`,
70
+ * NUL and every other separator by construction.
71
+ * @param {unknown} id
72
+ * @returns {boolean}
73
+ */
74
+ export function isSafeSessionId(id) {
75
+ return (
76
+ typeof id === 'string' &&
77
+ id.length > 0 &&
78
+ id.length <= SESSION_ID_MAX &&
79
+ id !== '.' &&
80
+ id !== '..' &&
81
+ SESSION_ID_RE.test(id)
82
+ );
83
+ }
84
+
85
+ /** @param {unknown} sessionId */
86
+ function assertSafeSessionId(sessionId) {
87
+ if (!isSafeSessionId(sessionId)) {
88
+ throw new TypeError(`unsafe session id: ${JSON.stringify(String(sessionId)).slice(0, 80)}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Path of a session's check-in file.
94
+ * @param {string} sessionId
95
+ * @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
96
+ * @returns {string}
97
+ * @throws {TypeError} when `sessionId` fails {@link isSafeSessionId}
98
+ */
99
+ export function checkinPath(sessionId, opts) {
100
+ assertSafeSessionId(sessionId);
101
+ return path.join(navigatorDir(opts), 'checkin', `${sessionId}.json`);
102
+ }
103
+
104
+ /**
105
+ * Path of a session's conditions ("Auflagen") file.
106
+ * @param {string} sessionId
107
+ * @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
108
+ * @returns {string}
109
+ * @throws {TypeError} when `sessionId` fails {@link isSafeSessionId}
110
+ */
111
+ export function auflagenPath(sessionId, opts) {
112
+ assertSafeSessionId(sessionId);
113
+ return path.join(navigatorDir(opts), 'auflagen', `${sessionId}.json`);
114
+ }
115
+
116
+ /**
117
+ * Format a date as `YYYY-MM-DDTHH:MM:SSZ` (the output of `date -u +%FT%TZ`).
118
+ * @param {Date} [date]
119
+ * @returns {string}
120
+ */
121
+ export function utcSecondsTimestamp(date = new Date()) {
122
+ return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
123
+ }
124
+
125
+ /** @param {unknown} v */
126
+ function isPlainObject(v) {
127
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
128
+ }
129
+
130
+ /** @param {unknown} v */
131
+ function isNonEmptyString(v) {
132
+ return typeof v === 'string' && v.length > 0;
133
+ }
134
+
135
+ /** @param {unknown} v */
136
+ function isIsoTimestamp(v) {
137
+ return typeof v === 'string' && !Number.isNaN(Date.parse(v));
138
+ }
139
+
140
+ function isLeaseTimestamp(v) {
141
+ return typeof v === 'string' && LEASE_TS_RE.test(v) && isIsoTimestamp(v);
142
+ }
143
+
144
+ const REQUIRED_CHECKIN_FIELDS = [
145
+ 'session', 'plattform', 'repo', 'modus', 'auftrag_ref',
146
+ 'kandidaten', 'schreibbereich', 'rueckfall', 'zeit',
147
+ ];
148
+
149
+ /** Repo name shape accepted in a check-in: `name` or `group/name`, no path parts. */
150
+ const REPO_NAME_RE = /^[A-Za-z0-9._-]{1,100}(?:\/[A-Za-z0-9._-]{1,100})?$/;
151
+
152
+ /** Lease `adresse` shape (a ListAgents peer name): no whitespace, no control bytes. */
153
+ const ADRESSE_RE = /^[A-Za-z0-9._:@-]{1,128}$/;
154
+
155
+ /** True for an issue reference: a non-negative integer or a `#123` / `123` string. */
156
+ function isIssueRef(v) {
157
+ if (typeof v === 'number') return Number.isInteger(v) && v >= 0;
158
+ return typeof v === 'string' && /^#?\d{1,9}$/.test(v);
159
+ }
160
+
161
+ /**
162
+ * Per-field type checks. Each returns an error message or null. Applied to every
163
+ * PRESENT field; presence of the required ones is checked separately.
164
+ * @type {Record<string, (v: unknown) => string|null>}
165
+ */
166
+ const CHECKIN_FIELD_CHECKS = {
167
+ session: (v) =>
168
+ isSafeSessionId(v) ? null : 'session must be a safe id: [A-Za-z0-9._-], 1..128 chars, not . or ..',
169
+ plattform: (v) => (PLATTFORMEN.includes(/** @type {string} */ (v)) ? null : `plattform must be one of ${PLATTFORMEN.join('|')}`),
170
+ // A repo NAME (optionally `group/name`), never a filesystem path: the value
171
+ // travels in `orchestrator.fleet.checkin`, which may leave the host over the
172
+ // optional events webhook (docs/events-schema.md: "never the absolute root").
173
+ repo: (v) => (typeof v === 'string' && REPO_NAME_RE.test(v) ? null : 'repo must be a repo name [A-Za-z0-9._-] (optionally group/name), not a path'),
174
+ repo_id: (v) => (v === null || typeof v === 'string' ? null : 'repo_id must be a string or null'),
175
+ worktree: (v) => (typeof v === 'boolean' ? null : 'worktree must be a boolean'),
176
+ modus: (v) => (MODI.includes(/** @type {string} */ (v)) ? null : `modus must be one of ${MODI.join('|')}`),
177
+ auftrag_ref: (v) => (isNonEmptyString(v) ? null : 'auftrag_ref must be a non-empty string'),
178
+ konto_slot: (v) =>
179
+ v === null || typeof v === 'string' || (typeof v === 'number' && Number.isFinite(v))
180
+ ? null
181
+ : 'konto_slot must be a number, a string or null',
182
+ quota: (v) => (typeof v === 'string' ? null : 'quota must be a string'),
183
+ stand: (v) => (isPlainObject(v) ? null : 'stand must be an object'),
184
+ // Issue numbers only (42 or "#42"), at most 64 — same webhook reasoning as `repo`.
185
+ kandidaten: (v) =>
186
+ Array.isArray(v) && v.length <= 64 && v.every(isIssueRef)
187
+ ? null
188
+ : 'kandidaten must be an array of at most 64 issue numbers (42 or "#42")',
189
+ schreibbereich: (v) =>
190
+ Array.isArray(v) && v.every((e) => typeof e === 'string') ? null : 'schreibbereich must be an array of strings',
191
+ bedarf: (v) => (isPlainObject(v) ? null : 'bedarf must be an object'),
192
+ vorab_geschrieben: (v) => (Array.isArray(v) ? null : 'vorab_geschrieben must be an array'),
193
+ rueckfall: (v) => (isNonEmptyString(v) ? null : 'rueckfall must be a non-empty string'),
194
+ zeit: (v) =>
195
+ typeof v === 'string' && UTC_SECONDS_RE.test(v) && isIsoTimestamp(v)
196
+ ? null
197
+ : 'zeit must be a UTC timestamp YYYY-MM-DDTHH:MM:SSZ',
198
+ };
199
+
200
+ /**
201
+ * Validate a check-in record (PRD !53 table 3.2).
202
+ * @param {unknown} obj
203
+ * @returns {string[]} error messages; empty means valid
204
+ */
205
+ export function validateCheckin(obj) {
206
+ if (!isPlainObject(obj)) return ['check-in must be a JSON object'];
207
+ const rec = /** @type {Record<string, unknown>} */ (obj);
208
+ const errors = [];
209
+ for (const field of REQUIRED_CHECKIN_FIELDS) {
210
+ if (rec[field] === undefined) errors.push(`missing required field: ${field}`);
211
+ }
212
+ for (const [field, check] of Object.entries(CHECKIN_FIELD_CHECKS)) {
213
+ if (rec[field] === undefined) continue;
214
+ const err = check(rec[field]);
215
+ if (err) errors.push(err);
216
+ }
217
+ return errors;
218
+ }
219
+
220
+ /**
221
+ * Validate a parsed lease; returns the first problem or null.
222
+ * @param {unknown} lease
223
+ * @returns {string|null}
224
+ */
225
+ function leaseProblem(lease) {
226
+ if (!isPlainObject(lease)) return 'lease is not a JSON object';
227
+ const l = /** @type {Record<string, unknown>} */ (lease);
228
+ // Shape-checked at the source ("escaped at generation"): both values can reach
229
+ // the operator's session-start banner, so control bytes are refused here.
230
+ if (!isSafeSessionId(l.session_id)) return 'lease.session_id missing or not a safe id';
231
+ if (!PLATTFORMEN.includes(/** @type {string} */ (l.plattform))) return `lease.plattform must be one of ${PLATTFORMEN.join('|')}`;
232
+ if (l.adresse !== undefined && l.adresse !== null && !(typeof l.adresse === 'string' && ADRESSE_RE.test(l.adresse))) {
233
+ return 'lease.adresse must be null or a peer name [A-Za-z0-9._:@-] (1..128 chars)';
234
+ }
235
+ if (l.uebergabe_an !== undefined && l.uebergabe_an !== null && typeof l.uebergabe_an !== 'string') {
236
+ return 'lease.uebergabe_an must be a string or null';
237
+ }
238
+ // Strict UTC form: a `Z`-less value would be read in the host's local time and
239
+ // could EXTEND the lease west of UTC — a fail-open reading, hence unreadable.
240
+ if (!isLeaseTimestamp(l.seit)) return 'lease.seit missing or not a UTC timestamp (…Z)';
241
+ if (!isLeaseTimestamp(l.laeuft_ab)) return 'lease.laeuft_ab missing or not a UTC timestamp (…Z)';
242
+ return null;
243
+ }
244
+
245
+ /**
246
+ * Read the navigator lease. Never throws; fail-closed.
247
+ *
248
+ * @param {{ now?: number, env?: Record<string, string|undefined>, home?: string }} [opts]
249
+ * @returns {Promise<{ state: 'active', lease: object } | { state: 'none' } | { state: 'unreadable', reason: string }>}
250
+ */
251
+ export async function readNavigatorLease({ now = Date.now(), env, home } = {}) {
252
+ let raw;
253
+ try {
254
+ raw = await fs.readFile(leasePath({ env, home }), 'utf8');
255
+ } catch (err) {
256
+ if (err && /** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') return { state: 'none' };
257
+ const code = err && /** @type {NodeJS.ErrnoException} */ (err).code;
258
+ return { state: 'unreadable', reason: `read failed: ${code || 'unknown error'}` };
259
+ }
260
+ let lease;
261
+ try {
262
+ lease = JSON.parse(raw);
263
+ } catch {
264
+ return { state: 'unreadable', reason: 'lease is not valid JSON' };
265
+ }
266
+ const problem = leaseProblem(lease);
267
+ if (problem) return { state: 'unreadable', reason: problem };
268
+ if (Date.parse(lease.laeuft_ab) <= now) return { state: 'none' };
269
+ return { state: 'active', lease };
270
+ }
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { digestSha256Short } from './crypto-digest-utils.mjs';
14
14
  import { resolveHostPath } from './config/host-paths.mjs';
15
+ import { maskSource } from './js-mask.mjs';
15
16
  import { readFileSync, statSync } from 'node:fs';
16
17
  import { homedir } from 'node:os';
17
18
  import { dirname, join, resolve } from 'node:path';
@@ -117,25 +118,34 @@ let _warnedFallback = false;
117
118
  */
118
119
  const _cache = { mtime: null, result: null };
119
120
 
121
+ /** One quoted string literal — its interior in group 2. */
122
+ const STRING_LITERAL_RE = /(['"`])((?:\\.|(?!\1)[^\\\n])*)\1/g;
123
+
120
124
  /**
121
- * Parse the type-enum values out of a Zod `z.enum([...])` call in source text.
125
+ * Parse the enum values out of a Zod `z.enum([...])` call in source text.
122
126
  *
123
- * @param {string} text
127
+ * `codeText` must be the source with its COMMENTS blanked
128
+ * (`maskSource(text, { keepLiterals: true })`): the values are the quoted
129
+ * literals of the enum body and nothing else. Splitting the raw body on commas
130
+ * rendered comment prose between the literals as enum values — 6 of 16
131
+ * `statusEnum` entries against the real baseline schema, one fused onto
132
+ * `'maintenance'` (#1298) — and every one reached agent prompts as an allowed
133
+ * `status`. Masking via the shared lexer also keeps a `//` inside a literal
134
+ * intact and a `]` inside a comment from ending the body early.
135
+ *
136
+ * @param {string} codeText - comment-masked source
124
137
  * @param {string} exportName - e.g. 'vaultNoteTypeSchema'
125
138
  * @returns {string[]}
126
139
  */
127
- function _extractEnum(text, exportName) {
140
+ function _extractEnum(codeText, exportName) {
128
141
  // Match: export const <name> = z.enum([ ...values... ]);
129
142
  const re = new RegExp(
130
143
  `export\\s+const\\s+${exportName}\\s*=\\s*z\\.enum\\(\\s*\\[([^\\]]+)\\]`,
131
144
  's',
132
145
  );
133
- const match = text.match(re);
146
+ const match = codeText.match(re);
134
147
  if (!match) return [];
135
- return match[1]
136
- .split(',')
137
- .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
138
- .filter(Boolean);
148
+ return [...match[1].matchAll(STRING_LITERAL_RE)].map((m) => m[2]).filter(Boolean);
139
149
  }
140
150
 
141
151
  /**
@@ -145,8 +155,9 @@ function _extractEnum(text, exportName) {
145
155
  * @returns {{ typeEnum: string[], statusEnum: string[], requiredFields: string[], idRegex: string, tagsRegex: string, schemaText: string }}
146
156
  */
147
157
  function _parseSchema(text) {
148
- const typeEnum = _extractEnum(text, 'vaultNoteTypeSchema');
149
- const statusEnum = _extractEnum(text, 'vaultNoteStatusSchema');
158
+ const codeText = maskSource(text, { keepLiterals: true });
159
+ const typeEnum = _extractEnum(codeText, 'vaultNoteTypeSchema');
160
+ const statusEnum = _extractEnum(codeText, 'vaultNoteStatusSchema');
150
161
 
151
162
  // Required fields are id, type, created, updated — stable; extracted
152
163
  // from the schema object declaration (non-optional fields).
@@ -5,11 +5,36 @@
5
5
  // Required env: TYPECHECK_CMD, TEST_CMD
6
6
  // Each may equal "skip" or empty string → treated as skip.
7
7
 
8
- import { runCheck } from './gate-helpers.mjs';
8
+ import { runCheck, commandKillFields } from './gate-helpers.mjs';
9
9
 
10
10
  const typecheckCmd = process.env.TYPECHECK_CMD;
11
11
  const testCmd = process.env.TEST_CMD;
12
12
 
13
+ /**
14
+ * Per-command wall-clock ceiling, published by `scripts/run-quality-gate.mjs`
15
+ * as `GATE_TIMEOUT_MS` (#1425 A3 / #1432). It carries the ALREADY-RESOLVED
16
+ * value — operator override `SO_GATE_TIMEOUT_MS` > Session Config
17
+ * `gate.timeout-path-b-ms` > 900 000 — so this script only has to read it.
18
+ *
19
+ * Absent or non-numeric (this gate invoked directly, not through the wrapper)
20
+ * → the option is OMITTED, and `runCheck` falls back to `resolveGateTimeoutMs()`
21
+ * exactly as before. An empty object is deliberate: passing `timeoutMs:
22
+ * undefined` would NOT trigger that fallback in every spread order.
23
+ */
24
+ const CHECK_OPTS = (() => {
25
+ const raw = Number((process.env.GATE_TIMEOUT_MS || '').trim());
26
+ const opts = Number.isFinite(raw) && raw > 0 ? { timeoutMs: raw } : {};
27
+ // GATE_LEDGER_ROOT (#1425 A4, W5 fix-pass): the wrapper publishes the ledger
28
+ // root it resolved (--ledger-root > repo root) so the gate-process register
29
+ // is written where the WRAPPER decided, not where this sub-script happens to
30
+ // run. Without it every runCheck() here defaulted to process.cwd(), and a test
31
+ // that spawned this script from the checkout wrote real lines into the live
32
+ // .orchestrator/runtime/gate-processes.jsonl — the reaper's kill population.
33
+ const ledgerRoot = (process.env.GATE_LEDGER_ROOT || "").trim();
34
+ if (ledgerRoot && ledgerRoot.startsWith("/")) opts.repoRoot = ledgerRoot;
35
+ return opts;
36
+ })();
37
+
13
38
  if (!typecheckCmd) {
14
39
  process.stderr.write('TYPECHECK_CMD must be set\n');
15
40
  process.exit(1);
@@ -23,17 +48,27 @@ let tcResult = { status: 'skip', output: '' };
23
48
  let testResult = { status: 'skip', output: '' };
24
49
 
25
50
  if (typecheckCmd !== 'skip') {
26
- tcResult = await runCheck(typecheckCmd);
51
+ tcResult = await runCheck(typecheckCmd, CHECK_OPTS);
27
52
  }
28
53
 
29
54
  if (testCmd !== 'skip') {
30
- testResult = await runCheck(testCmd);
55
+ testResult = await runCheck(testCmd, CHECK_OPTS);
31
56
  }
32
57
 
33
58
  const result = {
34
59
  variant: 'baseline',
35
- typecheck: tcResult.status,
36
- test: testResult.status,
60
+ // Each command object also carries its INNER kill ladder (`timed_out`,
61
+ // `kill_signals`, `survivors` pids) when `runCheck` spawned it — omitted for
62
+ // skip/stub (#1457). `run-quality-gate.mjs` joins them into its event.
63
+ typecheck: { status: tcResult.status, ...commandKillFields(tcResult) },
64
+ test: { status: testResult.status, ...commandKillFields(testResult) },
65
+ // Stub map, same shape as gate-full: a command `runCheck` recognised as a
66
+ // stub (`echo …`/`true`) spawned nothing, so `joinGateKillFields` may count
67
+ // it as measured instead of declaring the whole inner ladder unknown.
68
+ stubbed: {
69
+ ...(tcResult.stubbed ? { typecheck: tcResult.stubbed } : {}),
70
+ ...(testResult.stubbed ? { test: testResult.stubbed } : {}),
71
+ },
37
72
  typecheck_output: tcResult.output,
38
73
  test_output: testResult.output,
39
74
  };
@@ -11,6 +11,7 @@ import {
11
11
  extractTestCounts,
12
12
  extractFailedTestFiles,
13
13
  collectDebugArtifacts,
14
+ commandKillFields,
14
15
  } from './gate-helpers.mjs';
15
16
 
16
17
  const typecheckCmd = process.env.TYPECHECK_CMD;
@@ -18,6 +19,31 @@ const testCmd = process.env.TEST_CMD;
18
19
  const lintCmd = process.env.LINT_CMD;
19
20
  const sessionStartRef = process.env.SESSION_START_REF ?? '';
20
21
 
22
+ /**
23
+ * Per-command wall-clock ceiling, published by `scripts/run-quality-gate.mjs`
24
+ * as `GATE_TIMEOUT_MS` (#1425 A3 / #1432). It carries the ALREADY-RESOLVED
25
+ * value — operator override `SO_GATE_TIMEOUT_MS` > Session Config
26
+ * `gate.timeout-path-b-ms` > 900 000 — so this script only has to read it.
27
+ *
28
+ * Absent or non-numeric (this gate invoked directly, not through the wrapper)
29
+ * → the option is OMITTED, and `runCheck` falls back to `resolveGateTimeoutMs()`
30
+ * exactly as before. An empty object is deliberate: passing `timeoutMs:
31
+ * undefined` would NOT trigger that fallback in every spread order.
32
+ */
33
+ const CHECK_OPTS = (() => {
34
+ const raw = Number((process.env.GATE_TIMEOUT_MS || '').trim());
35
+ const opts = Number.isFinite(raw) && raw > 0 ? { timeoutMs: raw } : {};
36
+ // GATE_LEDGER_ROOT (#1425 A4, W5 fix-pass): the wrapper publishes the ledger
37
+ // root it resolved (--ledger-root > repo root) so the gate-process register
38
+ // is written where the WRAPPER decided, not where this sub-script happens to
39
+ // run. Without it every runCheck() here defaulted to process.cwd(), and a test
40
+ // that spawned this script from the checkout wrote real lines into the live
41
+ // .orchestrator/runtime/gate-processes.jsonl — the reaper's kill population.
42
+ const ledgerRoot = (process.env.GATE_LEDGER_ROOT || "").trim();
43
+ if (ledgerRoot && ledgerRoot.startsWith("/")) opts.repoRoot = ledgerRoot;
44
+ return opts;
45
+ })();
46
+
21
47
  if (!typecheckCmd) {
22
48
  process.stderr.write('TYPECHECK_CMD must be set\n');
23
49
  process.exit(1);
@@ -34,7 +60,7 @@ if (!lintCmd) {
34
60
  const startTime = Date.now();
35
61
 
36
62
  // --- Typecheck ---
37
- const tcResult = runCheck(typecheckCmd);
63
+ const tcResult = await runCheck(typecheckCmd, CHECK_OPTS);
38
64
  const tcErrorCount =
39
65
  tcResult.status === 'fail'
40
66
  ? extractCount(tcResult.fullOutput ?? tcResult.output, /error TS\d+/)
@@ -44,7 +70,7 @@ const tcErrorCount =
44
70
  // NOTE: `testCounts`, NOT `failed` — a local `failed` is already bound near
45
71
  // the bottom of this file and drives `process.exit(failed ? 2 : 0)`.
46
72
  // Shadowing it would corrupt the gate's exit code.
47
- const testResult = runCheck(testCmd);
73
+ const testResult = await runCheck(testCmd, CHECK_OPTS);
48
74
  const testCounts =
49
75
  testResult.status !== 'skip'
50
76
  ? extractTestCounts(testResult.fullOutput ?? testResult.output)
@@ -98,7 +124,7 @@ const fileFields = testCounts.files
98
124
  : {};
99
125
 
100
126
  // --- Lint ---
101
- const lintResult = runCheck(lintCmd);
127
+ const lintResult = await runCheck(lintCmd, CHECK_OPTS);
102
128
  const lintWarnings =
103
129
  lintResult.status !== 'skip'
104
130
  ? extractCount(lintResult.fullOutput ?? lintResult.output, /warning/i)
@@ -120,7 +146,10 @@ if (lintResult.stubbed) stubbed.lint = lintResult.stubbed;
120
146
  const output = {
121
147
  variant: 'full-gate',
122
148
  duration_seconds: durationSeconds,
123
- typecheck: { status: tcResult.status, error_count: tcErrorCount },
149
+ // Each command object also carries its INNER kill ladder (`timed_out`,
150
+ // `kill_signals`, `survivors` pids) when `runCheck` spawned it — omitted for
151
+ // skip/stub (#1457). `run-quality-gate.mjs` joins them into its event.
152
+ typecheck: { status: tcResult.status, error_count: tcErrorCount, ...commandKillFields(tcResult) },
124
153
  // `failed` is published explicitly (#967 item 1) rather than left to be
125
154
  // re-derived as `total - passed` downstream: the derivation and the parse can
126
155
  // disagree, and only an explicit third number lets a consumer check
@@ -136,8 +165,9 @@ const output = {
136
165
  // read just passed/failed/total, so the stdout JSON contract stays one
137
166
  // document and the test-case triple keeps `total === passed + failed`.
138
167
  ...fileFields,
168
+ ...commandKillFields(testResult),
139
169
  },
140
- lint: { status: lintResult.status, warnings: lintWarnings },
170
+ lint: { status: lintResult.status, warnings: lintWarnings, ...commandKillFields(lintResult) },
141
171
  debug_artifacts: debugArtifacts,
142
172
  stubbed,
143
173
  };