agentic-orchestrator 0.1.2 → 0.1.4

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 (300) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/CLAUDE.md +126 -0
  3. package/README.md +166 -25
  4. package/agentic/orchestrator/adapters.yaml +3 -0
  5. package/agentic/orchestrator/gates.yaml +47 -0
  6. package/agentic/orchestrator/policy.yaml +89 -0
  7. package/agentic/orchestrator/schemas/adapters.schema.json +12 -0
  8. package/agentic/orchestrator/schemas/gates.schema.json +6 -1
  9. package/agentic/orchestrator/schemas/index.schema.json +14 -0
  10. package/agentic/orchestrator/schemas/multi-project.schema.json +41 -0
  11. package/agentic/orchestrator/schemas/policy.schema.json +449 -52
  12. package/agentic/orchestrator/schemas/state.schema.json +16 -0
  13. package/agentic/orchestrator/tools/catalog.json +68 -0
  14. package/agentic/orchestrator/tools/schemas/input/cost.get.input.schema.json +10 -0
  15. package/agentic/orchestrator/tools/schemas/input/cost.record.input.schema.json +13 -0
  16. package/agentic/orchestrator/tools/schemas/input/feature.send_message.input.schema.json +11 -0
  17. package/agentic/orchestrator/tools/schemas/input/performance.get_analytics.input.schema.json +10 -0
  18. package/agentic/orchestrator/tools/schemas/input/performance.record_outcome.input.schema.json +18 -0
  19. package/agentic/orchestrator/tools/schemas/output/cost.get.output.schema.json +13 -0
  20. package/agentic/orchestrator/tools/schemas/output/cost.record.output.schema.json +13 -0
  21. package/agentic/orchestrator/tools/schemas/output/feature.ready_to_merge.output.schema.json +7 -0
  22. package/agentic/orchestrator/tools/schemas/output/feature.send_message.output.schema.json +23 -0
  23. package/agentic/orchestrator/tools/schemas/output/performance.get_analytics.output.schema.json +46 -0
  24. package/agentic/orchestrator/tools/schemas/output/performance.record_outcome.output.schema.json +10 -0
  25. package/agentic/orchestrator/tools.md +5 -0
  26. package/apps/control-plane/scripts/validate-architecture-rules.mjs +28 -2
  27. package/apps/control-plane/scripts/validate-docker-mcp-contract.mjs +12 -0
  28. package/apps/control-plane/scripts/validate-mcp-contracts.ts +92 -0
  29. package/apps/control-plane/src/application/adapters/adapter-registry.ts +169 -0
  30. package/apps/control-plane/src/application/multi-project-loader.ts +119 -0
  31. package/apps/control-plane/src/application/services/activity-monitor-service.ts +199 -0
  32. package/apps/control-plane/src/application/services/cost-tracking-service.ts +82 -0
  33. package/apps/control-plane/src/application/services/dependency-scheduler-service.ts +86 -0
  34. package/apps/control-plane/src/application/services/feature-deletion-service.ts +8 -7
  35. package/apps/control-plane/src/application/services/gate-interpolation-service.ts +15 -0
  36. package/apps/control-plane/src/application/services/gate-service.ts +38 -2
  37. package/apps/control-plane/src/application/services/instance-isolation-service.ts +18 -0
  38. package/apps/control-plane/src/application/services/issue-tracker-service.ts +469 -0
  39. package/apps/control-plane/src/application/services/merge-service.ts +67 -3
  40. package/apps/control-plane/src/application/services/notifier-service.ts +295 -0
  41. package/apps/control-plane/src/application/services/performance-analytics-service.ts +122 -0
  42. package/apps/control-plane/src/application/services/plan-service.ts +51 -0
  43. package/apps/control-plane/src/application/services/pr-monitor-service.ts +262 -0
  44. package/apps/control-plane/src/application/services/reactions-service.ts +175 -0
  45. package/apps/control-plane/src/application/services/reporting-service.ts +17 -2
  46. package/apps/control-plane/src/application/services/run-lease-service.ts +16 -38
  47. package/apps/control-plane/src/application/tools/tool-metadata.ts +4 -1
  48. package/apps/control-plane/src/cli/aop.ts +1 -1
  49. package/apps/control-plane/src/cli/attach-command-handler.ts +120 -0
  50. package/apps/control-plane/src/cli/cleanup-command-handler.ts +190 -0
  51. package/apps/control-plane/src/cli/cli-argument-parser.ts +69 -3
  52. package/apps/control-plane/src/cli/dashboard-command-handler.ts +57 -0
  53. package/apps/control-plane/src/cli/help-command-handler.ts +163 -0
  54. package/apps/control-plane/src/cli/init-command-handler.ts +609 -0
  55. package/apps/control-plane/src/cli/resume-command-handler.ts +1 -0
  56. package/apps/control-plane/src/cli/retry-command-handler.ts +138 -0
  57. package/apps/control-plane/src/cli/run-command-handler.ts +115 -3
  58. package/apps/control-plane/src/cli/send-command-handler.ts +65 -0
  59. package/apps/control-plane/src/cli/status-command-handler.ts +102 -2
  60. package/apps/control-plane/src/cli/types.ts +26 -1
  61. package/apps/control-plane/src/core/constants.ts +8 -2
  62. package/apps/control-plane/src/core/error-codes.ts +3 -1
  63. package/apps/control-plane/src/core/gates.ts +170 -50
  64. package/apps/control-plane/src/core/kernel.ts +280 -5
  65. package/apps/control-plane/src/core/path-layout.ts +12 -0
  66. package/apps/control-plane/src/core/tool-caller.ts +36 -0
  67. package/apps/control-plane/src/core/workspace-hooks.ts +87 -0
  68. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +258 -9
  69. package/apps/control-plane/src/providers/providers.ts +235 -14
  70. package/apps/control-plane/src/supervisor/build-wave-executor.ts +129 -8
  71. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +123 -5
  72. package/apps/control-plane/src/supervisor/run-coordinator.ts +143 -6
  73. package/apps/control-plane/src/supervisor/runtime.ts +135 -6
  74. package/apps/control-plane/src/supervisor/types.ts +12 -21
  75. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +8 -0
  76. package/apps/control-plane/test/activity-monitor.spec.ts +294 -0
  77. package/apps/control-plane/test/adapter-registry.spec.ts +132 -0
  78. package/apps/control-plane/test/batch-operations.spec.ts +112 -0
  79. package/apps/control-plane/test/bootstrap-attach.spec.ts +102 -0
  80. package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +252 -0
  81. package/apps/control-plane/test/bootstrap.spec.ts +560 -0
  82. package/apps/control-plane/test/cleanup-command.spec.ts +301 -0
  83. package/apps/control-plane/test/cli-helpers.spec.ts +404 -1
  84. package/apps/control-plane/test/cli.unit.spec.ts +182 -1
  85. package/apps/control-plane/test/collision-queue.spec.ts +104 -1
  86. package/apps/control-plane/test/core-utils.spec.ts +175 -2
  87. package/apps/control-plane/test/cost-tracking.spec.ts +143 -0
  88. package/apps/control-plane/test/dashboard-api.integration.spec.ts +247 -0
  89. package/apps/control-plane/test/dashboard-client.spec.ts +116 -0
  90. package/apps/control-plane/test/dashboard-command.spec.ts +103 -0
  91. package/apps/control-plane/test/dependency-scheduler.spec.ts +189 -0
  92. package/apps/control-plane/test/epoch-tracking.spec.ts +4 -4
  93. package/apps/control-plane/test/feature-deletion-service.spec.ts +422 -0
  94. package/apps/control-plane/test/feature-lifecycle.spec.ts +202 -0
  95. package/apps/control-plane/test/git-spawn-error.spec.ts +24 -0
  96. package/apps/control-plane/test/incremental-gates.spec.ts +137 -0
  97. package/apps/control-plane/test/init-wizard.spec.ts +506 -0
  98. package/apps/control-plane/test/instance-isolation.spec.ts +83 -0
  99. package/apps/control-plane/test/issue-tracker.spec.ts +890 -0
  100. package/apps/control-plane/test/kernel.coverage.spec.ts +3 -5
  101. package/apps/control-plane/test/kernel.coverage2.spec.ts +871 -0
  102. package/apps/control-plane/test/kernel.spec.ts +13 -11
  103. package/apps/control-plane/test/lock-service.spec.ts +508 -0
  104. package/apps/control-plane/test/mcp-helpers.spec.ts +176 -0
  105. package/apps/control-plane/test/mcp.spec.ts +50 -15
  106. package/apps/control-plane/test/merge-service.spec.ts +67 -4
  107. package/apps/control-plane/test/multi-project.spec.ts +372 -0
  108. package/apps/control-plane/test/notifier-service.spec.ts +388 -0
  109. package/apps/control-plane/test/parallel-gates.spec.ts +312 -0
  110. package/apps/control-plane/test/patch-service.spec.ts +253 -0
  111. package/apps/control-plane/test/performance-analytics.spec.ts +338 -0
  112. package/apps/control-plane/test/planning-wave-executor.spec.ts +168 -0
  113. package/apps/control-plane/test/pr-monitor.spec.ts +385 -0
  114. package/apps/control-plane/test/providers.spec.ts +344 -1
  115. package/apps/control-plane/test/reactions.spec.ts +392 -0
  116. package/apps/control-plane/test/resume-command.spec.ts +390 -0
  117. package/apps/control-plane/test/run-coordinator.spec.ts +481 -2
  118. package/apps/control-plane/test/schema-date-time.spec.ts +46 -0
  119. package/apps/control-plane/test/service-retry-paths.spec.ts +30 -0
  120. package/apps/control-plane/test/services.spec.ts +95 -2
  121. package/apps/control-plane/test/session-management.spec.ts +450 -0
  122. package/apps/control-plane/test/spec-ingestion.spec.ts +190 -0
  123. package/apps/control-plane/test/supervisor-collaborators.spec.ts +699 -2
  124. package/apps/control-plane/test/supervisor.spec.ts +36 -30
  125. package/apps/control-plane/test/supervisor.unit.spec.ts +405 -0
  126. package/apps/control-plane/test/worker-decision-loop.spec.ts +57 -0
  127. package/apps/control-plane/test/workspace-hooks.spec.ts +177 -0
  128. package/apps/control-plane/vitest.config.ts +21 -5
  129. package/dist/apps/control-plane/application/adapters/adapter-registry.d.ts +44 -0
  130. package/dist/apps/control-plane/application/adapters/adapter-registry.js +76 -0
  131. package/dist/apps/control-plane/application/adapters/adapter-registry.js.map +1 -0
  132. package/dist/apps/control-plane/application/multi-project-loader.d.ts +31 -0
  133. package/dist/apps/control-plane/application/multi-project-loader.js +82 -0
  134. package/dist/apps/control-plane/application/multi-project-loader.js.map +1 -0
  135. package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +43 -0
  136. package/dist/apps/control-plane/application/services/activity-monitor-service.js +132 -0
  137. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -0
  138. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +28 -0
  139. package/dist/apps/control-plane/application/services/cost-tracking-service.js +48 -0
  140. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -0
  141. package/dist/apps/control-plane/application/services/dependency-scheduler-service.d.ts +26 -0
  142. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js +75 -0
  143. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js.map +1 -0
  144. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +2 -0
  145. package/dist/apps/control-plane/application/services/feature-deletion-service.js +6 -7
  146. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  147. package/dist/apps/control-plane/application/services/gate-interpolation-service.d.ts +7 -0
  148. package/dist/apps/control-plane/application/services/gate-interpolation-service.js +7 -0
  149. package/dist/apps/control-plane/application/services/gate-interpolation-service.js.map +1 -0
  150. package/dist/apps/control-plane/application/services/gate-service.js +32 -2
  151. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  152. package/dist/apps/control-plane/application/services/instance-isolation-service.d.ts +11 -0
  153. package/dist/apps/control-plane/application/services/instance-isolation-service.js +17 -0
  154. package/dist/apps/control-plane/application/services/instance-isolation-service.js.map +1 -0
  155. package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +65 -0
  156. package/dist/apps/control-plane/application/services/issue-tracker-service.js +358 -0
  157. package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -0
  158. package/dist/apps/control-plane/application/services/merge-service.d.ts +4 -0
  159. package/dist/apps/control-plane/application/services/merge-service.js +44 -2
  160. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  161. package/dist/apps/control-plane/application/services/notifier-service.d.ts +74 -0
  162. package/dist/apps/control-plane/application/services/notifier-service.js +212 -0
  163. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -0
  164. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +39 -0
  165. package/dist/apps/control-plane/application/services/performance-analytics-service.js +75 -0
  166. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -0
  167. package/dist/apps/control-plane/application/services/plan-service.d.ts +1 -0
  168. package/dist/apps/control-plane/application/services/plan-service.js +53 -0
  169. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  170. package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +44 -0
  171. package/dist/apps/control-plane/application/services/pr-monitor-service.js +192 -0
  172. package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -0
  173. package/dist/apps/control-plane/application/services/reactions-service.d.ts +67 -0
  174. package/dist/apps/control-plane/application/services/reactions-service.js +114 -0
  175. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -0
  176. package/dist/apps/control-plane/application/services/reporting-service.d.ts +1 -0
  177. package/dist/apps/control-plane/application/services/reporting-service.js +13 -2
  178. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  179. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +2 -0
  180. package/dist/apps/control-plane/application/services/run-lease-service.js +14 -38
  181. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  182. package/dist/apps/control-plane/application/tools/tool-metadata.js +3 -1
  183. package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
  184. package/dist/apps/control-plane/cli/aop.d.ts +1 -1
  185. package/dist/apps/control-plane/cli/aop.js +1 -1
  186. package/dist/apps/control-plane/cli/attach-command-handler.d.ts +12 -0
  187. package/dist/apps/control-plane/cli/attach-command-handler.js +98 -0
  188. package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -0
  189. package/dist/apps/control-plane/cli/cleanup-command-handler.d.ts +12 -0
  190. package/dist/apps/control-plane/cli/cleanup-command-handler.js +162 -0
  191. package/dist/apps/control-plane/cli/cleanup-command-handler.js.map +1 -0
  192. package/dist/apps/control-plane/cli/cli-argument-parser.js +73 -3
  193. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  194. package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +7 -0
  195. package/dist/apps/control-plane/cli/dashboard-command-handler.js +45 -0
  196. package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -0
  197. package/dist/apps/control-plane/cli/help-command-handler.d.ts +8 -0
  198. package/dist/apps/control-plane/cli/help-command-handler.js +146 -0
  199. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -0
  200. package/dist/apps/control-plane/cli/init-command-handler.d.ts +26 -0
  201. package/dist/apps/control-plane/cli/init-command-handler.js +517 -0
  202. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -0
  203. package/dist/apps/control-plane/cli/resume-command-handler.js +1 -1
  204. package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
  205. package/dist/apps/control-plane/cli/retry-command-handler.d.ts +8 -0
  206. package/dist/apps/control-plane/cli/retry-command-handler.js +111 -0
  207. package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -0
  208. package/dist/apps/control-plane/cli/run-command-handler.d.ts +5 -0
  209. package/dist/apps/control-plane/cli/run-command-handler.js +82 -3
  210. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  211. package/dist/apps/control-plane/cli/send-command-handler.d.ts +8 -0
  212. package/dist/apps/control-plane/cli/send-command-handler.js +55 -0
  213. package/dist/apps/control-plane/cli/send-command-handler.js.map +1 -0
  214. package/dist/apps/control-plane/cli/status-command-handler.d.ts +12 -1
  215. package/dist/apps/control-plane/cli/status-command-handler.js +55 -2
  216. package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
  217. package/dist/apps/control-plane/cli/types.d.ts +26 -1
  218. package/dist/apps/control-plane/cli/types.js +15 -1
  219. package/dist/apps/control-plane/cli/types.js.map +1 -1
  220. package/dist/apps/control-plane/core/constants.d.ts +6 -0
  221. package/dist/apps/control-plane/core/constants.js +8 -2
  222. package/dist/apps/control-plane/core/constants.js.map +1 -1
  223. package/dist/apps/control-plane/core/error-codes.d.ts +2 -0
  224. package/dist/apps/control-plane/core/error-codes.js +3 -1
  225. package/dist/apps/control-plane/core/error-codes.js.map +1 -1
  226. package/dist/apps/control-plane/core/gates.d.ts +4 -0
  227. package/dist/apps/control-plane/core/gates.js +140 -43
  228. package/dist/apps/control-plane/core/gates.js.map +1 -1
  229. package/dist/apps/control-plane/core/kernel.d.ts +50 -1
  230. package/dist/apps/control-plane/core/kernel.js +220 -7
  231. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  232. package/dist/apps/control-plane/core/path-layout.d.ts +3 -0
  233. package/dist/apps/control-plane/core/path-layout.js +9 -0
  234. package/dist/apps/control-plane/core/path-layout.js.map +1 -1
  235. package/dist/apps/control-plane/core/tool-caller.d.ts +32 -0
  236. package/dist/apps/control-plane/core/tool-caller.js +2 -0
  237. package/dist/apps/control-plane/core/tool-caller.js.map +1 -0
  238. package/dist/apps/control-plane/core/workspace-hooks.d.ts +20 -0
  239. package/dist/apps/control-plane/core/workspace-hooks.js +69 -0
  240. package/dist/apps/control-plane/core/workspace-hooks.js.map +1 -0
  241. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +245 -9
  242. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  243. package/dist/apps/control-plane/providers/providers.d.ts +42 -3
  244. package/dist/apps/control-plane/providers/providers.js +216 -5
  245. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  246. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
  247. package/dist/apps/control-plane/supervisor/build-wave-executor.js +115 -6
  248. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  249. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
  250. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +109 -5
  251. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  252. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +15 -0
  253. package/dist/apps/control-plane/supervisor/run-coordinator.js +132 -6
  254. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  255. package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
  256. package/dist/apps/control-plane/supervisor/runtime.js +110 -6
  257. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  258. package/dist/apps/control-plane/supervisor/types.d.ts +9 -16
  259. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  260. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +3 -0
  261. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +5 -0
  262. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  263. package/eslint.config.mjs +2 -1
  264. package/package.json +12 -2
  265. package/packages/web-dashboard/next-env.d.ts +5 -0
  266. package/packages/web-dashboard/next.config.js +7 -0
  267. package/packages/web-dashboard/package.json +26 -0
  268. package/packages/web-dashboard/src/app/api/actions/route.ts +64 -0
  269. package/packages/web-dashboard/src/app/api/events/route.ts +51 -0
  270. package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +256 -0
  271. package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +10 -0
  272. package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +25 -0
  273. package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +63 -0
  274. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +16 -0
  275. package/packages/web-dashboard/src/app/api/projects/route.ts +31 -0
  276. package/packages/web-dashboard/src/app/api/status/route.ts +15 -0
  277. package/packages/web-dashboard/src/app/globals.css +2 -0
  278. package/packages/web-dashboard/src/app/layout.tsx +15 -0
  279. package/packages/web-dashboard/src/app/page.tsx +393 -0
  280. package/packages/web-dashboard/src/lib/aop-client.ts +244 -0
  281. package/packages/web-dashboard/src/lib/multi-project-config.ts +116 -0
  282. package/packages/web-dashboard/src/lib/orchestrator-tools.ts +284 -0
  283. package/packages/web-dashboard/src/lib/types.ts +58 -0
  284. package/packages/web-dashboard/tsconfig.json +40 -0
  285. package/packages/web-dashboard/vitest.config.ts +6 -0
  286. package/spec-files/completed/agentic_orchestrator_feature_gaps_closure_spec.md +1764 -0
  287. package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +348 -0
  288. package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +344 -0
  289. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +374 -0
  290. package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +1059 -0
  291. package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +466 -0
  292. package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +198 -0
  293. package/spec-files/outstanding/agentic_orchestrator_validator_hardening_spec.md +365 -0
  294. package/spec-files/progress.md +481 -52
  295. /package/spec-files/{agentic_orchestrator_cli_delete_command_spec.md → completed/agentic_orchestrator_cli_delete_command_spec.md} +0 -0
  296. /package/spec-files/{agentic_orchestrator_dot_aop_generated_artifacts_spec.md → completed/agentic_orchestrator_dot_aop_generated_artifacts_spec.md} +0 -0
  297. /package/spec-files/{agentic_orchestrator_mcp_formalization_spec.md → completed/agentic_orchestrator_mcp_formalization_spec.md} +0 -0
  298. /package/spec-files/{agentic_orchestrator_oop_refactor_spec.md → completed/agentic_orchestrator_oop_refactor_spec.md} +0 -0
  299. /package/spec-files/{agentic_orchestrator_single_global_orchestrator_spec.md → completed/agentic_orchestrator_single_global_orchestrator_spec.md} +0 -0
  300. /package/spec-files/{agentic_orchestrator_spec.md → completed/agentic_orchestrator_spec.md} +0 -0
@@ -1,5 +1,9 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { STATUS, TOOLS } from '../src/core/constants.js';
1
+ import fs from 'node:fs/promises';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { GATE_RESULT, STATUS, TOOLS } from '../src/core/constants.js';
4
+ import { ERROR_CODES } from '../src/core/error-codes.js';
5
+ import { BuildWaveExecutor } from '../src/supervisor/build-wave-executor.js';
6
+ import { PromptBundleLoader } from '../src/supervisor/prompt-bundle-loader.js';
3
7
  import { QaWaveExecutor } from '../src/supervisor/qa-wave-executor.js';
4
8
  import { SessionOrchestrator } from '../src/supervisor/session-orchestrator.js';
5
9
  import { UNASSIGNED_SESSION_ID } from '../src/supervisor/types.js';
@@ -61,6 +65,213 @@ describe('QaWaveExecutor collaborator branches', () => {
61
65
  qa_session_id: 'qa-new'
62
66
  });
63
67
  });
68
+
69
+ describe('QaWaveExecutor catch and reactions branches', () => {
70
+ beforeEach(() => { vi.clearAllMocks(); });
71
+
72
+ function makeBaseDeps() {
73
+ const state = {
74
+ runId: 'run-qa',
75
+ ownerInstanceId: 'owner-qa',
76
+ orchestratorSessionId: 'orch-qa',
77
+ sessionsByFeature: new Map<string, { planner: string; builder: string; qa: string }>([
78
+ ['feature_x', { planner: 'p-1', builder: 'b-1', qa: 'q-old' }]
79
+ ])
80
+ };
81
+ const provider = {
82
+ closeSession: vi.fn(async () => ({ closed: true })),
83
+ createSession: vi.fn(async () => ({ session_id: 'qa-new' }))
84
+ };
85
+ const kernel = {
86
+ updateFeatureSessionAssignment: vi.fn(async () => ({ data: {} }))
87
+ };
88
+ const promptProvider = {
89
+ loadRolePrompts: vi.fn(async () => ({ planner: null, builder: null, qa: 'qa-prompt' }))
90
+ };
91
+ const featureClusterPatcher = {
92
+ patchFeatureCluster: vi.fn(async () => undefined)
93
+ };
94
+ return { state, provider, kernel, promptProvider, featureClusterPatcher };
95
+ }
96
+
97
+ it('GIVEN_gates_run_throws_WHEN_qa_wave_runs_THEN_sets_gate_overall_to_fail_and_continues', async () => {
98
+ const { state, provider, kernel, promptProvider, featureClusterPatcher } = makeBaseDeps();
99
+ const toolCaller = {
100
+ callTool: vi.fn(async (_role: string, toolName: string) => {
101
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
102
+ return { ok: true, data: { front_matter: { status: STATUS.QA, gate_retry_count: 0 } } };
103
+ }
104
+ if (toolName === TOOLS.GATES_RUN) {
105
+ throw new Error('gates failed unexpectedly');
106
+ }
107
+ return { ok: true, data: {} };
108
+ })
109
+ };
110
+
111
+ const executor = new QaWaveExecutor({
112
+ kernel: kernel as never,
113
+ provider: provider as never,
114
+ toolCaller: toolCaller as never,
115
+ promptProvider: promptProvider as never,
116
+ featureClusterPatcher: featureClusterPatcher as never,
117
+ state: state as never
118
+ });
119
+
120
+ await executor.run(['feature_x'], 1);
121
+
122
+ expect(provider.createSession).toHaveBeenCalledWith('qa', 'feature_x', 'qa-prompt');
123
+ });
124
+
125
+ it('GIVEN_reactions_service_with_retries_WHEN_gate_fails_initially_THEN_retries_until_pass', async () => {
126
+ const { state, provider, kernel, promptProvider, featureClusterPatcher } = makeBaseDeps();
127
+ let gateCallCount = 0;
128
+ const toolCaller = {
129
+ callTool: vi.fn(async (_role: string, toolName: string) => {
130
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
131
+ return { ok: true, data: { front_matter: { status: STATUS.QA, gate_retry_count: 0 } } };
132
+ }
133
+ if (toolName === TOOLS.GATES_RUN) {
134
+ gateCallCount += 1;
135
+ return { ok: true, data: { overall: gateCallCount === 1 ? GATE_RESULT.FAIL : GATE_RESULT.PASS } };
136
+ }
137
+ return { ok: true, data: {} };
138
+ })
139
+ };
140
+ const reactionsService = {
141
+ retryDelayMs: vi.fn(() => 5),
142
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
143
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
144
+ recordRetry: vi.fn(async () => undefined),
145
+ shouldEscalate: vi.fn(() => false),
146
+ notifyEscalation: vi.fn(async () => undefined)
147
+ };
148
+
149
+ const executor = new QaWaveExecutor({
150
+ kernel: kernel as never,
151
+ provider: provider as never,
152
+ toolCaller: toolCaller as never,
153
+ promptProvider: promptProvider as never,
154
+ featureClusterPatcher: featureClusterPatcher as never,
155
+ state: state as never,
156
+ reactionsService: reactionsService as never
157
+ });
158
+
159
+ await executor.run(['feature_x'], 1);
160
+
161
+ expect(reactionsService.recordRetry).toHaveBeenCalledWith('feature_x', 1);
162
+ expect(reactionsService.notifyEscalation).not.toHaveBeenCalled();
163
+ });
164
+
165
+ it('GIVEN_reactions_service_retry_exhausted_WHEN_gate_still_fails_THEN_calls_shouldEscalate_and_notifyEscalation', async () => {
166
+ const { state, provider, kernel, promptProvider, featureClusterPatcher } = makeBaseDeps();
167
+ const toolCaller = {
168
+ callTool: vi.fn(async (_role: string, toolName: string) => {
169
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
170
+ return { ok: true, data: { front_matter: { status: STATUS.QA, gate_retry_count: 0 } } };
171
+ }
172
+ if (toolName === TOOLS.GATES_RUN) {
173
+ return { ok: true, data: { overall: GATE_RESULT.FAIL } };
174
+ }
175
+ return { ok: true, data: {} };
176
+ })
177
+ };
178
+ const reactionsService = {
179
+ retryDelayMs: vi.fn(() => 5),
180
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
181
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
182
+ recordRetry: vi.fn(async () => undefined),
183
+ shouldEscalate: vi.fn(() => true),
184
+ notifyEscalation: vi.fn(async () => undefined)
185
+ };
186
+
187
+ const executor = new QaWaveExecutor({
188
+ kernel: kernel as never,
189
+ provider: provider as never,
190
+ toolCaller: toolCaller as never,
191
+ promptProvider: promptProvider as never,
192
+ featureClusterPatcher: featureClusterPatcher as never,
193
+ state: state as never,
194
+ reactionsService: reactionsService as never
195
+ });
196
+
197
+ await executor.run(['feature_x'], 1);
198
+
199
+ expect(reactionsService.notifyEscalation).toHaveBeenCalledWith(
200
+ 'feature_x',
201
+ expect.objectContaining({ featureId: 'feature_x', gateName: 'full' })
202
+ );
203
+ });
204
+
205
+ it('GIVEN_reactions_service_WHEN_gate_passes_on_first_try_THEN_no_retry_attempted', async () => {
206
+ const { state, provider, kernel, promptProvider, featureClusterPatcher } = makeBaseDeps();
207
+ const toolCaller = {
208
+ callTool: vi.fn(async (_role: string, toolName: string) => {
209
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
210
+ return { ok: true, data: { front_matter: { status: STATUS.QA, gate_retry_count: 0 } } };
211
+ }
212
+ if (toolName === TOOLS.GATES_RUN) {
213
+ return { ok: true, data: { overall: GATE_RESULT.PASS } };
214
+ }
215
+ return { ok: true, data: {} };
216
+ })
217
+ };
218
+ const reactionsService = {
219
+ retryDelayMs: vi.fn(() => 5),
220
+ shouldRetry: vi.fn(),
221
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
222
+ recordRetry: vi.fn(async () => undefined),
223
+ shouldEscalate: vi.fn(() => false),
224
+ notifyEscalation: vi.fn(async () => undefined)
225
+ };
226
+
227
+ const executor = new QaWaveExecutor({
228
+ kernel: kernel as never,
229
+ provider: provider as never,
230
+ toolCaller: toolCaller as never,
231
+ promptProvider: promptProvider as never,
232
+ featureClusterPatcher: featureClusterPatcher as never,
233
+ state: state as never,
234
+ reactionsService: reactionsService as never
235
+ });
236
+
237
+ await executor.run(['feature_x'], 1);
238
+
239
+ expect(reactionsService.shouldRetry).not.toHaveBeenCalled();
240
+ });
241
+
242
+ it('GIVEN_feature_not_in_qa_status_WHEN_run_called_THEN_feature_skipped_entirely', async () => {
243
+ const { state, provider, kernel, promptProvider, featureClusterPatcher } = makeBaseDeps();
244
+ const toolCaller = {
245
+ callTool: vi.fn(async (_role: string, toolName: string) => {
246
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
247
+ return { ok: true, data: { front_matter: { status: STATUS.PLANNING } } };
248
+ }
249
+ return { ok: true, data: {} };
250
+ })
251
+ };
252
+ const workerDecisionRunner = {
253
+ execute: vi.fn(async () => ({
254
+ planSubmission: false, patchApplied: false, noteLogged: false,
255
+ requestHandled: false, priorityOrder: [] as string[], toolResults: []
256
+ }))
257
+ };
258
+
259
+ const executor = new QaWaveExecutor({
260
+ kernel: kernel as never,
261
+ provider: provider as never,
262
+ toolCaller: toolCaller as never,
263
+ promptProvider: promptProvider as never,
264
+ featureClusterPatcher: featureClusterPatcher as never,
265
+ state: state as never,
266
+ workerDecisionRunner: workerDecisionRunner as never
267
+ });
268
+
269
+ await executor.run(['feature_x'], 1);
270
+
271
+ expect(workerDecisionRunner.execute).not.toHaveBeenCalled();
272
+ expect(provider.createSession).not.toHaveBeenCalled();
273
+ });
274
+ });
64
275
  });
65
276
 
66
277
  describe('SessionOrchestrator collaborator branches', () => {
@@ -198,4 +409,490 @@ describe('SessionOrchestrator collaborator branches', () => {
198
409
  })
199
410
  );
200
411
  });
412
+
413
+ it('GIVEN_existing_sessions_with_unassigned_WHEN_closing_feature_cluster_THEN_closes_assigned_sessions_only', async () => {
414
+ const { orchestrator, provider, state } = createOrchestrator();
415
+ state.sessionsByFeature.set('feature_close', {
416
+ planner: 'planner-c',
417
+ builder: UNASSIGNED_SESSION_ID,
418
+ qa: 'qa-c'
419
+ });
420
+
421
+ await orchestrator.closeFeatureCluster('feature_close');
422
+
423
+ expect(provider.closeSession).toHaveBeenCalledWith('planner-c');
424
+ expect(provider.closeSession).toHaveBeenCalledWith('qa-c');
425
+ expect(provider.closeSession).not.toHaveBeenCalledWith(UNASSIGNED_SESSION_ID);
426
+ expect(state.sessionsByFeature.has('feature_close')).toBe(false);
427
+ });
428
+
429
+ it('GIVEN_no_sessions_in_map_WHEN_closing_feature_cluster_THEN_is_noop', async () => {
430
+ const { orchestrator, provider } = createOrchestrator();
431
+
432
+ await orchestrator.closeFeatureCluster('feature_nonexistent');
433
+
434
+ expect(provider.closeSession).not.toHaveBeenCalled();
435
+ });
436
+ });
437
+
438
+ describe('BuildWaveExecutor catch and reactions branches', () => {
439
+ function makeBaseBuildDeps() {
440
+ const workerDecisionRunner = {
441
+ execute: vi.fn(async () => ({
442
+ planSubmission: false,
443
+ patchApplied: false,
444
+ noteLogged: false,
445
+ requestHandled: false,
446
+ priorityOrder: [] as string[],
447
+ toolResults: []
448
+ }))
449
+ };
450
+ return { workerDecisionRunner };
451
+ }
452
+
453
+ it('GIVEN_reactions_service_retry_catch_throws_WHEN_build_wave_THEN_sets_gate_fail_and_checks_escalation', async () => {
454
+ const { workerDecisionRunner } = makeBaseBuildDeps();
455
+ let gatesCallCount = 0;
456
+ const toolCaller = {
457
+ callTool: vi.fn(async (_role: string, toolName: string) => {
458
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
459
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
460
+ }
461
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
462
+ return { ok: true, data: {} };
463
+ }
464
+ if (toolName === TOOLS.GATES_RUN) {
465
+ gatesCallCount += 1;
466
+ if (gatesCallCount === 1) {
467
+ return { ok: true, data: { overall: GATE_RESULT.FAIL } };
468
+ }
469
+ throw new Error('retry gate exploded');
470
+ }
471
+ return { ok: true, data: {} };
472
+ })
473
+ };
474
+ const reactionsService = {
475
+ retryDelayMs: vi.fn(() => 5),
476
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
477
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
478
+ recordRetry: vi.fn(async () => undefined),
479
+ shouldEscalate: vi.fn(() => false),
480
+ notifyEscalation: vi.fn(async () => undefined)
481
+ };
482
+
483
+ const executor = new BuildWaveExecutor({
484
+ toolCaller: toolCaller as never,
485
+ workerDecisionRunner: workerDecisionRunner as never,
486
+ reactionsService: reactionsService as never
487
+ });
488
+
489
+ await executor.run(['feature_build'], 1);
490
+
491
+ expect(reactionsService.recordRetry).toHaveBeenCalledWith('feature_build', 1);
492
+ expect(reactionsService.shouldEscalate).toHaveBeenCalled();
493
+ });
494
+
495
+ it('GIVEN_reactions_service_retry_succeeds_WHEN_build_wave_THEN_breaks_and_skips_escalation', async () => {
496
+ const { workerDecisionRunner } = makeBaseBuildDeps();
497
+ let gatesCallCount = 0;
498
+ const toolCaller = {
499
+ callTool: vi.fn(async (_role: string, toolName: string) => {
500
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
501
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
502
+ }
503
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
504
+ return { ok: true, data: {} };
505
+ }
506
+ if (toolName === TOOLS.GATES_RUN) {
507
+ gatesCallCount += 1;
508
+ return { ok: true, data: { overall: gatesCallCount === 1 ? GATE_RESULT.FAIL : GATE_RESULT.PASS } };
509
+ }
510
+ return { ok: true, data: {} };
511
+ })
512
+ };
513
+ const reactionsService = {
514
+ retryDelayMs: vi.fn(() => 5),
515
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
516
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
517
+ recordRetry: vi.fn(async () => undefined),
518
+ shouldEscalate: vi.fn(() => false),
519
+ notifyEscalation: vi.fn(async () => undefined)
520
+ };
521
+
522
+ const executor = new BuildWaveExecutor({
523
+ toolCaller: toolCaller as never,
524
+ workerDecisionRunner: workerDecisionRunner as never,
525
+ reactionsService: reactionsService as never
526
+ });
527
+
528
+ await executor.run(['feature_build'], 1);
529
+
530
+ expect(reactionsService.recordRetry).toHaveBeenCalledWith('feature_build', 1);
531
+ // Gate passed in retry → break taken → shouldEscalate NOT called (gateOverall=PASS short-circuits)
532
+ expect(reactionsService.shouldEscalate).not.toHaveBeenCalled();
533
+ expect(reactionsService.notifyEscalation).not.toHaveBeenCalled();
534
+ });
535
+ });
536
+
537
+ describe('PromptBundleLoader behavior', () => {
538
+ it('GIVEN_missing_prompt_behavior_error_WHEN_file_not_found_THEN_throws_missing_role_prompt', async () => {
539
+ const readFileSpy = vi.spyOn(fs, 'readFile').mockRejectedValue(new Error('ENOENT: no such file') as never);
540
+ const runtimeStateReader = {
541
+ getRepoRoot: vi.fn(() => '/fake/repo'),
542
+ getAgentsConfig: vi.fn(() => ({
543
+ roles: { planner: { system_prompt_path: 'prompts/planner.md' } },
544
+ missing_prompt_behavior: 'error'
545
+ }))
546
+ };
547
+ const loader = new PromptBundleLoader(runtimeStateReader as never);
548
+
549
+ await expect(loader.loadRolePrompts()).rejects.toMatchObject({
550
+ code: ERROR_CODES.MISSING_ROLE_PROMPT
551
+ });
552
+
553
+ readFileSpy.mockRestore();
554
+ });
555
+
556
+ it('GIVEN_cache_set_WHEN_peek_cache_called_THEN_returns_cached_value', () => {
557
+ const runtimeStateReader = {
558
+ getRepoRoot: vi.fn(() => '/fake/repo'),
559
+ getAgentsConfig: vi.fn(() => ({ roles: {}, missing_prompt_behavior: 'ignore' }))
560
+ };
561
+ const loader = new PromptBundleLoader(runtimeStateReader as never);
562
+ const bundle = { planner: 'planner prompt', builder: null, qa: 'qa prompt' };
563
+
564
+ loader.setCache(bundle);
565
+
566
+ expect(loader.peekCache()).toEqual(bundle);
567
+ });
568
+
569
+ it('GIVEN_cache_set_to_null_WHEN_peek_cache_called_THEN_returns_null', () => {
570
+ const runtimeStateReader = {
571
+ getRepoRoot: vi.fn(() => '/fake/repo'),
572
+ getAgentsConfig: vi.fn(() => ({ roles: {}, missing_prompt_behavior: 'ignore' }))
573
+ };
574
+ const loader = new PromptBundleLoader(runtimeStateReader as never);
575
+
576
+ loader.setCache(null);
577
+
578
+ expect(loader.peekCache()).toBeNull();
579
+ });
580
+
581
+ it('GIVEN_agentsConfig_without_roles_or_behavior_WHEN_loadRolePrompts_THEN_uses_defaults', async () => {
582
+ const runtimeStateReader = {
583
+ getRepoRoot: vi.fn(() => '/fake/repo'),
584
+ getAgentsConfig: vi.fn(() => ({}))
585
+ };
586
+ const loader = new PromptBundleLoader(runtimeStateReader as never);
587
+ const bundle = await loader.loadRolePrompts();
588
+ expect(bundle.planner).toBeNull();
589
+ expect(bundle.builder).toBeNull();
590
+ expect(bundle.qa).toBeNull();
591
+ });
592
+ });
593
+
594
+ describe('BuildWaveExecutor waitBeforeRetry branch', () => {
595
+ function makeBaseDeps() {
596
+ const workerDecisionRunner = {
597
+ execute: vi.fn(async () => ({
598
+ planSubmission: false,
599
+ patchApplied: false,
600
+ noteLogged: false,
601
+ requestHandled: false,
602
+ priorityOrder: [] as string[],
603
+ toolResults: []
604
+ }))
605
+ };
606
+ return { workerDecisionRunner };
607
+ }
608
+
609
+ it('GIVEN_reactionsService_with_waitBeforeRetry_function_WHEN_gate_fails_THEN_calls_waitBeforeRetry', async () => {
610
+ const { workerDecisionRunner } = makeBaseDeps();
611
+ let gatesCallCount = 0;
612
+ const toolCaller = {
613
+ callTool: vi.fn(async (_role: string, toolName: string) => {
614
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
615
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
616
+ }
617
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
618
+ return { ok: true, data: {} };
619
+ }
620
+ if (toolName === TOOLS.GATES_RUN) {
621
+ gatesCallCount += 1;
622
+ return { ok: true, data: { overall: gatesCallCount === 1 ? GATE_RESULT.FAIL : GATE_RESULT.PASS } };
623
+ }
624
+ return { ok: true, data: {} };
625
+ })
626
+ };
627
+ const waitBeforeRetry = vi.fn(async () => undefined);
628
+ const reactionsService = {
629
+ retryDelayMs: vi.fn(() => 0),
630
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
631
+ waitBeforeRetry,
632
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
633
+ recordRetry: vi.fn(async () => undefined),
634
+ shouldEscalate: vi.fn(() => false),
635
+ notifyEscalation: vi.fn(async () => undefined)
636
+ };
637
+
638
+ const executor = new BuildWaveExecutor({
639
+ toolCaller: toolCaller as never,
640
+ workerDecisionRunner: workerDecisionRunner as never,
641
+ reactionsService: reactionsService as never
642
+ });
643
+
644
+ await executor.run(['feature_wait'], 1);
645
+
646
+ expect(waitBeforeRetry).toHaveBeenCalledTimes(1);
647
+ });
648
+ });
649
+
650
+ describe('QaWaveExecutor waitBeforeRetry branch', () => {
651
+ it('GIVEN_reactionsService_with_waitBeforeRetry_WHEN_gate_fails_then_passes_THEN_calls_waitBeforeRetry', async () => {
652
+ const workerDecisionRunner = {
653
+ execute: vi.fn(async () => ({
654
+ planSubmission: false,
655
+ patchApplied: false,
656
+ noteLogged: false,
657
+ requestHandled: false,
658
+ priorityOrder: [] as string[],
659
+ toolResults: []
660
+ }))
661
+ };
662
+ const state = {
663
+ runId: 'run-qa-retry',
664
+ ownerInstanceId: 'owner-qa-retry',
665
+ orchestratorSessionId: null,
666
+ sessionsByFeature: new Map<string, { planner: string; builder: string; qa: string }>()
667
+ };
668
+ let gatesCallCount = 0;
669
+ const toolCaller = {
670
+ callTool: vi.fn(async (_role: string, toolName: string) => {
671
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
672
+ return { ok: true, data: { front_matter: { status: STATUS.QA, gate_retry_count: 0 } } };
673
+ }
674
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
675
+ return { ok: true, data: {} };
676
+ }
677
+ if (toolName === TOOLS.GATES_RUN) {
678
+ gatesCallCount += 1;
679
+ return { ok: true, data: { overall: gatesCallCount === 1 ? GATE_RESULT.FAIL : GATE_RESULT.PASS } };
680
+ }
681
+ return { ok: true, data: {} };
682
+ })
683
+ };
684
+ const waitBeforeRetry = vi.fn(async () => undefined);
685
+ const reactionsService = {
686
+ retryDelayMs: vi.fn(() => 0),
687
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
688
+ waitBeforeRetry,
689
+ buildRepairPrompt: vi.fn(() => 'repair prompt'),
690
+ recordRetry: vi.fn(async () => undefined),
691
+ shouldEscalate: vi.fn(() => false),
692
+ notifyEscalation: vi.fn(async () => undefined)
693
+ };
694
+ const provider = {
695
+ closeSession: vi.fn(async () => ({ closed: true })),
696
+ createSession: vi.fn(async () => ({ session_id: 'qa-retry-session' }))
697
+ };
698
+ const kernel = {
699
+ updateFeatureSessionAssignment: vi.fn(async () => ({ data: { updated: true } }))
700
+ };
701
+ const promptProvider = {
702
+ loadRolePrompts: vi.fn(async () => ({ planner: null, builder: null, qa: null }))
703
+ };
704
+ const featureClusterPatcher = {
705
+ patchFeatureCluster: vi.fn(async () => undefined)
706
+ };
707
+ const executor = new QaWaveExecutor({
708
+ kernel: kernel as never,
709
+ provider: provider as never,
710
+ toolCaller: toolCaller as never,
711
+ promptProvider: promptProvider as never,
712
+ featureClusterPatcher: featureClusterPatcher as never,
713
+ state: state as never,
714
+ workerDecisionRunner: workerDecisionRunner as never,
715
+ reactionsService: reactionsService as never
716
+ });
717
+
718
+ await executor.run(['feature_qa_wait'], 1);
719
+
720
+ expect(waitBeforeRetry).toHaveBeenCalledTimes(1);
721
+ });
722
+ });
723
+
724
+ describe('BuildWaveExecutor error message fallback', () => {
725
+ it('GIVEN_gates_throw_without_message_WHEN_run_THEN_gateLogs_empty_string', async () => {
726
+ const toolCaller = {
727
+ callTool: vi.fn(async (_role: string, toolName: string) => {
728
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
729
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
730
+ }
731
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
732
+ return { ok: true, data: {} };
733
+ }
734
+ if (toolName === TOOLS.GATES_RUN) {
735
+ throw {}; // no message property → typed.message ?? '' = ''
736
+ }
737
+ return { ok: true, data: {} };
738
+ })
739
+ };
740
+ const workerDecisionRunner = {
741
+ execute: vi.fn(async () => ({
742
+ planSubmission: false,
743
+ patchApplied: false,
744
+ noteLogged: false,
745
+ requestHandled: false,
746
+ priorityOrder: [] as string[],
747
+ toolResults: []
748
+ }))
749
+ };
750
+ const reactionsService = {
751
+ retryDelayMs: vi.fn(() => 0),
752
+ shouldRetry: vi.fn(() => false),
753
+ buildRepairPrompt: vi.fn(() => 'prompt'),
754
+ recordRetry: vi.fn(async () => undefined),
755
+ shouldEscalate: vi.fn(() => false),
756
+ notifyEscalation: vi.fn(async () => undefined)
757
+ };
758
+ const executor = new BuildWaveExecutor({
759
+ toolCaller: toolCaller as never,
760
+ workerDecisionRunner: workerDecisionRunner as never,
761
+ reactionsService: reactionsService as never
762
+ });
763
+ await executor.run(['feature_x'], 1);
764
+ });
765
+
766
+ it('GIVEN_retry_gates_return_no_overall_WHEN_run_THEN_defaults_to_PASS', async () => {
767
+ let gatesCallCount = 0;
768
+ const toolCaller = {
769
+ callTool: vi.fn(async (_role: string, toolName: string) => {
770
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
771
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
772
+ }
773
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
774
+ return { ok: true, data: {} };
775
+ }
776
+ if (toolName === TOOLS.GATES_RUN) {
777
+ gatesCallCount++;
778
+ if (gatesCallCount === 1) {return { ok: true, data: { overall: GATE_RESULT.FAIL } };}
779
+ return { ok: true, data: {} };
780
+ }
781
+ return { ok: true, data: {} };
782
+ })
783
+ };
784
+ const workerDecisionRunner = {
785
+ execute: vi.fn(async () => ({
786
+ planSubmission: false,
787
+ patchApplied: false,
788
+ noteLogged: false,
789
+ requestHandled: false,
790
+ priorityOrder: [] as string[],
791
+ toolResults: []
792
+ }))
793
+ };
794
+ const reactionsService = {
795
+ retryDelayMs: vi.fn(() => 0),
796
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
797
+ buildRepairPrompt: vi.fn(() => 'repair'),
798
+ recordRetry: vi.fn(async () => undefined),
799
+ shouldEscalate: vi.fn(() => false),
800
+ notifyEscalation: vi.fn(async () => undefined)
801
+ };
802
+ const executor = new BuildWaveExecutor({
803
+ toolCaller: toolCaller as never,
804
+ workerDecisionRunner: workerDecisionRunner as never,
805
+ reactionsService: reactionsService as never
806
+ });
807
+ await executor.run(['feature_x'], 1);
808
+ // retry returned no overall → defaults to PASS → gateOverall = PASS → shouldEscalate not called
809
+ expect(reactionsService.shouldEscalate).not.toHaveBeenCalled();
810
+ });
811
+
812
+ it('GIVEN_retry_gates_still_fail_WHEN_run_THEN_gateExitCode_is_1', async () => {
813
+ const toolCaller = {
814
+ callTool: vi.fn(async (_role: string, toolName: string) => {
815
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
816
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
817
+ }
818
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
819
+ return { ok: true, data: {} };
820
+ }
821
+ if (toolName === TOOLS.GATES_RUN) {
822
+ return { ok: true, data: { overall: GATE_RESULT.FAIL } };
823
+ }
824
+ return { ok: true, data: {} };
825
+ })
826
+ };
827
+ const workerDecisionRunner = {
828
+ execute: vi.fn(async () => ({
829
+ planSubmission: false,
830
+ patchApplied: false,
831
+ noteLogged: false,
832
+ requestHandled: false,
833
+ priorityOrder: [] as string[],
834
+ toolResults: []
835
+ }))
836
+ };
837
+ const reactionsService = {
838
+ retryDelayMs: vi.fn(() => 0),
839
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
840
+ buildRepairPrompt: vi.fn(() => 'repair'),
841
+ recordRetry: vi.fn(async () => undefined),
842
+ shouldEscalate: vi.fn(() => false),
843
+ notifyEscalation: vi.fn(async () => undefined)
844
+ };
845
+ const executor = new BuildWaveExecutor({
846
+ toolCaller: toolCaller as never,
847
+ workerDecisionRunner: workerDecisionRunner as never,
848
+ reactionsService: reactionsService as never
849
+ });
850
+ await executor.run(['feature_x'], 1);
851
+ // retry still fails (FAIL) → shouldEscalate should be checked
852
+ expect(reactionsService.shouldEscalate).toHaveBeenCalled();
853
+ });
854
+
855
+ it('GIVEN_retry_gates_throw_without_message_WHEN_run_THEN_handled', async () => {
856
+ let gatesCallCount = 0;
857
+ const toolCaller = {
858
+ callTool: vi.fn(async (_role: string, toolName: string) => {
859
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
860
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
861
+ }
862
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
863
+ return { ok: true, data: {} };
864
+ }
865
+ if (toolName === TOOLS.GATES_RUN) {
866
+ gatesCallCount++;
867
+ if (gatesCallCount === 1) {return { ok: true, data: { overall: GATE_RESULT.FAIL } };}
868
+ throw {}; // retry throws without message → typed.message ?? '' = ''
869
+ }
870
+ return { ok: true, data: {} };
871
+ })
872
+ };
873
+ const workerDecisionRunner = {
874
+ execute: vi.fn(async () => ({
875
+ planSubmission: false,
876
+ patchApplied: false,
877
+ noteLogged: false,
878
+ requestHandled: false,
879
+ priorityOrder: [] as string[],
880
+ toolResults: []
881
+ }))
882
+ };
883
+ const reactionsService = {
884
+ retryDelayMs: vi.fn(() => 0),
885
+ shouldRetry: vi.fn().mockReturnValueOnce(true).mockReturnValue(false),
886
+ buildRepairPrompt: vi.fn(() => 'repair'),
887
+ recordRetry: vi.fn(async () => undefined),
888
+ shouldEscalate: vi.fn(() => false),
889
+ notifyEscalation: vi.fn(async () => undefined)
890
+ };
891
+ const executor = new BuildWaveExecutor({
892
+ toolCaller: toolCaller as never,
893
+ workerDecisionRunner: workerDecisionRunner as never,
894
+ reactionsService: reactionsService as never
895
+ });
896
+ await executor.run(['feature_x'], 1);
897
+ });
201
898
  });