agentic-orchestrator 0.1.3 → 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 (294) 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 +7 -5
  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/attach-command-handler.ts +120 -0
  49. package/apps/control-plane/src/cli/cleanup-command-handler.ts +190 -0
  50. package/apps/control-plane/src/cli/cli-argument-parser.ts +69 -3
  51. package/apps/control-plane/src/cli/dashboard-command-handler.ts +57 -0
  52. package/apps/control-plane/src/cli/help-command-handler.ts +163 -0
  53. package/apps/control-plane/src/cli/init-command-handler.ts +609 -0
  54. package/apps/control-plane/src/cli/retry-command-handler.ts +138 -0
  55. package/apps/control-plane/src/cli/run-command-handler.ts +115 -3
  56. package/apps/control-plane/src/cli/send-command-handler.ts +65 -0
  57. package/apps/control-plane/src/cli/status-command-handler.ts +102 -2
  58. package/apps/control-plane/src/cli/types.ts +26 -1
  59. package/apps/control-plane/src/core/constants.ts +8 -2
  60. package/apps/control-plane/src/core/error-codes.ts +3 -1
  61. package/apps/control-plane/src/core/gates.ts +170 -50
  62. package/apps/control-plane/src/core/kernel.ts +280 -5
  63. package/apps/control-plane/src/core/path-layout.ts +12 -0
  64. package/apps/control-plane/src/core/tool-caller.ts +36 -0
  65. package/apps/control-plane/src/core/workspace-hooks.ts +87 -0
  66. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +258 -9
  67. package/apps/control-plane/src/providers/providers.ts +235 -14
  68. package/apps/control-plane/src/supervisor/build-wave-executor.ts +129 -8
  69. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +123 -5
  70. package/apps/control-plane/src/supervisor/run-coordinator.ts +143 -6
  71. package/apps/control-plane/src/supervisor/runtime.ts +135 -6
  72. package/apps/control-plane/src/supervisor/types.ts +12 -21
  73. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +8 -0
  74. package/apps/control-plane/test/activity-monitor.spec.ts +294 -0
  75. package/apps/control-plane/test/adapter-registry.spec.ts +132 -0
  76. package/apps/control-plane/test/batch-operations.spec.ts +112 -0
  77. package/apps/control-plane/test/bootstrap-attach.spec.ts +102 -0
  78. package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +252 -0
  79. package/apps/control-plane/test/bootstrap.spec.ts +560 -0
  80. package/apps/control-plane/test/cleanup-command.spec.ts +301 -0
  81. package/apps/control-plane/test/cli-helpers.spec.ts +404 -1
  82. package/apps/control-plane/test/cli.unit.spec.ts +182 -1
  83. package/apps/control-plane/test/collision-queue.spec.ts +104 -1
  84. package/apps/control-plane/test/core-utils.spec.ts +175 -2
  85. package/apps/control-plane/test/cost-tracking.spec.ts +143 -0
  86. package/apps/control-plane/test/dashboard-api.integration.spec.ts +247 -0
  87. package/apps/control-plane/test/dashboard-client.spec.ts +116 -0
  88. package/apps/control-plane/test/dashboard-command.spec.ts +103 -0
  89. package/apps/control-plane/test/dependency-scheduler.spec.ts +189 -0
  90. package/apps/control-plane/test/epoch-tracking.spec.ts +4 -4
  91. package/apps/control-plane/test/feature-deletion-service.spec.ts +422 -0
  92. package/apps/control-plane/test/feature-lifecycle.spec.ts +202 -0
  93. package/apps/control-plane/test/git-spawn-error.spec.ts +24 -0
  94. package/apps/control-plane/test/incremental-gates.spec.ts +137 -0
  95. package/apps/control-plane/test/init-wizard.spec.ts +506 -0
  96. package/apps/control-plane/test/instance-isolation.spec.ts +83 -0
  97. package/apps/control-plane/test/issue-tracker.spec.ts +890 -0
  98. package/apps/control-plane/test/kernel.coverage.spec.ts +3 -5
  99. package/apps/control-plane/test/kernel.coverage2.spec.ts +871 -0
  100. package/apps/control-plane/test/kernel.spec.ts +13 -11
  101. package/apps/control-plane/test/lock-service.spec.ts +508 -0
  102. package/apps/control-plane/test/mcp-helpers.spec.ts +176 -0
  103. package/apps/control-plane/test/mcp.spec.ts +50 -15
  104. package/apps/control-plane/test/merge-service.spec.ts +67 -4
  105. package/apps/control-plane/test/multi-project.spec.ts +372 -0
  106. package/apps/control-plane/test/notifier-service.spec.ts +388 -0
  107. package/apps/control-plane/test/parallel-gates.spec.ts +312 -0
  108. package/apps/control-plane/test/patch-service.spec.ts +253 -0
  109. package/apps/control-plane/test/performance-analytics.spec.ts +338 -0
  110. package/apps/control-plane/test/planning-wave-executor.spec.ts +168 -0
  111. package/apps/control-plane/test/pr-monitor.spec.ts +385 -0
  112. package/apps/control-plane/test/providers.spec.ts +344 -1
  113. package/apps/control-plane/test/reactions.spec.ts +392 -0
  114. package/apps/control-plane/test/resume-command.spec.ts +390 -0
  115. package/apps/control-plane/test/run-coordinator.spec.ts +481 -2
  116. package/apps/control-plane/test/schema-date-time.spec.ts +46 -0
  117. package/apps/control-plane/test/service-retry-paths.spec.ts +30 -0
  118. package/apps/control-plane/test/services.spec.ts +95 -2
  119. package/apps/control-plane/test/session-management.spec.ts +450 -0
  120. package/apps/control-plane/test/spec-ingestion.spec.ts +190 -0
  121. package/apps/control-plane/test/supervisor-collaborators.spec.ts +699 -2
  122. package/apps/control-plane/test/supervisor.spec.ts +36 -30
  123. package/apps/control-plane/test/supervisor.unit.spec.ts +405 -0
  124. package/apps/control-plane/test/worker-decision-loop.spec.ts +57 -0
  125. package/apps/control-plane/test/workspace-hooks.spec.ts +177 -0
  126. package/apps/control-plane/vitest.config.ts +21 -5
  127. package/dist/apps/control-plane/application/adapters/adapter-registry.d.ts +44 -0
  128. package/dist/apps/control-plane/application/adapters/adapter-registry.js +76 -0
  129. package/dist/apps/control-plane/application/adapters/adapter-registry.js.map +1 -0
  130. package/dist/apps/control-plane/application/multi-project-loader.d.ts +31 -0
  131. package/dist/apps/control-plane/application/multi-project-loader.js +82 -0
  132. package/dist/apps/control-plane/application/multi-project-loader.js.map +1 -0
  133. package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +43 -0
  134. package/dist/apps/control-plane/application/services/activity-monitor-service.js +132 -0
  135. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -0
  136. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +28 -0
  137. package/dist/apps/control-plane/application/services/cost-tracking-service.js +48 -0
  138. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -0
  139. package/dist/apps/control-plane/application/services/dependency-scheduler-service.d.ts +26 -0
  140. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js +75 -0
  141. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js.map +1 -0
  142. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +2 -0
  143. package/dist/apps/control-plane/application/services/feature-deletion-service.js +5 -5
  144. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  145. package/dist/apps/control-plane/application/services/gate-interpolation-service.d.ts +7 -0
  146. package/dist/apps/control-plane/application/services/gate-interpolation-service.js +7 -0
  147. package/dist/apps/control-plane/application/services/gate-interpolation-service.js.map +1 -0
  148. package/dist/apps/control-plane/application/services/gate-service.js +32 -2
  149. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  150. package/dist/apps/control-plane/application/services/instance-isolation-service.d.ts +11 -0
  151. package/dist/apps/control-plane/application/services/instance-isolation-service.js +17 -0
  152. package/dist/apps/control-plane/application/services/instance-isolation-service.js.map +1 -0
  153. package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +65 -0
  154. package/dist/apps/control-plane/application/services/issue-tracker-service.js +358 -0
  155. package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -0
  156. package/dist/apps/control-plane/application/services/merge-service.d.ts +4 -0
  157. package/dist/apps/control-plane/application/services/merge-service.js +44 -2
  158. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  159. package/dist/apps/control-plane/application/services/notifier-service.d.ts +74 -0
  160. package/dist/apps/control-plane/application/services/notifier-service.js +212 -0
  161. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -0
  162. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +39 -0
  163. package/dist/apps/control-plane/application/services/performance-analytics-service.js +75 -0
  164. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -0
  165. package/dist/apps/control-plane/application/services/plan-service.d.ts +1 -0
  166. package/dist/apps/control-plane/application/services/plan-service.js +53 -0
  167. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  168. package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +44 -0
  169. package/dist/apps/control-plane/application/services/pr-monitor-service.js +192 -0
  170. package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -0
  171. package/dist/apps/control-plane/application/services/reactions-service.d.ts +67 -0
  172. package/dist/apps/control-plane/application/services/reactions-service.js +114 -0
  173. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -0
  174. package/dist/apps/control-plane/application/services/reporting-service.d.ts +1 -0
  175. package/dist/apps/control-plane/application/services/reporting-service.js +13 -2
  176. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  177. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +2 -0
  178. package/dist/apps/control-plane/application/services/run-lease-service.js +14 -38
  179. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  180. package/dist/apps/control-plane/application/tools/tool-metadata.js +3 -1
  181. package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
  182. package/dist/apps/control-plane/cli/attach-command-handler.d.ts +12 -0
  183. package/dist/apps/control-plane/cli/attach-command-handler.js +98 -0
  184. package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -0
  185. package/dist/apps/control-plane/cli/cleanup-command-handler.d.ts +12 -0
  186. package/dist/apps/control-plane/cli/cleanup-command-handler.js +162 -0
  187. package/dist/apps/control-plane/cli/cleanup-command-handler.js.map +1 -0
  188. package/dist/apps/control-plane/cli/cli-argument-parser.js +68 -3
  189. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  190. package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +7 -0
  191. package/dist/apps/control-plane/cli/dashboard-command-handler.js +45 -0
  192. package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -0
  193. package/dist/apps/control-plane/cli/help-command-handler.d.ts +8 -0
  194. package/dist/apps/control-plane/cli/help-command-handler.js +146 -0
  195. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -0
  196. package/dist/apps/control-plane/cli/init-command-handler.d.ts +26 -0
  197. package/dist/apps/control-plane/cli/init-command-handler.js +517 -0
  198. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -0
  199. package/dist/apps/control-plane/cli/retry-command-handler.d.ts +8 -0
  200. package/dist/apps/control-plane/cli/retry-command-handler.js +111 -0
  201. package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -0
  202. package/dist/apps/control-plane/cli/run-command-handler.d.ts +5 -0
  203. package/dist/apps/control-plane/cli/run-command-handler.js +82 -3
  204. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  205. package/dist/apps/control-plane/cli/send-command-handler.d.ts +8 -0
  206. package/dist/apps/control-plane/cli/send-command-handler.js +55 -0
  207. package/dist/apps/control-plane/cli/send-command-handler.js.map +1 -0
  208. package/dist/apps/control-plane/cli/status-command-handler.d.ts +12 -1
  209. package/dist/apps/control-plane/cli/status-command-handler.js +55 -2
  210. package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
  211. package/dist/apps/control-plane/cli/types.d.ts +25 -1
  212. package/dist/apps/control-plane/cli/types.js +15 -1
  213. package/dist/apps/control-plane/cli/types.js.map +1 -1
  214. package/dist/apps/control-plane/core/constants.d.ts +6 -0
  215. package/dist/apps/control-plane/core/constants.js +8 -2
  216. package/dist/apps/control-plane/core/constants.js.map +1 -1
  217. package/dist/apps/control-plane/core/error-codes.d.ts +2 -0
  218. package/dist/apps/control-plane/core/error-codes.js +3 -1
  219. package/dist/apps/control-plane/core/error-codes.js.map +1 -1
  220. package/dist/apps/control-plane/core/gates.d.ts +4 -0
  221. package/dist/apps/control-plane/core/gates.js +140 -43
  222. package/dist/apps/control-plane/core/gates.js.map +1 -1
  223. package/dist/apps/control-plane/core/kernel.d.ts +48 -1
  224. package/dist/apps/control-plane/core/kernel.js +218 -5
  225. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  226. package/dist/apps/control-plane/core/path-layout.d.ts +3 -0
  227. package/dist/apps/control-plane/core/path-layout.js +9 -0
  228. package/dist/apps/control-plane/core/path-layout.js.map +1 -1
  229. package/dist/apps/control-plane/core/tool-caller.d.ts +32 -0
  230. package/dist/apps/control-plane/core/tool-caller.js +2 -0
  231. package/dist/apps/control-plane/core/tool-caller.js.map +1 -0
  232. package/dist/apps/control-plane/core/workspace-hooks.d.ts +20 -0
  233. package/dist/apps/control-plane/core/workspace-hooks.js +69 -0
  234. package/dist/apps/control-plane/core/workspace-hooks.js.map +1 -0
  235. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +245 -9
  236. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  237. package/dist/apps/control-plane/providers/providers.d.ts +39 -4
  238. package/dist/apps/control-plane/providers/providers.js +160 -10
  239. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  240. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
  241. package/dist/apps/control-plane/supervisor/build-wave-executor.js +115 -6
  242. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  243. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
  244. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +109 -5
  245. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  246. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +15 -0
  247. package/dist/apps/control-plane/supervisor/run-coordinator.js +132 -6
  248. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  249. package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
  250. package/dist/apps/control-plane/supervisor/runtime.js +110 -6
  251. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  252. package/dist/apps/control-plane/supervisor/types.d.ts +9 -16
  253. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  254. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +3 -0
  255. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +5 -0
  256. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  257. package/eslint.config.mjs +2 -1
  258. package/package.json +12 -2
  259. package/packages/web-dashboard/next-env.d.ts +5 -0
  260. package/packages/web-dashboard/next.config.js +7 -0
  261. package/packages/web-dashboard/package.json +26 -0
  262. package/packages/web-dashboard/src/app/api/actions/route.ts +64 -0
  263. package/packages/web-dashboard/src/app/api/events/route.ts +51 -0
  264. package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +256 -0
  265. package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +10 -0
  266. package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +25 -0
  267. package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +63 -0
  268. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +16 -0
  269. package/packages/web-dashboard/src/app/api/projects/route.ts +31 -0
  270. package/packages/web-dashboard/src/app/api/status/route.ts +15 -0
  271. package/packages/web-dashboard/src/app/globals.css +2 -0
  272. package/packages/web-dashboard/src/app/layout.tsx +15 -0
  273. package/packages/web-dashboard/src/app/page.tsx +393 -0
  274. package/packages/web-dashboard/src/lib/aop-client.ts +244 -0
  275. package/packages/web-dashboard/src/lib/multi-project-config.ts +116 -0
  276. package/packages/web-dashboard/src/lib/orchestrator-tools.ts +284 -0
  277. package/packages/web-dashboard/src/lib/types.ts +58 -0
  278. package/packages/web-dashboard/tsconfig.json +40 -0
  279. package/packages/web-dashboard/vitest.config.ts +6 -0
  280. package/spec-files/completed/agentic_orchestrator_feature_gaps_closure_spec.md +1764 -0
  281. package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +348 -0
  282. package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +344 -0
  283. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +374 -0
  284. package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +1059 -0
  285. package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +466 -0
  286. package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +198 -0
  287. package/spec-files/outstanding/agentic_orchestrator_validator_hardening_spec.md +365 -0
  288. package/spec-files/progress.md +481 -52
  289. /package/spec-files/{agentic_orchestrator_cli_delete_command_spec.md → completed/agentic_orchestrator_cli_delete_command_spec.md} +0 -0
  290. /package/spec-files/{agentic_orchestrator_dot_aop_generated_artifacts_spec.md → completed/agentic_orchestrator_dot_aop_generated_artifacts_spec.md} +0 -0
  291. /package/spec-files/{agentic_orchestrator_mcp_formalization_spec.md → completed/agentic_orchestrator_mcp_formalization_spec.md} +0 -0
  292. /package/spec-files/{agentic_orchestrator_oop_refactor_spec.md → completed/agentic_orchestrator_oop_refactor_spec.md} +0 -0
  293. /package/spec-files/{agentic_orchestrator_single_global_orchestrator_spec.md → completed/agentic_orchestrator_single_global_orchestrator_spec.md} +0 -0
  294. /package/spec-files/{agentic_orchestrator_spec.md → completed/agentic_orchestrator_spec.md} +0 -0
@@ -71,12 +71,17 @@ async function initFeature(kernel: AopKernel, featureId: string) {
71
71
  const init = await kernel.invoke('feature.init', { feature_id: featureId }, { actor_type: 'orchestrator', actor_id: 'test' });
72
72
  expect(init.ok).toBe(true);
73
73
  }
74
-
75
- async function readIndex() {
76
- const raw = await fs.readFile(path.join(repoRoot, '.aop', 'features', 'index.json'), 'utf8');
74
+ async function readRunLease() {
75
+ const leaseFile = path.join(repoRoot, '.aop', 'runtime', 'default', 'run-lease.json');
76
+ const raw = await fs.readFile(leaseFile, 'utf8');
77
77
  return JSON.parse(raw);
78
78
  }
79
79
 
80
+ async function writeRunLease(data: Record<string, unknown>) {
81
+ const leaseFile = path.join(repoRoot, '.aop', 'runtime', 'default', 'run-lease.json');
82
+ await fs.writeFile(leaseFile, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
83
+ }
84
+
80
85
  function expectConcreteWorkerSessions(assignment: Record<string, string>) {
81
86
  expect(assignment.planner_session_id).not.toBe('unassigned');
82
87
  expect(assignment.builder_session_id).not.toBe('unassigned');
@@ -131,8 +136,7 @@ describe('SupervisorRuntime topology', () => {
131
136
 
132
137
  await runtime.start([{ feature_id: 'feature_a' }, { feature_id: 'feature_b' }]);
133
138
 
134
- const index = await readIndex();
135
- const sessions = index.runtime_sessions;
139
+ const sessions = await readRunLease();
136
140
 
137
141
  expect(sessions.orchestrator_session_id).not.toBe('unknown');
138
142
  expectConcreteWorkerSessions(sessions.feature_sessions.feature_a);
@@ -164,8 +168,7 @@ describe('SupervisorRuntime topology', () => {
164
168
 
165
169
  await runtime.start([{ feature_id: 'feature_a' }, { feature_id: 'feature_b' }, { feature_id: 'feature_c' }]);
166
170
 
167
- const index = await readIndex();
168
- const sessions = index.runtime_sessions;
171
+ const sessions = await readRunLease();
169
172
  expect(sessions.orchestrator_session_id).not.toBe('unknown');
170
173
  expectConcreteWorkerSessions(sessions.feature_sessions.feature_a);
171
174
  expectConcreteWorkerSessions(sessions.feature_sessions.feature_b);
@@ -201,8 +204,7 @@ describe('SupervisorRuntime topology', () => {
201
204
  const result = await runtime.start(featureIds.map((feature_id) => ({ feature_id })));
202
205
  expect(result.queue_depth).toBe(2);
203
206
 
204
- const index = await readIndex();
205
- const sessions = index.runtime_sessions;
207
+ const sessions = await readRunLease();
206
208
  const active = ['feature_a', 'feature_b', 'feature_c'];
207
209
  const queued = ['feature_d', 'feature_e'];
208
210
 
@@ -220,6 +222,15 @@ describe('SupervisorRuntime topology', () => {
220
222
 
221
223
  it('promotes queued feature when an admitted feature is already terminal at admission time', async () => {
222
224
  const { kernel, tooling, provider } = await boot();
225
+ kernel.policy.reactions = {
226
+ gate_failed: {
227
+ enabled: false,
228
+ max_retries: 0,
229
+ action: 'notify_only',
230
+ escalate_after: 0,
231
+ retry_delay_ms: 0
232
+ }
233
+ };
223
234
 
224
235
  for (const featureId of ['feature_a', 'feature_b']) {
225
236
  await writeFeatureSpec(repoRoot, featureId);
@@ -264,8 +275,7 @@ describe('SupervisorRuntime topology', () => {
264
275
  const result = await runtime.start([{ feature_id: 'feature_a' }, { feature_id: 'feature_b' }]);
265
276
  expect(result.queue_depth).toBe(0);
266
277
 
267
- const index = await readIndex();
268
- const sessions = index.runtime_sessions;
278
+ const sessions = await readRunLease();
269
279
  expectConcreteWorkerSessions(sessions.feature_sessions.feature_b);
270
280
  expect(sessions.feature_sessions.feature_a).toBeUndefined();
271
281
  expect(countAllocatedSessionIds(sessions)).toBe(4);
@@ -287,12 +297,11 @@ describe('SupervisorRuntime topology', () => {
287
297
  });
288
298
 
289
299
  await runtime.start(featureIds.map((feature_id) => ({ feature_id })));
290
- const firstIndex = await readIndex();
291
- expect(Object.keys(firstIndex.runtime_sessions.feature_sessions).sort()).toEqual(featureIds);
300
+ const firstLease = await readRunLease();
301
+ expect(Object.keys(firstLease.feature_sessions).sort()).toEqual(featureIds);
292
302
 
293
303
  await runtime.start([{ feature_id: 'feature_a' }, { feature_id: 'feature_b' }]);
294
- const secondIndex = await readIndex();
295
- const secondSessions = secondIndex.runtime_sessions;
304
+ const secondSessions = await readRunLease();
296
305
 
297
306
  expect(Object.keys(secondSessions.feature_sessions).sort()).toEqual(['feature_a', 'feature_b']);
298
307
  expect(countAllocatedSessionIds(secondSessions)).toBe(7);
@@ -336,10 +345,9 @@ describe('SupervisorRuntime topology', () => {
336
345
  });
337
346
  await first.start([{ feature_id: 'feature_takeover' }]);
338
347
 
339
- const indexPath = path.join(repoRoot, '.aop', 'features', 'index.json');
340
- const index = await readIndex();
341
- index.runtime_sessions.lease_expires_at = new Date(Date.now() - 60_000).toISOString();
342
- await fs.writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`, 'utf8');
348
+ const lease = await readRunLease();
349
+ lease.lease_expires_at = new Date(Date.now() - 60_000).toISOString();
350
+ await writeRunLease(lease);
343
351
 
344
352
  const withoutTakeover = new SupervisorRuntime(kernel, provider, tooling.inProcessClient, {
345
353
  run_id: 'run:stale-target-no-takeover',
@@ -363,9 +371,9 @@ describe('SupervisorRuntime topology', () => {
363
371
 
364
372
  await withTakeover.start([{ feature_id: 'feature_takeover' }]);
365
373
 
366
- const postTakeover = await readIndex();
367
- expect(postTakeover.runtime_sessions.run_id).toBe('run:stale-target-with-takeover');
368
- expect(postTakeover.runtime_sessions.owner_instance_id).toContain('supervisor:');
374
+ const postTakeover = await readRunLease();
375
+ expect(postTakeover.run_id).toBe('run:stale-target-with-takeover');
376
+ expect(postTakeover.owner_instance_id).toContain('supervisor:');
369
377
  });
370
378
 
371
379
  it('recreates orchestrator on timed-out reattach with epoch increment and orphan audit metadata', async () => {
@@ -388,18 +396,16 @@ describe('SupervisorRuntime topology', () => {
388
396
  });
389
397
 
390
398
  await runtime.start([{ feature_id: 'feature_recovery' }]);
391
- const firstIndex = await readIndex();
392
- const firstOrchestratorSession = firstIndex.runtime_sessions.orchestrator_session_id;
399
+ const firstLease = await readRunLease();
400
+ const firstOrchestratorSession = firstLease.orchestrator_session_id;
393
401
 
394
- const indexPath = path.join(repoRoot, '.aop', 'features', 'index.json');
395
- firstIndex.runtime_sessions.lease_expires_at = new Date(Date.now() - 60_000).toISOString();
396
- await fs.writeFile(indexPath, `${JSON.stringify(firstIndex, null, 2)}\n`, 'utf8');
402
+ firstLease.lease_expires_at = new Date(Date.now() - 60_000).toISOString();
403
+ await writeRunLease(firstLease);
397
404
 
398
405
  await runtime.start([{ feature_id: 'feature_recovery' }]);
399
406
 
400
- const recoveredIndex = await readIndex();
401
- const sessions = recoveredIndex.runtime_sessions;
402
- expect(sessions.orchestrator_epoch).toBe(firstIndex.runtime_sessions.orchestrator_epoch + 1);
407
+ const sessions = await readRunLease();
408
+ expect(sessions.orchestrator_epoch).toBe(firstLease.orchestrator_epoch + 1);
403
409
  expect(sessions.orchestrator_session_id).not.toBe(firstOrchestratorSession);
404
410
  expect(provider.orchestratorSessionIds).toHaveLength(2);
405
411
  expect(provider.orchestratorSessionIds[0]).toBe(firstOrchestratorSession);
@@ -520,3 +520,408 @@ describe('SupervisorRuntime unit behavior', () => {
520
520
  vi.restoreAllMocks();
521
521
  });
522
522
  });
523
+
524
+ describe('SupervisorRuntime delegation methods', () => {
525
+ it('GIVEN_policy_with_agent_idle_threshold_WHEN_runtime_created_THEN_applies_configured_idle_threshold', () => {
526
+ const { kernel, provider, toolClient } = makeRuntime();
527
+ kernel.getPolicySnapshot.mockReturnValue({
528
+ recovery: { orchestrator_session_reattach_timeout_ms: 5000, orphan_session_cleanup_enabled: true },
529
+ supervisor: { agent_idle_threshold_ms: 60000 }
530
+ });
531
+
532
+ expect(() => new SupervisorRuntime(kernel as any, provider as any, toolClient as any)).not.toThrow();
533
+ });
534
+
535
+ it('GIVEN_planner_role_with_sessions_WHEN_calling_tool_THEN_uses_planner_session', async () => {
536
+ const { runtime, toolClient } = makeRuntime();
537
+ runtime.sessionsByFeature.set('feature_a', {
538
+ planner: 'planner-session',
539
+ builder: 'builder-session',
540
+ qa: 'qa-session'
541
+ });
542
+ toolClient.call.mockResolvedValueOnce({ ok: true, data: { result: 'plan-submitted' } });
543
+
544
+ await runtime.callTool('planner', TOOLS.PLAN_SUBMIT, { feature_id: 'feature_a', plan_json: {} });
545
+
546
+ expect(toolClient.call).toHaveBeenCalledWith(
547
+ TOOLS.PLAN_SUBMIT,
548
+ expect.anything(),
549
+ expect.objectContaining({ session_id: 'planner-session', actor_type: 'planner' })
550
+ );
551
+ });
552
+
553
+ it('GIVEN_qa_role_with_sessions_WHEN_calling_tool_THEN_uses_qa_session', async () => {
554
+ const { runtime, toolClient } = makeRuntime();
555
+ runtime.sessionsByFeature.set('feature_a', {
556
+ planner: 'planner-session',
557
+ builder: 'builder-session',
558
+ qa: 'qa-session'
559
+ });
560
+ toolClient.call.mockResolvedValueOnce({ ok: true, data: {} });
561
+
562
+ await runtime.callTool('qa', TOOLS.GATES_RUN, { feature_id: 'feature_a', mode: 'full' });
563
+
564
+ expect(toolClient.call).toHaveBeenCalledWith(
565
+ TOOLS.GATES_RUN,
566
+ expect.anything(),
567
+ expect.objectContaining({ session_id: 'qa-session', actor_type: 'qa' })
568
+ );
569
+ });
570
+
571
+ it('GIVEN_orchestrator_role_WHEN_calling_tool_THEN_uses_orchestrator_session', async () => {
572
+ const { runtime, toolClient } = makeRuntime();
573
+ runtime.orchestratorSessionId = 'orch-main';
574
+ toolClient.call.mockResolvedValueOnce({ ok: true, data: {} });
575
+
576
+ await runtime.callTool('orchestrator', TOOLS.FEATURE_LOG_APPEND, { feature_id: 'global', note: 'test' });
577
+
578
+ expect(toolClient.call).toHaveBeenCalledWith(
579
+ TOOLS.FEATURE_LOG_APPEND,
580
+ expect.anything(),
581
+ expect.objectContaining({ session_id: 'orch-main', actor_type: 'orchestrator' })
582
+ );
583
+ });
584
+
585
+ it('GIVEN_no_feature_id_in_args_WHEN_calling_tool_THEN_uses_bootstrap_session', async () => {
586
+ const { runtime, toolClient } = makeRuntime();
587
+ toolClient.call.mockResolvedValueOnce({ ok: true, data: {} });
588
+
589
+ await runtime.callTool('builder', TOOLS.FEATURE_STATE_GET, {});
590
+
591
+ expect(toolClient.call).toHaveBeenCalledWith(
592
+ TOOLS.FEATURE_STATE_GET,
593
+ expect.anything(),
594
+ expect.objectContaining({ session_id: 'bootstrap:builder' })
595
+ );
596
+ });
597
+
598
+ it('GIVEN_feature_not_in_sessions_map_WHEN_calling_tool_THEN_uses_bootstrap_session', async () => {
599
+ const { runtime, toolClient } = makeRuntime();
600
+ toolClient.call.mockResolvedValueOnce({ ok: true, data: {} });
601
+
602
+ await runtime.callTool('planner', TOOLS.FEATURE_STATE_GET, { feature_id: 'feature_not_tracked' });
603
+
604
+ expect(toolClient.call).toHaveBeenCalledWith(
605
+ TOOLS.FEATURE_STATE_GET,
606
+ expect.anything(),
607
+ expect.objectContaining({ session_id: 'bootstrap:planner' })
608
+ );
609
+ });
610
+
611
+ it('GIVEN_unknown_role_with_sessions_WHEN_calling_tool_THEN_uses_bootstrap_session', async () => {
612
+ const { runtime, toolClient } = makeRuntime();
613
+ runtime.sessionsByFeature.set('feature_a', {
614
+ planner: 'planner-session',
615
+ builder: 'builder-session',
616
+ qa: 'qa-session'
617
+ });
618
+ toolClient.call.mockResolvedValueOnce({ ok: true, data: {} });
619
+
620
+ await runtime.callTool('unknown' as never, TOOLS.FEATURE_STATE_GET, { feature_id: 'feature_a' });
621
+
622
+ expect(toolClient.call).toHaveBeenCalledWith(
623
+ TOOLS.FEATURE_STATE_GET,
624
+ expect.anything(),
625
+ expect.objectContaining({ session_id: 'bootstrap:unknown' })
626
+ );
627
+ });
628
+
629
+ it('GIVEN_feature_id_WHEN_running_post_qa_reconciliation_wave_THEN_delegates_to_planning_executor', async () => {
630
+ const { runtime } = makeRuntime();
631
+ const callTool = vi.spyOn(runtime, 'callTool');
632
+ callTool.mockImplementation(async (_role, toolName) => {
633
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
634
+ return {
635
+ ok: true,
636
+ data: {
637
+ state: { front_matter: { status: STATUS.QA } },
638
+ plan: { plan_version: 1, acceptance_criteria: [], risk: [] },
639
+ spec: '',
640
+ qa_test_index: { summary: { pending: 0, failed: 0, running: 0 } },
641
+ latest_evidence: {}
642
+ }
643
+ } as any;
644
+ }
645
+ return { ok: true, data: {} } as any;
646
+ });
647
+
648
+ await runtime.runPostQaReconciliationWave(['feature_a'], 1);
649
+
650
+ expect(callTool).toHaveBeenCalledWith('planner', TOOLS.FEATURE_GET_CONTEXT, { feature_id: 'feature_a' });
651
+ });
652
+
653
+ it('GIVEN_orphan_cleanup_disabled_WHEN_reattach_fails_THEN_skips_orphan_log', async () => {
654
+ const { runtime, kernel, provider, toolClient } = makeRuntime();
655
+ kernel.getPolicySnapshot.mockReturnValue({
656
+ recovery: {
657
+ orchestrator_session_reattach_timeout_ms: 10,
658
+ orphan_session_cleanup_enabled: false
659
+ }
660
+ });
661
+ kernel.getRuntimeSessions.mockResolvedValue({
662
+ run_id: 'run:test',
663
+ owner_instance_id: 'owner:test',
664
+ orchestrator_session_id: 'orch-old',
665
+ orchestrator_epoch: 4,
666
+ feature_sessions: {}
667
+ });
668
+ provider.reattachSession.mockImplementation(
669
+ () => new Promise(() => { /* intentionally unresolved to force timeout */ })
670
+ );
671
+ provider.createSession.mockResolvedValue({ session_id: 'orch-new' });
672
+
673
+ await runtime.ensureGlobalOrchestratorSession();
674
+
675
+ expect(runtime.orchestratorSessionId).toBe('orch-new');
676
+ const logCalls = toolClient.call.mock.calls.filter((call: unknown[]) => call[0] === TOOLS.FEATURE_LOG_APPEND);
677
+ expect(logCalls).toHaveLength(0);
678
+ });
679
+
680
+ it('GIVEN_feature_id_WHEN_initializeFeatureCluster_called_THEN_delegates_to_session_orchestrator', async () => {
681
+ const { runtime, provider, toolClient } = makeRuntime();
682
+ runtime.orchestratorSessionId = 'orch-main';
683
+ provider.createSession.mockResolvedValue({ session_id: 'created-session' });
684
+ vi.spyOn(runtime, 'loadRolePrompts').mockResolvedValue({
685
+ planner: { system: 'planner-prompt', tools: [], model_hint: null },
686
+ builder: { system: 'builder-prompt', tools: [], model_hint: null },
687
+ qa: { system: 'qa-prompt', tools: [], model_hint: null }
688
+ } as never);
689
+ toolClient.call.mockResolvedValue({ ok: true, data: { front_matter: { version: 1, cluster: {} } } });
690
+
691
+ await runtime.initializeFeatureCluster('feature_init');
692
+
693
+ // createSession should have been called for planner, builder, qa roles
694
+ expect(provider.createSession).toHaveBeenCalledTimes(3);
695
+ });
696
+
697
+ it('GIVEN_cluster_patch_WHEN_patchFeatureCluster_called_THEN_delegates_to_session_orchestrator', async () => {
698
+ const { runtime, toolClient } = makeRuntime();
699
+ toolClient.call.mockResolvedValue({ ok: true, data: { front_matter: { version: 1, cluster: {} } } });
700
+
701
+ await runtime.patchFeatureCluster('feature_patch', { planner: 'planner-session', builder: 'builder-session', qa: 'qa-session' });
702
+
703
+ expect(toolClient.call).toHaveBeenCalledWith(
704
+ TOOLS.FEATURE_STATE_PATCH,
705
+ expect.objectContaining({ feature_id: 'feature_patch' }),
706
+ expect.any(Object)
707
+ );
708
+ });
709
+
710
+ it('GIVEN_runtime_WHEN_ensureGlobalOrchestratorSession_called_THEN_creates_orchestrator_session', async () => {
711
+ const { runtime, provider, kernel } = makeRuntime();
712
+ kernel.getRuntimeSessions.mockResolvedValue({
713
+ run_id: 'none',
714
+ owner_instance_id: 'none',
715
+ orchestrator_session_id: null,
716
+ orchestrator_epoch: 0,
717
+ feature_sessions: {}
718
+ });
719
+ provider.createSession.mockResolvedValue({ session_id: 'new-orch-session', role: 'orchestrator', feature_id: 'global', system_prompt_loaded: false });
720
+
721
+ await runtime.ensureGlobalOrchestratorSession();
722
+ expect(provider.createSession).toHaveBeenCalled();
723
+ });
724
+
725
+ it('GIVEN_active_feature_ids_WHEN_cleanupOrphanWorkerSessions_called_THEN_delegates_to_session_orchestrator', async () => {
726
+ const { runtime, kernel } = makeRuntime();
727
+ kernel.getRuntimeSessions.mockResolvedValue({
728
+ run_id: 'run:test',
729
+ owner_instance_id: 'owner:test',
730
+ orchestrator_session_id: 'orch-session',
731
+ orchestrator_epoch: 1,
732
+ feature_sessions: {}
733
+ });
734
+
735
+ // Should complete without throwing
736
+ await expect(runtime.cleanupOrphanWorkerSessions(['feature_a', 'feature_b'])).resolves.toBeUndefined();
737
+ });
738
+
739
+ it('GIVEN_queue_WHEN_reconcileQueuedFeatures_called_THEN_delegates_to_session_orchestrator', async () => {
740
+ const { runtime } = makeRuntime();
741
+ // reconcileQueuedFeatures delegates to sessionOrchestrator using internal queue
742
+ await expect(runtime.reconcileQueuedFeatures()).resolves.toBeUndefined();
743
+ });
744
+
745
+ it('GIVEN_prompts_cached_WHEN_loadRolePrompts_called_THEN_returns_prompt_bundle', async () => {
746
+ const { runtime } = makeRuntime();
747
+ vi.spyOn(runtime, 'loadRolePrompts').mockResolvedValue({
748
+ planner: { system: 'p', tools: [], model_hint: null },
749
+ builder: { system: 'b', tools: [], model_hint: null },
750
+ qa: { system: 'q', tools: [], model_hint: null }
751
+ } as never);
752
+
753
+ const bundle = await runtime.loadRolePrompts();
754
+ expect(bundle.planner.system).toBe('p');
755
+ });
756
+
757
+ it('GIVEN_runtime_WHEN_sessionsByFeature_set_THEN_roundtrips_value', () => {
758
+ const { runtime } = makeRuntime();
759
+ const newMap = new Map([['feat1', { planner: 's1', builder: 's2', qa: 's3' }]]);
760
+ runtime.sessionsByFeature = newMap as never;
761
+ expect(runtime.sessionsByFeature.get('feat1')).toEqual({ planner: 's1', builder: 's2', qa: 's3' });
762
+ });
763
+
764
+ it('GIVEN_runtime_WHEN_queue_set_THEN_roundtrips_value', () => {
765
+ const { runtime } = makeRuntime();
766
+ const newQueue = [{ feature_id: 'q1' }, { feature_id: 'q2' }];
767
+ runtime.queue = newQueue as never;
768
+ expect(runtime.queue).toHaveLength(2);
769
+ expect(runtime.queue[0]).toEqual({ feature_id: 'q1' });
770
+ });
771
+
772
+ it('GIVEN_runtime_WHEN_runMetadata_set_and_get_THEN_roundtrips_value', () => {
773
+ const { runtime } = makeRuntime();
774
+ const meta = { run_id: 'abc', custom_field: 42 };
775
+ runtime.runMetadata = meta;
776
+ expect(runtime.runMetadata).toEqual(meta);
777
+ });
778
+
779
+ it('GIVEN_runtime_WHEN_promptsCache_set_and_get_THEN_roundtrips_value', () => {
780
+ const { runtime } = makeRuntime();
781
+ const bundle = {
782
+ planner: { system: 'planner-sys', tools: [], model_hint: null },
783
+ builder: { system: 'builder-sys', tools: [], model_hint: null },
784
+ qa: { system: 'qa-sys', tools: [], model_hint: null }
785
+ };
786
+ runtime.promptsCache = bundle as never;
787
+ expect(runtime.promptsCache).toEqual(bundle);
788
+ });
789
+
790
+ it('GIVEN_runtime_WHEN_promptsCache_set_to_null_THEN_returns_null', () => {
791
+ const { runtime } = makeRuntime();
792
+ runtime.promptsCache = null;
793
+ expect(runtime.promptsCache).toBeNull();
794
+ });
795
+
796
+ it('GIVEN_provider_with_config_ref_WHEN_providerConfigRefHash_called_THEN_returns_stable_hash', () => {
797
+ const { runtime, provider } = makeRuntime();
798
+ provider.selection.provider_config_ref = '/path/to/config.yaml';
799
+ const hash = runtime.providerConfigRefHash();
800
+ expect(typeof hash).toBe('string');
801
+ expect(hash.length).toBeGreaterThan(0);
802
+ // Calling again returns the same value
803
+ expect(runtime.providerConfigRefHash()).toBe(hash);
804
+ });
805
+
806
+ it('GIVEN_provider_with_null_config_ref_WHEN_providerConfigRefHash_called_THEN_returns_hash_of_none', () => {
807
+ const { runtime, provider } = makeRuntime();
808
+ provider.selection.provider_config_ref = null;
809
+ const hash = runtime.providerConfigRefHash();
810
+ expect(typeof hash).toBe('string');
811
+ expect(hash.length).toBeGreaterThan(0);
812
+ });
813
+
814
+ it('GIVEN_cli_override_iterations_WHEN_runtime_created_THEN_uses_cli_value', () => {
815
+ const { runtime } = makeRuntime({ max_iterations_per_phase: 12 });
816
+ // The resolved value is stored internally; verifying via runMetadata isn't needed
817
+ // but the construction should not throw
818
+ expect(runtime).toBeDefined();
819
+ });
820
+
821
+ it('GIVEN_policy_max_iterations_WHEN_no_cli_override_THEN_uses_policy_value', () => {
822
+ const { kernel } = makeRuntime();
823
+ kernel.getPolicySnapshot.mockReturnValue({
824
+ recovery: { orchestrator_session_reattach_timeout_ms: 5000, orphan_session_cleanup_enabled: true },
825
+ supervisor: { max_iterations_per_phase: 10, max_parallel_gate_runs: 2 }
826
+ });
827
+ const runtime2 = new SupervisorRuntime(kernel as any, { selection: { provider: 'custom', model: 'm', provider_config_ref: null }, createSession: vi.fn(), reattachSession: vi.fn(), closeSession: vi.fn() } as any, { call: vi.fn() } as any, { run_id: 'r', owner_instance_id: 'o' });
828
+ expect(runtime2).toBeDefined();
829
+ });
830
+
831
+ afterEach(() => {
832
+ vi.restoreAllMocks();
833
+ });
834
+ });
835
+
836
+ describe('PromptBundleLoader branches', () => {
837
+ it('GIVEN_behavior_error_WHEN_prompt_file_missing_THEN_throws_MISSING_ROLE_PROMPT', async () => {
838
+ const { PromptBundleLoader } = await import('../src/supervisor/prompt-bundle-loader.js');
839
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-prompt-'));
840
+ const reader = {
841
+ getRepoRoot: () => tmpDir,
842
+ getAgentsConfig: () => ({
843
+ roles: {
844
+ planner: { system_prompt_path: 'prompts/planner.md' }
845
+ },
846
+ missing_prompt_behavior: 'error'
847
+ })
848
+ };
849
+ const loader = new PromptBundleLoader(reader as never);
850
+ await expect(loader.loadRolePrompts()).rejects.toMatchObject({ code: ERROR_CODES.MISSING_ROLE_PROMPT });
851
+ await fs.rm(tmpDir, { recursive: true, force: true });
852
+ });
853
+ });
854
+
855
+ describe('BuildWaveExecutor branches', () => {
856
+ it('GIVEN_feature_in_building_status_WHEN_gate_passes_THEN_completes_without_retry', async () => {
857
+ const { BuildWaveExecutor } = await import('../src/supervisor/build-wave-executor.js');
858
+ const toolCaller = {
859
+ callTool: vi.fn(async (_role: string, toolName: string, _args?: Record<string, unknown>) => {
860
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
861
+ return { ok: true, data: { front_matter: { status: STATUS.BUILDING, gate_retry_count: 0 } } };
862
+ }
863
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
864
+ return { ok: true, data: { plan: null } };
865
+ }
866
+ if (toolName === TOOLS.GATES_RUN) {
867
+ return { ok: true, data: { overall: 'pass', evidence_path: 'evidence.json' } };
868
+ }
869
+ return { ok: true, data: {} };
870
+ })
871
+ };
872
+ const executor = new BuildWaveExecutor({ toolCaller: toolCaller as never });
873
+ await expect(executor.run(['feature_a'], 5)).resolves.toBeUndefined();
874
+ const gateRuns = toolCaller.callTool.mock.calls.filter((c) => c[1] === TOOLS.GATES_RUN);
875
+ expect(gateRuns).toHaveLength(1);
876
+ });
877
+ });
878
+
879
+ describe('QaWaveExecutor gate run throws exception', () => {
880
+ it('GIVEN_gates_run_throws_WHEN_qa_wave_executed_THEN_gate_overall_is_FAIL', async () => {
881
+ const { QaWaveExecutor } = await import('../src/supervisor/qa-wave-executor.js');
882
+ const sessionsByFeature = new Map<string, Record<string, string>>();
883
+ sessionsByFeature.set('feature_qa', { planner: 's1', builder: 's2', qa: 's3' });
884
+ const toolCaller = {
885
+ callTool: vi.fn(async (_role: string, toolName: string) => {
886
+ if (toolName === TOOLS.FEATURE_STATE_GET) {
887
+ return { ok: true, data: { front_matter: { status: STATUS.QA, gate_retry_count: 0 } } };
888
+ }
889
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
890
+ return { ok: true, data: { plan: null } };
891
+ }
892
+ if (toolName === TOOLS.GATES_RUN) {
893
+ throw new Error('gate execution failed');
894
+ }
895
+ return { ok: true, data: {} };
896
+ })
897
+ };
898
+ const state = {
899
+ runId: 'run:test',
900
+ ownerInstanceId: 'owner:test',
901
+ orchestratorSessionId: 'orch-sess',
902
+ sessionsByFeature
903
+ };
904
+ const kernel = {
905
+ updateFeatureSessionAssignment: vi.fn(async () => ({ data: { updated: true } }))
906
+ };
907
+ const provider = {
908
+ closeSession: vi.fn(async () => ({ closed: true })),
909
+ createSession: vi.fn(async () => ({ session_id: 'new-session' }))
910
+ };
911
+ const promptProvider = {
912
+ loadRolePrompts: vi.fn(async () => ({ planner: null, builder: null, qa: null }))
913
+ };
914
+ const featureClusterPatcher = {
915
+ patchFeatureCluster: vi.fn(async () => ({}))
916
+ };
917
+ const executor = new QaWaveExecutor({
918
+ kernel: kernel as never,
919
+ provider: provider as never,
920
+ toolCaller: toolCaller as never,
921
+ promptProvider: promptProvider as never,
922
+ featureClusterPatcher: featureClusterPatcher as never,
923
+ state: state as never
924
+ });
925
+ await expect(executor.run(['feature_qa'], 5)).resolves.toBeUndefined();
926
+ });
927
+ });
@@ -585,3 +585,60 @@ describe('WorkerDecisionLoop', () => {
585
585
  expect(result.priorityOrder).toEqual(['feature_c', 'feature_a']);
586
586
  });
587
587
  });
588
+
589
+ describe('WorkerDecisionLoop amend_plan revision_of branches', () => {
590
+ function makeProvider(runWorkerResult: Record<string, unknown>) {
591
+ return {
592
+ selection: { provider: 'custom', model: 'model-test', provider_config_ref: null },
593
+ runWorker: vi.fn(async () => runWorkerResult)
594
+ };
595
+ }
596
+
597
+ function makeToolCaller() {
598
+ return {
599
+ callTool: vi.fn(async (_role: string, toolName: string) => {
600
+ if (toolName === TOOLS.FEATURE_GET_CONTEXT) {
601
+ return { ok: true, data: { refreshed: true } };
602
+ }
603
+ return { ok: true, data: { accepted: true } };
604
+ })
605
+ };
606
+ }
607
+
608
+ it('GIVEN_amend_plan_with_non_integer_revision_of_WHEN_existing_version_present_THEN_falls_back_to_existing_version', async () => {
609
+ const provider = makeProvider({
610
+ type: 'REQUEST',
611
+ request: {
612
+ action: 'amend_plan',
613
+ expected_plan_version: 'not-a-number',
614
+ plan_json: {
615
+ feature_id: 'feature_a',
616
+ plan_version: 3
617
+ }
618
+ }
619
+ });
620
+ const toolCaller = makeToolCaller();
621
+ const loop = new WorkerDecisionLoop({
622
+ provider: provider as never,
623
+ toolCaller: toolCaller as never
624
+ });
625
+
626
+ const result = await loop.execute({
627
+ role: 'planner',
628
+ featureId: 'feature_a',
629
+ contextBundle: { plan: { plan_version: 2 } },
630
+ instructions: 'amend'
631
+ });
632
+
633
+ expect(result.requestHandled).toBe(true);
634
+ expect(result.planSubmission).toBe(true);
635
+ expect(toolCaller.callTool).toHaveBeenCalledWith(
636
+ 'planner',
637
+ TOOLS.PLAN_UPDATE,
638
+ expect.objectContaining({
639
+ feature_id: 'feature_a',
640
+ expected_plan_version: 2
641
+ })
642
+ );
643
+ });
644
+ });