session-orchestrator 3.23.0 → 4.0.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 (393) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +13 -0
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1401 -0
  81. package/NOTICE +11 -6
  82. package/README.md +127 -92
  83. package/agents/db-specialist.md +0 -1
  84. package/agents/eval-judge.md +1 -1
  85. package/agents/skill-applied-judge.md +1 -1
  86. package/assets/wave-lifecycle.svg +98 -0
  87. package/commands/release.md +6 -3
  88. package/commands/session.md +18 -3
  89. package/docs/README.md +4 -0
  90. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  91. package/docs/baseline.md +67 -0
  92. package/docs/ci-setup.md +249 -48
  93. package/docs/codex-setup.md +66 -22
  94. package/docs/components.md +37 -16
  95. package/docs/cursor-setup.md +6 -2
  96. package/docs/events-schema.md +51 -10
  97. package/docs/instruction-delivery.md +62 -0
  98. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  99. package/docs/migration-v4.md +341 -0
  100. package/docs/pi-setup.md +6 -1
  101. package/docs/plugin-architecture-v3.md +1 -1
  102. package/docs/rule-authoring.md +85 -19
  103. package/docs/scope-collision-guard.md +8 -8
  104. package/docs/session-config-reference.md +120 -61
  105. package/docs/session-config-template.md +40 -33
  106. package/docs/telemetry/telemetry-claims.md +11 -10
  107. package/docs/telemetry.md +187 -4
  108. package/docs/vault-docs-architecture.md +50 -11
  109. package/hooks/_lib/atomic-json.mjs +111 -0
  110. package/hooks/_lib/hook-import-set.json +1487 -0
  111. package/hooks/_lib/subagent-paths.mjs +143 -0
  112. package/hooks/_lib/subagent-transcript.mjs +562 -0
  113. package/hooks/config-protection.mjs +2 -2
  114. package/hooks/cwd-change-restore.mjs +11 -31
  115. package/hooks/enforce-commands.mjs +69 -0
  116. package/hooks/enforce-scope.mjs +35 -6
  117. package/hooks/hooks-codex.json +1 -1
  118. package/hooks/hooks-cursor.json +10 -0
  119. package/hooks/hooks-pi.json +5 -0
  120. package/hooks/hooks.json +6 -1
  121. package/hooks/loop-guard.mjs +3 -3
  122. package/hooks/on-session-end.mjs +280 -14
  123. package/hooks/on-session-start.mjs +153 -4
  124. package/hooks/on-stop.mjs +371 -17
  125. package/hooks/operator-steer.mjs +2 -2
  126. package/hooks/post-bash-write-verify.mjs +189 -4
  127. package/hooks/post-edit-import-probe.mjs +344 -0
  128. package/hooks/post-subagent-discovery-validator.mjs +278 -392
  129. package/hooks/post-tool-batch-wave-signal.mjs +272 -44
  130. package/hooks/post-tool-failure-corrective-context.mjs +11 -34
  131. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  132. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  133. package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
  134. package/hooks/skill-invocation-telemetry.mjs +17 -5
  135. package/hooks/subagent-telemetry.mjs +24 -30
  136. package/monitors/monitors.json +3 -3
  137. package/package.json +9 -1
  138. package/pi/prompts/session.md +2 -2
  139. package/plugin.json +27 -0
  140. package/scripts/autopilot.mjs +26 -12
  141. package/scripts/backfill-abandoned-sessions.mjs +130 -15
  142. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  143. package/scripts/dialectic-deriver.mjs +73 -8
  144. package/scripts/emit-event.mjs +10 -2
  145. package/scripts/export-hw-learnings.mjs +113 -1
  146. package/scripts/generate-agents-skills.mjs +378 -0
  147. package/scripts/generate-cursor-adapter.mjs +45 -8
  148. package/scripts/generate-hook-import-set.mjs +249 -0
  149. package/scripts/lib/agent-status.mjs +13 -2
  150. package/scripts/lib/auq/parse.mjs +5 -29
  151. package/scripts/lib/auto-dialectic.mjs +68 -0
  152. package/scripts/lib/auto-dream.mjs +38 -36
  153. package/scripts/lib/autonomy/suitability.mjs +6 -0
  154. package/scripts/lib/autopilot/loop.mjs +2 -2
  155. package/scripts/lib/autopilot/worktree-pipeline.mjs +82 -6
  156. package/scripts/lib/build-live-signals.mjs +25 -22
  157. package/scripts/lib/ci-status-banner.mjs +220 -75
  158. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  159. package/scripts/lib/cold-start-detector.mjs +23 -14
  160. package/scripts/lib/config/auto-dream.mjs +2 -1
  161. package/scripts/lib/config/block-header.mjs +63 -0
  162. package/scripts/lib/config/block-preprocess.mjs +177 -0
  163. package/scripts/lib/config/broken-window.mjs +2 -1
  164. package/scripts/lib/config/cold-start.mjs +2 -1
  165. package/scripts/lib/config/config-protection.mjs +22 -2
  166. package/scripts/lib/config/context-coverage.mjs +2 -1
  167. package/scripts/lib/config/cross-repo.mjs +2 -1
  168. package/scripts/lib/config/custom-phases.mjs +2 -1
  169. package/scripts/lib/config/dialectic.mjs +2 -1
  170. package/scripts/lib/config/discovery-validator.mjs +9 -3
  171. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  172. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  173. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  174. package/scripts/lib/config/docs-staleness.mjs +2 -1
  175. package/scripts/lib/config/drift-check.mjs +2 -1
  176. package/scripts/lib/config/eval.mjs +2 -1
  177. package/scripts/lib/config/events-rotation.mjs +2 -1
  178. package/scripts/lib/config/evolve.mjs +8 -2
  179. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  180. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  181. package/scripts/lib/config/handover-gate.mjs +2 -1
  182. package/scripts/lib/config/health-endpoints.mjs +388 -0
  183. package/scripts/lib/config/issue-budget.mjs +2 -1
  184. package/scripts/lib/config/loop-guard.mjs +2 -1
  185. package/scripts/lib/config/memory.mjs +2 -1
  186. package/scripts/lib/config/moc-staleness.mjs +2 -1
  187. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  188. package/scripts/lib/config/private-config-dir.mjs +67 -0
  189. package/scripts/lib/config/reconcile.mjs +2 -1
  190. package/scripts/lib/config/remote-hosts.mjs +234 -0
  191. package/scripts/lib/config/section-extractor.mjs +7 -1
  192. package/scripts/lib/config/skill-evolution.mjs +2 -1
  193. package/scripts/lib/config/slopcheck.mjs +2 -1
  194. package/scripts/lib/config/state-md-lock.mjs +2 -1
  195. package/scripts/lib/config/templates-first.mjs +2 -1
  196. package/scripts/lib/config/test.mjs +2 -1
  197. package/scripts/lib/config/vault-integration.mjs +7 -1
  198. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  199. package/scripts/lib/config/vault-staleness.mjs +2 -1
  200. package/scripts/lib/config/vault-sync.mjs +2 -1
  201. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  202. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  203. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  204. package/scripts/lib/config.mjs +31 -3
  205. package/scripts/lib/convergence-monitor.mjs +82 -16
  206. package/scripts/lib/dispatcher/enumerate.mjs +2 -17
  207. package/scripts/lib/dispatcher/rank.mjs +124 -48
  208. package/scripts/lib/ecosystem-health.mjs +16 -2
  209. package/scripts/lib/eval/engine.mjs +9 -1
  210. package/scripts/lib/eval/session-resolve.mjs +23 -4
  211. package/scripts/lib/events-schema.mjs +48 -0
  212. package/scripts/lib/events.mjs +256 -7
  213. package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
  214. package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
  215. package/scripts/lib/frontmatter-guard.mjs +131 -13
  216. package/scripts/lib/gates/gate-full.mjs +26 -0
  217. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  218. package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
  219. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  220. package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
  221. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  222. package/scripts/lib/host-identity.mjs +50 -11
  223. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  224. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  225. package/scripts/lib/learnings/io.mjs +60 -6
  226. package/scripts/lib/memory-banner.mjs +20 -8
  227. package/scripts/lib/memory-proposals/store.mjs +30 -22
  228. package/scripts/lib/owner-config-banner.mjs +43 -6
  229. package/scripts/lib/owner-config-loader.mjs +21 -10
  230. package/scripts/lib/owner-interview.mjs +3 -3
  231. package/scripts/lib/owner-yaml.mjs +207 -14
  232. package/scripts/lib/peer-discovery.mjs +20 -2
  233. package/scripts/lib/platform.mjs +108 -15
  234. package/scripts/lib/plugin-update-banner.mjs +406 -0
  235. package/scripts/lib/project-hygiene.mjs +38 -2
  236. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  237. package/scripts/lib/quality-gate.mjs +133 -44
  238. package/scripts/lib/reconcile/emitter.mjs +68 -6
  239. package/scripts/lib/reconcile/engine.mjs +249 -9
  240. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  241. package/scripts/lib/reconcile/writer.mjs +40 -18
  242. package/scripts/lib/scope-gate.mjs +36 -0
  243. package/scripts/lib/session-close-backfill.mjs +125 -18
  244. package/scripts/lib/session-discovery.mjs +57 -3
  245. package/scripts/lib/session-end/phase-skip.mjs +2 -2
  246. package/scripts/lib/session-id.mjs +12 -23
  247. package/scripts/lib/session-identity/own-session.mjs +187 -11
  248. package/scripts/lib/session-lock-shape.mjs +43 -0
  249. package/scripts/lib/session-lock.mjs +5 -10
  250. package/scripts/lib/session-registry.mjs +25 -9
  251. package/scripts/lib/session-schema/constants.mjs +36 -2
  252. package/scripts/lib/session-schema/validator.mjs +38 -4
  253. package/scripts/lib/session-start-probes.mjs +18 -1
  254. package/scripts/lib/session-transition.mjs +1 -1
  255. package/scripts/lib/sessions-canonical.mjs +446 -0
  256. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  257. package/scripts/lib/skill-health/join.mjs +17 -4
  258. package/scripts/lib/state-md.mjs +78 -0
  259. package/scripts/lib/sunset/walker.mjs +6 -0
  260. package/scripts/lib/telemetry/schema.mjs +255 -17
  261. package/scripts/lib/telemetry/sync.mjs +417 -24
  262. package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
  263. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  264. package/scripts/lib/validate/check-agents.mjs +3 -3
  265. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  266. package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
  267. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
  268. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  269. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  270. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  271. package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
  272. package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
  273. package/scripts/lib/validate/check-unwired-features.mjs +0 -9
  274. package/scripts/lib/validate/check-validator-registration.mjs +254 -0
  275. package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
  276. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  277. package/scripts/lib/validate/markdown-fences.mjs +196 -0
  278. package/scripts/lib/vault-backfill/template.mjs +63 -6
  279. package/scripts/lib/vault-mirror/process.mjs +165 -42
  280. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  281. package/scripts/lib/vault-status/board-lock.mjs +185 -0
  282. package/scripts/lib/vault-status/board-writer.mjs +174 -135
  283. package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
  284. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  285. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  286. package/scripts/lib/wave-executor/remote-dispatch.mjs +502 -0
  287. package/scripts/lib/wave-resource-gate.mjs +133 -7
  288. package/scripts/lib/wave-sizing.mjs +4 -1
  289. package/scripts/lib/wave-transcript-tail.mjs +142 -8
  290. package/scripts/materialize-wave-scope.mjs +32 -9
  291. package/scripts/memory-propose.mjs +146 -8
  292. package/scripts/migrate-cold-start-seed.mjs +4 -1
  293. package/scripts/parse-config.mjs +60 -3
  294. package/scripts/promote-vault-strict.mjs +4 -15
  295. package/scripts/release.mjs +337 -29
  296. package/scripts/repair-invalid-sessions.mjs +3 -3
  297. package/scripts/run-quality-gate.mjs +128 -11
  298. package/scripts/site-numbers.mjs +36 -4
  299. package/scripts/sweep-expired-learnings.mjs +90 -0
  300. package/scripts/sync-vault-schema.mjs +3 -1
  301. package/scripts/telemetry.mjs +2 -2
  302. package/scripts/validate-plugin.mjs +187 -0
  303. package/scripts/validate-wave-scope.mjs +28 -8
  304. package/scripts/vault-consolidate.mjs +3 -11
  305. package/scripts/vault-integration-watcher.mjs +2 -4
  306. package/scripts/vault-mirror.mjs +111 -26
  307. package/scripts/wave-scope-binding.mjs +215 -0
  308. package/skills/_shared/instruction-file-resolution.md +10 -0
  309. package/skills/_shared/parallel-aware-auq.md +31 -2
  310. package/skills/_shared/parallel-aware-preamble.md +18 -4
  311. package/skills/_shared/platform-tools.md +1 -1
  312. package/skills/_shared/state-ownership.md +1 -1
  313. package/skills/architecture/SKILL.md +7 -5
  314. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  315. package/skills/autopilot/SKILL.md +4 -18
  316. package/skills/claude-md-drift-check/SKILL.md +5 -1
  317. package/skills/claude-md-drift-check/checker.mjs +62 -2
  318. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  319. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  320. package/skills/discovery/probes-arch.md +20 -18
  321. package/skills/dispatcher/SKILL.md +3 -2
  322. package/skills/ecosystem-health/SKILL.md +4 -1
  323. package/skills/ecosystem-health/wizard.md +5 -0
  324. package/skills/evolve/SKILL.md +87 -11
  325. package/skills/frontmatter-guard/SKILL.md +11 -5
  326. package/skills/npm-publish/SKILL.md +1 -1
  327. package/skills/reconcile/SKILL.md +38 -2
  328. package/skills/remote-offload/SKILL.md +89 -0
  329. package/skills/session-end/SKILL.md +18 -905
  330. package/skills/session-end/phase-3-6-tail.md +19 -9
  331. package/skills/session-end/plan-verification.md +221 -155
  332. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  333. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  334. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  335. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  336. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  337. package/skills/session-end/references/session-summary-template.md +62 -0
  338. package/skills/session-plan/SKILL.md +49 -0
  339. package/skills/session-start/SKILL.md +41 -900
  340. package/skills/session-start/phase-8-5-express-path.md +1 -1
  341. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  342. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  343. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  344. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  345. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  346. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  347. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  348. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  349. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  350. package/skills/vault-sync/validator.mjs +21 -27
  351. package/skills/wave-executor/SKILL.md +16 -2
  352. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  353. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  354. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  355. package/skills/wave-executor/wave-loop.md +14 -1271
  356. package/templates/_shared/journey-manifest.md +10 -6
  357. package/.cursor/commands/autopilot-multi.md +0 -14
  358. package/.cursor/commands/contract-version-bump.md +0 -14
  359. package/.cursor/commands/journey-audit.md +0 -14
  360. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  361. package/.cursor/skills/daily/SKILL.md +0 -12
  362. package/.cursor/skills/domain-model/SKILL.md +0 -13
  363. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  364. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  365. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  366. package/commands/autopilot-multi.md +0 -74
  367. package/commands/contract-version-bump.md +0 -28
  368. package/commands/journey-audit.md +0 -43
  369. package/pi/prompts/autopilot-multi.md +0 -12
  370. package/pi/prompts/contract-version-bump.md +0 -12
  371. package/pi/prompts/journey-audit.md +0 -12
  372. package/scripts/autopilot-multi.mjs +0 -885
  373. package/scripts/backfill-learnings-expires.mjs +0 -196
  374. package/scripts/backfill-learnings.mjs +0 -203
  375. package/scripts/fleet-instruction-scan.mjs +0 -141
  376. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  377. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  378. package/scripts/lib/webhook-url.mjs +0 -105
  379. package/scripts/lifecycle-sim-v6.mjs +0 -347
  380. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  381. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  382. package/scripts/upload-social-preview.mjs +0 -316
  383. package/skills/_shared/model-selection.md +0 -64
  384. package/skills/contract-version-bump/SKILL.md +0 -219
  385. package/skills/daily/SKILL.md +0 -222
  386. package/skills/daily/generate.sh +0 -92
  387. package/skills/daily/templates/daily.md.tpl +0 -36
  388. package/skills/journey-audit/SKILL.md +0 -269
  389. package/skills/skill-creator/SKILL.md +0 -168
  390. package/skills/ubiquitous-language/SKILL.md +0 -97
  391. package/skills/vault-sync/package-lock.json +0 -40
  392. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  393. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -1,885 +0,0 @@
1
- // scripts/autopilot-multi.mjs
2
- //
3
- // `autopilot --multi-story` CLI entrypoint. Orchestrates N parallel issue
4
- // pipelines using worktree isolation. v1 thin-slice: apply mode runtime loop
5
- // wired in by W3 P5-Orchestration-Glue agent.
6
- //
7
- // References:
8
- // - "Autopilot Phase D — Per-Story Worktree Pipelines" (#341; archived in the private Meta-Vault) (PRD, status: shaped)
9
- // - scripts/lib/autopilot/dep-graph.mjs (NEW, parallel-built by C2 agent)
10
- // - scripts/lib/autopilot/worktree-pipeline.mjs (NEW, parallel-built by C3 agent)
11
- // - scripts/lib/autopilot/multi-killswitch.mjs (NEW, parallel-built by C4 agent)
12
- //
13
- // CLI guard at bottom — top-level main() never runs on import (deep-2 #368).
14
-
15
- import { parseArgs } from 'node:util';
16
- import { execFile as execFileCb, spawnSync } from 'node:child_process';
17
- import { promisify } from 'node:util';
18
- import path from 'node:path';
19
- import fs from 'node:fs';
20
- import { resolveRepoSpec } from './lib/vcs-repo-spec.mjs';
21
-
22
- const execFile = promisify(execFileCb);
23
-
24
- // ---------------------------------------------------------------------------
25
- // Constants
26
- // ---------------------------------------------------------------------------
27
-
28
- const HELP_TEXT = `autopilot-multi — Phase D --multi-story orchestrator (v3.6 scaffold)
29
-
30
- USAGE:
31
- autopilot-multi [OPTIONS]
32
-
33
- OPTIONS:
34
- --max-stories <N> Max parallel story loops (1..10, default: 3)
35
- --max-hours <H> Wall-clock budget in hours (0.5..24, default: 8)
36
- --inactivity-timeout <S> Seconds without completion → stop (60..3600, default: 300)
37
- --draft-mr <policy> off | on-loop-start | on-green (default: off)
38
- --stall-seconds <S> Per-loop STALL_TIMEOUT threshold (60..3600, default: 600)
39
- --dry-run Emit plan only, do not execute (default: true)
40
- --apply Execute the plan (mutex with --dry-run)
41
- --json Machine-readable output to stdout
42
- --verbose Diagnostic output to stderr
43
- --deconflict-paths <glob> Required when bg-isolation=none AND max-stories>1.
44
- Confirms per-story file ownership is planned.
45
- See .claude/rules/parallel-sessions.md PSA-001/002/003.
46
- -h, --help Show this help and exit
47
- --version Print version and exit
48
-
49
- EXIT CODES:
50
- 0 Success
51
- 1 User error (bad flags)
52
- 2 System error (libs/binaries/probe failed) — or a terminal stop reason
53
- (kill-switch / spiral / cohort-abort / spawn-error)
54
- 3 no productive work — apply run completed but zero loops did any work
55
- `;
56
-
57
- const FLAGS = {
58
- 'max-stories': { type: 'string', default: '3' },
59
- 'max-hours': { type: 'string', default: '8' },
60
- 'inactivity-timeout': { type: 'string', default: '300' },
61
- 'draft-mr': { type: 'string', default: 'off' },
62
- 'stall-seconds': { type: 'string', default: '600' },
63
- 'dry-run': { type: 'boolean', default: true },
64
- 'apply': { type: 'boolean', default: false },
65
- 'json': { type: 'boolean', default: false },
66
- 'verbose': { type: 'boolean', default: false },
67
- 'help': { type: 'boolean', default: false, short: 'h' },
68
- 'version': { type: 'boolean', default: false },
69
- // bg-isolation: none requires explicit file-scope deconfliction acknowledgement
70
- // when max-stories > 1. Pass a glob to confirm per-story file ownership is
71
- // planned. See .claude/rules/parallel-sessions.md PSA-001/002/003 and #431.
72
- 'deconflict-paths': { type: 'string', default: '' },
73
- };
74
-
75
- const VALID_DRAFT_MR_POLICIES = new Set(['off', 'on-loop-start', 'on-green']);
76
-
77
- // ---------------------------------------------------------------------------
78
- // parseFlags — parses + validates argv, returns typed flags object
79
- // ---------------------------------------------------------------------------
80
-
81
- /**
82
- * Parse and validate CLI flags.
83
- *
84
- * @param {string[]} argv
85
- * @returns {{
86
- * maxStories: number,
87
- * maxHours: number,
88
- * inactivityTimeoutMs: number,
89
- * draftMrPolicy: string,
90
- * stallTimeoutSeconds: number,
91
- * dryRun: boolean,
92
- * apply: boolean,
93
- * json: boolean,
94
- * verbose: boolean,
95
- * help: boolean,
96
- * version: boolean,
97
- * deconflictPaths: string,
98
- * }}
99
- */
100
- export function parseFlags(argv) {
101
- const { values } = parseArgs({ args: argv, options: FLAGS, allowPositionals: false });
102
-
103
- // Mutex: --dry-run and --apply cannot both be explicitly set to true.
104
- // Since --dry-run defaults to true, treat apply=true + the user explicitly
105
- // also passing --dry-run as a mutex violation.
106
- if (values['apply'] && values['dry-run'] && argv.includes('--dry-run')) {
107
- throw new UserError('--dry-run and --apply are mutually exclusive; pick one.');
108
- }
109
-
110
- // Resolve safe default: if neither --apply nor explicit --dry-run, dry-run wins.
111
- const apply = /** @type {boolean} */ (values['apply']);
112
- // When apply is true and --dry-run was not explicitly passed, disable dry-run.
113
- const dryRun = apply ? false : /** @type {boolean} */ (values['dry-run']);
114
-
115
- // max-stories: int, clamp [1, 10]
116
- const rawMaxStories = parseInt(/** @type {string} */ (values['max-stories']), 10);
117
- if (isNaN(rawMaxStories)) {
118
- throw new UserError(`--max-stories must be an integer, got: ${values['max-stories']}`);
119
- }
120
- const maxStories = Math.max(1, Math.min(10, rawMaxStories));
121
-
122
- // max-hours: float, clamp [0.5, 24]
123
- const rawMaxHours = parseFloat(/** @type {string} */ (values['max-hours']));
124
- if (isNaN(rawMaxHours)) {
125
- throw new UserError(`--max-hours must be a number, got: ${values['max-hours']}`);
126
- }
127
- const maxHours = Math.max(0.5, Math.min(24, rawMaxHours));
128
-
129
- // inactivity-timeout: int seconds, clamp [60, 3600]
130
- const rawInactivity = parseInt(/** @type {string} */ (values['inactivity-timeout']), 10);
131
- if (isNaN(rawInactivity)) {
132
- throw new UserError(`--inactivity-timeout must be an integer, got: ${values['inactivity-timeout']}`);
133
- }
134
- const inactivityTimeoutMs = Math.max(60, Math.min(3600, rawInactivity)) * 1000;
135
-
136
- // draft-mr: validate against allowed set
137
- const draftMrPolicy = /** @type {string} */ (values['draft-mr']);
138
- if (!VALID_DRAFT_MR_POLICIES.has(draftMrPolicy)) {
139
- throw new UserError(
140
- `--draft-mr must be one of: off, on-loop-start, on-green. Got: "${draftMrPolicy}"`
141
- );
142
- }
143
-
144
- // stall-seconds: int, clamp [60, 3600]
145
- const rawStall = parseInt(/** @type {string} */ (values['stall-seconds']), 10);
146
- if (isNaN(rawStall)) {
147
- throw new UserError(`--stall-seconds must be an integer, got: ${values['stall-seconds']}`);
148
- }
149
- const stallTimeoutSeconds = Math.max(60, Math.min(3600, rawStall));
150
-
151
- return {
152
- maxStories,
153
- maxHours,
154
- inactivityTimeoutMs,
155
- draftMrPolicy,
156
- stallTimeoutSeconds,
157
- dryRun,
158
- apply,
159
- json: /** @type {boolean} */ (values['json']),
160
- verbose: /** @type {boolean} */ (values['verbose']),
161
- help: /** @type {boolean} */ (values['help']),
162
- version: /** @type {boolean} */ (values['version']),
163
- deconflictPaths: /** @type {string} */ (values['deconflict-paths'] ?? ''),
164
- };
165
- }
166
-
167
- // ---------------------------------------------------------------------------
168
- // buildOrchestratorState — compose plan snapshot from flags + probe + issues
169
- // ---------------------------------------------------------------------------
170
-
171
- /**
172
- * Build a serialisable orchestrator state / plan object.
173
- *
174
- * @param {{
175
- * maxStories: number,
176
- * maxHours: number,
177
- * inactivityTimeoutMs: number,
178
- * draftMrPolicy: string,
179
- * stallTimeoutSeconds: number,
180
- * dryRun: boolean,
181
- * apply: boolean,
182
- * json: boolean,
183
- * verbose: boolean,
184
- * }} flags
185
- * @param {object|null} snapshot Resource-probe snapshot (null in dry-run without probe)
186
- * @param {Array<{iid: number, title: string, blocks: number[], blockedBy: number[], labels: string[]}>} issues
187
- * @param {string} [parentRunId] Unique run-id for this orchestrator session
188
- * @returns {{
189
- * concurrencyCap: number,
190
- * totalIssues: number,
191
- * readyIssues: number,
192
- * executionPlan: Array<{iid: number, title: string}>,
193
- * flags: object,
194
- * issues: Array<{iid: number, title: string, blocks: number[], blockedBy: number[], labels: string[]}>,
195
- * parentRunId: string,
196
- * }}
197
- */
198
- export function buildOrchestratorState(flags, snapshot, issues, parentRunId = '') {
199
- // If a snapshot is available and has a cap, use it; otherwise fall back to maxStories.
200
- const snapshotCap = snapshot && typeof snapshot.cpuCores === 'number'
201
- ? snapshot.cpuCores
202
- : flags.maxStories;
203
- const concurrencyCap = Math.min(flags.maxStories, snapshotCap);
204
-
205
- const executionPlan = issues
206
- .slice(0, concurrencyCap)
207
- .map(({ iid, title }) => ({ iid, title }));
208
-
209
- return {
210
- concurrencyCap,
211
- totalIssues: issues.length,
212
- readyIssues: issues.length,
213
- executionPlan,
214
- issues,
215
- parentRunId,
216
- flags: {
217
- maxStories: flags.maxStories,
218
- maxHours: flags.maxHours,
219
- inactivityTimeoutMs: flags.inactivityTimeoutMs,
220
- draftMrPolicy: flags.draftMrPolicy,
221
- stallTimeoutSeconds: flags.stallTimeoutSeconds,
222
- dryRun: flags.dryRun,
223
- apply: flags.apply,
224
- },
225
- };
226
- }
227
-
228
- // ---------------------------------------------------------------------------
229
- // Session Config helpers
230
- // ---------------------------------------------------------------------------
231
-
232
- /**
233
- * Load Session Config via `node scripts/parse-config.mjs` (same subprocess
234
- * pattern used by scripts/autopilot.mjs). Returns null on failure — callers
235
- * apply their own defaults.
236
- *
237
- * @returns {object|null}
238
- */
239
- function loadSessionConfig() {
240
- try {
241
- const scriptPath = path.resolve(
242
- new URL('.', import.meta.url).pathname,
243
- 'parse-config.mjs',
244
- );
245
- const result = spawnSync(
246
- process.execPath,
247
- [scriptPath],
248
- { encoding: 'utf8', timeout: 10_000 },
249
- );
250
- if (result.status !== 0 || !result.stdout) return null;
251
- return JSON.parse(result.stdout);
252
- } catch {
253
- return null;
254
- }
255
- }
256
-
257
- // ---------------------------------------------------------------------------
258
- // Apply-mode helpers
259
- // ---------------------------------------------------------------------------
260
-
261
- /**
262
- * Extract issue relation refs (blocks / blocked_by) from a raw glab issue
263
- * object. The glab issue list JSON does not embed these fields by default —
264
- * they are left as [] for v1.
265
- *
266
- * @param {object} _issue Raw glab issue object
267
- * @param {string} _field Relation field name ('blocks' | 'blocked_by')
268
- * @returns {number[]}
269
- */
270
- // TODO Phase D.2 (#341): query relations via glab api (e.g. glab api
271
- // projects/:id/issues/:iid/links) to populate blocks/blocked_by.
272
- function extractRefs(_issue, _field) {
273
- return [];
274
- }
275
-
276
- /**
277
- * Fetch the ready-backlog from GitLab via `glab issue list`.
278
- * Uses execFile (shell: false) per SEC-014 / cli-design rules.
279
- *
280
- * Host-pinning (#904): a bare `glab` spawn (no shell wrapper, no `-R`) falls
281
- * back to the ambient `GITLAB_HOST` config default, which can silently
282
- * resolve to the WRONG GitLab instance on a multi-host machine. Injecting
283
- * `GITLAB_HOST` into the spawn env is NOT a fix — it is live-refuted whenever
284
- * `~/.ssh/config` maps the hostname to a `HostName` IP alias (glab then
285
- * reports "none of the git remotes ... correspond to the GITLAB_HOST"). Only
286
- * `-R <spec>` survives that mismatch. See scripts/lib/vcs-repo-spec.mjs
287
- * docblock; reference idiom: scripts/lib/spiral-carryover.mjs.
288
- *
289
- * @param {Function} execFileFn Promisified execFile
290
- * @param {object} [opts]
291
- * @param {string} [opts.repoRoot] Repo root to resolve the `-R` spec from (default: process.cwd())
292
- * @param {'gitlab'|'github'} [opts.vcs] VCS backend (default: 'gitlab')
293
- * @param {(o: { repoRoot: string, vcs: 'gitlab'|'github' }) => string|undefined} [opts.resolveRepoSpecFn]
294
- * DI seam for `resolveRepoSpec` (testability).
295
- * @returns {Promise<Array<{iid: number, title: string, blocks: number[], blockedBy: number[], labels: string[]}>>}
296
- */
297
- async function fetchReadyBacklog(execFileFn, opts = {}) {
298
- const resolveRepoSpecFn = opts.resolveRepoSpecFn ?? resolveRepoSpec;
299
- const vcs = opts.vcs === 'github' ? 'github' : 'gitlab';
300
- const spec = resolveRepoSpecFn({ repoRoot: opts.repoRoot ?? process.cwd(), vcs });
301
-
302
- const args = ['issue', 'list', '--label', 'status:ready', '--per-page', '50', '--output', 'json'];
303
- // Never emit '-R undefined' — resolveRepoSpec returns undefined when it
304
- // cannot auto-detect a remote; omit the flag entirely in that case.
305
- if (spec) args.push('-R', spec);
306
-
307
- const { stdout } = await execFileFn('glab', args, { shell: false, timeout: 30_000 });
308
- const issues = JSON.parse(stdout);
309
- return issues.map((i) => ({
310
- iid: i.iid ?? i.id,
311
- title: i.title ?? '',
312
- blocks: extractRefs(i, 'blocks'), // best-effort; default to []
313
- blockedBy: extractRefs(i, 'blocked_by'), // best-effort; default to []
314
- labels: i.labels?.map((l) => l.name ?? l) ?? [],
315
- }));
316
- }
317
-
318
- /**
319
- * Summarise a completed apply run into a canonical result envelope.
320
- *
321
- * Success/exit-code semantics (#905): a `reason` that is not a terminal
322
- * stop-reason (see `terminalReasons`) used to be reported as `success: true`
323
- * even when every registered loop did zero productive work (e.g. the inner
324
- * loop fell back to manual mode with 0 iterations completed) — the classic
325
- * "fallback counts as complete" bug. `success` now additionally requires
326
- * either an empty backlog (no loops were ever registered — the legitimate
327
- * "nothing to do" case) or at least one loop that actually completed work.
328
- *
329
- * @param {import('./lib/autopilot/multi-killswitch.mjs').LoopRegistration[]} allLoops
330
- * @param {string} reason
331
- * @param {number} startMs
332
- * @param {{ kill: string, detail: string }|null} [killDetail]
333
- * @returns {{ success: boolean, exitCode: number, data: object }}
334
- */
335
- function finalize(allLoops, reason, startMs, killDetail = null) {
336
- const terminalReasons = new Set([
337
- 'first-kill-switch',
338
- 'spiral',
339
- 'cohort-abort',
340
- 'spawn-error',
341
- STALE_SUBAGENT_MIN,
342
- ]);
343
- const isTerminal = terminalReasons.has(reason);
344
- const completed = allLoops.filter((l) => l.status === 'complete').length;
345
- const failed = allLoops.filter((l) => l.status === 'failed').length;
346
- const noWork = allLoops.filter((l) => l.status === 'no-work').length;
347
- const fellBackToManual = allLoops.filter((l) => l.fallbackToManual === true).length;
348
-
349
- // Empty-backlog regression guard: zero registered loops is a legitimate
350
- // "nothing to do" outcome, not a "no productive work" outcome — do not
351
- // conflate the two.
352
- const success = !isTerminal && (allLoops.length === 0 || completed > 0);
353
-
354
- // Exit-code triage: success -> 0; a terminal stop-reason -> 2 (unchanged;
355
- // this is the only way `!success` can coincide with an empty allLoops); a
356
- // non-terminal reason with >=1 registered loop but zero productive loops
357
- // -> 3 ("no productive work" — the run completed cleanly but nothing landed).
358
- const exitCode = success ? 0 : (isTerminal ? 2 : 3);
359
-
360
- return {
361
- success,
362
- exitCode,
363
- data: {
364
- reason,
365
- loopCount: allLoops.length,
366
- completed,
367
- failed,
368
- noWork,
369
- fellBackToManual,
370
- elapsedMs: Date.now() - startMs,
371
- killDetail: killDetail ?? null,
372
- },
373
- };
374
- }
375
-
376
- /** Sentinel import — resolved lazily in runApplyLoop. Kept at module scope to
377
- * allow finalize() to reference STALE_SUBAGENT_MIN without a forward-ref issue. */
378
- let STALE_SUBAGENT_MIN = 'stale-subagent-min';
379
-
380
- /**
381
- * Core apply-mode orchestration loop.
382
- *
383
- * Iteratively spawns up to N concurrent worktree-pipelines, polls completions,
384
- * evaluates cross-loop kill-switches, and returns a canonical result envelope
385
- * when work is exhausted or a stop condition fires.
386
- *
387
- * @param {{ issues: Array<object>, parentRunId: string, flags: object }} state
388
- * @param {{ depGraph: object, wtPipeline: object, mkLib: object, probe: object }} libs
389
- * @param {{ bgIsolation?: string, [key: string]: unknown }} [opts]
390
- * @returns {Promise<{ success: boolean, data: object }>}
391
- */
392
- async function runApplyLoop(state, libs, opts) {
393
- // bg-isolation routing: "none" → skip worktree creation and spawn sub-sessions
394
- // in the main tree. "worktree" (default) → each story pipeline creates its
395
- // own worktree via worktree-pipeline.mjs (existing path).
396
- const bgIsolation = opts?.bgIsolation ?? 'worktree';
397
- const { depGraph, wtPipeline, mkLib, probe } = libs;
398
- const { flags } = state;
399
-
400
- // Patch module-level sentinel with the actual export from the loaded lib.
401
- STALE_SUBAGENT_MIN = mkLib.STALE_SUBAGENT_MIN ?? 'stale-subagent-min';
402
-
403
- const startMs = Date.now();
404
- const maxMs = flags.maxHours * 3600 * 1000;
405
-
406
- // Early exit when there is nothing to process.
407
- if (!state.issues || state.issues.length === 0) {
408
- return finalize([], 'backlog-empty', startMs);
409
- }
410
-
411
- // Build graph and tracking state.
412
- const graph = depGraph.buildGraph(state.issues);
413
- /** @type {Map<string, { promise: Promise<object>, registration: object }>} */
414
- const inFlight = new Map(); // loopId → { promise, registration }
415
- /** @type {Set<number>} */
416
- const completed = new Set();
417
- /** @type {object[]} */
418
- const allLoops = []; // LoopRegistration[]
419
- let lastCompletionAt = startMs;
420
-
421
- // TODO Phase D.2 (#341): implement real cross-loop dependency commit-waiting.
422
- // Currently all issues are treated as independent (blocks/blockedBy are []
423
- // for v1). When commit-based deps land, nextReady() will need to check
424
- // whether the blocker's commit is present on the target branch before
425
- // yielding the blocked issue.
426
-
427
- while (true) {
428
- // 1. Stop condition check.
429
- const activeLoopRegs = Array.from(inFlight.values()).map((v) => v.registration);
430
- const readyBacklog = depGraph.nextReady(graph, new Set(inFlight.keys().map((id) => {
431
- const reg = inFlight.get(id)?.registration;
432
- return reg?.issueIid;
433
- }).filter((x) => x !== null && x !== undefined)), completed);
434
-
435
- const orchState = {
436
- activeLoops: activeLoopRegs,
437
- readyBacklog,
438
- lastCompletionAt,
439
- };
440
-
441
- const stopDecision = mkLib.shouldStopOrchestrator(orchState, {
442
- inactivityTimeoutMs: flags.inactivityTimeoutMs,
443
- });
444
- if (stopDecision.stop) {
445
- return finalize(allLoops, stopDecision.reason, startMs);
446
- }
447
-
448
- if (Date.now() - startMs > maxMs) {
449
- return finalize(allLoops, 'max-hours', startMs);
450
- }
451
-
452
- // 2. Per-iteration resource probe + cohort kill-switch check.
453
- let snapshot = null;
454
- try {
455
- snapshot = await probe.probe();
456
- } catch {
457
- // Resource probe failure is non-fatal — continue with maxStories cap.
458
- }
459
-
460
- const recommendedCap = snapshot ? mkLib.calculateConcurrencyCap(snapshot) : flags.maxStories;
461
- const effectiveCap = Math.min(flags.maxStories, recommendedCap);
462
-
463
- const crossKill = mkLib.evaluateMultiKillSwitches(allLoops, {
464
- staleSubagentMinSeconds: flags.stallTimeoutSeconds,
465
- });
466
- if (crossKill !== null) {
467
- return finalize(allLoops, crossKill.kill, startMs, crossKill);
468
- }
469
-
470
- // 3. Spawn loops up to cap.
471
- const spawnableBacklog = [...readyBacklog];
472
- while (inFlight.size < effectiveCap && spawnableBacklog.length > 0) {
473
- const issue = spawnableBacklog.shift();
474
- const loopId = `loop-${issue.iid}-${Date.now()}`;
475
- /** @type {object} */
476
- const registration = {
477
- loopId,
478
- pid: process.pid, // best-effort; loops are in-process for v1
479
- parentRunId: state.parentRunId,
480
- issueIid: issue.iid,
481
- status: 'running',
482
- killSwitch: null,
483
- iterationsCompleted: 0,
484
- fallbackToManual: false,
485
- spiralRecoveryCount: 0,
486
- startedAt: Date.now(),
487
- lastActivityAt: Date.now(),
488
- };
489
- allLoops.push(registration);
490
-
491
- // bg-isolation: "none" → main-tree spawn (no worktree, faster for
492
- // monorepos with heavy build state but requires file-scope discipline).
493
- // bg-isolation: "worktree" (default) → per-story worktree isolation.
494
- // worktree-pipeline.mjs respects the bgIsolation option and skips
495
- // EnterWorktree when set to "none".
496
- const promise = wtPipeline.runStoryPipeline({
497
- issueIid: issue.iid,
498
- issueTitle: issue.title,
499
- branchName: `issue-${issue.iid}`,
500
- parentRunId: state.parentRunId,
501
- repoRoot: process.cwd(),
502
- draftMrPolicy: flags.draftMrPolicy,
503
- bgIsolation,
504
- // TODO Phase D.2 (#341): on-green MR-draft trigger — loop result does not
505
- // yet signal "first green test"; wire once runStoryPipeline
506
- // propagates a firstGreenAt timestamp in StoryResult.
507
- }).then((result) => {
508
- // Three-way classification (#905): a StoryResult with no kill-switch
509
- // AND zero completed iterations (or an explicit fallback-to-manual
510
- // flag) is NOT the same as a completed loop — it did no productive
511
- // work. Conflating the two used to make an empty apply run report
512
- // false success. See finalize() for the corresponding success/
513
- // exit-code semantics and multi-killswitch.mjs's LoopRegistration
514
- // typedef (out of scope here) for the 'queued'|'running'|'complete'|
515
- // 'failed' status enum this 'no-work' value extends locally.
516
- registration.iterationsCompleted = result.iterationsCompleted ?? 0;
517
- registration.fallbackToManual = result.fallbackToManual === true;
518
- registration.status =
519
- result.killSwitch ? 'failed'
520
- : (registration.fallbackToManual || registration.iterationsCompleted === 0) ? 'no-work'
521
- : 'complete';
522
- registration.killSwitch = result.killSwitch ?? null;
523
- completed.add(issue.iid);
524
- lastCompletionAt = Date.now();
525
- return result;
526
- }).catch((err) => {
527
- registration.status = 'failed';
528
- registration.killSwitch = 'spawn-error';
529
- registration.killSwitchDetail = err.message;
530
- completed.add(issue.iid);
531
- lastCompletionAt = Date.now();
532
- return { error: err.message };
533
- });
534
-
535
- inFlight.set(loopId, { promise, registration });
536
- }
537
-
538
- // 4. Cohort abort decision for newly-failed loops.
539
- for (const [, { registration }] of inFlight.entries()) {
540
- if (registration.status === 'failed' && registration.killSwitch) {
541
- const cohortDecision = mkLib.decideCohortAction(allLoops, registration.loopId);
542
- if (cohortDecision.action === 'cohort-abort') {
543
- // TODO Phase D.2 (#341): send real SIGTERM to in-process promises.
544
- // In v1 loops are in-process Promises; enforcement is logged only.
545
- // Full SIGTERM requires spawning each pipeline in a child process
546
- // with a dedicated pid, tracked on LoopRegistration.pid.
547
- process.stderr.write(
548
- `[autopilot-multi] cohort-abort triggered by loop ${registration.loopId}: ${cohortDecision.reason}\n`,
549
- );
550
- return finalize(allLoops, 'cohort-abort', startMs, { kill: 'cohort-abort', detail: cohortDecision.reason });
551
- }
552
- }
553
- }
554
-
555
- // 5. Wait for next completion (Promise.race) OR settle after 5 s tick.
556
- if (inFlight.size > 0) {
557
- await Promise.race([
558
- Promise.race(Array.from(inFlight.values()).map((v) => v.promise.then(() => null))),
559
- new Promise((r) => setTimeout(r, 5000)),
560
- ]);
561
- // Sweep completed / failed / no-work entries. 'no-work' MUST be swept
562
- // here exactly like 'complete'/'failed' — omitting it leaves a settled
563
- // promise permanently registered as active, which starves
564
- // shouldStopOrchestrator's "backlog-empty" (activeLoops.length === 0)
565
- // condition and hangs the orchestrator forever (#905 sweep regression).
566
- for (const [id, { registration }] of inFlight.entries()) {
567
- if (
568
- registration.status === 'complete' ||
569
- registration.status === 'failed' ||
570
- registration.status === 'no-work'
571
- ) {
572
- inFlight.delete(id);
573
- }
574
- }
575
- } else if (readyBacklog.length === 0) {
576
- // No active, no ready — backlog-empty stop condition fires on the next tick.
577
- await new Promise((r) => setTimeout(r, 1000));
578
- }
579
- }
580
- }
581
-
582
- // ---------------------------------------------------------------------------
583
- // Internal helpers
584
- // ---------------------------------------------------------------------------
585
-
586
- /** Typed user-error (exit 1). */
587
- class UserError extends Error {
588
- constructor(message) {
589
- super(message);
590
- this.name = 'UserError';
591
- }
592
- }
593
-
594
- /**
595
- * Write a canonical JSON envelope to stdout.
596
- *
597
- * @param {boolean} success
598
- * @param {object|null} data
599
- * @param {{code: string, message: string}|null} error
600
- */
601
- function writeJsonEnvelope(success, data, error) {
602
- const envelope = success
603
- ? { success: true, data }
604
- : { success: false, error };
605
- process.stdout.write(JSON.stringify(envelope) + '\n');
606
- }
607
-
608
- /**
609
- * Emit a diagnostic line to stderr (only when verbose or always for warnings).
610
- *
611
- * @param {string} message
612
- * @param {boolean} [verbose]
613
- * @param {boolean} [force] If true, always emit regardless of verbose flag.
614
- */
615
- function diag(message, verbose, force = false) {
616
- if (verbose || force) {
617
- process.stderr.write(`[autopilot-multi] ${message}\n`);
618
- }
619
- }
620
-
621
- // ---------------------------------------------------------------------------
622
- // main — primary entrypoint
623
- // ---------------------------------------------------------------------------
624
-
625
- /**
626
- * CLI main function. Does NOT execute on import — see CLI guard below.
627
- *
628
- * @param {string[]} argv
629
- * @param {object} [opts] Dependency-injection seam for tests.
630
- * @param {Function} [opts.exit] Override process.exit (for testing).
631
- * @returns {Promise<void>}
632
- */
633
- export async function main(argv = process.argv.slice(2), opts = {}) {
634
- const exitFn = opts.exit ?? process.exit;
635
-
636
- // Unique run-id for this orchestrator session — passed to each child loop.
637
- const parentRunId = `multi-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
638
-
639
- // -------------------------------------------------------------------------
640
- // 1. Parse flags
641
- // -------------------------------------------------------------------------
642
- let flags;
643
- try {
644
- flags = parseFlags(argv);
645
- } catch (err) {
646
- if (err instanceof UserError) {
647
- process.stderr.write(`autopilot-multi: ${err.message}\n`);
648
- exitFn(1);
649
- return;
650
- }
651
- process.stderr.write(`autopilot-multi: flag parse error: ${err.message}\n`);
652
- exitFn(1);
653
- return;
654
- }
655
-
656
- // -------------------------------------------------------------------------
657
- // 2. --help
658
- // -------------------------------------------------------------------------
659
- if (flags.help) {
660
- process.stdout.write(HELP_TEXT);
661
- exitFn(0);
662
- return;
663
- }
664
-
665
- // -------------------------------------------------------------------------
666
- // 3. --version
667
- // -------------------------------------------------------------------------
668
- if (flags.version) {
669
- try {
670
- const pkgPath = path.join(import.meta.dirname, '..', 'package.json');
671
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
672
- process.stdout.write(`${pkg.version}\n`);
673
- } catch {
674
- process.stderr.write('autopilot-multi: could not read package.json version\n');
675
- exitFn(2);
676
- return;
677
- }
678
- exitFn(0);
679
- return;
680
- }
681
-
682
- // -------------------------------------------------------------------------
683
- // 4. Read Session Config and enforce bg-isolation policy (#431)
684
- //
685
- // bg-isolation: "none" — skip worktree creation; spawn sub-sessions in the
686
- // main tree. Requires file-scope discipline: with >1 concurrent story,
687
- // agents WILL edit the same working copy simultaneously. A hard-error fires
688
- // unless --deconflict-paths=<glob> is provided to signal that the operator
689
- // has planned per-story file ownership.
690
- //
691
- // bg-isolation: "worktree" (default) — each story gets its own git worktree;
692
- // safe for parallel writes but costs disk space and EnterWorktree latency.
693
- //
694
- // Caution: CC 2.1.133 silently flipped worktree.baseRef default from "head"
695
- // to "origin/<default>", breaking users relying on unpushed commits. Apply
696
- // the same operator-awareness here: any future CC change to worktree defaults
697
- // may silently affect this path. See #431 and SKILL.md § bg-isolation.
698
- // -------------------------------------------------------------------------
699
- const sessionConfig = loadSessionConfig();
700
- const bgIsolation = /** @type {string} */ (
701
- sessionConfig?.autopilot?.['bg-isolation'] ?? 'worktree'
702
- );
703
-
704
- if (bgIsolation === 'none' && flags.maxStories > 1 && !flags.deconflictPaths) {
705
- // Hard-error per PSA-001/002/003 — see .claude/rules/parallel-sessions.md
706
- const msg =
707
- `autopilot-multi: bg-isolation: "none" with --max-stories=${flags.maxStories} ` +
708
- `requires explicit file-scope deconfliction. ` +
709
- `Set max-stories=1 OR provide --deconflict-paths=<glob> to confirm you have ` +
710
- `planned per-story file ownership. ` +
711
- `See .claude/rules/parallel-sessions.md PSA-001/002/003.`;
712
- process.stderr.write(`${msg}\n`);
713
- if (flags.json) {
714
- writeJsonEnvelope(false, null, { code: 'BG_ISOLATION_CONFLICT', message: msg });
715
- }
716
- exitFn(1);
717
- return;
718
- }
719
-
720
- diag(
721
- `bg-isolation=${bgIsolation}` +
722
- (bgIsolation === 'none' ? ' (main-tree mode; worktree creation skipped)' : ''),
723
- flags.verbose,
724
- );
725
-
726
- // -------------------------------------------------------------------------
727
- // 5. Dynamic-import sibling libs (graceful degradation)
728
- // -------------------------------------------------------------------------
729
- let buildGraph, nextReady, calculateConcurrencyCap, probe;
730
- let depGraphMod, mkLibMod, wtPipelineMod, resourceProbeMod;
731
-
732
- try {
733
- depGraphMod = await import('./lib/autopilot/dep-graph.mjs');
734
- mkLibMod = await import('./lib/autopilot/multi-killswitch.mjs');
735
- wtPipelineMod = await import('./lib/autopilot/worktree-pipeline.mjs');
736
- resourceProbeMod = await import('./lib/resource-probe.mjs');
737
-
738
- buildGraph = depGraphMod.buildGraph;
739
- nextReady = depGraphMod.nextReady;
740
- calculateConcurrencyCap = mkLibMod.calculateConcurrencyCap;
741
- probe = resourceProbeMod.probe;
742
- } catch {
743
- const msg = 'multi-story libs not yet shipped; this is the v3.6 CLI scaffold';
744
- process.stderr.write(`autopilot-multi: ${msg}\n`);
745
- if (flags.json) {
746
- writeJsonEnvelope(false, null, { code: 'LIBS_NOT_AVAILABLE', message: msg });
747
- }
748
- exitFn(2);
749
- return;
750
- }
751
-
752
- // -------------------------------------------------------------------------
753
- // 6. Resource probe + concurrency cap advisory
754
- // -------------------------------------------------------------------------
755
- let snapshot = null;
756
- try {
757
- snapshot = await probe();
758
- } catch (err) {
759
- process.stderr.write(
760
- `autopilot-multi: resource-probe failed: ${err.message}; continuing without snapshot\n`
761
- );
762
- }
763
-
764
- const recommendedCap = snapshot ? calculateConcurrencyCap(snapshot) : flags.maxStories;
765
-
766
- if (flags.maxStories > recommendedCap && !flags.apply) {
767
- process.stderr.write(
768
- `autopilot-multi: WARN requested N=${flags.maxStories} but resource-probe recommends` +
769
- ` N=${recommendedCap}; pass --apply to override\n`
770
- );
771
- }
772
-
773
- diag(
774
- `resource-probe snapshot received; recommendedCap=${recommendedCap}`,
775
- flags.verbose
776
- );
777
-
778
- // -------------------------------------------------------------------------
779
- // 7. Build issue backlog
780
- // -------------------------------------------------------------------------
781
- let issues;
782
-
783
- if (flags.dryRun) {
784
- // Dry-run: synthesize a stub backlog so plan output is always available.
785
- issues = [{ iid: 999, title: 'dry-run-stub', blocks: [], blockedBy: [], labels: ['status:ready'] }];
786
- diag('dry-run mode — using synthetic issue stub instead of calling glab', flags.verbose, true);
787
- } else {
788
- // Apply path: fetch real issues via glab (execFile, shell: false — SEC-014).
789
- try {
790
- issues = await fetchReadyBacklog(execFile, { repoRoot: process.cwd() });
791
- } catch (err) {
792
- const msg = `glab issue list failed: ${err.message}`;
793
- process.stderr.write(`autopilot-multi: ${msg}\n`);
794
- if (flags.json) {
795
- writeJsonEnvelope(false, null, { code: 'GLAB_FAILED', message: msg });
796
- }
797
- exitFn(2);
798
- return;
799
- }
800
- }
801
-
802
- // -------------------------------------------------------------------------
803
- // 8. Build dep-graph + initial schedulable set
804
- // -------------------------------------------------------------------------
805
- const graph = buildGraph(issues);
806
- const initialSet = nextReady(graph, new Set(), new Set());
807
-
808
- diag(
809
- `dep-graph built; total=${issues.length} ready=${initialSet.length ?? initialSet.size ?? 0}`,
810
- flags.verbose
811
- );
812
-
813
- // -------------------------------------------------------------------------
814
- // 9. Dry-run path (default): emit orchestration plan
815
- // -------------------------------------------------------------------------
816
- if (flags.dryRun) {
817
- const plan = buildOrchestratorState(flags, snapshot, issues, parentRunId);
818
-
819
- if (flags.json) {
820
- writeJsonEnvelope(true, { plan });
821
- } else {
822
- process.stdout.write('autopilot-multi — dry-run orchestration plan\n');
823
- process.stdout.write(` concurrencyCap : ${plan.concurrencyCap}\n`);
824
- process.stdout.write(` totalIssues : ${plan.totalIssues}\n`);
825
- process.stdout.write(` readyIssues : ${plan.readyIssues}\n`);
826
- process.stdout.write(` executionPlan :\n`);
827
- for (const item of plan.executionPlan) {
828
- process.stdout.write(` #${item.iid} ${item.title}\n`);
829
- }
830
- process.stdout.write('\nPass --apply to execute.\n');
831
- }
832
-
833
- exitFn(0);
834
- return;
835
- }
836
-
837
- // -------------------------------------------------------------------------
838
- // 10. Apply path: run the multi-story orchestration loop
839
- // -------------------------------------------------------------------------
840
- const state = buildOrchestratorState(flags, snapshot, issues, parentRunId);
841
- const libs = {
842
- depGraph: depGraphMod,
843
- wtPipeline: wtPipelineMod,
844
- mkLib: mkLibMod,
845
- probe: resourceProbeMod,
846
- };
847
- // Pass bgIsolation into opts so runApplyLoop can route the dispatch path.
848
- // bg-isolation: "none" → spawn sub-sessions in the main tree (no worktree creation).
849
- // bg-isolation: "worktree" (default) → each story pipeline creates its own worktree.
850
- const runOpts = { ...opts, bgIsolation };
851
-
852
- const result = await runApplyLoop(state, libs, runOpts);
853
-
854
- if (flags.json) {
855
- console.log(JSON.stringify(result));
856
- } else {
857
- console.log(
858
- `Multi-story apply complete: ${result.data.reason}` +
859
- ` — ${result.data.completed}/${result.data.loopCount} loops succeeded, ` +
860
- `${result.data.failed} failed, ${result.data.noWork} did no work` +
861
- ` (${result.data.fellBackToManual} fell back to manual)`,
862
- );
863
- }
864
-
865
- exitFn(result.exitCode);
866
- }
867
-
868
- // ---------------------------------------------------------------------------
869
- // Test-only exports — internal functions exercised directly by
870
- // tests/scripts/autopilot-multi.test.mjs (no behaviour change on the CLI
871
- // path; mirrors the export pattern in scripts/archive-closed-prds.mjs).
872
- // ---------------------------------------------------------------------------
873
-
874
- export { finalize, fetchReadyBacklog, runApplyLoop };
875
-
876
- // ---------------------------------------------------------------------------
877
- // CLI guard — prevents top-level execution on import (deep-2 #368)
878
- // ---------------------------------------------------------------------------
879
-
880
- if (import.meta.url === `file://${process.argv[1]}`) {
881
- main(process.argv.slice(2)).catch((err) => {
882
- console.error('autopilot-multi failed:', err.message);
883
- process.exit(2);
884
- });
885
- }