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,300 @@
1
+ #!/usr/bin/env node
2
+ // scripts/check-sessions-integrity.mjs
3
+ //
4
+ // CLI front-end for `checkSessionsIntegrity` (GitLab #1417).
5
+ //
6
+ // WHY THIS EXISTS:
7
+ // session-end step 4a (`skills/session-end/session-metrics-write.md`) has to
8
+ // prove that the session record it JUST appended to
9
+ // `.orchestrator/metrics/sessions.jsonl` is schema-valid AND renderable by
10
+ // vault-mirror — step 4 before it only proves the line is JSON. The #1408
11
+ // record parsed fine and was still invalid (`ended_at` instead of
12
+ // `completed_at`, four required fields missing), so vault-mirror dropped it as
13
+ // `skipped-invalid` and that session got no vault note; nobody was told until
14
+ // the NEXT session-start banner. Until now the check existed only as a
15
+ // `node --input-type=module -e` one-liner inside the skill body, because the
16
+ // function had no CLI entry point (`scripts/repair-invalid-sessions.mjs` is a
17
+ // REPAIR path whose `--dry-run` exit reports repairability, not validity).
18
+ //
19
+ // WHAT IT CHECKS (exactly what the banner checks — no second opinion here):
20
+ // every parseable record in the ledger is run through `validateSession()` AND
21
+ // through vault-mirror's real render path. `--session-id` narrows the VERDICT
22
+ // to one record; other sessions' findings are still printed (they are not this
23
+ // close's to fix) but never decide the exit code.
24
+ //
25
+ // WHY A MISSING RECORD EXITS 1 (the deliberate call, #1417):
26
+ // `checkSessionsIntegrity` is silent both when a named record is sound and
27
+ // when it does not exist — "no finding" cannot tell those apart. Reporting the
28
+ // absent case as success would make this guard fail-open in precisely the
29
+ // shape it exists to catch: a close that proceeds with NO ledger record, one
30
+ // notch worse than the #1408 invalid one. So with `--session-id` the ledger is
31
+ // additionally read for that id and its absence is a named failure ("record
32
+ // not found"), never a silent pass. Without `--session-id` there is no id to
33
+ // be absent, and an empty/missing ledger is simply nothing to judge (exit 0).
34
+ //
35
+ // USAGE:
36
+ // node scripts/check-sessions-integrity.mjs [--repo-root <path>] [--session-id <id>] [--json]
37
+ //
38
+ // EXIT CODES:
39
+ // 0 every judged record validates and mirrors
40
+ // 1 a judged record is schema-invalid, is dropped by vault-mirror, or (with
41
+ // --session-id) is not in the ledger at all
42
+ // 2 tool error — unknown/incomplete flag, or an unreadable repo root
43
+ //
44
+ // Node stdlib only apart from two repo-local imports; safe to call from a hook,
45
+ // a skill body or CI without any dependency install.
46
+
47
+ import { existsSync, readFileSync, statSync } from 'node:fs';
48
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
49
+ import { fileURLToPath } from 'node:url';
50
+
51
+ import { checkSessionsIntegrity, SESSIONS_PATH } from './lib/sessions-integrity-banner.mjs';
52
+ import { isMainModule } from './lib/is-main-module.mjs';
53
+
54
+ const SELF_DIR = dirname(fileURLToPath(import.meta.url));
55
+
56
+ const HELP_TEXT = `check-sessions-integrity.mjs — session-ledger schema + vault-mirror gate (#1417)
57
+
58
+ USAGE
59
+ node scripts/check-sessions-integrity.mjs [options]
60
+
61
+ DESCRIPTION
62
+ Runs every parseable record in .orchestrator/metrics/sessions.jsonl through
63
+ validateSession() AND through vault-mirror's real render path — the same two
64
+ populations the session-start banner reports. A record can pass one and fail
65
+ the other, so both are checked and reported separately.
66
+
67
+ This is the command form of session-end step 4a: after appending a session
68
+ record, prove it is sound BEFORE the close continues.
69
+
70
+ OPTIONS
71
+ --repo-root <path> Repo root holding .orchestrator/metrics/ (default: cwd).
72
+ --session-id <id> Judge only the record(s) carrying this session_id. Other
73
+ sessions' findings are still printed but never decide the
74
+ exit code — they are pre-existing, not this close's to fix.
75
+ An id with NO record in the ledger is a FAILURE (exit 1),
76
+ not a silent pass: absent and sound are indistinguishable
77
+ to the checker, and the fail-open reading is the dangerous
78
+ one.
79
+ --json Emit one JSON result object on stdout (diagnostics stay
80
+ on stderr).
81
+ --help, -h Show this help and exit.
82
+ --version Print the package version and exit.
83
+
84
+ EXIT CODES
85
+ 0 every judged record validates and mirrors
86
+ 1 a judged record is schema-invalid, is dropped by vault-mirror, or (with
87
+ --session-id) is not in the ledger at all
88
+ 2 tool error — unknown/incomplete flag, or an unreadable repo root
89
+
90
+ EXAMPLES
91
+ node scripts/check-sessions-integrity.mjs
92
+ node scripts/check-sessions-integrity.mjs --session-id main-2026-09-21-session-4
93
+ node scripts/check-sessions-integrity.mjs --json | jq '.findings'
94
+ `;
95
+
96
+ /**
97
+ * Parse argv. Returns `error` (a message) instead of throwing, so the caller
98
+ * owns the exit code.
99
+ *
100
+ * @param {string[]} argv
101
+ * @returns {{opts: {repoRoot: string|null, sessionId: string|null, json: boolean,
102
+ * help: boolean, version: boolean}, error: string|null}}
103
+ */
104
+ export function parseArgs(argv) {
105
+ const opts = { repoRoot: null, sessionId: null, json: false, help: false, version: false };
106
+ for (let i = 0; i < argv.length; i++) {
107
+ const arg = argv[i];
108
+ switch (arg) {
109
+ case '--repo-root':
110
+ case '--session-id': {
111
+ const value = argv[i + 1];
112
+ // A flag whose value is missing — or is itself the next flag — must be a
113
+ // usage error, never a silently empty filter that judges nothing.
114
+ if (typeof value !== 'string' || value === '' || value.startsWith('--')) {
115
+ return { opts, error: `${arg} requires a value` };
116
+ }
117
+ if (arg === '--repo-root') opts.repoRoot = value;
118
+ else opts.sessionId = value;
119
+ i += 1;
120
+ break;
121
+ }
122
+ case '--json':
123
+ opts.json = true;
124
+ break;
125
+ case '--help':
126
+ case '-h':
127
+ opts.help = true;
128
+ break;
129
+ case '--version':
130
+ opts.version = true;
131
+ break;
132
+ default:
133
+ return { opts, error: `unknown flag: ${arg}` };
134
+ }
135
+ }
136
+ return { opts, error: null };
137
+ }
138
+
139
+ /**
140
+ * Count the ledger records carrying `sessionId`. Unparseable lines are skipped —
141
+ * the same posture the banner takes: this judges schema integrity, not file
142
+ * corruption.
143
+ *
144
+ * @param {string} repoRoot
145
+ * @param {string} sessionId
146
+ * @returns {number}
147
+ */
148
+ function countLedgerRecords(repoRoot, sessionId) {
149
+ const filePath = join(repoRoot, SESSIONS_PATH);
150
+ if (!existsSync(filePath)) return 0;
151
+ let matched = 0;
152
+ for (const line of readFileSync(filePath, 'utf8').split('\n')) {
153
+ if (line.trim() === '') continue;
154
+ let record;
155
+ try {
156
+ record = JSON.parse(line);
157
+ } catch {
158
+ continue;
159
+ }
160
+ if (record && typeof record === 'object' && record.session_id === sessionId) matched += 1;
161
+ }
162
+ return matched;
163
+ }
164
+
165
+ /**
166
+ * Run the check and return a plain result object. Never throws; never exits.
167
+ *
168
+ * @param {{repoRoot?: string, sessionId?: string|null}} [opts]
169
+ * @returns {{ok: boolean, exitCode: 0|1|2, repoRoot: string, sessionId: string|null,
170
+ * total: number|null, matched: number|null, severity: 'warn'|'alert'|null,
171
+ * message: string|null, findings: {kind: string, line: number, sessionId: string, error: string}[],
172
+ * error: string|null}}
173
+ */
174
+ export function runSessionsIntegrityCheck({ repoRoot, sessionId = null } = {}) {
175
+ const root = resolve(repoRoot ?? process.cwd());
176
+ const base = {
177
+ ok: false,
178
+ exitCode: 2,
179
+ repoRoot: root,
180
+ sessionId: sessionId ?? null,
181
+ total: null,
182
+ matched: null,
183
+ severity: null,
184
+ message: null,
185
+ findings: [],
186
+ error: null,
187
+ };
188
+
189
+ try {
190
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
191
+ return { ...base, error: `repo root is not a directory: ${root}` };
192
+ }
193
+ } catch (err) {
194
+ return { ...base, error: `repo root unreadable: ${err?.message ?? String(err)}` };
195
+ }
196
+
197
+ const banner = checkSessionsIntegrity({ repoRoot: root });
198
+
199
+ const findings = banner
200
+ ? [
201
+ ...banner.schemaInvalid.map((f) => ({ kind: 'schema-invalid', ...f })),
202
+ ...banner.mirrorSkipped.map((f) => ({ kind: 'mirror-skipped', ...f })),
203
+ ]
204
+ : [];
205
+
206
+ const result = {
207
+ ...base,
208
+ total: banner ? banner.total : null,
209
+ severity: banner ? banner.severity : null,
210
+ message: banner ? banner.message : null,
211
+ findings,
212
+ };
213
+
214
+ if (sessionId === null || sessionId === undefined) {
215
+ // No filter: every finding decides.
216
+ const ok = findings.length === 0;
217
+ return { ...result, ok, exitCode: ok ? 0 : 1 };
218
+ }
219
+
220
+ const matched = countLedgerRecords(root, sessionId);
221
+ const mine = findings.filter((f) => f.sessionId === sessionId);
222
+
223
+ if (matched === 0) {
224
+ return {
225
+ ...result,
226
+ matched,
227
+ ok: false,
228
+ exitCode: 1,
229
+ error:
230
+ `no record with session_id "${sessionId}" in ${SESSIONS_PATH} — ` +
231
+ 'it was never written, or it was written under a different id. ' +
232
+ 'Re-emit it via scripts/emit-session.mjs.',
233
+ };
234
+ }
235
+
236
+ const ok = mine.length === 0;
237
+ return { ...result, matched, ok, exitCode: ok ? 0 : 1 };
238
+ }
239
+
240
+ function readVersion() {
241
+ try {
242
+ const pkg = JSON.parse(readFileSync(join(SELF_DIR, '..', 'package.json'), 'utf8'));
243
+ return typeof pkg.version === 'string' ? pkg.version : 'unknown';
244
+ } catch {
245
+ return 'unknown';
246
+ }
247
+ }
248
+
249
+ // CLI entry — only when run directly, never on import (an import must do
250
+ // nothing: this module is loaded by tests and is reachable from hook code).
251
+ if (isMainModule(import.meta.url)) {
252
+ const { opts, error } = parseArgs(process.argv.slice(2));
253
+
254
+ if (error !== null) {
255
+ process.stderr.write(`[check-sessions-integrity] ${error}\n`);
256
+ process.stderr.write('Run with --help for usage.\n');
257
+ process.exit(2);
258
+ }
259
+
260
+ if (opts.help) {
261
+ process.stdout.write(HELP_TEXT);
262
+ process.exit(0);
263
+ }
264
+
265
+ if (opts.version) {
266
+ process.stdout.write(`${readVersion()}\n`);
267
+ process.exit(0);
268
+ }
269
+
270
+ const repoRootArg =
271
+ opts.repoRoot === null ? undefined : isAbsolute(opts.repoRoot) ? opts.repoRoot : resolve(process.cwd(), opts.repoRoot);
272
+
273
+ const result = runSessionsIntegrityCheck({ repoRoot: repoRootArg, sessionId: opts.sessionId });
274
+
275
+ if (opts.json) {
276
+ process.stdout.write(`${JSON.stringify(result)}\n`);
277
+ process.exit(result.exitCode);
278
+ }
279
+
280
+ // The banner text covers EVERY record, including other sessions' pre-existing
281
+ // findings — printed for visibility, even when the verdict below is clean.
282
+ if (result.message) process.stderr.write(`${result.message}\n`);
283
+ if (result.error) process.stderr.write(`[check-sessions-integrity] ${result.error}\n`);
284
+
285
+ if (result.exitCode === 2) process.exit(2);
286
+
287
+ if (result.ok) {
288
+ const scope = result.sessionId ? `record "${result.sessionId}"` : 'every ledger record';
289
+ process.stdout.write(`[check-sessions-integrity] OK — ${scope} validates and mirrors\n`);
290
+ process.exit(0);
291
+ }
292
+
293
+ const scope = result.sessionId ? `session "${result.sessionId}"` : 'the sessions ledger';
294
+ process.stderr.write(
295
+ `[check-sessions-integrity] FAIL — ${scope} is not sound. ` +
296
+ 'Re-emit the affected record(s) via scripts/emit-session.mjs ' +
297
+ '(bulk repair: node scripts/repair-invalid-sessions.mjs --apply).\n',
298
+ );
299
+ process.exit(1);
300
+ }
@@ -20,6 +20,7 @@ import path from 'node:path';
20
20
  import { fileURLToPath } from 'node:url';
21
21
 
22
22
  import { CURSOR_TO_CANONICAL_EVENT } from './lib/cursor-hook-bridge.mjs';
23
+ import { shellQuote as shQuote } from './lib/sh-quote.mjs';
23
24
 
24
25
  const __filename = fileURLToPath(import.meta.url);
25
26
  const SCRIPT_DIR = path.dirname(__filename);
@@ -62,10 +63,6 @@ function _isDir(p) {
62
63
  try { return statSync(p).isDirectory(); } catch { return false; }
63
64
  }
64
65
 
65
- function shQuote(value) {
66
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
67
- }
68
-
69
66
  function linkPath(source, dest, label) {
70
67
  mkdirSync(path.dirname(dest), { recursive: true });
71
68
  if (_isSymlink(dest)) {
@@ -49,6 +49,7 @@ import { readPeerCards } from './lib/peer-cards/reader.mjs';
49
49
  import { filterRealSessions } from './lib/session-schema/filters.mjs';
50
50
  import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
51
51
  import { recordDialecticRun } from './lib/learnings/evolve-telemetry.mjs';
52
+ import { DEFAULT_BUDGET_TOKENS } from './lib/config/dialectic.mjs';
52
53
 
53
54
  // ---------------------------------------------------------------------------
54
55
  // Constants
@@ -57,8 +58,11 @@ import { recordDialecticRun } from './lib/learnings/evolve-telemetry.mjs';
57
58
  /** Allowed LLM model identifiers. Per #506 EARS unwanted-behaviour: fail-fast on others. */
58
59
  export const ALLOWED_MODELS = Object.freeze(['haiku', 'sonnet', 'opus']);
59
60
 
60
- /** Default per-call budget (input + output tokens). Per #506 AC2. */
61
- export const DEFAULT_BUDGET = Object.freeze({ input: 8000, output: 4000 });
61
+ /**
62
+ * Default per-call budget (input + output tokens). Per #506 AC2; the input
63
+ * ceiling is `DEFAULT_BUDGET_TOKENS` from the config parser (SSOT, #1380).
64
+ */
65
+ export const DEFAULT_BUDGET = Object.freeze({ input: DEFAULT_BUDGET_TOKENS, output: 4000 });
62
66
 
63
67
  /** Industry-standard heuristic: ~4 chars per token for English prose. */
64
68
  const CHARS_PER_TOKEN = 4;
@@ -309,19 +313,21 @@ export function buildPrompt(payload, model, randomNonce = () => randomBytes(4).t
309
313
  '',
310
314
  '```diff',
311
315
  '# target: user',
312
- '<full proposed body of USER.md, replacing existing content>',
316
+ '<the `## ` sections of USER.md you change or add>',
313
317
  '```',
314
318
  '',
315
319
  '```diff',
316
320
  '# target: agent',
317
- '<full proposed body of AGENT.md, replacing existing content>',
321
+ '<the `## ` sections of AGENT.md you change or add>',
318
322
  '```',
319
323
  '',
320
324
  'Rules:',
321
325
  '- Emit at most one block per target. Omit a target entirely when no update is warranted.',
322
- '- The block body is the FULL replacement body — not a unified diff hunk.',
326
+ '- The block body is a set of whole `## ` sections — not a unified diff hunk. It is merged per section:',
327
+ ' omitted sections stay UNCHANGED (nothing is deleted), so emit ONLY the sections you change or newly',
328
+ ' ground, and reuse an existing heading verbatim to update that section.',
323
329
  '- Preserve existing peer-card frontmatter; do not include `---` frontmatter lines in your block.',
324
- '- Do not invent new sections that are not grounded in the supplied learnings or sessions.',
330
+ '- Do not invent sections without evidence from the supplied learnings or sessions — nothing downstream rejects them.',
325
331
  `- Operate within the dialectic budget appropriate for model '${model}'.`,
326
332
  '',
327
333
  ].join('\n');
@@ -505,6 +511,26 @@ async function readSteering(repoRoot) {
505
511
  * @property {number} [budget]
506
512
  */
507
513
 
514
+ /**
515
+ * Record a THROWN abort (`unknown-model`, `subagent-crash`) before the caller
516
+ * rethrows the original error (#1221). The record is guarded on its own:
517
+ * `recordDialecticRun()` is best-effort today, but the rethrow contract must
518
+ * not depend on that staying true — a telemetry failure never replaces the
519
+ * error the caller is about to rethrow.
520
+ *
521
+ * @param {string} repoRoot
522
+ * @param {'unknown-model' | 'subagent-crash'} status
523
+ * @param {number} t0 — `Date.now()` at run start
524
+ * @returns {Promise<void>}
525
+ */
526
+ async function recordThrownAbort(repoRoot, status, t0) {
527
+ try {
528
+ await recordDialecticRun({ repoRoot, status, durationMs: Date.now() - t0 });
529
+ } catch {
530
+ /* swallowed on purpose — see docblock */
531
+ }
532
+ }
533
+
508
534
  /**
509
535
  * Run the deriver loop: load inputs → build payload → check budget → dispatch
510
536
  * agent → parse response → guard empty-card. Returns a structured verdict.
@@ -546,15 +572,20 @@ export async function runDialecticDeriver({
546
572
 
547
573
  // #1206 — telemetry start marker for the mechanical `orchestrator.dialectic.
548
574
  // completed` emits below. Mechanical (measured), not the coordinator's own
549
- // typed DURATION_MS placeholder — covers every return path THIS function
550
- // can determine on its own; see the module header for the two abort classes
551
- // (`unknown-model`, thrown above; `subagent-crash`) and APPLY-mode success
552
- // that only the skill-prose caller can record (merge + Agent() dispatch
553
- // both happen one layer up, outside this pure pipeline).
575
+ // typed DURATION_MS placeholder — covers every outcome THIS function can
576
+ // see: the returns, and since #1221 the two THROWN aborts (`unknown-model`,
577
+ // `subagent-crash`), recorded at the throw point before the original error
578
+ // is rethrown. Only APPLY-mode success stays with the skill-prose caller —
579
+ // the merge happens one layer up, outside this pure pipeline.
554
580
  const t0 = Date.now();
555
581
 
556
582
  // Gate 1: model fail-fast. Throws Error with the canonical message.
557
- validateModel(model);
583
+ try {
584
+ validateModel(model);
585
+ } catch (err) {
586
+ await recordThrownAbort(repoRoot, 'unknown-model', t0);
587
+ throw err;
588
+ }
558
589
 
559
590
  // Load inputs (best-effort, never throws on missing).
560
591
  const [learnings, sessions, peerCardsResult, steering] = await Promise.all([
@@ -611,7 +642,13 @@ export async function runDialecticDeriver({
611
642
 
612
643
  // Dispatch — DI boundary. Caller wires the real Agent({...}) wrapper or a mock.
613
644
  const maxTokens = typeof budget?.output === 'number' ? budget.output : DEFAULT_BUDGET.output;
614
- const response = await dispatchAgent({ model, prompt, maxTokens });
645
+ let response;
646
+ try {
647
+ response = await dispatchAgent({ model, prompt, maxTokens });
648
+ } catch (err) {
649
+ await recordThrownAbort(repoRoot, 'subagent-crash', t0);
650
+ throw err;
651
+ }
615
652
 
616
653
  const text = typeof response?.text === 'string' ? response.text : '';
617
654
  const { diff } = parseResponse(text);