agentic-orchestrator 0.1.28 → 0.2.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 (836) hide show
  1. package/.claude/settings.local.json +46 -1
  2. package/.cortexrc +28 -0
  3. package/.github/agents/copilot-instructions.md +29 -0
  4. package/.github/copilot-instructions.md +93 -0
  5. package/.vscode/settings.json +13 -0
  6. package/.vscode/tms.code-snippets +223 -0
  7. package/AGENTS.md +72 -1
  8. package/Agentic-Orchestrator.iml +12 -11
  9. package/CLAUDE.md +72 -1
  10. package/CONSTITUTION.md +504 -0
  11. package/FUTURE-ENHANCEMENTS.md +85 -0
  12. package/NEXT-TASKS.md +25 -0
  13. package/PROMPTS.md +161 -0
  14. package/README.md +126 -29
  15. package/agentic/orchestrator/agents.yaml +4 -3
  16. package/agentic/orchestrator/defaults/policy.defaults.yaml +39 -3
  17. package/agentic/orchestrator/gates.yaml +15 -3
  18. package/agentic/orchestrator/policy.yaml +47 -3
  19. package/agentic/orchestrator/prompts/builder.system.md +69 -20
  20. package/agentic/orchestrator/prompts/planner-intake.system.md +149 -0
  21. package/agentic/orchestrator/prompts/planner.system.md +113 -40
  22. package/agentic/orchestrator/prompts/qa.system.md +73 -18
  23. package/agentic/orchestrator/prompts/reconciler.system.md +119 -0
  24. package/agentic/orchestrator/schemas/agents.schema.json +89 -1
  25. package/agentic/orchestrator/schemas/execution-control.schema.json +242 -0
  26. package/agentic/orchestrator/schemas/index.schema.json +234 -0
  27. package/agentic/orchestrator/schemas/intake.review.schema.json +82 -0
  28. package/agentic/orchestrator/schemas/organizer-ordering-artifact.schema.json +75 -0
  29. package/agentic/orchestrator/schemas/plan.schema.json +44 -0
  30. package/agentic/orchestrator/schemas/policy.schema.json +238 -9
  31. package/agentic/orchestrator/schemas/policy.user.schema.json +129 -1
  32. package/agentic/orchestrator/schemas/spec.manifest.bootstrap.schema.json +101 -0
  33. package/agentic/orchestrator/schemas/spec.manifest.verified.schema.json +80 -0
  34. package/agentic/orchestrator/schemas/state.schema.json +298 -3
  35. package/agentic/orchestrator/tools/catalog.json +145 -15
  36. package/agentic/orchestrator/tools/schemas/input/doctor.run.input.schema.json +18 -0
  37. package/agentic/orchestrator/tools/schemas/input/evidence.latest.input.schema.json +4 -0
  38. package/agentic/orchestrator/tools/schemas/input/evidence.verify_chain.input.schema.json +13 -0
  39. package/agentic/orchestrator/tools/schemas/input/feature.intake_submit.input.schema.json +11 -0
  40. package/agentic/orchestrator/tools/schemas/input/feature.question_answer.input.schema.json +15 -0
  41. package/agentic/orchestrator/tools/schemas/input/feature.question_create.input.schema.json +21 -0
  42. package/agentic/orchestrator/tools/schemas/input/feature.question_list.input.schema.json +13 -0
  43. package/agentic/orchestrator/tools/schemas/input/feature.ready_to_merge.input.schema.json +5 -0
  44. package/agentic/orchestrator/tools/schemas/input/feature.send_message.input.schema.json +1 -1
  45. package/agentic/orchestrator/tools/schemas/input/replay.timeline_get.input.schema.json +32 -0
  46. package/agentic/orchestrator/tools/schemas/input/repo.conflict_abort.input.schema.json +16 -0
  47. package/agentic/orchestrator/tools/schemas/input/repo.conflict_files.input.schema.json +16 -0
  48. package/agentic/orchestrator/tools/schemas/input/repo.reconcile_mainline.input.schema.json +37 -0
  49. package/agentic/orchestrator/tools/schemas/input/repo.resolve_conflict.input.schema.json +40 -0
  50. package/agentic/orchestrator/tools/schemas/input/runtime.execution_request_list.input.schema.json +7 -0
  51. package/agentic/orchestrator/tools/schemas/input/runtime.execution_request_submit.input.schema.json +25 -0
  52. package/agentic/orchestrator/tools/schemas/output/doctor.run.output.schema.json +34 -0
  53. package/agentic/orchestrator/tools/schemas/output/evidence.verify_chain.output.schema.json +23 -0
  54. package/agentic/orchestrator/tools/schemas/output/feature.get_context.output.schema.json +62 -2
  55. package/agentic/orchestrator/tools/schemas/output/feature.intake_submit.output.schema.json +24 -0
  56. package/agentic/orchestrator/tools/schemas/output/feature.question_answer.output.schema.json +21 -0
  57. package/agentic/orchestrator/tools/schemas/output/feature.question_create.output.schema.json +12 -0
  58. package/agentic/orchestrator/tools/schemas/output/feature.question_list.output.schema.json +14 -0
  59. package/agentic/orchestrator/tools/schemas/output/feature.ready_to_merge.output.schema.json +31 -0
  60. package/agentic/orchestrator/tools/schemas/output/feature.send_message.output.schema.json +8 -18
  61. package/agentic/orchestrator/tools/schemas/output/replay.timeline_get.output.schema.json +64 -0
  62. package/agentic/orchestrator/tools/schemas/output/repo.conflict_abort.output.schema.json +16 -0
  63. package/agentic/orchestrator/tools/schemas/output/repo.conflict_files.output.schema.json +22 -0
  64. package/agentic/orchestrator/tools/schemas/output/repo.reconcile_mainline.output.schema.json +61 -0
  65. package/agentic/orchestrator/tools/schemas/output/repo.resolve_conflict.output.schema.json +19 -0
  66. package/agentic/orchestrator/tools/schemas/output/report.dashboard.output.schema.json +26 -0
  67. package/agentic/orchestrator/tools/schemas/output/runtime.execution_request_list.output.schema.json +17 -0
  68. package/agentic/orchestrator/tools/schemas/output/runtime.execution_request_submit.output.schema.json +24 -0
  69. package/agentic/orchestrator/tools.md +13 -0
  70. package/apps/control-plane/scripts/validate-mcp-contracts.ts +1 -1
  71. package/apps/control-plane/src/application/kernel-tool-wiring.ts +140 -2
  72. package/apps/control-plane/src/application/services/activity-monitor-service.ts +44 -1
  73. package/apps/control-plane/src/application/services/bootstrap-manifest-generator-service.ts +251 -0
  74. package/apps/control-plane/src/application/services/checkpoint-service.ts +87 -27
  75. package/apps/control-plane/src/application/services/collision-override-service.ts +906 -0
  76. package/apps/control-plane/src/application/services/collision-queue-service.ts +129 -38
  77. package/apps/control-plane/src/application/services/cost-tracking-service.ts +94 -0
  78. package/apps/control-plane/src/application/services/execution-control-service.ts +599 -0
  79. package/apps/control-plane/src/application/services/feature-deletion-service.ts +37 -1
  80. package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +182 -4
  81. package/apps/control-plane/src/application/services/feature-send-message-service.ts +17 -8
  82. package/apps/control-plane/src/application/services/feature-state-service.ts +191 -6
  83. package/apps/control-plane/src/application/services/gate-service.ts +121 -2
  84. package/apps/control-plane/src/application/services/git-reconciliation-service.ts +1591 -0
  85. package/apps/control-plane/src/application/services/intake-service.ts +1468 -0
  86. package/apps/control-plane/src/application/services/merge-service.ts +308 -17
  87. package/apps/control-plane/src/application/services/notifier-service.ts +3 -1
  88. package/apps/control-plane/src/application/services/performance-analytics-service.ts +75 -0
  89. package/apps/control-plane/src/application/services/plan-service.ts +336 -20
  90. package/apps/control-plane/src/application/services/question-service.ts +693 -0
  91. package/apps/control-plane/src/application/services/reactions-service.ts +73 -17
  92. package/apps/control-plane/src/application/services/replay-timeline-service.ts +295 -0
  93. package/apps/control-plane/src/application/services/reporting-service.ts +194 -10
  94. package/apps/control-plane/src/application/services/run-lease-service.ts +121 -5
  95. package/apps/control-plane/src/application/services/worktree-watchdog-service.ts +95 -8
  96. package/apps/control-plane/src/application/tools/tool-metadata.ts +7 -0
  97. package/apps/control-plane/src/application/usage-types.ts +138 -0
  98. package/apps/control-plane/src/cli/add-command-handler.ts +162 -0
  99. package/apps/control-plane/src/cli/answer-command-handler.ts +113 -0
  100. package/apps/control-plane/src/cli/attach-command-handler.ts +12 -3
  101. package/apps/control-plane/src/cli/cli-argument-parser.ts +133 -11
  102. package/apps/control-plane/src/cli/collision-command-handler.ts +113 -0
  103. package/apps/control-plane/src/cli/command-catalog.ts +479 -0
  104. package/apps/control-plane/src/cli/complete-command-handler.ts +23 -0
  105. package/apps/control-plane/src/cli/completion-command-handler.ts +25 -0
  106. package/apps/control-plane/src/cli/completion-resolver.ts +319 -0
  107. package/apps/control-plane/src/cli/completion-shell-renderer.ts +58 -0
  108. package/apps/control-plane/src/cli/dashboard-command-handler.ts +110 -1
  109. package/apps/control-plane/src/cli/dashboard-runtime-runner.ts +1036 -0
  110. package/apps/control-plane/src/cli/dashboard-runtime.ts +31 -0
  111. package/apps/control-plane/src/cli/help-command-handler.ts +17 -185
  112. package/apps/control-plane/src/cli/init-command-handler.ts +51 -6
  113. package/apps/control-plane/src/cli/merge-command-handler.ts +200 -0
  114. package/apps/control-plane/src/cli/questions-command-handler.ts +70 -0
  115. package/apps/control-plane/src/cli/replay-command-handler.ts +98 -0
  116. package/apps/control-plane/src/cli/resume-command-handler.ts +231 -16
  117. package/apps/control-plane/src/cli/retry-command-handler.ts +229 -17
  118. package/apps/control-plane/src/cli/retry-resume-decision.ts +75 -0
  119. package/apps/control-plane/src/cli/rollback-command-handler.ts +4 -2
  120. package/apps/control-plane/src/cli/run-command-handler.ts +35 -1
  121. package/apps/control-plane/src/cli/spec-ingestion-service.ts +45 -55
  122. package/apps/control-plane/src/cli/spec-preparation.ts +114 -0
  123. package/apps/control-plane/src/cli/spec-utils.ts +90 -11
  124. package/apps/control-plane/src/cli/status-command-handler.ts +122 -0
  125. package/apps/control-plane/src/cli/types.ts +41 -3
  126. package/apps/control-plane/src/core/collisions.ts +150 -31
  127. package/apps/control-plane/src/core/constants.ts +18 -1
  128. package/apps/control-plane/src/core/error-codes.ts +39 -0
  129. package/apps/control-plane/src/core/execution-control.ts +56 -0
  130. package/apps/control-plane/src/core/feature-resume-phase.ts +118 -0
  131. package/apps/control-plane/src/core/gate-freshness.ts +359 -0
  132. package/apps/control-plane/src/core/gate-log-extractor.ts +97 -0
  133. package/apps/control-plane/src/core/gates.ts +90 -1
  134. package/apps/control-plane/src/core/intake-artifacts.ts +295 -0
  135. package/apps/control-plane/src/core/kernel-types.ts +11 -0
  136. package/apps/control-plane/src/core/kernel.ts +604 -16
  137. package/apps/control-plane/src/core/mainline-conflict.ts +22 -0
  138. package/apps/control-plane/src/core/merge-repair.ts +149 -0
  139. package/apps/control-plane/src/core/path-layout.ts +46 -2
  140. package/apps/control-plane/src/core/path-rules.ts +11 -3
  141. package/apps/control-plane/src/core/plan-submit-recovery.ts +130 -0
  142. package/apps/control-plane/src/core/questions.ts +49 -0
  143. package/apps/control-plane/src/core/runtime-sessions.ts +4 -0
  144. package/apps/control-plane/src/core/schemas.ts +40 -1
  145. package/apps/control-plane/src/core/tool-caller.ts +25 -1
  146. package/apps/control-plane/src/core/utils/index-normalizer.ts +25 -4
  147. package/apps/control-plane/src/core/worktree-diff.ts +66 -0
  148. package/apps/control-plane/src/index.ts +29 -1
  149. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +300 -6
  150. package/apps/control-plane/src/mcp/kernel-tool-executor.ts +17 -0
  151. package/apps/control-plane/src/mcp/tool-runtime.ts +63 -4
  152. package/apps/control-plane/src/providers/api-worker-provider.ts +62 -15
  153. package/apps/control-plane/src/providers/cli-worker-provider.ts +1037 -61
  154. package/apps/control-plane/src/providers/output-parsers/generic-output-parser.ts +99 -1
  155. package/apps/control-plane/src/providers/output-parsers/types.ts +2 -0
  156. package/apps/control-plane/src/providers/provider-defaults.ts +116 -7
  157. package/apps/control-plane/src/providers/providers.ts +225 -21
  158. package/apps/control-plane/src/providers/worker-provider-factory.ts +26 -2
  159. package/apps/control-plane/src/supervisor/artifact-stager.ts +52 -0
  160. package/apps/control-plane/src/supervisor/build-wave-executor.ts +477 -166
  161. package/apps/control-plane/src/supervisor/execution-enrollment-service.ts +408 -0
  162. package/apps/control-plane/src/supervisor/organizer-enrollment-scheduler.ts +117 -0
  163. package/apps/control-plane/src/supervisor/organizer-sidecar-service.ts +394 -0
  164. package/apps/control-plane/src/supervisor/plan-conformance-scorer.ts +2 -5
  165. package/apps/control-plane/src/supervisor/planner-phase.ts +85 -0
  166. package/apps/control-plane/src/supervisor/planning-wave-executor.ts +993 -64
  167. package/apps/control-plane/src/supervisor/prompt-bundle-loader.ts +20 -1
  168. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +384 -177
  169. package/apps/control-plane/src/supervisor/run-coordinator.ts +723 -20
  170. package/apps/control-plane/src/supervisor/runtime.ts +485 -9
  171. package/apps/control-plane/src/supervisor/session-orchestrator.ts +220 -1
  172. package/apps/control-plane/src/supervisor/types.ts +152 -1
  173. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +1030 -92
  174. package/apps/control-plane/test/activity-monitor.spec.ts +76 -0
  175. package/apps/control-plane/test/add-command-handler.spec.ts +189 -0
  176. package/apps/control-plane/test/application/services/feature-state-service.spec.ts +208 -0
  177. package/apps/control-plane/test/artifact-stager.spec.ts +93 -0
  178. package/apps/control-plane/test/batch-operations.spec.ts +58 -0
  179. package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +50 -2
  180. package/apps/control-plane/test/bootstrap-manifest-generator-service.spec.ts +99 -0
  181. package/apps/control-plane/test/bootstrap.spec.ts +177 -4
  182. package/apps/control-plane/test/checkpoint-service.spec.ts +977 -29
  183. package/apps/control-plane/test/cli-argument-parser.spec.ts +119 -0
  184. package/apps/control-plane/test/cli-helpers.spec.ts +1202 -12
  185. package/apps/control-plane/test/cli.unit.spec.ts +797 -16
  186. package/apps/control-plane/test/collision-command-handler.spec.ts +182 -0
  187. package/apps/control-plane/test/collision-override-service.spec.ts +878 -0
  188. package/apps/control-plane/test/collision-queue.spec.ts +430 -2
  189. package/apps/control-plane/test/collisions.spec.ts +209 -1
  190. package/apps/control-plane/test/core-utils.spec.ts +61 -0
  191. package/apps/control-plane/test/cost-tracking.spec.ts +224 -0
  192. package/apps/control-plane/test/dashboard-api.integration.spec.ts +185 -5
  193. package/apps/control-plane/test/dashboard-client.spec.ts +948 -0
  194. package/apps/control-plane/test/dashboard-command.spec.ts +138 -6
  195. package/apps/control-plane/test/dashboard-runtime-runner.spec.ts +1550 -0
  196. package/apps/control-plane/test/dashboard-runtime.spec.ts +138 -0
  197. package/apps/control-plane/test/dashboard-ui-utils.spec.ts +56 -12
  198. package/apps/control-plane/test/dependency-scheduler.spec.ts +7 -1
  199. package/apps/control-plane/test/env-file.spec.ts +76 -0
  200. package/apps/control-plane/test/execution-control-service.spec.ts +535 -0
  201. package/apps/control-plane/test/execution-enrollment-service.spec.ts +648 -0
  202. package/apps/control-plane/test/feature-lifecycle.spec.ts +126 -0
  203. package/apps/control-plane/test/feature-resume-phase.spec.ts +164 -0
  204. package/apps/control-plane/test/feature-send-message-service.spec.ts +161 -0
  205. package/apps/control-plane/test/feature-state-service.spec.ts +295 -0
  206. package/apps/control-plane/test/fs.spec.ts +80 -0
  207. package/apps/control-plane/test/gate-freshness.spec.ts +590 -0
  208. package/apps/control-plane/test/gate-log-extractor.spec.ts +170 -0
  209. package/apps/control-plane/test/gates.spec.ts +108 -0
  210. package/apps/control-plane/test/git-reconciliation-service.spec.ts +2307 -0
  211. package/apps/control-plane/test/helpers.ts +65 -0
  212. package/apps/control-plane/test/incremental-gates.spec.ts +271 -0
  213. package/apps/control-plane/test/index-normalizer.spec.ts +98 -0
  214. package/apps/control-plane/test/init-wizard.spec.ts +17 -0
  215. package/apps/control-plane/test/intake-artifacts.spec.ts +203 -0
  216. package/apps/control-plane/test/intake-service.spec.ts +3176 -0
  217. package/apps/control-plane/test/kernel-collision-replay.spec.ts +3 -2
  218. package/apps/control-plane/test/kernel-tool-executor.spec.ts +77 -0
  219. package/apps/control-plane/test/kernel-tool-wiring.spec.ts +279 -0
  220. package/apps/control-plane/test/kernel.branches.spec.ts +15 -2
  221. package/apps/control-plane/test/kernel.coverage.spec.ts +7 -3
  222. package/apps/control-plane/test/kernel.coverage2.spec.ts +731 -2
  223. package/apps/control-plane/test/kernel.spec.ts +464 -2
  224. package/apps/control-plane/test/mainline-conflict.spec.ts +66 -0
  225. package/apps/control-plane/test/mcp-helpers.spec.ts +79 -0
  226. package/apps/control-plane/test/mcp.spec.ts +177 -13
  227. package/apps/control-plane/test/merge-command-handler.spec.ts +531 -0
  228. package/apps/control-plane/test/merge-service.spec.ts +570 -4
  229. package/apps/control-plane/test/notifier-service.spec.ts +26 -0
  230. package/apps/control-plane/test/organizer-enrollment-scheduler.spec.ts +340 -0
  231. package/apps/control-plane/test/organizer-ordering-artifact.spec.ts +95 -0
  232. package/apps/control-plane/test/organizer-sidecar-service.spec.ts +468 -0
  233. package/apps/control-plane/test/output-loop-detector.spec.ts +6 -0
  234. package/apps/control-plane/test/path-layout.spec.ts +70 -0
  235. package/apps/control-plane/test/performance-analytics.spec.ts +124 -0
  236. package/apps/control-plane/test/plan-conformance-scorer.spec.ts +53 -0
  237. package/apps/control-plane/test/plan-service.spec.ts +686 -4
  238. package/apps/control-plane/test/planning-wave-executor.spec.ts +3272 -86
  239. package/apps/control-plane/test/policy-loader-service.spec.ts +5 -0
  240. package/apps/control-plane/test/prompt-overlay.spec.ts +65 -0
  241. package/apps/control-plane/test/provider-command-runner-epipe.spec.ts +64 -0
  242. package/apps/control-plane/test/providers/api-worker-provider.spec.ts +129 -0
  243. package/apps/control-plane/test/providers/cli-worker-provider.spec.ts +148 -0
  244. package/apps/control-plane/test/providers/usage-types.spec.ts +98 -0
  245. package/apps/control-plane/test/providers.spec.ts +293 -16
  246. package/apps/control-plane/test/question-command-handlers.spec.ts +156 -0
  247. package/apps/control-plane/test/question-service.spec.ts +1119 -0
  248. package/apps/control-plane/test/reactions.spec.ts +114 -0
  249. package/apps/control-plane/test/replay-command-handler.spec.ts +144 -0
  250. package/apps/control-plane/test/replay-timeline-service.spec.ts +459 -0
  251. package/apps/control-plane/test/response.spec.ts +31 -0
  252. package/apps/control-plane/test/resume-command.spec.ts +757 -9
  253. package/apps/control-plane/test/retry-resume-decision.spec.ts +133 -0
  254. package/apps/control-plane/test/rollback-command-handler.spec.ts +334 -0
  255. package/apps/control-plane/test/rollback-command.spec.ts +120 -0
  256. package/apps/control-plane/test/run-coordinator.spec.ts +3062 -404
  257. package/apps/control-plane/test/schemas/state.schema.spec.ts +71 -0
  258. package/apps/control-plane/test/service-retry-paths.spec.ts +112 -0
  259. package/apps/control-plane/test/services.spec.ts +472 -2
  260. package/apps/control-plane/test/session-management.spec.ts +346 -1
  261. package/apps/control-plane/test/spec-ingestion.spec.ts +102 -28
  262. package/apps/control-plane/test/spec-preparation.spec.ts +182 -0
  263. package/apps/control-plane/test/supervisor-collaborators.spec.ts +191 -3
  264. package/apps/control-plane/test/supervisor.calltool.spec.ts +198 -0
  265. package/apps/control-plane/test/supervisor.spec.ts +95 -16
  266. package/apps/control-plane/test/supervisor.unit.spec.ts +385 -18
  267. package/apps/control-plane/test/tool-runtime.spec.ts +122 -0
  268. package/apps/control-plane/test/worker-decision-loop.spec.ts +3479 -476
  269. package/apps/control-plane/test/worker-execution-policy.spec.ts +1416 -6
  270. package/apps/control-plane/test/worker-provider-adapters.spec.ts +1894 -37
  271. package/apps/control-plane/test/worker-provider-factory.spec.ts +81 -0
  272. package/apps/control-plane/test/worktree-watchdog-service.spec.ts +125 -0
  273. package/apps/control-plane/vitest.config.ts +5 -0
  274. package/config/agentic/orchestrator/agents.yaml +22 -1
  275. package/config/agentic/orchestrator/gates.yaml +24 -7
  276. package/config/agentic/orchestrator/policy.yaml +23 -1
  277. package/config/agentic/orchestrator/prompts/builder.system.md +69 -20
  278. package/config/agentic/orchestrator/prompts/organizer.system.md +85 -0
  279. package/config/agentic/orchestrator/prompts/overrides/builder.claude.md +28 -0
  280. package/config/agentic/orchestrator/prompts/overrides/builder.codex.md +28 -0
  281. package/config/agentic/orchestrator/prompts/overrides/planner.claude.md +20 -0
  282. package/config/agentic/orchestrator/prompts/overrides/planner.codex.md +20 -0
  283. package/config/agentic/orchestrator/prompts/planner-intake.system.md +149 -0
  284. package/config/agentic/orchestrator/prompts/planner.system.md +113 -40
  285. package/config/agentic/orchestrator/prompts/qa.system.md +75 -18
  286. package/config/agentic/orchestrator/prompts/reconciler.system.md +119 -0
  287. package/dist/apps/control-plane/application/kernel-tool-wiring.d.ts +26 -2
  288. package/dist/apps/control-plane/application/kernel-tool-wiring.js +40 -2
  289. package/dist/apps/control-plane/application/kernel-tool-wiring.js.map +1 -1
  290. package/dist/apps/control-plane/application/services/activity-monitor-service.js +37 -1
  291. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
  292. package/dist/apps/control-plane/application/services/bootstrap-manifest-generator-service.d.ts +4 -0
  293. package/dist/apps/control-plane/application/services/bootstrap-manifest-generator-service.js +188 -0
  294. package/dist/apps/control-plane/application/services/bootstrap-manifest-generator-service.js.map +1 -0
  295. package/dist/apps/control-plane/application/services/checkpoint-service.d.ts +5 -0
  296. package/dist/apps/control-plane/application/services/checkpoint-service.js +69 -24
  297. package/dist/apps/control-plane/application/services/checkpoint-service.js.map +1 -1
  298. package/dist/apps/control-plane/application/services/collision-override-service.d.ts +139 -0
  299. package/dist/apps/control-plane/application/services/collision-override-service.js +568 -0
  300. package/dist/apps/control-plane/application/services/collision-override-service.js.map +1 -0
  301. package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +15 -0
  302. package/dist/apps/control-plane/application/services/collision-queue-service.js +92 -33
  303. package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
  304. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +11 -0
  305. package/dist/apps/control-plane/application/services/cost-tracking-service.js +75 -0
  306. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
  307. package/dist/apps/control-plane/application/services/execution-control-service.d.ts +75 -0
  308. package/dist/apps/control-plane/application/services/execution-control-service.js +421 -0
  309. package/dist/apps/control-plane/application/services/execution-control-service.js.map +1 -0
  310. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +1 -0
  311. package/dist/apps/control-plane/application/services/feature-deletion-service.js +23 -1
  312. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  313. package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +24 -1
  314. package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +132 -3
  315. package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
  316. package/dist/apps/control-plane/application/services/feature-send-message-service.js +16 -8
  317. package/dist/apps/control-plane/application/services/feature-send-message-service.js.map +1 -1
  318. package/dist/apps/control-plane/application/services/feature-state-service.d.ts +36 -0
  319. package/dist/apps/control-plane/application/services/feature-state-service.js +163 -6
  320. package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
  321. package/dist/apps/control-plane/application/services/gate-service.d.ts +2 -1
  322. package/dist/apps/control-plane/application/services/gate-service.js +95 -5
  323. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  324. package/dist/apps/control-plane/application/services/git-reconciliation-service.d.ts +92 -0
  325. package/dist/apps/control-plane/application/services/git-reconciliation-service.js +1097 -0
  326. package/dist/apps/control-plane/application/services/git-reconciliation-service.js.map +1 -0
  327. package/dist/apps/control-plane/application/services/intake-service.d.ts +63 -0
  328. package/dist/apps/control-plane/application/services/intake-service.js +1050 -0
  329. package/dist/apps/control-plane/application/services/intake-service.js.map +1 -0
  330. package/dist/apps/control-plane/application/services/merge-service.d.ts +5 -1
  331. package/dist/apps/control-plane/application/services/merge-service.js +233 -18
  332. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  333. package/dist/apps/control-plane/application/services/notifier-service.d.ts +1 -1
  334. package/dist/apps/control-plane/application/services/notifier-service.js +1 -0
  335. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
  336. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +11 -0
  337. package/dist/apps/control-plane/application/services/performance-analytics-service.js +59 -0
  338. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
  339. package/dist/apps/control-plane/application/services/plan-service.d.ts +5 -0
  340. package/dist/apps/control-plane/application/services/plan-service.js +254 -15
  341. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  342. package/dist/apps/control-plane/application/services/question-service.d.ts +72 -0
  343. package/dist/apps/control-plane/application/services/question-service.js +507 -0
  344. package/dist/apps/control-plane/application/services/question-service.js.map +1 -0
  345. package/dist/apps/control-plane/application/services/reactions-service.d.ts +2 -0
  346. package/dist/apps/control-plane/application/services/reactions-service.js +60 -17
  347. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
  348. package/dist/apps/control-plane/application/services/replay-timeline-service.d.ts +39 -0
  349. package/dist/apps/control-plane/application/services/replay-timeline-service.js +205 -0
  350. package/dist/apps/control-plane/application/services/replay-timeline-service.js.map +1 -0
  351. package/dist/apps/control-plane/application/services/reporting-service.d.ts +59 -0
  352. package/dist/apps/control-plane/application/services/reporting-service.js +121 -9
  353. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  354. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +20 -0
  355. package/dist/apps/control-plane/application/services/run-lease-service.js +81 -4
  356. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  357. package/dist/apps/control-plane/application/services/worktree-watchdog-service.d.ts +10 -0
  358. package/dist/apps/control-plane/application/services/worktree-watchdog-service.js +65 -8
  359. package/dist/apps/control-plane/application/services/worktree-watchdog-service.js.map +1 -1
  360. package/dist/apps/control-plane/application/tools/tool-metadata.js +7 -0
  361. package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
  362. package/dist/apps/control-plane/application/usage-types.d.ts +65 -0
  363. package/dist/apps/control-plane/application/usage-types.js +75 -0
  364. package/dist/apps/control-plane/application/usage-types.js.map +1 -0
  365. package/dist/apps/control-plane/cli/add-command-handler.d.ts +18 -0
  366. package/dist/apps/control-plane/cli/add-command-handler.js +110 -0
  367. package/dist/apps/control-plane/cli/add-command-handler.js.map +1 -0
  368. package/dist/apps/control-plane/cli/answer-command-handler.d.ts +8 -0
  369. package/dist/apps/control-plane/cli/answer-command-handler.js +96 -0
  370. package/dist/apps/control-plane/cli/answer-command-handler.js.map +1 -0
  371. package/dist/apps/control-plane/cli/attach-command-handler.js +8 -3
  372. package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -1
  373. package/dist/apps/control-plane/cli/cli-argument-parser.js +131 -11
  374. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  375. package/dist/apps/control-plane/cli/collision-command-handler.d.ts +8 -0
  376. package/dist/apps/control-plane/cli/collision-command-handler.js +90 -0
  377. package/dist/apps/control-plane/cli/collision-command-handler.js.map +1 -0
  378. package/dist/apps/control-plane/cli/command-catalog.d.ts +21 -0
  379. package/dist/apps/control-plane/cli/command-catalog.js +416 -0
  380. package/dist/apps/control-plane/cli/command-catalog.js.map +1 -0
  381. package/dist/apps/control-plane/cli/complete-command-handler.d.ts +15 -0
  382. package/dist/apps/control-plane/cli/complete-command-handler.js +26 -0
  383. package/dist/apps/control-plane/cli/complete-command-handler.js.map +1 -0
  384. package/dist/apps/control-plane/cli/completion-command-handler.d.ts +8 -0
  385. package/dist/apps/control-plane/cli/completion-command-handler.js +20 -0
  386. package/dist/apps/control-plane/cli/completion-command-handler.js.map +1 -0
  387. package/dist/apps/control-plane/cli/completion-resolver.d.ts +1 -0
  388. package/dist/apps/control-plane/cli/completion-resolver.js +250 -0
  389. package/dist/apps/control-plane/cli/completion-resolver.js.map +1 -0
  390. package/dist/apps/control-plane/cli/completion-shell-renderer.d.ts +3 -0
  391. package/dist/apps/control-plane/cli/completion-shell-renderer.js +53 -0
  392. package/dist/apps/control-plane/cli/completion-shell-renderer.js.map +1 -0
  393. package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +1 -0
  394. package/dist/apps/control-plane/cli/dashboard-command-handler.js +83 -1
  395. package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -1
  396. package/dist/apps/control-plane/cli/dashboard-runtime-runner.d.ts +81 -0
  397. package/dist/apps/control-plane/cli/dashboard-runtime-runner.js +724 -0
  398. package/dist/apps/control-plane/cli/dashboard-runtime-runner.js.map +1 -0
  399. package/dist/apps/control-plane/cli/dashboard-runtime.d.ts +1 -0
  400. package/dist/apps/control-plane/cli/dashboard-runtime.js +26 -0
  401. package/dist/apps/control-plane/cli/dashboard-runtime.js.map +1 -0
  402. package/dist/apps/control-plane/cli/help-command-handler.js +13 -172
  403. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
  404. package/dist/apps/control-plane/cli/init-command-handler.js +51 -6
  405. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
  406. package/dist/apps/control-plane/cli/merge-command-handler.d.ts +8 -0
  407. package/dist/apps/control-plane/cli/merge-command-handler.js +139 -0
  408. package/dist/apps/control-plane/cli/merge-command-handler.js.map +1 -0
  409. package/dist/apps/control-plane/cli/questions-command-handler.d.ts +8 -0
  410. package/dist/apps/control-plane/cli/questions-command-handler.js +59 -0
  411. package/dist/apps/control-plane/cli/questions-command-handler.js.map +1 -0
  412. package/dist/apps/control-plane/cli/replay-command-handler.d.ts +15 -0
  413. package/dist/apps/control-plane/cli/replay-command-handler.js +55 -0
  414. package/dist/apps/control-plane/cli/replay-command-handler.js.map +1 -0
  415. package/dist/apps/control-plane/cli/resume-command-handler.d.ts +2 -0
  416. package/dist/apps/control-plane/cli/resume-command-handler.js +180 -17
  417. package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
  418. package/dist/apps/control-plane/cli/retry-command-handler.js +202 -16
  419. package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -1
  420. package/dist/apps/control-plane/cli/retry-resume-decision.d.ts +26 -0
  421. package/dist/apps/control-plane/cli/retry-resume-decision.js +61 -0
  422. package/dist/apps/control-plane/cli/retry-resume-decision.js.map +1 -0
  423. package/dist/apps/control-plane/cli/rollback-command-handler.js +3 -2
  424. package/dist/apps/control-plane/cli/rollback-command-handler.js.map +1 -1
  425. package/dist/apps/control-plane/cli/run-command-handler.js +26 -2
  426. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  427. package/dist/apps/control-plane/cli/spec-ingestion-service.d.ts +2 -0
  428. package/dist/apps/control-plane/cli/spec-ingestion-service.js +37 -48
  429. package/dist/apps/control-plane/cli/spec-ingestion-service.js.map +1 -1
  430. package/dist/apps/control-plane/cli/spec-preparation.d.ts +14 -0
  431. package/dist/apps/control-plane/cli/spec-preparation.js +81 -0
  432. package/dist/apps/control-plane/cli/spec-preparation.js.map +1 -0
  433. package/dist/apps/control-plane/cli/spec-utils.d.ts +4 -0
  434. package/dist/apps/control-plane/cli/spec-utils.js +70 -11
  435. package/dist/apps/control-plane/cli/spec-utils.js.map +1 -1
  436. package/dist/apps/control-plane/cli/status-command-handler.js +69 -0
  437. package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
  438. package/dist/apps/control-plane/cli/types.d.ts +41 -4
  439. package/dist/apps/control-plane/cli/types.js +9 -1
  440. package/dist/apps/control-plane/cli/types.js.map +1 -1
  441. package/dist/apps/control-plane/core/collisions.d.ts +37 -19
  442. package/dist/apps/control-plane/core/collisions.js +87 -12
  443. package/dist/apps/control-plane/core/collisions.js.map +1 -1
  444. package/dist/apps/control-plane/core/constants.d.ts +17 -1
  445. package/dist/apps/control-plane/core/constants.js +18 -1
  446. package/dist/apps/control-plane/core/constants.js.map +1 -1
  447. package/dist/apps/control-plane/core/error-codes.d.ts +39 -0
  448. package/dist/apps/control-plane/core/error-codes.js +39 -0
  449. package/dist/apps/control-plane/core/error-codes.js.map +1 -1
  450. package/dist/apps/control-plane/core/execution-control.d.ts +45 -0
  451. package/dist/apps/control-plane/core/execution-control.js +2 -0
  452. package/dist/apps/control-plane/core/execution-control.js.map +1 -0
  453. package/dist/apps/control-plane/core/feature-resume-phase.d.ts +3 -0
  454. package/dist/apps/control-plane/core/feature-resume-phase.js +88 -0
  455. package/dist/apps/control-plane/core/feature-resume-phase.js.map +1 -0
  456. package/dist/apps/control-plane/core/gate-freshness.d.ts +48 -0
  457. package/dist/apps/control-plane/core/gate-freshness.js +267 -0
  458. package/dist/apps/control-plane/core/gate-freshness.js.map +1 -0
  459. package/dist/apps/control-plane/core/gate-log-extractor.d.ts +22 -0
  460. package/dist/apps/control-plane/core/gate-log-extractor.js +66 -0
  461. package/dist/apps/control-plane/core/gate-log-extractor.js.map +1 -0
  462. package/dist/apps/control-plane/core/gates.d.ts +11 -2
  463. package/dist/apps/control-plane/core/gates.js +67 -3
  464. package/dist/apps/control-plane/core/gates.js.map +1 -1
  465. package/dist/apps/control-plane/core/intake-artifacts.d.ts +109 -0
  466. package/dist/apps/control-plane/core/intake-artifacts.js +143 -0
  467. package/dist/apps/control-plane/core/intake-artifacts.js.map +1 -0
  468. package/dist/apps/control-plane/core/kernel-types.d.ts +8 -0
  469. package/dist/apps/control-plane/core/kernel.d.ts +256 -8
  470. package/dist/apps/control-plane/core/kernel.js +400 -14
  471. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  472. package/dist/apps/control-plane/core/mainline-conflict.d.ts +7 -0
  473. package/dist/apps/control-plane/core/mainline-conflict.js +20 -0
  474. package/dist/apps/control-plane/core/mainline-conflict.js.map +1 -0
  475. package/dist/apps/control-plane/core/merge-repair.d.ts +35 -0
  476. package/dist/apps/control-plane/core/merge-repair.js +99 -0
  477. package/dist/apps/control-plane/core/merge-repair.js.map +1 -0
  478. package/dist/apps/control-plane/core/path-layout.d.ts +10 -0
  479. package/dist/apps/control-plane/core/path-layout.js +32 -2
  480. package/dist/apps/control-plane/core/path-layout.js.map +1 -1
  481. package/dist/apps/control-plane/core/path-rules.js +9 -3
  482. package/dist/apps/control-plane/core/path-rules.js.map +1 -1
  483. package/dist/apps/control-plane/core/plan-submit-recovery.d.ts +22 -0
  484. package/dist/apps/control-plane/core/plan-submit-recovery.js +78 -0
  485. package/dist/apps/control-plane/core/plan-submit-recovery.js.map +1 -0
  486. package/dist/apps/control-plane/core/questions.d.ts +40 -0
  487. package/dist/apps/control-plane/core/questions.js +2 -0
  488. package/dist/apps/control-plane/core/questions.js.map +1 -0
  489. package/dist/apps/control-plane/core/runtime-sessions.d.ts +4 -0
  490. package/dist/apps/control-plane/core/schemas.d.ts +2 -0
  491. package/dist/apps/control-plane/core/schemas.js +31 -1
  492. package/dist/apps/control-plane/core/schemas.js.map +1 -1
  493. package/dist/apps/control-plane/core/tool-caller.d.ts +18 -1
  494. package/dist/apps/control-plane/core/utils/index-normalizer.js +17 -4
  495. package/dist/apps/control-plane/core/utils/index-normalizer.js.map +1 -1
  496. package/dist/apps/control-plane/core/worktree-diff.d.ts +4 -0
  497. package/dist/apps/control-plane/core/worktree-diff.js +52 -0
  498. package/dist/apps/control-plane/core/worktree-diff.js.map +1 -0
  499. package/dist/apps/control-plane/index.d.ts +10 -2
  500. package/dist/apps/control-plane/index.js +9 -2
  501. package/dist/apps/control-plane/index.js.map +1 -1
  502. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +236 -6
  503. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  504. package/dist/apps/control-plane/mcp/kernel-tool-executor.js +16 -0
  505. package/dist/apps/control-plane/mcp/kernel-tool-executor.js.map +1 -1
  506. package/dist/apps/control-plane/mcp/tool-runtime.d.ts +5 -0
  507. package/dist/apps/control-plane/mcp/tool-runtime.js +40 -5
  508. package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
  509. package/dist/apps/control-plane/providers/api-worker-provider.d.ts +2 -2
  510. package/dist/apps/control-plane/providers/api-worker-provider.js +40 -9
  511. package/dist/apps/control-plane/providers/api-worker-provider.js.map +1 -1
  512. package/dist/apps/control-plane/providers/cli-worker-provider.d.ts +59 -3
  513. package/dist/apps/control-plane/providers/cli-worker-provider.js +758 -46
  514. package/dist/apps/control-plane/providers/cli-worker-provider.js.map +1 -1
  515. package/dist/apps/control-plane/providers/output-parsers/generic-output-parser.js +91 -1
  516. package/dist/apps/control-plane/providers/output-parsers/generic-output-parser.js.map +1 -1
  517. package/dist/apps/control-plane/providers/output-parsers/types.d.ts +2 -0
  518. package/dist/apps/control-plane/providers/provider-defaults.d.ts +12 -0
  519. package/dist/apps/control-plane/providers/provider-defaults.js +103 -7
  520. package/dist/apps/control-plane/providers/provider-defaults.js.map +1 -1
  521. package/dist/apps/control-plane/providers/providers.d.ts +50 -4
  522. package/dist/apps/control-plane/providers/providers.js +145 -14
  523. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  524. package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +2 -0
  525. package/dist/apps/control-plane/providers/worker-provider-factory.js +8 -1
  526. package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
  527. package/dist/apps/control-plane/supervisor/artifact-stager.d.ts +5 -0
  528. package/dist/apps/control-plane/supervisor/artifact-stager.js +45 -0
  529. package/dist/apps/control-plane/supervisor/artifact-stager.js.map +1 -0
  530. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +24 -1
  531. package/dist/apps/control-plane/supervisor/build-wave-executor.js +362 -150
  532. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  533. package/dist/apps/control-plane/supervisor/execution-enrollment-service.d.ts +41 -0
  534. package/dist/apps/control-plane/supervisor/execution-enrollment-service.js +311 -0
  535. package/dist/apps/control-plane/supervisor/execution-enrollment-service.js.map +1 -0
  536. package/dist/apps/control-plane/supervisor/organizer-enrollment-scheduler.d.ts +15 -0
  537. package/dist/apps/control-plane/supervisor/organizer-enrollment-scheduler.js +93 -0
  538. package/dist/apps/control-plane/supervisor/organizer-enrollment-scheduler.js.map +1 -0
  539. package/dist/apps/control-plane/supervisor/organizer-sidecar-service.d.ts +44 -0
  540. package/dist/apps/control-plane/supervisor/organizer-sidecar-service.js +311 -0
  541. package/dist/apps/control-plane/supervisor/organizer-sidecar-service.js.map +1 -0
  542. package/dist/apps/control-plane/supervisor/plan-conformance-scorer.js +2 -5
  543. package/dist/apps/control-plane/supervisor/plan-conformance-scorer.js.map +1 -1
  544. package/dist/apps/control-plane/supervisor/planner-phase.d.ts +3 -0
  545. package/dist/apps/control-plane/supervisor/planner-phase.js +70 -0
  546. package/dist/apps/control-plane/supervisor/planner-phase.js.map +1 -0
  547. package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +42 -0
  548. package/dist/apps/control-plane/supervisor/planning-wave-executor.js +753 -55
  549. package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
  550. package/dist/apps/control-plane/supervisor/prompt-bundle-loader.js +19 -1
  551. package/dist/apps/control-plane/supervisor/prompt-bundle-loader.js.map +1 -1
  552. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +21 -0
  553. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +287 -156
  554. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  555. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +30 -1
  556. package/dist/apps/control-plane/supervisor/run-coordinator.js +561 -17
  557. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  558. package/dist/apps/control-plane/supervisor/runtime.d.ts +84 -0
  559. package/dist/apps/control-plane/supervisor/runtime.js +393 -3
  560. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  561. package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +54 -0
  562. package/dist/apps/control-plane/supervisor/session-orchestrator.js +176 -1
  563. package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
  564. package/dist/apps/control-plane/supervisor/types.d.ts +142 -1
  565. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  566. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +68 -2
  567. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +723 -89
  568. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  569. package/docs/core/ARCHITECTURE.md +227 -0
  570. package/docs/core/DECISIONS.md +94 -0
  571. package/docs/core/DOMAIN-LOGIC.md +60 -0
  572. package/docs/core/PATTERNS.md +201 -0
  573. package/docs/core/TROUBLESHOOTING.md +347 -0
  574. package/docs/core/intentgraph-dependencies.json +39860 -0
  575. package/docs/core/intentgraph.index.json +46580 -0
  576. package/docs/plans/2026-03-10-gate-failure-targeted-repair-design.md +224 -0
  577. package/docs/plans/2026-03-10-gate-failure-targeted-repair.md +1032 -0
  578. package/docs/superpowers/plans/2026-03-16-provider-cli-config.md +743 -0
  579. package/docs/superpowers/plans/2026-03-23-reconcile-divergence-fix.md +777 -0
  580. package/docs/superpowers/plans/2026-03-28-ordering-agent-implementation.md +1754 -0
  581. package/docs/superpowers/plans/2026-03-29-drop-zone-and-provider-optimization.md +1108 -0
  582. package/docs/superpowers/plans/2026-03-29-merge-target-feature-branch.md +685 -0
  583. package/docs/superpowers/plans/2026-03-29-organizer-sidecar-runtime-loop.md +1289 -0
  584. package/docs/superpowers/specs/2026-03-23-reconcile-divergence-fix-design.md +118 -0
  585. package/docs/superpowers/specs/2026-03-28-ordering-agent-spec-audit-design.md +50 -0
  586. package/docs/superpowers/specs/2026-03-29-drop-zone-and-provider-optimization-design.md +254 -0
  587. package/docs/superpowers/specs/2026-03-29-merge-target-feature-branch-design.md +152 -0
  588. package/docs/superpowers/specs/2026-03-29-organizer-sidecar-runtime-loop-design.md +225 -0
  589. package/package.json +3 -2
  590. package/packages/web-dashboard/package.json +2 -1
  591. package/packages/web-dashboard/src/app/analytics/page.tsx +36 -2
  592. package/packages/web-dashboard/src/app/api/actions/route.ts +274 -63
  593. package/packages/web-dashboard/src/app/api/actions/status/route.ts +35 -0
  594. package/packages/web-dashboard/src/app/api/analytics/provider/route.ts +18 -0
  595. package/packages/web-dashboard/src/app/api/collisions/approve/route.ts +58 -0
  596. package/packages/web-dashboard/src/app/api/features/[id]/checkpoint-diff/route.ts +36 -0
  597. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/route.ts +29 -0
  598. package/packages/web-dashboard/src/app/api/features/[id]/conflicts/abort/route.ts +29 -0
  599. package/packages/web-dashboard/src/app/api/features/[id]/conflicts/files/route.ts +30 -0
  600. package/packages/web-dashboard/src/app/api/features/[id]/conflicts/resolve/route.ts +51 -0
  601. package/packages/web-dashboard/src/app/api/features/[id]/conflicts/route.ts +75 -0
  602. package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +16 -2
  603. package/packages/web-dashboard/src/app/api/features/[id]/files/route.ts +26 -0
  604. package/packages/web-dashboard/src/app/api/features/[id]/gate-history/route.ts +27 -0
  605. package/packages/web-dashboard/src/app/api/features/[id]/genealogy/route.ts +26 -0
  606. package/packages/web-dashboard/src/app/api/features/[id]/history/run/[runId]/route.ts +20 -0
  607. package/packages/web-dashboard/src/app/api/features/[id]/history/runs/route.ts +34 -0
  608. package/packages/web-dashboard/src/app/api/features/[id]/intake-workspace/route.ts +20 -0
  609. package/packages/web-dashboard/src/app/api/features/[id]/live-output/route.ts +74 -0
  610. package/packages/web-dashboard/src/app/api/features/[id]/plan/amend/route.ts +21 -0
  611. package/packages/web-dashboard/src/app/api/features/[id]/plan-progress/route.ts +20 -0
  612. package/packages/web-dashboard/src/app/api/features/[id]/planner-artifacts/[artifact]/route.ts +78 -0
  613. package/packages/web-dashboard/src/app/api/features/[id]/planner-lifecycle/route.ts +20 -0
  614. package/packages/web-dashboard/src/app/api/features/[id]/planning-workspace/route.ts +20 -0
  615. package/packages/web-dashboard/src/app/api/features/[id]/questions/[questionId]/answer/route.ts +27 -0
  616. package/packages/web-dashboard/src/app/api/features/[id]/questions/route.ts +18 -0
  617. package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +14 -7
  618. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +57 -2
  619. package/packages/web-dashboard/src/app/api/features/[id]/spec/route.ts +30 -0
  620. package/packages/web-dashboard/src/app/api/features/[id]/triage/route.ts +83 -0
  621. package/packages/web-dashboard/src/app/api/features/[id]/worker-events/route.ts +40 -0
  622. package/packages/web-dashboard/src/app/api/launch/preview/route.ts +86 -0
  623. package/packages/web-dashboard/src/app/api/launch/submit/route.ts +180 -0
  624. package/packages/web-dashboard/src/app/api/mainline/status/route.ts +74 -0
  625. package/packages/web-dashboard/src/app/api/merge-queue/route.ts +13 -0
  626. package/packages/web-dashboard/src/app/api/policy/budget/route.ts +14 -0
  627. package/packages/web-dashboard/src/app/api/projects/route.ts +11 -7
  628. package/packages/web-dashboard/src/app/api/reconciler/queue/route.ts +47 -0
  629. package/packages/web-dashboard/src/app/api/run/route.ts +26 -2
  630. package/packages/web-dashboard/src/app/api/runtime/events/route.ts +227 -0
  631. package/packages/web-dashboard/src/app/api/runtime/operations/route.ts +269 -0
  632. package/packages/web-dashboard/src/app/api/runtime/questions/route.ts +11 -0
  633. package/packages/web-dashboard/src/app/api/runtime/runs/route.ts +80 -0
  634. package/packages/web-dashboard/src/app/api/status/route.ts +4 -2
  635. package/packages/web-dashboard/src/app/feature/[id]/page.tsx +32 -42
  636. package/packages/web-dashboard/src/app/globals.css +34 -3
  637. package/packages/web-dashboard/src/app/launch/page.tsx +357 -0
  638. package/packages/web-dashboard/src/app/layout.tsx +23 -1
  639. package/packages/web-dashboard/src/app/page.tsx +263 -272
  640. package/packages/web-dashboard/src/components/dashboard/attention-strip.tsx +52 -0
  641. package/packages/web-dashboard/src/components/dashboard/collision-approval-drawer.tsx +185 -0
  642. package/packages/web-dashboard/src/components/dashboard/command-center-header.tsx +102 -0
  643. package/packages/web-dashboard/src/components/dashboard/mainline-status-banner.tsx +84 -0
  644. package/packages/web-dashboard/src/components/dashboard/merged-archive.tsx +36 -0
  645. package/packages/web-dashboard/src/components/dashboard/prioritized-queues.tsx +98 -0
  646. package/packages/web-dashboard/src/components/dashboard/reconciler-queue-card.tsx +115 -0
  647. package/packages/web-dashboard/src/components/dashboard/secondary-diagnostics-rail.tsx +48 -0
  648. package/packages/web-dashboard/src/components/dashboard/task-filter-bar.tsx +74 -0
  649. package/packages/web-dashboard/src/components/dashboard/triage-drawer.tsx +455 -0
  650. package/packages/web-dashboard/src/components/diff-viewer.tsx +19 -3
  651. package/packages/web-dashboard/src/components/evidence-viewer.tsx +65 -51
  652. package/packages/web-dashboard/src/components/feature-card.tsx +90 -7
  653. package/packages/web-dashboard/src/components/feature-cost-panel.tsx +112 -11
  654. package/packages/web-dashboard/src/components/feature-list-view.tsx +25 -4
  655. package/packages/web-dashboard/src/components/features/runtime-inspector/EventsTimelineView.tsx +260 -0
  656. package/packages/web-dashboard/src/components/features/runtime-inspector/OperationsListView.tsx +172 -0
  657. package/packages/web-dashboard/src/components/features/runtime-inspector/RuntimeInspectorPanel.tsx +896 -0
  658. package/packages/web-dashboard/src/components/filter-bar.tsx +7 -39
  659. package/packages/web-dashboard/src/components/focus/ActionableRiskList.tsx +46 -0
  660. package/packages/web-dashboard/src/components/focus/AgentRolePerformanceCard.tsx +200 -0
  661. package/packages/web-dashboard/src/components/focus/BlockedGuidanceBanner.tsx +149 -0
  662. package/packages/web-dashboard/src/components/focus/CheckpointInspector.tsx +123 -0
  663. package/packages/web-dashboard/src/components/focus/CheckpointRail.tsx +118 -0
  664. package/packages/web-dashboard/src/components/focus/CheckpointScrubber.tsx +249 -0
  665. package/packages/web-dashboard/src/components/focus/CollisionApprovalBanner.tsx +192 -0
  666. package/packages/web-dashboard/src/components/focus/CollisionRadar.tsx +136 -0
  667. package/packages/web-dashboard/src/components/focus/ConflictStatusCard.tsx +52 -0
  668. package/packages/web-dashboard/src/components/focus/ContextSidebar.tsx +108 -0
  669. package/packages/web-dashboard/src/components/focus/DiagnosisPanel.tsx +68 -0
  670. package/packages/web-dashboard/src/components/focus/FeatureDecisionBanner.tsx +68 -0
  671. package/packages/web-dashboard/src/components/focus/FeatureQuestionAnswerPanel.tsx +167 -0
  672. package/packages/web-dashboard/src/components/focus/FocusHeader.tsx +54 -0
  673. package/packages/web-dashboard/src/components/focus/FocusLayout.tsx +283 -0
  674. package/packages/web-dashboard/src/components/focus/GateFlakinessSummary.tsx +144 -0
  675. package/packages/web-dashboard/src/components/focus/GenealogyTree.tsx +34 -0
  676. package/packages/web-dashboard/src/components/focus/HeroBlock.tsx +67 -0
  677. package/packages/web-dashboard/src/components/focus/LiveAgentConsole.tsx +277 -0
  678. package/packages/web-dashboard/src/components/focus/MergeQueueCard.tsx +78 -0
  679. package/packages/web-dashboard/src/components/focus/OperationalSummaryCard.tsx +227 -0
  680. package/packages/web-dashboard/src/components/focus/PinnedActions.tsx +96 -0
  681. package/packages/web-dashboard/src/components/focus/PlanAmendmentPanel.tsx +250 -0
  682. package/packages/web-dashboard/src/components/focus/PlanProgressPanel.tsx +133 -0
  683. package/packages/web-dashboard/src/components/focus/PlannerArtifactViewer.tsx +158 -0
  684. package/packages/web-dashboard/src/components/focus/PlannerLifecycleHeader.tsx +141 -0
  685. package/packages/web-dashboard/src/components/focus/ProgressSnapshotCard.tsx +113 -0
  686. package/packages/web-dashboard/src/components/focus/RecentMaterialChanges.tsx +69 -0
  687. package/packages/web-dashboard/src/components/focus/RoleLogViewer.tsx +436 -0
  688. package/packages/web-dashboard/src/components/focus/RunHistoryBrowser.tsx +62 -0
  689. package/packages/web-dashboard/src/components/focus/SpecViewer.tsx +172 -0
  690. package/packages/web-dashboard/src/components/focus/TabBar.tsx +33 -0
  691. package/packages/web-dashboard/src/components/focus/UsageBurnChart.tsx +212 -0
  692. package/packages/web-dashboard/src/components/focus/VerificationSummaryCard.tsx +122 -0
  693. package/packages/web-dashboard/src/components/focus/tabs/ChangesTab.tsx +325 -0
  694. package/packages/web-dashboard/src/components/focus/tabs/ConflictsTab.tsx +395 -0
  695. package/packages/web-dashboard/src/components/focus/tabs/GatesQaTab.tsx +38 -0
  696. package/packages/web-dashboard/src/components/focus/tabs/HistoryTab.tsx +213 -0
  697. package/packages/web-dashboard/src/components/focus/tabs/IntakeTab.tsx +429 -0
  698. package/packages/web-dashboard/src/components/focus/tabs/OverviewTab.tsx +217 -0
  699. package/packages/web-dashboard/src/components/focus/tabs/PlanningTab.tsx +390 -0
  700. package/packages/web-dashboard/src/components/focus/tabs/ReviewTab.tsx +497 -0
  701. package/packages/web-dashboard/src/components/focus/tabs/RuntimeTab.tsx +213 -0
  702. package/packages/web-dashboard/src/components/focus/tabs/TranscriptTab.tsx +315 -0
  703. package/packages/web-dashboard/src/components/gate-results.tsx +2 -2
  704. package/packages/web-dashboard/src/components/human-input-panel.tsx +33 -57
  705. package/packages/web-dashboard/src/components/kanban-board.tsx +4 -0
  706. package/packages/web-dashboard/src/components/launch/launch-draft-card.tsx +131 -0
  707. package/packages/web-dashboard/src/components/plan-viewer.tsx +147 -69
  708. package/packages/web-dashboard/src/components/quick-launch-panel.tsx +20 -47
  709. package/packages/web-dashboard/src/components/summary-bar.tsx +30 -76
  710. package/packages/web-dashboard/src/lib/aop-client.ts +2484 -36
  711. package/packages/web-dashboard/src/lib/blocked-state-guidance.ts +475 -0
  712. package/packages/web-dashboard/src/lib/collision-radar.ts +136 -0
  713. package/packages/web-dashboard/src/lib/dashboard-action-states.ts +204 -0
  714. package/packages/web-dashboard/src/lib/dashboard-runtime-client.ts +439 -0
  715. package/packages/web-dashboard/src/lib/dashboard-utils.ts +179 -18
  716. package/packages/web-dashboard/src/lib/drop-zone-utils.ts +92 -0
  717. package/packages/web-dashboard/src/lib/focus-detail-derivations.ts +958 -0
  718. package/packages/web-dashboard/src/lib/focus-view.ts +300 -0
  719. package/packages/web-dashboard/src/lib/health-diagnosis.ts +356 -0
  720. package/packages/web-dashboard/src/lib/launch-contracts.ts +77 -0
  721. package/packages/web-dashboard/src/lib/launch-markdown.ts +107 -0
  722. package/packages/web-dashboard/src/lib/launch-page-preview.ts +89 -0
  723. package/packages/web-dashboard/src/lib/live-feed.ts +1 -1
  724. package/packages/web-dashboard/src/lib/multi-project-config.ts +33 -0
  725. package/packages/web-dashboard/src/lib/orchestrator-tools.ts +845 -59
  726. package/packages/web-dashboard/src/lib/planner-workspace.ts +1285 -0
  727. package/packages/web-dashboard/src/lib/review-contracts.ts +5 -3
  728. package/packages/web-dashboard/src/lib/runtime-files.ts +285 -0
  729. package/packages/web-dashboard/src/lib/tool-catalog.ts +51 -0
  730. package/packages/web-dashboard/src/lib/types.ts +731 -3
  731. package/packages/web-dashboard/src/lib/usage-burn.ts +175 -0
  732. package/packages/web-dashboard/src/lib/worktree-diff.ts +128 -0
  733. package/packages/web-dashboard/src/styles/dashboard.module.css +1742 -459
  734. package/packages/web-dashboard/test/api/actions/route.spec.ts +675 -0
  735. package/packages/web-dashboard/test/api/features/diff.route.spec.ts +57 -0
  736. package/packages/web-dashboard/test/api/features/feature.route.spec.ts +99 -0
  737. package/packages/web-dashboard/test/api/features/live-output.route.spec.ts +123 -0
  738. package/packages/web-dashboard/test/api/features/plan-amend.route.spec.ts +95 -0
  739. package/packages/web-dashboard/test/api/features/planner-workspaces.route.spec.ts +162 -0
  740. package/packages/web-dashboard/test/api/features/question-answer.route.spec.ts +99 -0
  741. package/packages/web-dashboard/test/api/features/triage.route.spec.ts +195 -0
  742. package/packages/web-dashboard/test/api/launch/preview.route.spec.ts +149 -0
  743. package/packages/web-dashboard/test/api/launch/submit.route.spec.ts +382 -0
  744. package/packages/web-dashboard/test/api/runtime/events/route.spec.ts +164 -0
  745. package/packages/web-dashboard/test/api/runtime/operations/route.spec.ts +156 -0
  746. package/packages/web-dashboard/test/api/runtime/runs/route.spec.ts +112 -0
  747. package/packages/web-dashboard/test/components/changes-tab.spec.tsx +76 -0
  748. package/packages/web-dashboard/test/components/command-center-root.spec.tsx +87 -0
  749. package/packages/web-dashboard/test/components/diagnosis-panel.spec.tsx +59 -0
  750. package/packages/web-dashboard/test/components/feature-card.spec.tsx +45 -0
  751. package/packages/web-dashboard/test/components/focus-layout.spec.tsx +299 -0
  752. package/packages/web-dashboard/test/components/gate-results.spec.tsx +39 -0
  753. package/packages/web-dashboard/test/components/gates-qa-tab.spec.tsx +118 -0
  754. package/packages/web-dashboard/test/components/human-input-panel.spec.tsx +54 -0
  755. package/packages/web-dashboard/test/components/intake-tab.spec.tsx +210 -0
  756. package/packages/web-dashboard/test/components/kanban-board.spec.tsx +35 -0
  757. package/packages/web-dashboard/test/components/launch-draft-card.spec.tsx +54 -0
  758. package/packages/web-dashboard/test/components/launch-page.spec.tsx +79 -0
  759. package/packages/web-dashboard/test/components/overview-tab.spec.tsx +236 -0
  760. package/packages/web-dashboard/test/components/planning-tab.spec.tsx +202 -0
  761. package/packages/web-dashboard/test/components/review-tab.spec.tsx +169 -0
  762. package/packages/web-dashboard/test/components/role-log-viewer.spec.ts +42 -0
  763. package/packages/web-dashboard/test/components/runtime-inspector.spec.tsx +22 -0
  764. package/packages/web-dashboard/test/components/runtime-tab.spec.tsx +133 -0
  765. package/packages/web-dashboard/test/components/transcript-tab.spec.tsx +46 -0
  766. package/packages/web-dashboard/test/components/triage-drawer.spec.tsx +159 -0
  767. package/packages/web-dashboard/test/lib/aop-client.spec.ts +235 -0
  768. package/packages/web-dashboard/test/lib/dashboard-runtime-client.spec.ts +144 -0
  769. package/packages/web-dashboard/test/lib/focus-detail-derivations.spec.ts +314 -0
  770. package/packages/web-dashboard/test/lib/focus-view.spec.ts +248 -0
  771. package/packages/web-dashboard/test/lib/health-diagnosis.spec.ts +277 -0
  772. package/packages/web-dashboard/test/lib/launch-markdown.spec.ts +36 -0
  773. package/packages/web-dashboard/test/lib/multi-project-config.spec.ts +54 -0
  774. package/packages/web-dashboard/test/lib/orchestrator-tools.spec.ts +352 -0
  775. package/packages/web-dashboard/test/lib/planner-workspace.spec.ts +289 -0
  776. package/packages/web-dashboard/test/lib/worktree-diff.spec.ts +119 -0
  777. package/packages/web-dashboard/vitest.config.ts +2 -0
  778. package/spec-files/completed/agentic_orchestrator_add_feature_to_active_execution_spec.md +557 -0
  779. package/spec-files/completed/agentic_orchestrator_dashboard_command_center_redesign_spec.md +1147 -0
  780. package/spec-files/completed/agentic_orchestrator_execution_mode_spec.md +18 -16
  781. package/spec-files/completed/agentic_orchestrator_feature_focus_view_track_a_spec.md +672 -0
  782. package/spec-files/completed/agentic_orchestrator_feature_focus_view_track_b_spec.md +794 -0
  783. package/spec-files/completed/agentic_orchestrator_feature_focus_view_track_c_decision_centric_remediation_spec.md +1037 -0
  784. package/spec-files/completed/agentic_orchestrator_feature_focus_view_ux_redesign_spec.md +1432 -0
  785. package/spec-files/completed/agentic_orchestrator_focus_plan_tab_intake_planning_workspace_spec.md +921 -0
  786. package/spec-files/completed/agentic_orchestrator_intentional_collision_override_spec.md +584 -0
  787. package/spec-files/completed/agentic_orchestrator_interactive_planning_intake_and_requirements_verification_spec.md +1185 -0
  788. package/spec-files/completed/agentic_orchestrator_reactive_execution_enrollment_spec.md +864 -0
  789. package/spec-files/{outstanding → completed}/agentic_orchestrator_runtime_inspection_spec.md +92 -19
  790. package/spec-files/completed/agentic_orchestrator_scope_aware_run_lease_spec.md +408 -0
  791. package/spec-files/completed/git-reconciliation-engine.md +827 -0
  792. package/spec-files/outstanding/agentic_orchestrator_dashboard_quick_launch_and_control_surface_spec.md +331 -0
  793. package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +16 -6
  794. package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +60 -9
  795. package/spec-files/outstanding/agentic_orchestrator_focus_plan_tab_execution_contract_workspace_spec.md +616 -0
  796. package/spec-files/outstanding/agentic_orchestrator_headless_standby_dashboard_runtime_spec.md +310 -0
  797. package/spec-files/outstanding/agentic_orchestrator_human_input_interaction_protocol_spec.md +175 -72
  798. package/spec-files/outstanding/agentic_orchestrator_interactive_rename_cleanup_spec.md +197 -0
  799. package/spec-files/outstanding/agentic_orchestrator_interactive_resume_and_reconciliation_disposition_spec.md +412 -0
  800. package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +166 -137
  801. package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +3 -3
  802. package/spec-files/outstanding/agentic_orchestrator_phase_specific_agent_profiles_and_token_telemetry_spec.md +303 -0
  803. package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +18 -5
  804. package/spec-files/outstanding/agentic_orchestrator_policy_stratification_spec.md +225 -0
  805. package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +77 -50
  806. package/spec-files/outstanding/agentic_orchestrator_ready_to_merge_branch_handoff_spec.md +724 -0
  807. package/spec-files/outstanding/agentic_orchestrator_remove_deterministic_mode_spec.md +263 -0
  808. package/spec-files/outstanding/agentic_orchestrator_request_more_context_and_dashboard_human_input_spec.md +456 -0
  809. package/spec-files/outstanding/agentic_orchestrator_spec_coverage_and_reconciliation_enforcement_spec.md +1411 -0
  810. package/spec-files/outstanding/agentic_orchestrator_spec_ordering_agent_spec.md +370 -0
  811. package/spec-files/outstanding/shadow_workspace_implementation_spec.md +1 -1
  812. package/spec-files/progress.md +2026 -120
  813. package/specs/001-runtime-inspection/checklists/requirements.md +35 -0
  814. package/specs/001-runtime-inspection/design.md +338 -0
  815. package/specs/001-runtime-inspection/spec.md +95 -0
  816. package/specs/002-scope-aware-lease/checklists/requirements.md +35 -0
  817. package/specs/002-scope-aware-lease/contracts/lease-registry.schema.json +101 -0
  818. package/specs/002-scope-aware-lease/data-model.md +236 -0
  819. package/specs/002-scope-aware-lease/plan.md +766 -0
  820. package/specs/002-scope-aware-lease/quickstart.md +150 -0
  821. package/specs/002-scope-aware-lease/research.md +135 -0
  822. package/specs/002-scope-aware-lease/spec.md +128 -0
  823. package/specs/002-scope-aware-lease/tasks.md +767 -0
  824. package/tsconfig.json +1 -1
  825. package/vitest.config.ts +28 -0
  826. package/ARCHITECTURE_ADHERENCE_ANALYSIS.md +0 -871
  827. package/packages/web-dashboard/next-env.d.ts +0 -6
  828. package/packages/web-dashboard/src/components/detail-panel.tsx +0 -1124
  829. package/packages/web-dashboard/src/components/review-workspace.tsx +0 -1162
  830. /package/spec-files/{outstanding → completed}/agentic_orchestrator_artifact_database_publishing_spec.md +0 -0
  831. /package/spec-files/{outstanding → completed}/agentic_orchestrator_cli_shell_tab_completion_spec.md +0 -0
  832. /package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_diff_and_agent_console_spec.md +0 -0
  833. /package/spec-files/{outstanding → completed}/agentic_orchestrator_performance_improvements_spec.md +0 -0
  834. /package/spec-files/{outstanding → completed}/agentic_orchestrator_persistent_worker_runtime_spec.md +0 -0
  835. /package/spec-files/{outstanding → completed}/agentic_orchestrator_provider_auth_bootstrap_spec.md +0 -0
  836. /package/spec-files/{outstanding → completed}/agentic_orchestrator_real_worker_provider_execution_spec.md +0 -0
@@ -4,11 +4,18 @@ import path from 'node:path';
4
4
  import { describe, expect, it, vi } from 'vitest';
5
5
  import { ERROR_CODES } from '../src/core/error-codes.js';
6
6
  import { ApiWorkerProvider } from '../src/providers/api-worker-provider.js';
7
- import { CliWorkerProvider } from '../src/providers/cli-worker-provider.js';
8
- import type { ProviderCommandResult, ProviderSelection } from '../src/providers/providers.js';
7
+ import {
8
+ CliWorkerProvider,
9
+ cliWorkerProviderTestExports,
10
+ } from '../src/providers/cli-worker-provider.js';
11
+ import type {
12
+ ProviderCommandResult,
13
+ ProviderCommandRunner,
14
+ ProviderSelection,
15
+ } from '../src/providers/providers.js';
9
16
  import type { ProviderOutputParser } from '../src/providers/output-parsers/types.js';
10
17
  import type { WorkerOutputEnvelope } from '../src/providers/output-parsers/types.js';
11
- import { GenericCliOutputParser } from '../src/providers/output-parsers/generic-output-parser.js';
18
+ import { GenericCliOutputParser } from '../src/providers/output-parsers/index.js';
12
19
 
13
20
  const parserContext = {
14
21
  sessionId: 'session-1',
@@ -32,6 +39,584 @@ function makeSelection(
32
39
  };
33
40
  }
34
41
 
42
+ describe('cliWorkerProviderTestExports', () => {
43
+ it('GIVEN_non_record_context_WHEN_projectInteractiveContext_THEN_returns_null', () => {
44
+ expect(cliWorkerProviderTestExports.projectInteractiveContext(null)).toBeNull();
45
+ });
46
+
47
+ it('GIVEN_mixed_context_shapes_WHEN_projectInteractiveContext_THEN_normalizes_fallback_fields', () => {
48
+ const projected = cliWorkerProviderTestExports.projectInteractiveContext({
49
+ state: {
50
+ front_matter: {
51
+ status: 'intake',
52
+ status_reason: 'awaiting_input',
53
+ gate_profile: 'fast',
54
+ gates: 'invalid-shape',
55
+ },
56
+ },
57
+ plan: {
58
+ summary: 'Ship the telemetry dashboard.',
59
+ allowed_areas: 'packages/web-dashboard/src',
60
+ forbidden_areas: ['apps/control-plane/src/providers'],
61
+ files: 'invalid-shape',
62
+ acceptance_criteria: 'missing-array',
63
+ },
64
+ latest_evidence: {
65
+ overall: 'pass',
66
+ mode: 'fast',
67
+ status_reason: 'clean',
68
+ failed_steps: 'invalid-shape',
69
+ },
70
+ qa_test_index: {
71
+ pending: ['qa-001'],
72
+ },
73
+ intake: {
74
+ summary: 'invalid-shape',
75
+ bootstrap_manifest: 'invalid-shape',
76
+ verified_manifest: { manifest_version: '2.0.0' },
77
+ review: {
78
+ status: 'awaiting_input',
79
+ questions_open: 1,
80
+ questions_resolved: 0,
81
+ ambiguities: [
82
+ {
83
+ id: 'AMB-001',
84
+ summary: 'Derived USD remains optional.',
85
+ status: 'open',
86
+ obligation_ids: 'invalid-shape',
87
+ },
88
+ {
89
+ id: 'AMB-002',
90
+ summary: 'Closed ambiguity should be filtered out.',
91
+ status: 'resolved',
92
+ obligation_ids: ['OBL-001'],
93
+ },
94
+ null,
95
+ ],
96
+ clarification_answers: [
97
+ {
98
+ question_id: 'question-1',
99
+ ambiguity_ids: 'invalid-shape',
100
+ answer: 'Use tokens as the primary metric.',
101
+ answered_at: '2026-03-18T18:07:02.505Z',
102
+ resolution_status: 'candidate',
103
+ },
104
+ null,
105
+ ],
106
+ },
107
+ },
108
+ });
109
+
110
+ expect(projected).toEqual({
111
+ state: {
112
+ status: 'intake',
113
+ status_reason: 'awaiting_input',
114
+ gate_profile: 'fast',
115
+ gates: null,
116
+ },
117
+ accepted_plan: {
118
+ summary: 'Ship the telemetry dashboard.',
119
+ allowed_areas: [],
120
+ forbidden_areas: ['apps/control-plane/src/providers'],
121
+ files: null,
122
+ acceptance_criteria: [],
123
+ },
124
+ latest_evidence: {
125
+ overall: 'pass',
126
+ mode: 'fast',
127
+ status_reason: 'clean',
128
+ failed_steps: [],
129
+ },
130
+ qa_test_index: {
131
+ pending: ['qa-001'],
132
+ },
133
+ intake: {
134
+ summary: null,
135
+ bootstrap_manifest_version: null,
136
+ verified_manifest_version: '2.0.0',
137
+ review: {
138
+ status: 'awaiting_input',
139
+ questions_open: 1,
140
+ questions_resolved: 0,
141
+ open_ambiguities: [
142
+ {
143
+ id: 'AMB-001',
144
+ summary: 'Derived USD remains optional.',
145
+ obligation_ids: [],
146
+ },
147
+ ],
148
+ clarification_answers: [
149
+ {
150
+ question_id: 'question-1',
151
+ ambiguity_ids: [],
152
+ answer: 'Use tokens as the primary metric.',
153
+ answered_at: '2026-03-18T18:07:02.505Z',
154
+ resolution_status: 'candidate',
155
+ },
156
+ ],
157
+ },
158
+ },
159
+ });
160
+
161
+ expect(
162
+ cliWorkerProviderTestExports.projectInteractiveContext({
163
+ intake: {
164
+ summary: {},
165
+ },
166
+ }),
167
+ ).toEqual({
168
+ intake: {
169
+ summary: {},
170
+ bootstrap_manifest_version: null,
171
+ verified_manifest_version: null,
172
+ review: null,
173
+ },
174
+ });
175
+ });
176
+
177
+ it('GIVEN_nullish_projection_fields_WHEN_projectInteractiveContext_THEN_preserves_record_shapes_with_null_defaults', () => {
178
+ expect(
179
+ cliWorkerProviderTestExports.projectInteractiveContext({
180
+ state: {
181
+ front_matter: {
182
+ gates: {},
183
+ },
184
+ },
185
+ plan: {
186
+ acceptance_criteria: [],
187
+ },
188
+ latest_evidence: {
189
+ failed_steps: [],
190
+ },
191
+ intake: {
192
+ bootstrap_manifest: {},
193
+ verified_manifest: {},
194
+ review: {
195
+ ambiguities: [
196
+ {
197
+ status: 'open',
198
+ obligation_ids: ['OBL-777'],
199
+ },
200
+ ],
201
+ clarification_answers: [
202
+ {
203
+ ambiguity_ids: ['AMB-777'],
204
+ },
205
+ ],
206
+ },
207
+ },
208
+ }),
209
+ ).toEqual({
210
+ state: {
211
+ status: null,
212
+ status_reason: null,
213
+ gate_profile: null,
214
+ gates: {},
215
+ },
216
+ accepted_plan: {
217
+ summary: null,
218
+ allowed_areas: [],
219
+ forbidden_areas: [],
220
+ files: null,
221
+ acceptance_criteria: [],
222
+ },
223
+ latest_evidence: {
224
+ overall: null,
225
+ mode: null,
226
+ status_reason: null,
227
+ failed_steps: [],
228
+ },
229
+ intake: {
230
+ summary: null,
231
+ bootstrap_manifest_version: null,
232
+ verified_manifest_version: null,
233
+ review: {
234
+ status: null,
235
+ questions_open: null,
236
+ questions_resolved: null,
237
+ open_ambiguities: [
238
+ {
239
+ id: null,
240
+ summary: null,
241
+ obligation_ids: ['OBL-777'],
242
+ },
243
+ ],
244
+ clarification_answers: [
245
+ {
246
+ question_id: null,
247
+ ambiguity_ids: ['AMB-777'],
248
+ answer: null,
249
+ answered_at: null,
250
+ resolution_status: null,
251
+ },
252
+ ],
253
+ },
254
+ },
255
+ });
256
+ });
257
+
258
+ it('GIVEN_intake_state_without_explicit_phase_WHEN_buildInteractiveWorkerPrompt_THEN_infers_intake_contract_and_omits_optional_sections', () => {
259
+ const prompt = cliWorkerProviderTestExports.buildInteractiveWorkerPrompt(
260
+ {
261
+ role: 'planner',
262
+ feature_id: 'feature-intake',
263
+ repo_root: '/tmp/repo',
264
+ context_bundle: {
265
+ state: {
266
+ front_matter: {
267
+ status: 'intake',
268
+ },
269
+ },
270
+ },
271
+ instructions: 42,
272
+ last_tool_results: 'invalid-shape',
273
+ working_directory: 99,
274
+ },
275
+ null,
276
+ );
277
+
278
+ expect(prompt).toContain('Feature: feature-intake');
279
+ expect(prompt).toContain('Intake artifacts are on disk');
280
+ expect(prompt).toContain('Update the intake artifacts directly in the working directory.');
281
+ expect(prompt).not.toContain('Working directory (edit files here):');
282
+ expect(prompt).not.toContain('Instructions:');
283
+ expect(prompt).not.toContain('Recent tool results:');
284
+ });
285
+
286
+ it('GIVEN_non_string_prompt_fields_WHEN_buildInteractiveWorkerPrompt_THEN_uses_safe_empty_fallbacks', () => {
287
+ const prompt = cliWorkerProviderTestExports.buildInteractiveWorkerPrompt(
288
+ {
289
+ role: 7,
290
+ feature_id: 11,
291
+ repo_root: '/tmp/repo',
292
+ context_bundle: {},
293
+ },
294
+ null,
295
+ );
296
+
297
+ expect(prompt).toContain('You are the agent running in interactive mode.');
298
+ expect(prompt).toContain('Feature: ');
299
+ expect(prompt).toContain('Edit files directly in the conflicted worktree.');
300
+ expect(prompt).not.toContain('Planning artifacts are on disk');
301
+ expect(prompt).not.toContain('Implementation artifacts are on disk');
302
+ expect(prompt).not.toContain('QA artifacts are on disk');
303
+ expect(prompt).not.toContain('Reconciliation artifacts are on disk');
304
+ });
305
+
306
+ it('GIVEN_builder_role_without_workingDir_WHEN_buildInteractiveWorkerPrompt_THEN_omits_staged_artifacts_section', () => {
307
+ const prompt = cliWorkerProviderTestExports.buildInteractiveWorkerPrompt(
308
+ {
309
+ role: 'builder',
310
+ feature_id: 'feat-no-worktree',
311
+ repo_root: '/tmp/repo',
312
+ context_bundle: {},
313
+ // working_directory intentionally omitted so workingDir is null
314
+ },
315
+ null,
316
+ );
317
+
318
+ expect(prompt).toContain('You are the builder agent running in interactive mode.');
319
+ expect(prompt).toContain('Feature: feat-no-worktree');
320
+ expect(prompt).not.toContain('Staged artifacts');
321
+ expect(prompt).toContain('Runtime artifacts:');
322
+ expect(prompt).toContain('/tmp/repo/.aop/features/feat-no-worktree/state.md');
323
+ });
324
+
325
+ it('GIVEN_template_helpers_WHEN_resolving_commands_THEN_apply_defaults_placeholders_env_and_capabilities', () => {
326
+ const selection = makeSelection('custom', {
327
+ provider_config_env: 'WORKER_TOKEN',
328
+ provider_config_ref: 'secret-token',
329
+ agent_config: {
330
+ command: ' worker-cli ',
331
+ args: ['run', '{prompt}'],
332
+ run_resume: {
333
+ command: 'resume-cli',
334
+ args: ['resume', '{session_id}'],
335
+ },
336
+ attach_command: 'attach-cli',
337
+ attach_args: ['attach', '{session_id}'],
338
+ send: {
339
+ command: 'send-cli',
340
+ args: ['send', '{message}'],
341
+ },
342
+ },
343
+ cli_extra_args: ['--json'],
344
+ });
345
+
346
+ expect(cliWorkerProviderTestExports.resolveRunTemplate(selection, false)).toEqual({
347
+ command: 'worker-cli',
348
+ args: ['run', '{prompt}', '--json'],
349
+ });
350
+ expect(cliWorkerProviderTestExports.resolveRunTemplate(selection, true)).toEqual({
351
+ command: 'resume-cli',
352
+ args: ['resume', '{session_id}', '--json'],
353
+ });
354
+ expect(cliWorkerProviderTestExports.resolveAttachTemplate(selection)).toEqual({
355
+ command: 'attach-cli',
356
+ args: ['attach', '{session_id}', '--json'],
357
+ });
358
+ expect(cliWorkerProviderTestExports.resolveSendTemplate(selection)).toEqual({
359
+ command: 'send-cli',
360
+ args: ['send', '{message}', '--json'],
361
+ });
362
+ expect(
363
+ cliWorkerProviderTestExports.resolveRunTemplate(
364
+ makeSelection('custom', {
365
+ agent_config: {
366
+ run: {
367
+ command: ' ',
368
+ args: ['ok', 1],
369
+ },
370
+ } as unknown as Record<string, unknown>,
371
+ }),
372
+ false,
373
+ ),
374
+ ).toBeUndefined();
375
+
376
+ expect(
377
+ cliWorkerProviderTestExports.applyTemplateArgs(
378
+ [
379
+ '{session_id}',
380
+ '{prompt}',
381
+ '{feature_id}',
382
+ '{role}',
383
+ '{message}',
384
+ '{output_last_message_path}',
385
+ '{model}',
386
+ 'literal',
387
+ ],
388
+ {
389
+ sessionId: 'session-a',
390
+ prompt: 'do work',
391
+ featureId: 'feature-a',
392
+ role: 'planner',
393
+ message: undefined,
394
+ outputLastMessagePath: undefined,
395
+ model: undefined,
396
+ },
397
+ ),
398
+ ).toEqual(['session-a', 'do work', 'feature-a', 'planner', '', '', '', 'literal']);
399
+
400
+ const codexEnv = cliWorkerProviderTestExports.buildRunCommandEnv(selection);
401
+ expect(codexEnv['WORKER_TOKEN']).toBe('secret-token');
402
+
403
+ const originalClaudeCode = process.env['CLAUDECODE'];
404
+ const originalEntrypoint = process.env['CLAUDE_CODE_ENTRYPOINT'];
405
+ process.env['CLAUDECODE'] = 'nested-session';
406
+ process.env['CLAUDE_CODE_ENTRYPOINT'] = 'entry';
407
+ try {
408
+ const claudeEnv = cliWorkerProviderTestExports.buildRunCommandEnv(
409
+ makeSelection('claude', {
410
+ provider_config_env: 'WORKER_TOKEN',
411
+ provider_config_ref: 'secret-token',
412
+ }),
413
+ );
414
+ expect(claudeEnv['WORKER_TOKEN']).toBe('secret-token');
415
+ expect(claudeEnv).not.toHaveProperty('CLAUDECODE');
416
+ expect(claudeEnv).not.toHaveProperty('CLAUDE_CODE_ENTRYPOINT');
417
+ } finally {
418
+ if (originalClaudeCode === undefined) {
419
+ delete process.env['CLAUDECODE'];
420
+ } else {
421
+ process.env['CLAUDECODE'] = originalClaudeCode;
422
+ }
423
+ if (originalEntrypoint === undefined) {
424
+ delete process.env['CLAUDE_CODE_ENTRYPOINT'];
425
+ } else {
426
+ process.env['CLAUDE_CODE_ENTRYPOINT'] = originalEntrypoint;
427
+ }
428
+ }
429
+
430
+ expect(
431
+ cliWorkerProviderTestExports.resolveCliWorkerCapabilities(makeSelection('claude')),
432
+ ).toEqual({
433
+ supportsIdleDetection: false,
434
+ supportsSpawnDetection: true,
435
+ });
436
+ expect(
437
+ cliWorkerProviderTestExports.resolveCliWorkerCapabilities(makeSelection('codex')),
438
+ ).toEqual({
439
+ supportsIdleDetection: true,
440
+ supportsSpawnDetection: true,
441
+ });
442
+ expect(cliWorkerProviderTestExports.sanitizeClaudeProjectKey('/tmp/work tree')).toBe(
443
+ '-tmp-work-tree',
444
+ );
445
+ expect(
446
+ cliWorkerProviderTestExports.resolveClaudeTranscriptPath('/tmp/work tree', 'session-a'),
447
+ ).toContain(path.join('.claude', 'projects', '-tmp-work-tree', 'session-a.jsonl'));
448
+ });
449
+
450
+ it('GIVEN_claude_defaults_WHEN_resolving_attach_and_send_templates_THEN_both_resume_the_target_session', () => {
451
+ const selection = makeSelection('claude');
452
+
453
+ expect(cliWorkerProviderTestExports.resolveAttachTemplate(selection)).toEqual({
454
+ command: 'claude',
455
+ args: ['--dangerously-skip-permissions', '--resume', '{session_id}'],
456
+ });
457
+ expect(cliWorkerProviderTestExports.resolveSendTemplate(selection)).toEqual({
458
+ command: 'claude',
459
+ args: [
460
+ '--dangerously-skip-permissions',
461
+ '--resume',
462
+ '{session_id}',
463
+ '--print',
464
+ '--output-format',
465
+ 'text',
466
+ '{message}',
467
+ ],
468
+ });
469
+ });
470
+
471
+ it('GIVEN_transcript_helper_inputs_WHEN_rendering_THEN_covers_progress_tool_and_user_fallback_paths', () => {
472
+ expect(cliWorkerProviderTestExports.collectClaudeTranscriptTextBlocks(null)).toEqual([]);
473
+ expect(
474
+ cliWorkerProviderTestExports.collectClaudeTranscriptTextBlocks([
475
+ { type: 'image', text: 'ignored' },
476
+ { type: 'text', text: 'first block' },
477
+ { type: 'text', text: ' ' },
478
+ ]),
479
+ ).toEqual(['first block']);
480
+
481
+ expect(
482
+ cliWorkerProviderTestExports.summarizeClaudeToolInput({ description: 'describe step' }),
483
+ ).toBe('describe step');
484
+ expect(cliWorkerProviderTestExports.summarizeClaudeToolInput({ command: 'npm test' })).toBe(
485
+ 'npm test',
486
+ );
487
+ expect(
488
+ cliWorkerProviderTestExports.summarizeClaudeToolInput({ file_path: '/tmp/spec.md' }),
489
+ ).toBe('/tmp/spec.md');
490
+ expect(
491
+ cliWorkerProviderTestExports.summarizeClaudeToolInput({
492
+ pattern: 'needle',
493
+ path: '/tmp/repo',
494
+ }),
495
+ ).toBe('needle @ /tmp/repo');
496
+ expect(cliWorkerProviderTestExports.summarizeClaudeToolInput({ pattern: 'needle-only' })).toBe(
497
+ 'needle-only',
498
+ );
499
+ expect(
500
+ cliWorkerProviderTestExports.summarizeClaudeToolInput({ specs: ['one', 'two', 3] }),
501
+ ).toBe('one, two');
502
+ expect(cliWorkerProviderTestExports.summarizeClaudeToolInput({})).toBeNull();
503
+
504
+ expect(cliWorkerProviderTestExports.renderClaudeTranscriptEntry({})).toEqual([]);
505
+
506
+ const rendered = cliWorkerProviderTestExports.renderClaudeTranscript(
507
+ [
508
+ '',
509
+ 'not-json',
510
+ JSON.stringify({ type: 'progress', data: { type: 'token_stream' } }),
511
+ JSON.stringify({ type: 'assistant' }),
512
+ JSON.stringify({
513
+ type: 'assistant',
514
+ message: {
515
+ content: [
516
+ null,
517
+ { type: 'text', text: ' ' },
518
+ { type: 'tool_use', input: {} },
519
+ { type: 'tool_use', name: 'Describe', input: { description: 'describe step' } },
520
+ { type: 'tool_use', name: 'Shell', input: { command: 'npm test' } },
521
+ { type: 'tool_use', name: 'Read', input: { file_path: '/tmp/spec.md' } },
522
+ {
523
+ type: 'tool_use',
524
+ name: 'Search',
525
+ input: { pattern: 'needle', path: '/tmp/repo' },
526
+ },
527
+ { type: 'tool_use', name: 'Pattern', input: { pattern: 'needle-only' } },
528
+ { type: 'tool_use', name: 'Batch', input: { specs: ['one', 'two', 3] } },
529
+ ],
530
+ },
531
+ }),
532
+ JSON.stringify({
533
+ type: 'user',
534
+ message: {
535
+ content: [{ type: 'tool_result', content: [{ type: 'text', text: 'first block' }] }],
536
+ },
537
+ }),
538
+ JSON.stringify({
539
+ type: 'user',
540
+ message: {
541
+ content: [{ type: 'tool_result', content: 'plain result' }],
542
+ },
543
+ }),
544
+ JSON.stringify({
545
+ type: 'user',
546
+ message: {
547
+ content: [{ type: 'tool_result', content: { ignored: true } }],
548
+ },
549
+ toolUseResult: {
550
+ stdout: 'stdout fallback',
551
+ stderr: 'stderr fallback',
552
+ },
553
+ }),
554
+ ].join('\n'),
555
+ );
556
+
557
+ expect(rendered).toContain('[progress] token_stream');
558
+ expect(rendered).toContain('[tool] tool');
559
+ expect(rendered).toContain('[tool] Describe: describe step');
560
+ expect(rendered).toContain('[tool] Shell: npm test');
561
+ expect(rendered).toContain('[tool] Read: /tmp/spec.md');
562
+ expect(rendered).toContain('[tool] Search: needle @ /tmp/repo');
563
+ expect(rendered).toContain('[tool] Pattern: needle-only');
564
+ expect(rendered).toContain('[tool] Batch: one, two');
565
+ expect(rendered).toContain('first block');
566
+ expect(rendered).toContain('plain result');
567
+ expect(rendered).toContain('stdout fallback');
568
+ expect(rendered).toContain('stderr fallback');
569
+ });
570
+
571
+ it('GIVEN_watchdog_failure_shapes_WHEN_classifying_provider_errors_THEN_returns_specific_error_codes', () => {
572
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
573
+ outputParser: new GenericCliOutputParser(),
574
+ workerResponseTimeoutMs: 1000,
575
+ }) as unknown as {
576
+ classifyRunFailureErrorCode: (
577
+ result: ProviderCommandResult,
578
+ watchdogOutcome: {
579
+ reason: string;
580
+ timedOut: boolean;
581
+ idleForMs: number | null;
582
+ elapsedMs: number;
583
+ },
584
+ ) => string;
585
+ };
586
+
587
+ expect(
588
+ provider.classifyRunFailureErrorCode(
589
+ { exitCode: 1, signal: null },
590
+ { reason: 'spawn_timeout', timedOut: true, idleForMs: null, elapsedMs: 10 },
591
+ ),
592
+ ).toBe(ERROR_CODES.PROVIDER_SPAWN_TIMEOUT);
593
+ expect(
594
+ provider.classifyRunFailureErrorCode(
595
+ { exitCode: 1, signal: null },
596
+ { reason: 'idle_timeout', timedOut: true, idleForMs: 250, elapsedMs: 10 },
597
+ ),
598
+ ).toBe(ERROR_CODES.PROVIDER_STALL_TIMEOUT);
599
+ expect(
600
+ provider.classifyRunFailureErrorCode(
601
+ { exitCode: 1, signal: null },
602
+ { reason: 'hard_timeout', timedOut: true, idleForMs: null, elapsedMs: 10 },
603
+ ),
604
+ ).toBe(ERROR_CODES.PROVIDER_HARD_TIMEOUT);
605
+ expect(
606
+ provider.classifyRunFailureErrorCode(
607
+ { exitCode: 1, signal: 'SIGTERM' },
608
+ { reason: 'clean_exit', timedOut: false, idleForMs: null, elapsedMs: 10 },
609
+ ),
610
+ ).toBe(ERROR_CODES.PROVIDER_PROCESS_KILLED);
611
+ expect(
612
+ provider.classifyRunFailureErrorCode(
613
+ { exitCode: 1, signal: null },
614
+ { reason: 'clean_exit', timedOut: false, idleForMs: null, elapsedMs: 10 },
615
+ ),
616
+ ).toBe(ERROR_CODES.PROVIDER_RUNTIME_UNAVAILABLE);
617
+ });
618
+ });
619
+
35
620
  describe('GenericCliOutputParser', () => {
36
621
  it('GIVEN_empty_output_WHEN_parse_THEN_throws_provider_output_invalid', () => {
37
622
  const parser = new GenericCliOutputParser();
@@ -43,6 +628,26 @@ describe('GenericCliOutputParser', () => {
43
628
  }
44
629
  });
45
630
 
631
+ it('GIVEN_interactive_empty_output_WHEN_parse_THEN_returns_note_fallback', () => {
632
+ const parser = new GenericCliOutputParser();
633
+ const parsed = parser.parse(' ', {
634
+ ...parserContext,
635
+ executionMode: 'interactive',
636
+ });
637
+
638
+ expect(parsed.outputs).toEqual([
639
+ {
640
+ type: 'NOTE',
641
+ content: 'Interactive worker exited without a textual summary.',
642
+ },
643
+ ]);
644
+ expect(parsed.provider_meta).toEqual({
645
+ provider: parserContext.provider,
646
+ model: parserContext.model,
647
+ parse_fallback_reason: 'empty_output',
648
+ });
649
+ });
650
+
46
651
  it('GIVEN_invalid_json_WHEN_parse_THEN_throws_provider_output_invalid', () => {
47
652
  const parser = new GenericCliOutputParser();
48
653
  try {
@@ -53,6 +658,40 @@ describe('GenericCliOutputParser', () => {
53
658
  }
54
659
  });
55
660
 
661
+ it('GIVEN_prefixed_json_output_WHEN_parse_THEN_extracts_first_json_object', () => {
662
+ const parser = new GenericCliOutputParser();
663
+ const parsed = parser.parse(
664
+ [
665
+ 'Now I have enough context. Let me produce the full implementation in patches.',
666
+ '',
667
+ '{"outputs":[{"type":"NOTE","content":"done"}]}',
668
+ ].join('\n'),
669
+ parserContext,
670
+ );
671
+
672
+ expect(parsed.outputs).toEqual([{ type: 'NOTE', content: 'done' }]);
673
+ });
674
+
675
+ it('GIVEN_interactive_plain_text_output_WHEN_parse_THEN_returns_note_fallback', () => {
676
+ const parser = new GenericCliOutputParser();
677
+ const parsed = parser.parse('Completed edits directly in worktree', {
678
+ ...parserContext,
679
+ executionMode: 'interactive',
680
+ });
681
+
682
+ expect(parsed.outputs).toEqual([
683
+ {
684
+ type: 'NOTE',
685
+ content: 'Completed edits directly in worktree',
686
+ },
687
+ ]);
688
+ expect(parsed.provider_meta).toEqual({
689
+ provider: parserContext.provider,
690
+ model: parserContext.model,
691
+ parse_fallback_reason: 'invalid_json',
692
+ });
693
+ });
694
+
56
695
  it('GIVEN_object_without_outputs_or_type_WHEN_parse_THEN_throws_provider_output_invalid', () => {
57
696
  const parser = new GenericCliOutputParser();
58
697
  try {
@@ -204,6 +843,98 @@ describe('ApiWorkerProvider', () => {
204
843
  expect(await provider.reattachSession('session-2')).toMatchObject({ session_id: 'session-2' });
205
844
  expect(await provider.closeSession('session-2')).toEqual({ closed: true });
206
845
  });
846
+
847
+ it('GIVEN_runtime_selection_with_different_provider_WHEN_runWorker_THEN_throws_invalid_argument', async () => {
848
+ const provider = new ApiWorkerProvider(makeSelection('gemini'), {
849
+ outputParser: new GenericCliOutputParser(),
850
+ workerResponseTimeoutMs: 1000,
851
+ });
852
+
853
+ try {
854
+ await provider.runWorker({
855
+ role: 'builder',
856
+ feature_id: 'feature-a',
857
+ runtime_selection: {
858
+ provider: 'codex',
859
+ model: 'codex-model',
860
+ provider_config_env: null,
861
+ provider_config_ref: null,
862
+ agent_config: null,
863
+ },
864
+ });
865
+ throw new Error('expected provider mismatch to throw');
866
+ } catch (error) {
867
+ expect(error).toMatchObject({
868
+ code: ERROR_CODES.INVALID_ARGUMENT,
869
+ details: {
870
+ provider: 'gemini',
871
+ runtime_provider: 'codex',
872
+ },
873
+ });
874
+ }
875
+ });
876
+
877
+ it('GIVEN_runtime_selection_override_WHEN_runWorker_THEN_uses_override_model_and_falls_back_to_default_mock_config', async () => {
878
+ const parse = vi.fn(
879
+ (): WorkerOutputEnvelope => ({
880
+ session_id: 'builder-feature-runtime-api',
881
+ role: 'builder',
882
+ feature_id: 'feature-runtime',
883
+ outputs: [{ type: 'NOTE', content: 'runtime' }],
884
+ provider_meta: { provider: 'gemini', model: 'runtime-model' },
885
+ }),
886
+ );
887
+ const provider = new ApiWorkerProvider(
888
+ makeSelection('gemini', {
889
+ provider_config_env: 'GEMINI_TOKEN',
890
+ provider_config_ref: 'default-token',
891
+ agent_config: {
892
+ mock_response: {
893
+ outputs: [{ type: 'NOTE', content: 'runtime' }],
894
+ },
895
+ },
896
+ }),
897
+ {
898
+ outputParser: { parse },
899
+ workerResponseTimeoutMs: 1000,
900
+ workerSpawnTimeoutMs: 0,
901
+ workerIdleTimeoutMs: -1,
902
+ workerWatchdogPollIntervalMs: Number.NaN,
903
+ workerKillGracePeriodMs: 0,
904
+ },
905
+ );
906
+
907
+ await provider.runWorker({
908
+ role: 'builder',
909
+ feature_id: 'feature-runtime',
910
+ execution_mode: 'interactive',
911
+ runtime_selection: {
912
+ provider: 'gemini',
913
+ model: 'runtime-model',
914
+ provider_config_env: null,
915
+ provider_config_ref: 'runtime-token',
916
+ agent_config: 'not-an-object' as never,
917
+ },
918
+ });
919
+
920
+ expect(parse).toHaveBeenCalledWith(
921
+ JSON.stringify({
922
+ outputs: [{ type: 'NOTE', content: 'runtime' }],
923
+ }),
924
+ expect.objectContaining({
925
+ provider: 'gemini',
926
+ model: 'runtime-model',
927
+ executionMode: 'interactive',
928
+ }),
929
+ );
930
+ expect(provider.getCapabilities()).toEqual({
931
+ supportsInteractiveMode: false,
932
+ supportsWorkingDirectory: false,
933
+ supportsPauseResume: false,
934
+ supportsMessagePassing: false,
935
+ supportsAcknowledgment: false,
936
+ });
937
+ });
207
938
  });
208
939
 
209
940
  describe('CliWorkerProvider', () => {
@@ -278,7 +1009,14 @@ describe('CliWorkerProvider', () => {
278
1009
  const firstCall = run.mock.calls.at(0);
279
1010
  expect(firstCall).toBeDefined();
280
1011
  expect(firstCall?.[0]).toBe('codex');
281
- expect(firstCall?.[1]).toEqual(['exec', '--dangerously-bypass-approvals-and-sandbox']);
1012
+ expect(firstCall?.[1]).toEqual([
1013
+ 'exec',
1014
+ '--dangerously-bypass-approvals-and-sandbox',
1015
+ '--output-last-message',
1016
+ expect.stringMatching(/last-message\.txt$/),
1017
+ '--disable',
1018
+ 'apps',
1019
+ ]);
282
1020
  const options = firstCall?.[2] as
283
1021
  | { stdin?: string; timeoutMs?: number; env?: NodeJS.ProcessEnv }
284
1022
  | undefined;
@@ -288,6 +1026,59 @@ describe('CliWorkerProvider', () => {
288
1026
  expect(parse).toHaveBeenCalledTimes(1);
289
1027
  });
290
1028
 
1029
+ it('GIVEN_codex_output_last_message_file_WHEN_stdout_is_empty_THEN_parses_captured_last_message', async () => {
1030
+ const parse = vi.fn(
1031
+ (): WorkerOutputEnvelope => ({
1032
+ session_id: 'builder-feature-last-message',
1033
+ role: 'builder',
1034
+ feature_id: 'feature-last-message',
1035
+ outputs: [{ type: 'NOTE', content: 'captured from file' }],
1036
+ provider_meta: { provider: 'codex', model: 'codex-model' },
1037
+ }),
1038
+ );
1039
+ const run = vi.fn(async (_command: string, args: string[]): Promise<ProviderCommandResult> => {
1040
+ const outputFlagIndex = args.indexOf('--output-last-message');
1041
+ const outputPath =
1042
+ outputFlagIndex >= 0 && typeof args[outputFlagIndex + 1] === 'string'
1043
+ ? args[outputFlagIndex + 1]
1044
+ : null;
1045
+ expect(outputPath).toMatch(/last-message\.txt$/);
1046
+ if (outputPath) {
1047
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
1048
+ await fs.writeFile(outputPath, '{"type":"NOTE","content":"captured from file"}', 'utf8');
1049
+ }
1050
+ return {
1051
+ exitCode: 0,
1052
+ signal: null,
1053
+ stdout: '',
1054
+ };
1055
+ });
1056
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1057
+ outputParser: { parse },
1058
+ commandRunner: { run },
1059
+ workerResponseTimeoutMs: 2_000,
1060
+ });
1061
+
1062
+ await provider.createSession('builder', 'feature-last-message', 'prompt');
1063
+ const result = await provider.runWorker({
1064
+ role: 'builder',
1065
+ feature_id: 'feature-last-message',
1066
+ });
1067
+
1068
+ expect(result).toMatchObject({
1069
+ role: 'builder',
1070
+ feature_id: 'feature-last-message',
1071
+ });
1072
+ expect(parse).toHaveBeenCalledWith(
1073
+ '{"type":"NOTE","content":"captured from file"}',
1074
+ expect.objectContaining({
1075
+ role: 'builder',
1076
+ featureId: 'feature-last-message',
1077
+ provider: 'codex',
1078
+ }),
1079
+ );
1080
+ });
1081
+
291
1082
  it('GIVEN_working_directory_WHEN_runWorker_THEN_command_runner_receives_cwd', async () => {
292
1083
  const run = vi.fn(
293
1084
  async (
@@ -305,6 +1096,7 @@ describe('CliWorkerProvider', () => {
305
1096
  const provider = new CliWorkerProvider(makeSelection('codex'), {
306
1097
  outputParser: new GenericCliOutputParser(),
307
1098
  commandRunner: { run },
1099
+ repoRoot: '/tmp/repo',
308
1100
  workerResponseTimeoutMs: 2_000,
309
1101
  });
310
1102
 
@@ -316,11 +1108,17 @@ describe('CliWorkerProvider', () => {
316
1108
  working_directory: '/tmp/feature-a-worktree',
317
1109
  });
318
1110
 
319
- const options = run.mock.calls.at(0)?.[2] as { cwd?: string } | undefined;
1111
+ const options = run.mock.calls.at(0)?.[2] as { cwd?: string; stdin?: string } | undefined;
320
1112
  expect(options?.cwd).toBe('/tmp/feature-a-worktree');
1113
+ expect(options?.stdin).toContain('interactive mode');
1114
+ expect(options?.stdin).toContain('Edit files directly in the working directory');
1115
+ expect(options?.stdin).toContain('/tmp/feature-a-worktree');
1116
+ expect(options?.stdin).toContain('plan.md');
1117
+ expect(options?.stdin).toContain('decisions.md');
1118
+ expect(options?.stdin).not.toContain('Execution context summary:');
321
1119
  });
322
1120
 
323
- it('GIVEN_claude_selection_WHEN_runWorker_THEN_passes_prompt_via_stdin_only', async () => {
1121
+ it('GIVEN_interactive_builder_with_instructions_and_tool_results_WHEN_runWorker_THEN_prompt_includes_both_sections', async () => {
324
1122
  const run = vi.fn(
325
1123
  async (
326
1124
  _command: string,
@@ -334,32 +1132,41 @@ describe('CliWorkerProvider', () => {
334
1132
  }),
335
1133
  }),
336
1134
  );
337
-
338
- const provider = new CliWorkerProvider(makeSelection('claude'), {
1135
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
339
1136
  outputParser: new GenericCliOutputParser(),
340
1137
  commandRunner: { run },
341
- workerResponseTimeoutMs: 2222,
1138
+ repoRoot: '/tmp/repo',
1139
+ workerResponseTimeoutMs: 2_000,
342
1140
  });
343
1141
 
344
- await provider.createSession('planner', 'feature-a', 'prompt');
1142
+ await provider.createSession('builder', 'feature-b', null);
345
1143
  await provider.runWorker({
346
- role: 'planner',
347
- feature_id: 'feature-a',
348
- instructions: 'do work',
1144
+ role: 'builder',
1145
+ feature_id: 'feature-b',
1146
+ execution_mode: 'interactive',
1147
+ instructions: 'Apply the fix set.',
1148
+ context_bundle: {
1149
+ state: { front_matter: { status: 'building', gate_profile: 'default' } },
1150
+ plan: {
1151
+ summary: 'Ship the launch flow.',
1152
+ allowed_areas: ['packages/web-dashboard/src/'],
1153
+ files: { modify: ['packages/web-dashboard/src/app/page.tsx'] },
1154
+ },
1155
+ },
1156
+ last_tool_results: [{ tool: 'repo.status', ok: true }] as never,
1157
+ working_directory: '/tmp/feature-b-worktree',
349
1158
  });
350
1159
 
351
- expect(run).toHaveBeenCalledTimes(1);
352
- const firstCall = run.mock.calls.at(0);
353
- expect(firstCall?.[0]).toBe('claude');
354
- expect(firstCall?.[1]).toEqual(['--print', '--output-format', 'text']);
355
- const options = firstCall?.[2] as
356
- | { stdin?: string; timeoutMs?: number; env?: NodeJS.ProcessEnv }
357
- | undefined;
358
- expect(options?.timeoutMs).toBe(4722);
359
- expect(options?.stdin).toContain('"feature_id":"feature-a"');
1160
+ const options = run.mock.calls.at(0)?.[2] as { stdin?: string } | undefined;
1161
+ expect(options?.stdin).toContain('Instructions:');
1162
+ expect(options?.stdin).toContain('Apply the fix set.');
1163
+ expect(options?.stdin).toContain('Execution context summary:');
1164
+ expect(options?.stdin).toContain('"Ship the launch flow."');
1165
+ expect(options?.stdin).toContain('Recent tool results:');
1166
+ expect(options?.stdin).toContain('"tool":"repo.status"');
360
1167
  });
361
1168
 
362
- it('GIVEN_system_prompt_passed_to_createSession_WHEN_runWorker_THEN_system_prompt_prepended_to_stdin', async () => {
1169
+ it('GIVEN_interactive_qa_run_WHEN_runWorker_THEN_prompt_uses_qa_specific_execution_contract', async () => {
363
1170
  const run = vi.fn(
364
1171
  async (
365
1172
  _command: string,
@@ -368,7 +1175,473 @@ describe('CliWorkerProvider', () => {
368
1175
  ): Promise<ProviderCommandResult> => ({
369
1176
  exitCode: 0,
370
1177
  signal: null,
371
- stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
1178
+ stdout: JSON.stringify({
1179
+ outputs: [{ type: 'NOTE', content: 'qa complete' }],
1180
+ }),
1181
+ }),
1182
+ );
1183
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1184
+ outputParser: new GenericCliOutputParser(),
1185
+ commandRunner: { run },
1186
+ repoRoot: '/tmp/repo',
1187
+ workerResponseTimeoutMs: 2_000,
1188
+ });
1189
+
1190
+ await provider.createSession('qa', 'feature-qa', null);
1191
+ await provider.runWorker({
1192
+ role: 'qa',
1193
+ feature_id: 'feature-qa',
1194
+ execution_mode: 'interactive',
1195
+ working_directory: '/tmp/feature-qa-worktree',
1196
+ });
1197
+
1198
+ const options = run.mock.calls.at(0)?.[2] as { stdin?: string } | undefined;
1199
+ expect(options?.stdin).toContain('QA artifacts are on disk');
1200
+ expect(options?.stdin).toContain('qa_test_index.json');
1201
+ expect(options?.stdin).toContain(
1202
+ 'Use plan.json, qa_test_index.json, and the latest evidence as your verification contract.',
1203
+ );
1204
+ expect(options?.stdin).toContain('question_type":"risk_ack"');
1205
+ });
1206
+
1207
+ it('GIVEN_interactive_reconciler_run_WHEN_runWorker_THEN_prompt_uses_reconciler_specific_execution_contract', async () => {
1208
+ const run = vi.fn(
1209
+ async (
1210
+ _command: string,
1211
+ _args: string[],
1212
+ _options?: Record<string, unknown>,
1213
+ ): Promise<ProviderCommandResult> => ({
1214
+ exitCode: 0,
1215
+ signal: null,
1216
+ stdout: JSON.stringify({
1217
+ outputs: [{ type: 'NOTE', content: 'reconciled' }],
1218
+ }),
1219
+ }),
1220
+ );
1221
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1222
+ outputParser: new GenericCliOutputParser(),
1223
+ commandRunner: { run },
1224
+ repoRoot: '/tmp/repo',
1225
+ workerResponseTimeoutMs: 2_000,
1226
+ });
1227
+
1228
+ await provider.createSession('reconciler', 'feature-reconcile', null);
1229
+ await provider.runWorker({
1230
+ role: 'reconciler',
1231
+ feature_id: 'feature-reconcile',
1232
+ execution_mode: 'interactive',
1233
+ working_directory: '/tmp/feature-reconcile-worktree',
1234
+ });
1235
+
1236
+ const options = run.mock.calls.at(0)?.[2] as { stdin?: string } | undefined;
1237
+ expect(options?.stdin).toContain('Reconciliation artifacts are on disk');
1238
+ expect(options?.stdin).toContain(
1239
+ 'Use spec.md, state.md, plan.json, and approved collision context as your reconciliation contract.',
1240
+ );
1241
+ expect(options?.stdin).toContain(
1242
+ 'Return REQUEST.action=ask_user_input only when preserving mainline semantics and feature intent requires an operator choice.',
1243
+ );
1244
+ expect(options?.stdin).not.toContain(
1245
+ 'Use the accepted plan as your execution contract and keep changes inside planned paths.',
1246
+ );
1247
+ });
1248
+
1249
+ it('GIVEN_planner_interactive_run_WHEN_runWorker_THEN_prompt_uses_plan_artifact_pointers', async () => {
1250
+ const run = vi.fn(
1251
+ async (
1252
+ _command: string,
1253
+ _args: string[],
1254
+ _options?: Record<string, unknown>,
1255
+ ): Promise<ProviderCommandResult> => ({
1256
+ exitCode: 0,
1257
+ signal: null,
1258
+ stdout: JSON.stringify({
1259
+ outputs: [{ type: 'NOTE', content: 'planning complete' }],
1260
+ }),
1261
+ }),
1262
+ );
1263
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1264
+ outputParser: new GenericCliOutputParser(),
1265
+ commandRunner: { run },
1266
+ repoRoot: '/tmp/repo',
1267
+ workerResponseTimeoutMs: 2_000,
1268
+ });
1269
+
1270
+ await provider.createSession('planner', 'feature-a', 'You are the planner role.');
1271
+ await provider.runWorker({
1272
+ role: 'planner',
1273
+ feature_id: 'feature-a',
1274
+ execution_mode: 'interactive',
1275
+ working_directory: '/tmp/repo/.aop/features/feature-a',
1276
+ });
1277
+
1278
+ const options = run.mock.calls.at(0)?.[2] as { cwd?: string; stdin?: string } | undefined;
1279
+ expect(options?.cwd).toBe('/tmp/repo/.aop/features/feature-a');
1280
+ expect(options?.stdin).toContain('/tmp/repo/.aop/features/feature-a/plan.md');
1281
+ expect(options?.stdin).toContain('/tmp/repo/.aop/features/feature-a/plan.json');
1282
+ expect(options?.stdin).toContain('PLAN_SUBMISSION');
1283
+ expect(options?.stdin).toContain(
1284
+ 'Update the planning artifacts directly in the working directory.',
1285
+ );
1286
+ });
1287
+
1288
+ it('GIVEN_planner_interactive_run_without_repo_root_WHEN_runWorker_THEN_prompt_uses_alignment_fallback_text', async () => {
1289
+ const run = vi.fn(
1290
+ async (
1291
+ _command: string,
1292
+ _args: string[],
1293
+ _options?: Record<string, unknown>,
1294
+ ): Promise<ProviderCommandResult> => ({
1295
+ exitCode: 0,
1296
+ signal: null,
1297
+ stdout: JSON.stringify({
1298
+ outputs: [{ type: 'NOTE', content: 'planning complete' }],
1299
+ }),
1300
+ }),
1301
+ );
1302
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1303
+ outputParser: new GenericCliOutputParser(),
1304
+ commandRunner: { run },
1305
+ workerResponseTimeoutMs: 2_000,
1306
+ });
1307
+
1308
+ await provider.createSession('planner', 'feature-a', 'You are the planner role.');
1309
+ await provider.runWorker({
1310
+ role: 'planner',
1311
+ feature_id: 'feature-a',
1312
+ execution_mode: 'interactive',
1313
+ working_directory: '/tmp/repo/.aop/features/feature-a',
1314
+ });
1315
+
1316
+ const options = run.mock.calls.at(0)?.[2] as { stdin?: string } | undefined;
1317
+ expect(options?.stdin).toContain(
1318
+ 'Keep plan.md aligned with the planning state and any plan_json you return.',
1319
+ );
1320
+ expect(options?.stdin).not.toContain('/plan.md');
1321
+ });
1322
+
1323
+ it('GIVEN_planner_intake_interactive_run_WHEN_runWorker_THEN_prompt_uses_intake_submission_contract', async () => {
1324
+ const run = vi.fn(
1325
+ async (
1326
+ _command: string,
1327
+ _args: string[],
1328
+ _options?: Record<string, unknown>,
1329
+ ): Promise<ProviderCommandResult> => ({
1330
+ exitCode: 0,
1331
+ signal: null,
1332
+ stdout: JSON.stringify({
1333
+ outputs: [{ type: 'NOTE', content: 'intake observed' }],
1334
+ }),
1335
+ }),
1336
+ );
1337
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1338
+ outputParser: new GenericCliOutputParser(),
1339
+ commandRunner: { run },
1340
+ repoRoot: '/tmp/repo',
1341
+ workerResponseTimeoutMs: 2_000,
1342
+ });
1343
+
1344
+ await provider.createSession('planner', 'feature-intake', 'You are the intake planner.');
1345
+ await provider.runWorker({
1346
+ role: 'planner',
1347
+ feature_id: 'feature-intake',
1348
+ execution_mode: 'interactive',
1349
+ planner_phase: 'intake',
1350
+ working_directory: '/tmp/repo/.aop/features/feature-intake',
1351
+ context_bundle: {
1352
+ intake: {
1353
+ review: {
1354
+ status: 'awaiting_input',
1355
+ questions_open: 0,
1356
+ questions_resolved: 1,
1357
+ ambiguities: [
1358
+ {
1359
+ id: 'AMB-110',
1360
+ summary: 'Derived USD remains optional and secondary.',
1361
+ status: 'open',
1362
+ },
1363
+ ],
1364
+ clarification_answers: [
1365
+ {
1366
+ question_id: 'q_answered',
1367
+ ambiguity_ids: ['AMB-110'],
1368
+ answer: 'Derived USD is optional and secondary; token metrics are primary.',
1369
+ answered_at: '2026-03-18T18:07:02.505Z',
1370
+ resolution_status: 'candidate',
1371
+ },
1372
+ ],
1373
+ },
1374
+ },
1375
+ },
1376
+ });
1377
+
1378
+ const options = run.mock.calls.at(0)?.[2] as { stdin?: string } | undefined;
1379
+ expect(options?.stdin).toContain(
1380
+ 'questions.json — persisted intake clarification questions and answers',
1381
+ );
1382
+ expect(options?.stdin).toContain(
1383
+ 'Update the intake artifacts directly in the working directory.',
1384
+ );
1385
+ expect(options?.stdin).toContain('"type":"INTAKE_SUBMISSION"');
1386
+ expect(options?.stdin).toContain(
1387
+ 'Do not emit PLAN_SUBMISSION while operating in the intake planner phase.',
1388
+ );
1389
+ expect(options?.stdin).toContain('clarification_answers');
1390
+ expect(options?.stdin).toContain(
1391
+ 'Derived USD is optional and secondary; token metrics are primary.',
1392
+ );
1393
+ expect(options?.stdin).not.toContain(
1394
+ 'Update the planning artifacts directly in the working directory.',
1395
+ );
1396
+ });
1397
+
1398
+ it('GIVEN_claude_selection_WHEN_runWorker_THEN_passes_prompt_via_stdin_only', async () => {
1399
+ const run = vi.fn(
1400
+ async (
1401
+ _command: string,
1402
+ _args: string[],
1403
+ _options?: Record<string, unknown>,
1404
+ ): Promise<ProviderCommandResult> => ({
1405
+ exitCode: 0,
1406
+ signal: null,
1407
+ stdout: JSON.stringify({
1408
+ outputs: [{ type: 'NOTE', content: 'ok' }],
1409
+ }),
1410
+ }),
1411
+ );
1412
+
1413
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
1414
+ outputParser: new GenericCliOutputParser(),
1415
+ commandRunner: { run },
1416
+ workerResponseTimeoutMs: 2222,
1417
+ });
1418
+
1419
+ await provider.createSession('planner', 'feature-a', 'prompt');
1420
+ await provider.runWorker({
1421
+ role: 'planner',
1422
+ feature_id: 'feature-a',
1423
+ instructions: 'do work',
1424
+ });
1425
+
1426
+ expect(run).toHaveBeenCalledTimes(1);
1427
+ const firstCall = run.mock.calls.at(0);
1428
+ expect(firstCall?.[0]).toBe('claude');
1429
+ expect(firstCall?.[1]).toEqual([
1430
+ '--dangerously-skip-permissions',
1431
+ '--session-id',
1432
+ expect.any(String),
1433
+ '--print',
1434
+ '--output-format',
1435
+ 'text',
1436
+ ]);
1437
+ const options = firstCall?.[2] as
1438
+ | { stdin?: string; timeoutMs?: number; env?: NodeJS.ProcessEnv }
1439
+ | undefined;
1440
+ expect(options?.timeoutMs).toBe(4722);
1441
+ expect(options?.stdin).toContain('"feature_id":"feature-a"');
1442
+ });
1443
+
1444
+ it('GIVEN_claude_selection_without_precreated_session_WHEN_runWorker_THEN_allocates_uuid_session_id', async () => {
1445
+ const run = vi.fn(
1446
+ async (
1447
+ _command: string,
1448
+ _args: string[],
1449
+ _options?: Record<string, unknown>,
1450
+ ): Promise<ProviderCommandResult> => ({
1451
+ exitCode: 0,
1452
+ signal: null,
1453
+ stdout: JSON.stringify({
1454
+ outputs: [{ type: 'NOTE', content: 'ok' }],
1455
+ }),
1456
+ }),
1457
+ );
1458
+
1459
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
1460
+ outputParser: new GenericCliOutputParser(),
1461
+ commandRunner: { run },
1462
+ workerResponseTimeoutMs: 2222,
1463
+ });
1464
+
1465
+ await provider.runWorker({
1466
+ role: 'orchestrator',
1467
+ feature_id: 'global',
1468
+ instructions: 'do work',
1469
+ });
1470
+
1471
+ const firstCall = run.mock.calls.at(0);
1472
+ const sessionId = firstCall?.[1]?.[2];
1473
+ expect(typeof sessionId).toBe('string');
1474
+ expect(sessionId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
1475
+ const options = firstCall?.[2] as { stdin?: string } | undefined;
1476
+ expect(options?.stdin).toContain(`"session_id":"${sessionId}"`);
1477
+ });
1478
+
1479
+ it('GIVEN_claude_session_with_successful_prior_run_WHEN_runWorker_again_THEN_resumes_existing_session', async () => {
1480
+ const run = vi.fn(
1481
+ async (
1482
+ _command: string,
1483
+ _args: string[],
1484
+ _options?: Record<string, unknown>,
1485
+ ): Promise<ProviderCommandResult> => ({
1486
+ exitCode: 0,
1487
+ signal: null,
1488
+ stdout: JSON.stringify({
1489
+ outputs: [{ type: 'NOTE', content: 'ok' }],
1490
+ }),
1491
+ }),
1492
+ );
1493
+
1494
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
1495
+ outputParser: new GenericCliOutputParser(),
1496
+ commandRunner: { run },
1497
+ workerResponseTimeoutMs: 2222,
1498
+ });
1499
+
1500
+ const session = await provider.createSession('planner', 'feature-resume', 'prompt');
1501
+ await provider.runWorker({
1502
+ role: 'planner',
1503
+ feature_id: 'feature-resume',
1504
+ instructions: 'first pass',
1505
+ });
1506
+ await provider.runWorker({
1507
+ role: 'planner',
1508
+ feature_id: 'feature-resume',
1509
+ instructions: 'second pass',
1510
+ });
1511
+
1512
+ expect(run).toHaveBeenCalledTimes(2);
1513
+ expect(run.mock.calls.at(0)?.[1]).toEqual([
1514
+ '--dangerously-skip-permissions',
1515
+ '--session-id',
1516
+ session.session_id,
1517
+ '--print',
1518
+ '--output-format',
1519
+ 'text',
1520
+ ]);
1521
+ expect(run.mock.calls.at(1)?.[1]).toEqual([
1522
+ '--dangerously-skip-permissions',
1523
+ '--resume',
1524
+ session.session_id,
1525
+ '--print',
1526
+ '--output-format',
1527
+ 'text',
1528
+ ]);
1529
+ });
1530
+
1531
+ it('GIVEN_claude_session_with_persisted_transcript_WHEN_runWorker_THEN_resumes_existing_session', async () => {
1532
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-claude-resume-'));
1533
+ const claudeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-claude-home-'));
1534
+ const workingDirectory = path.join(repoRoot, '.aop', 'features', 'feature-claude-resume');
1535
+ await fs.mkdir(workingDirectory, { recursive: true });
1536
+
1537
+ const homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(claudeHome);
1538
+ const run = vi.fn(
1539
+ async (
1540
+ _command: string,
1541
+ _args: string[],
1542
+ _options?: Record<string, unknown>,
1543
+ ): Promise<ProviderCommandResult> => ({
1544
+ exitCode: 0,
1545
+ signal: null,
1546
+ stdout: JSON.stringify({
1547
+ outputs: [{ type: 'NOTE', content: 'ok' }],
1548
+ }),
1549
+ }),
1550
+ );
1551
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
1552
+ outputParser: new GenericCliOutputParser(),
1553
+ commandRunner: { run },
1554
+ workerResponseTimeoutMs: 2222,
1555
+ });
1556
+
1557
+ try {
1558
+ const session = await provider.createSession('planner', 'feature-claude-resume', 'prompt');
1559
+ const transcriptPath = path.join(
1560
+ claudeHome,
1561
+ '.claude',
1562
+ 'projects',
1563
+ workingDirectory.replace(/[^A-Za-z0-9]/g, '-'),
1564
+ `${session.session_id}.jsonl`,
1565
+ );
1566
+ await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
1567
+ await fs.writeFile(transcriptPath, '{"type":"assistant","message":{"content":[]}}\n', 'utf8');
1568
+
1569
+ await provider.runWorker({
1570
+ role: 'planner',
1571
+ feature_id: 'feature-claude-resume',
1572
+ instructions: 'resume persisted session',
1573
+ working_directory: workingDirectory,
1574
+ });
1575
+
1576
+ expect(run).toHaveBeenCalledTimes(1);
1577
+ expect(run.mock.calls.at(0)?.[1]).toEqual([
1578
+ '--dangerously-skip-permissions',
1579
+ '--resume',
1580
+ session.session_id,
1581
+ '--print',
1582
+ '--output-format',
1583
+ 'text',
1584
+ ]);
1585
+ } finally {
1586
+ homedirSpy.mockRestore();
1587
+ await fs.rm(repoRoot, { recursive: true, force: true });
1588
+ await fs.rm(claudeHome, { recursive: true, force: true });
1589
+ }
1590
+ });
1591
+
1592
+ it('GIVEN_claude_selection_WHEN_runWorker_THEN_CLAUDECODE_stripped_from_env', async () => {
1593
+ const run = vi.fn(
1594
+ async (
1595
+ _command: string,
1596
+ _args: string[],
1597
+ _options?: Record<string, unknown>,
1598
+ ): Promise<ProviderCommandResult> => ({
1599
+ exitCode: 0,
1600
+ signal: null,
1601
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
1602
+ }),
1603
+ );
1604
+
1605
+ const originalClaudeCode = process.env['CLAUDECODE'];
1606
+ const originalEntrypoint = process.env['CLAUDE_CODE_ENTRYPOINT'];
1607
+ process.env['CLAUDECODE'] = 'test-session-id';
1608
+ process.env['CLAUDE_CODE_ENTRYPOINT'] = 'cli';
1609
+ try {
1610
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
1611
+ outputParser: new GenericCliOutputParser(),
1612
+ commandRunner: { run },
1613
+ workerResponseTimeoutMs: 2000,
1614
+ });
1615
+ await provider.createSession('builder', 'feature-b', null);
1616
+ await provider.runWorker({ role: 'builder', feature_id: 'feature-b', instructions: 'work' });
1617
+ } finally {
1618
+ if (originalClaudeCode === undefined) {
1619
+ delete process.env['CLAUDECODE'];
1620
+ } else {
1621
+ process.env['CLAUDECODE'] = originalClaudeCode;
1622
+ }
1623
+ if (originalEntrypoint === undefined) {
1624
+ delete process.env['CLAUDE_CODE_ENTRYPOINT'];
1625
+ } else {
1626
+ process.env['CLAUDE_CODE_ENTRYPOINT'] = originalEntrypoint;
1627
+ }
1628
+ }
1629
+
1630
+ const callOptions = run.mock.calls.at(0)?.[2] as { env?: NodeJS.ProcessEnv } | undefined;
1631
+ expect(callOptions?.env).not.toHaveProperty('CLAUDECODE');
1632
+ expect(callOptions?.env).not.toHaveProperty('CLAUDE_CODE_ENTRYPOINT');
1633
+ });
1634
+
1635
+ it('GIVEN_system_prompt_passed_to_createSession_WHEN_runWorker_THEN_system_prompt_prepended_to_stdin', async () => {
1636
+ const run = vi.fn(
1637
+ async (
1638
+ _command: string,
1639
+ _args: string[],
1640
+ _options?: Record<string, unknown>,
1641
+ ): Promise<ProviderCommandResult> => ({
1642
+ exitCode: 0,
1643
+ signal: null,
1644
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
372
1645
  }),
373
1646
  );
374
1647
  const provider = new CliWorkerProvider(makeSelection('claude'), {
@@ -445,16 +1718,155 @@ describe('CliWorkerProvider', () => {
445
1718
  expect(stdin).toMatch(/^Return exactly one JSON object/);
446
1719
  });
447
1720
 
448
- it('GIVEN_raw_logs_enabled_WHEN_runWorker_succeeds_THEN_stdout_is_persisted', async () => {
449
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-raw-logs-'));
450
- const provider = new CliWorkerProvider(makeSelection('codex'), {
1721
+ it('GIVEN_raw_logs_enabled_WHEN_runWorker_succeeds_THEN_stdout_is_persisted', async () => {
1722
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-raw-logs-'));
1723
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1724
+ outputParser: new GenericCliOutputParser(),
1725
+ commandRunner: {
1726
+ run: vi.fn(async () => ({
1727
+ exitCode: 0,
1728
+ signal: null,
1729
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
1730
+ })),
1731
+ },
1732
+ repoRoot,
1733
+ rawAgentLogsEnabled: true,
1734
+ rawAgentLogsRetentionDays: 60,
1735
+ workerResponseTimeoutMs: 1000,
1736
+ });
1737
+
1738
+ try {
1739
+ await provider.runWorker({
1740
+ role: 'builder',
1741
+ feature_id: 'feature-raw',
1742
+ });
1743
+
1744
+ const logsDir = path.join(repoRoot, '.aop', 'features', 'feature-raw', 'logs');
1745
+ const files = await fs.readdir(logsDir);
1746
+ expect(files.some((file) => /^builder-\d{13}\.txt$/.test(file))).toBe(true);
1747
+ } finally {
1748
+ await fs.rm(repoRoot, { recursive: true, force: true });
1749
+ }
1750
+ });
1751
+
1752
+ it('GIVEN_raw_logs_enabled_WHEN_worker_emits_chunks_THEN_live_console_artifact_is_updated', async () => {
1753
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-live-console-'));
1754
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
1755
+ outputParser: new GenericCliOutputParser(),
1756
+ commandRunner: {
1757
+ run: vi.fn(async (_command, _args, options) => {
1758
+ const typedOptions = options as {
1759
+ onOutputChunk?: (chunk: { stream: 'stdout' | 'stderr'; text: string }) => void;
1760
+ };
1761
+ typedOptions.onOutputChunk?.({ stream: 'stdout', text: 'Thinking...\n' });
1762
+ typedOptions.onOutputChunk?.({ stream: 'stdout', text: 'Done.\n' });
1763
+ return {
1764
+ exitCode: 0,
1765
+ signal: null,
1766
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
1767
+ };
1768
+ }),
1769
+ },
1770
+ repoRoot,
1771
+ rawAgentLogsEnabled: true,
1772
+ rawAgentLogsRetentionDays: 60,
1773
+ workerResponseTimeoutMs: 1000,
1774
+ });
1775
+
1776
+ try {
1777
+ await provider.createSession('planner', 'feature-live', 'Prompt');
1778
+ await provider.runWorker({
1779
+ role: 'planner',
1780
+ feature_id: 'feature-live',
1781
+ });
1782
+
1783
+ const liveDir = path.join(repoRoot, '.aop', 'features', 'feature-live', 'logs', 'live');
1784
+ const [content, metadataRaw] = await Promise.all([
1785
+ fs.readFile(path.join(liveDir, 'planner.txt'), 'utf8'),
1786
+ fs.readFile(path.join(liveDir, 'planner.json'), 'utf8'),
1787
+ ]);
1788
+ const metadata = JSON.parse(metadataRaw) as { status?: string; provider?: string };
1789
+
1790
+ expect(content).toContain('Thinking...');
1791
+ expect(content).toContain('Done.');
1792
+ expect(metadata.status).toBe('completed');
1793
+ expect(metadata.provider).toBe('codex');
1794
+ } finally {
1795
+ await fs.rm(repoRoot, { recursive: true, force: true });
1796
+ }
1797
+ });
1798
+
1799
+ it('GIVEN_claude_raw_logs_enabled_WHEN_transcript_updates_THEN_live_console_mirrors_transcript', async () => {
1800
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-live-claude-'));
1801
+ const claudeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-claude-home-'));
1802
+ const workingDirectory = path.join(repoRoot, '.aop', 'features', 'feature-live-claude');
1803
+ await fs.mkdir(workingDirectory, { recursive: true });
1804
+
1805
+ const homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(claudeHome);
1806
+ let sessionId = '';
1807
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
451
1808
  outputParser: new GenericCliOutputParser(),
452
1809
  commandRunner: {
453
- run: vi.fn(async () => ({
454
- exitCode: 0,
455
- signal: null,
456
- stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
457
- })),
1810
+ run: vi.fn(async (_command, args) => {
1811
+ expect(args).toEqual(
1812
+ expect.arrayContaining([
1813
+ '--session-id',
1814
+ sessionId,
1815
+ '--print',
1816
+ '--output-format',
1817
+ 'text',
1818
+ ]),
1819
+ );
1820
+
1821
+ const transcriptPath = path.join(
1822
+ claudeHome,
1823
+ '.claude',
1824
+ 'projects',
1825
+ workingDirectory.replace(/[^A-Za-z0-9]/g, '-'),
1826
+ `${sessionId}.jsonl`,
1827
+ );
1828
+ await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
1829
+ await fs.writeFile(
1830
+ transcriptPath,
1831
+ [
1832
+ JSON.stringify({
1833
+ type: 'progress',
1834
+ data: {
1835
+ type: 'hook_progress',
1836
+ hookEvent: 'SessionStart',
1837
+ hookName: 'SessionStart:startup',
1838
+ },
1839
+ }),
1840
+ JSON.stringify({
1841
+ type: 'assistant',
1842
+ message: {
1843
+ content: [
1844
+ { type: 'tool_use', name: 'Read', input: { file_path: '/tmp/spec.md' } },
1845
+ ],
1846
+ },
1847
+ }),
1848
+ JSON.stringify({
1849
+ type: 'user',
1850
+ message: {
1851
+ content: [{ type: 'tool_result', content: 'spec content' }],
1852
+ },
1853
+ }),
1854
+ JSON.stringify({
1855
+ type: 'assistant',
1856
+ message: {
1857
+ content: [{ type: 'text', text: 'Done.' }],
1858
+ },
1859
+ }),
1860
+ ].join('\n'),
1861
+ 'utf8',
1862
+ );
1863
+ await new Promise((resolve) => setTimeout(resolve, 700));
1864
+ return {
1865
+ exitCode: 0,
1866
+ signal: null,
1867
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
1868
+ };
1869
+ }),
458
1870
  },
459
1871
  repoRoot,
460
1872
  rawAgentLogsEnabled: true,
@@ -463,16 +1875,40 @@ describe('CliWorkerProvider', () => {
463
1875
  });
464
1876
 
465
1877
  try {
1878
+ const session = await provider.createSession('planner', 'feature-live-claude', 'Prompt');
1879
+ sessionId = session.session_id;
1880
+
466
1881
  await provider.runWorker({
467
- role: 'builder',
468
- feature_id: 'feature-raw',
1882
+ role: 'planner',
1883
+ feature_id: 'feature-live-claude',
1884
+ working_directory: workingDirectory,
469
1885
  });
470
1886
 
471
- const logsDir = path.join(repoRoot, '.aop', 'features', 'feature-raw', 'logs');
472
- const files = await fs.readdir(logsDir);
473
- expect(files.some((file) => /^builder-\d{13}\.txt$/.test(file))).toBe(true);
1887
+ const liveDir = path.join(
1888
+ repoRoot,
1889
+ '.aop',
1890
+ 'features',
1891
+ 'feature-live-claude',
1892
+ 'logs',
1893
+ 'live',
1894
+ );
1895
+ const [content, metadataRaw] = await Promise.all([
1896
+ fs.readFile(path.join(liveDir, 'planner.txt'), 'utf8'),
1897
+ fs.readFile(path.join(liveDir, 'planner.json'), 'utf8'),
1898
+ ]);
1899
+ const metadata = JSON.parse(metadataRaw) as { status?: string; provider?: string };
1900
+
1901
+ expect(content).toContain('[progress] SessionStart');
1902
+ expect(content).toContain('[tool] Read: /tmp/spec.md');
1903
+ expect(content).toContain('spec content');
1904
+ expect(content).toContain('Done.');
1905
+ expect(content).not.toContain('"outputs"');
1906
+ expect(metadata.status).toBe('completed');
1907
+ expect(metadata.provider).toBe('claude');
474
1908
  } finally {
1909
+ homedirSpy.mockRestore();
475
1910
  await fs.rm(repoRoot, { recursive: true, force: true });
1911
+ await fs.rm(claudeHome, { recursive: true, force: true });
476
1912
  }
477
1913
  });
478
1914
 
@@ -569,6 +2005,122 @@ describe('CliWorkerProvider', () => {
569
2005
  });
570
2006
  });
571
2007
 
2008
+ it('GIVEN_runtime_selection_override_WHEN_runWorker_THEN_applies_model_feature_role_placeholders_and_override_env', async () => {
2009
+ const run = vi.fn(
2010
+ async (
2011
+ _command: string,
2012
+ _args: string[],
2013
+ options?: Record<string, unknown>,
2014
+ ): Promise<ProviderCommandResult> => {
2015
+ const typedOptions = options as {
2016
+ onLifecycleEvent?: (event: {
2017
+ type: 'spawned' | 'activity' | 'heartbeat' | 'terminated';
2018
+ }) => void;
2019
+ registerKill?: (kill: (signal: 'SIGTERM' | 'SIGKILL') => void) => void;
2020
+ };
2021
+ typedOptions.registerKill?.(vi.fn());
2022
+ typedOptions.onLifecycleEvent?.({ type: 'spawned' });
2023
+ typedOptions.onLifecycleEvent?.({ type: 'activity' });
2024
+ return {
2025
+ exitCode: 0,
2026
+ signal: null,
2027
+ stdout: JSON.stringify({
2028
+ outputs: [{ type: 'NOTE', content: 'ok' }],
2029
+ }),
2030
+ };
2031
+ },
2032
+ );
2033
+ const provider = new CliWorkerProvider(
2034
+ makeSelection('custom', {
2035
+ provider_config_env: 'DEFAULT_TOKEN_ENV',
2036
+ provider_config_ref: 'default-token',
2037
+ agent_config: {
2038
+ run_command: 'worker-cli',
2039
+ run_args: ['run', '{feature_id}', '{role}', '{model}', '{prompt}'],
2040
+ },
2041
+ }),
2042
+ {
2043
+ outputParser: new GenericCliOutputParser(),
2044
+ commandRunner: { run },
2045
+ workerResponseTimeoutMs: 1_000,
2046
+ },
2047
+ );
2048
+
2049
+ await provider.runWorker({
2050
+ role: 'builder',
2051
+ feature_id: 'feature-runtime-override',
2052
+ instructions: 'Ship it.',
2053
+ runtime_selection: {
2054
+ provider: 'custom',
2055
+ model: 'runtime-model',
2056
+ provider_config_env: 'RUNTIME_TOKEN_ENV',
2057
+ provider_config_ref: 'runtime-token',
2058
+ agent_config: null,
2059
+ },
2060
+ });
2061
+
2062
+ const call = run.mock.calls.at(0);
2063
+ expect(call?.[0]).toBe('worker-cli');
2064
+ expect(call?.[1]).toEqual([
2065
+ 'run',
2066
+ 'feature-runtime-override',
2067
+ 'builder',
2068
+ 'runtime-model',
2069
+ expect.stringContaining('"feature_id":"feature-runtime-override"'),
2070
+ ]);
2071
+ const options = call?.[2] as { env?: NodeJS.ProcessEnv } | undefined;
2072
+ expect(options?.env?.RUNTIME_TOKEN_ENV).toBe('runtime-token');
2073
+ });
2074
+
2075
+ it('GIVEN_runtime_selection_with_different_provider_WHEN_runWorker_THEN_throws_invalid_argument', async () => {
2076
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2077
+ outputParser: new GenericCliOutputParser(),
2078
+ commandRunner: {
2079
+ run: vi.fn(),
2080
+ },
2081
+ workerResponseTimeoutMs: 1_000,
2082
+ });
2083
+
2084
+ await expect(
2085
+ provider.runWorker({
2086
+ role: 'builder',
2087
+ feature_id: 'feature-mismatch',
2088
+ runtime_selection: {
2089
+ provider: 'claude',
2090
+ model: 'claude-model',
2091
+ provider_config_env: null,
2092
+ provider_config_ref: null,
2093
+ agent_config: null,
2094
+ },
2095
+ }),
2096
+ ).rejects.toMatchObject({
2097
+ code: ERROR_CODES.INVALID_ARGUMENT,
2098
+ details: {
2099
+ provider: 'codex',
2100
+ runtime_provider: 'claude',
2101
+ },
2102
+ });
2103
+ });
2104
+
2105
+ it('GIVEN_command_runner_rejects_WHEN_runWorker_THEN_original_error_bubbles_after_watchdog_cleanup', async () => {
2106
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2107
+ outputParser: new GenericCliOutputParser(),
2108
+ commandRunner: {
2109
+ run: vi.fn(async () => {
2110
+ throw new Error('runner exploded');
2111
+ }),
2112
+ },
2113
+ workerResponseTimeoutMs: 1_000,
2114
+ });
2115
+
2116
+ await expect(
2117
+ provider.runWorker({
2118
+ role: 'builder',
2119
+ feature_id: 'feature-runner-error',
2120
+ }),
2121
+ ).rejects.toThrow('runner exploded');
2122
+ });
2123
+
572
2124
  it('GIVEN_attach_and_send_templates_WHEN_commands_succeed_THEN_methods_are_available', async () => {
573
2125
  const run = vi.fn(
574
2126
  async (
@@ -651,6 +2203,94 @@ describe('CliWorkerProvider', () => {
651
2203
  });
652
2204
  });
653
2205
 
2206
+ it('GIVEN_claude_control_commands_WHEN_attach_and_send_are_called_THEN_both_target_the_requested_session', async () => {
2207
+ const run = vi.fn(
2208
+ async (
2209
+ _command: string,
2210
+ _args: string[],
2211
+ _options?: Record<string, unknown>,
2212
+ ): Promise<ProviderCommandResult> => ({
2213
+ exitCode: 0,
2214
+ signal: null,
2215
+ stdout: '{}',
2216
+ }),
2217
+ );
2218
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
2219
+ outputParser: new GenericCliOutputParser(),
2220
+ commandRunner: { run },
2221
+ workerResponseTimeoutMs: 1000,
2222
+ });
2223
+
2224
+ await provider.attachToSession?.('session-a');
2225
+ await provider.sendMessage?.('session-a', 'hello');
2226
+
2227
+ expect(run.mock.calls.at(0)?.[1]).toEqual([
2228
+ '--dangerously-skip-permissions',
2229
+ '--resume',
2230
+ 'session-a',
2231
+ ]);
2232
+ expect(run.mock.calls.at(1)?.[1]).toEqual([
2233
+ '--dangerously-skip-permissions',
2234
+ '--resume',
2235
+ 'session-a',
2236
+ '--print',
2237
+ '--output-format',
2238
+ 'text',
2239
+ 'hello',
2240
+ ]);
2241
+ });
2242
+
2243
+ it('GIVEN_missing_claude_conversation_WHEN_send_called_THEN_bootstraps_the_session_with_the_stored_prompt', async () => {
2244
+ const run = vi
2245
+ .fn<
2246
+ (
2247
+ _command: string,
2248
+ _args: string[],
2249
+ _options?: Record<string, unknown>,
2250
+ ) => Promise<ProviderCommandResult>
2251
+ >()
2252
+ .mockResolvedValueOnce({
2253
+ exitCode: 1,
2254
+ signal: null,
2255
+ stderr: 'Error: No conversation found with session ID: session-a\n',
2256
+ })
2257
+ .mockResolvedValueOnce({
2258
+ exitCode: 0,
2259
+ signal: null,
2260
+ stdout: '{}',
2261
+ });
2262
+ const provider = new CliWorkerProvider(makeSelection('claude'), {
2263
+ outputParser: new GenericCliOutputParser(),
2264
+ commandRunner: { run },
2265
+ workerResponseTimeoutMs: 1000,
2266
+ });
2267
+
2268
+ const session = await provider.createSession('reconciler', 'global', 'system prompt');
2269
+ await provider.sendMessage?.(session.session_id, 'resolve the conflict');
2270
+
2271
+ expect(run).toHaveBeenCalledTimes(2);
2272
+ expect(run.mock.calls.at(0)?.[1]).toEqual([
2273
+ '--dangerously-skip-permissions',
2274
+ '--resume',
2275
+ session.session_id,
2276
+ '--print',
2277
+ '--output-format',
2278
+ 'text',
2279
+ 'resolve the conflict',
2280
+ ]);
2281
+ expect(run.mock.calls.at(1)?.[1]).toEqual([
2282
+ '--dangerously-skip-permissions',
2283
+ '--session-id',
2284
+ session.session_id,
2285
+ '--print',
2286
+ '--output-format',
2287
+ 'text',
2288
+ ]);
2289
+ expect(run.mock.calls.at(1)?.[2]).toMatchObject({
2290
+ stdin: 'system prompt\n---\nresolve the conflict',
2291
+ });
2292
+ });
2293
+
654
2294
  it('GIVEN_send_command_general_failure_WHEN_send_called_THEN_throws_internal_error', async () => {
655
2295
  const provider = new CliWorkerProvider(
656
2296
  makeSelection('custom', {
@@ -699,4 +2339,221 @@ describe('CliWorkerProvider', () => {
699
2339
  expect(await provider.reattachSession('session-b')).toMatchObject({ session_id: 'session-b' });
700
2340
  expect(await provider.closeSession(created.session_id)).toEqual({ closed: true });
701
2341
  });
2342
+
2343
+ it('GIVEN_existing_session_prompt_and_nonzero_exit_WHEN_next_run_retries_THEN_prompt_is_migrated_to_refreshed_session', async () => {
2344
+ const parse = vi.fn(
2345
+ (): WorkerOutputEnvelope => ({
2346
+ session_id: 'builder-feature-refresh',
2347
+ role: 'builder',
2348
+ feature_id: 'feature-refresh',
2349
+ outputs: [{ type: 'NOTE', content: 'ok' }],
2350
+ provider_meta: { provider: 'codex', model: 'codex-model' },
2351
+ }),
2352
+ );
2353
+ const run = vi
2354
+ .fn<ProviderCommandRunner['run']>()
2355
+ .mockResolvedValueOnce({
2356
+ exitCode: 1,
2357
+ signal: null,
2358
+ stderr: 'boom',
2359
+ })
2360
+ .mockResolvedValueOnce({
2361
+ exitCode: 0,
2362
+ signal: null,
2363
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
2364
+ });
2365
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2366
+ outputParser: { parse },
2367
+ commandRunner: { run },
2368
+ workerResponseTimeoutMs: 1_000,
2369
+ });
2370
+
2371
+ await provider.createSession('builder', 'feature-refresh', 'Preserve this prompt.');
2372
+ await expect(
2373
+ provider.runWorker({
2374
+ role: 'builder',
2375
+ feature_id: 'feature-refresh',
2376
+ }),
2377
+ ).rejects.toMatchObject({
2378
+ code: ERROR_CODES.PROVIDER_RUNTIME_UNAVAILABLE,
2379
+ });
2380
+
2381
+ await provider.runWorker({
2382
+ role: 'builder',
2383
+ feature_id: 'feature-refresh',
2384
+ });
2385
+
2386
+ const secondCallOptions = run.mock.calls.at(1)?.[2] as { stdin?: string } | undefined;
2387
+ expect(secondCallOptions?.stdin).toContain('Preserve this prompt.');
2388
+ });
2389
+
2390
+ it('GIVEN_process_kill_signal_WHEN_runWorker_fails_THEN_failure_is_classified_as_process_killed', async () => {
2391
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2392
+ outputParser: new GenericCliOutputParser(),
2393
+ commandRunner: {
2394
+ run: vi.fn(
2395
+ async (): Promise<ProviderCommandResult> => ({
2396
+ exitCode: 1,
2397
+ signal: 'SIGTERM' as NodeJS.Signals,
2398
+ stderr: 'terminated',
2399
+ }),
2400
+ ),
2401
+ },
2402
+ workerResponseTimeoutMs: 1_000,
2403
+ });
2404
+
2405
+ await expect(
2406
+ provider.runWorker({
2407
+ role: 'builder',
2408
+ feature_id: 'feature-killed',
2409
+ }),
2410
+ ).rejects.toMatchObject({
2411
+ code: ERROR_CODES.PROVIDER_PROCESS_KILLED,
2412
+ });
2413
+ });
2414
+
2415
+ it('GIVEN_raw_logs_enabled_with_blank_stdout_or_non_worker_role_WHEN_runWorker_completes_THEN_no_raw_log_is_persisted', async () => {
2416
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-raw-logs-skip-'));
2417
+ const parse = vi.fn(
2418
+ (): WorkerOutputEnvelope => ({
2419
+ session_id: 'session-skip',
2420
+ role: 'reconciler',
2421
+ feature_id: 'feature-skip',
2422
+ outputs: [{ type: 'NOTE', content: 'skip' }],
2423
+ provider_meta: { provider: 'codex', model: 'codex-model' },
2424
+ }),
2425
+ );
2426
+ const run = vi
2427
+ .fn<ProviderCommandRunner['run']>()
2428
+ .mockResolvedValueOnce({
2429
+ exitCode: 0,
2430
+ signal: null,
2431
+ stdout: ' ',
2432
+ })
2433
+ .mockResolvedValueOnce({
2434
+ exitCode: 0,
2435
+ signal: null,
2436
+ stdout: 'reconciler summary',
2437
+ });
2438
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2439
+ outputParser: { parse },
2440
+ commandRunner: { run },
2441
+ repoRoot,
2442
+ rawAgentLogsEnabled: true,
2443
+ workerResponseTimeoutMs: 1_000,
2444
+ });
2445
+
2446
+ try {
2447
+ await provider.runWorker({
2448
+ role: 'builder',
2449
+ feature_id: 'feature-skip',
2450
+ });
2451
+ await provider.runWorker({
2452
+ role: 'reconciler',
2453
+ feature_id: 'feature-skip',
2454
+ });
2455
+
2456
+ const logsDir = path.join(repoRoot, '.aop', 'features', 'feature-skip', 'logs');
2457
+ const files = await fs.readdir(logsDir);
2458
+ expect(files).toEqual(['live']);
2459
+ const liveFiles = await fs.readdir(path.join(logsDir, 'live'));
2460
+ expect(liveFiles).toContain('builder.json');
2461
+ expect(liveFiles).toContain('builder.txt');
2462
+ expect(liveFiles).not.toContain('reconciler.txt');
2463
+ } finally {
2464
+ await fs.rm(repoRoot, { recursive: true, force: true });
2465
+ }
2466
+ });
2467
+
2468
+ it('GIVEN_cleanup_directory_contains_nonmatching_file_WHEN_raw_log_cleanup_runs_THEN_nonmatching_file_is_ignored', async () => {
2469
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-raw-logs-ignore-'));
2470
+ const logsDir = path.join(repoRoot, '.aop', 'features', 'feature-ignore', 'logs');
2471
+ await fs.mkdir(logsDir, { recursive: true });
2472
+ await fs.writeFile(path.join(logsDir, 'notes.txt'), 'keep', 'utf8');
2473
+
2474
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2475
+ outputParser: new GenericCliOutputParser(),
2476
+ commandRunner: {
2477
+ run: vi.fn(async () => ({
2478
+ exitCode: 0,
2479
+ signal: null,
2480
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
2481
+ })),
2482
+ },
2483
+ repoRoot,
2484
+ rawAgentLogsEnabled: true,
2485
+ rawAgentLogsRetentionDays: 1,
2486
+ workerResponseTimeoutMs: 1_000,
2487
+ });
2488
+
2489
+ try {
2490
+ await provider.runWorker({
2491
+ role: 'planner',
2492
+ feature_id: 'feature-ignore',
2493
+ });
2494
+
2495
+ const files = await fs.readdir(logsDir);
2496
+ expect(files).toContain('notes.txt');
2497
+ } finally {
2498
+ await fs.rm(repoRoot, { recursive: true, force: true });
2499
+ }
2500
+ });
2501
+
2502
+ it('GIVEN_provider_capabilities_WHEN_requested_THEN_returns_cli_provider_flags', () => {
2503
+ const provider = new CliWorkerProvider(makeSelection('codex'), {
2504
+ outputParser: new GenericCliOutputParser(),
2505
+ commandRunner: {
2506
+ run: vi.fn(),
2507
+ },
2508
+ workerResponseTimeoutMs: 1_000,
2509
+ });
2510
+
2511
+ expect(provider.getCapabilities()).toEqual({
2512
+ supportsInteractiveMode: true,
2513
+ supportsWorkingDirectory: true,
2514
+ supportsPauseResume: false,
2515
+ supportsMessagePassing: true,
2516
+ supportsAcknowledgment: false,
2517
+ });
2518
+ });
2519
+
2520
+ it('GIVEN_selection_with_cli_extra_args_WHEN_runWorker_called_THEN_extra_args_appended_to_command', async () => {
2521
+ const run = vi.fn(
2522
+ async (): Promise<ProviderCommandResult> => ({
2523
+ exitCode: 0,
2524
+ signal: null,
2525
+ stdout: JSON.stringify({ outputs: [{ type: 'NOTE', content: 'ok' }] }),
2526
+ stderr: '',
2527
+ }),
2528
+ );
2529
+ const parse = vi.fn(
2530
+ (): WorkerOutputEnvelope => ({
2531
+ session_id: 'planner-feature-b-session',
2532
+ role: 'planner',
2533
+ feature_id: 'feature-b',
2534
+ outputs: [{ type: 'NOTE', content: 'ok' }],
2535
+ provider_meta: { provider: 'codex', model: 'codex-model' },
2536
+ }),
2537
+ );
2538
+ const selection = makeSelection('codex', {});
2539
+ selection.cli_extra_args = ['-c', 'mcp_servers.context7.enabled=false'];
2540
+
2541
+ const provider = new CliWorkerProvider(selection, {
2542
+ outputParser: { parse },
2543
+ commandRunner: { run },
2544
+ workerResponseTimeoutMs: 1800000,
2545
+ });
2546
+
2547
+ await provider.createSession('planner', 'feature-b', 'prompt');
2548
+ await provider.runWorker({ role: 'planner', feature_id: 'feature-b', instructions: 'do work' });
2549
+
2550
+ const firstCall = run.mock.calls[0];
2551
+ expect(firstCall).toBeDefined();
2552
+ if (!firstCall) {
2553
+ throw new Error('expected command runner to be called');
2554
+ }
2555
+ const args = (firstCall as unknown[])[1] as string[];
2556
+ expect(args).toContain('-c');
2557
+ expect(args).toContain('mcp_servers.context7.enabled=false');
2558
+ });
702
2559
  });