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,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { afterEach, describe, expect, it } from 'vitest';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
5
  import { LockService } from '../src/application/services/lock-service.js';
6
6
  import { ReportingService } from '../src/application/services/reporting-service.js';
7
7
  import { RunLeaseService } from '../src/application/services/run-lease-service.js';
@@ -80,6 +80,8 @@ function makeRunLeaseHarness(initialIndex: AnyRecord = makeIndex()) {
80
80
  lease_expires_at: at ?? new Date().toISOString()
81
81
  }) as RuntimeSessionsSnapshot;
82
82
 
83
+ let runLease: RuntimeSessionsSnapshot = clone(initialIndex.runtime_sessions ?? makeRuntimeSessions());
84
+
83
85
  const service = new RunLeaseService({
84
86
  runLeaseTtlSeconds: () => 30,
85
87
  withIndexLock: async <T>(operation: () => Promise<T>) => await operation(),
@@ -88,6 +90,10 @@ function makeRunLeaseHarness(initialIndex: AnyRecord = makeIndex()) {
88
90
  index = clone(nextIndex);
89
91
  writes.push(clone(nextIndex));
90
92
  },
93
+ readRunLease: async () => clone(runLease),
94
+ writeRunLease: async (data: RuntimeSessionsSnapshot) => {
95
+ runLease = clone(data);
96
+ },
91
97
  normalizeRuntimeSessions,
92
98
  isRunLeaseFresh: (runtimeSessions: AnyRecord) => new Date(runtimeSessions.lease_expires_at).getTime() > Date.now(),
93
99
  emptyRuntimeSessions
@@ -96,6 +102,7 @@ function makeRunLeaseHarness(initialIndex: AnyRecord = makeIndex()) {
96
102
  return {
97
103
  service,
98
104
  getIndex: () => clone(index),
105
+ getRunLease: () => clone(runLease),
99
106
  writes
100
107
  };
101
108
  }
@@ -218,6 +225,7 @@ async function makeReportingHarness() {
218
225
  }),
219
226
  readIndex: async () => clone(index),
220
227
  statePath: (featureId: string) => path.join(tmpDir, featureId, 'state.md'),
228
+ featureCostPath: (featureId: string) => path.join(tmpDir, featureId, 'cost.json'),
221
229
  readState: async (featureId: string) => ({
222
230
  frontMatter: clone(stateMap.get(featureId) ?? {})
223
231
  }),
@@ -379,7 +387,7 @@ describe('RunLeaseService', () => {
379
387
 
380
388
  const released = await harness.service.releaseRunLease('run:owner', 'owner:owner');
381
389
  expect(released.data.released).toBe(true);
382
- expect(harness.getIndex().runtime_sessions.run_id).toBe('none');
390
+ expect(harness.getRunLease().run_id).toBe('none');
383
391
  });
384
392
 
385
393
  it('reuses existing owner metadata and rejects not-owned lease operations', async () => {
@@ -691,3 +699,88 @@ describe('ReportingService', () => {
691
699
  expect(summary.data.qa_summary.pending_required).toBe(0);
692
700
  });
693
701
  });
702
+
703
+ describe('ReportingService worktree_branch coverage', () => {
704
+ const cleanups: Array<() => Promise<void>> = [];
705
+
706
+ afterEach(async () => {
707
+ while (cleanups.length > 0) {
708
+ const cleanup = cleanups.pop();
709
+ if (cleanup) {await cleanup();}
710
+ }
711
+ });
712
+
713
+ it('GIVEN_worktree_branch_string_in_frontMatter_WHEN_reportDashboard_THEN_uses_worktree_branch', async () => {
714
+ const harness = await makeReportingHarness();
715
+ cleanups.push(harness.cleanup);
716
+
717
+ await harness.ensureState('feature_a', {
718
+ status: STATUS.BUILDING,
719
+ worktree_branch: 'feature/my-branch',
720
+ locks: { held: [] },
721
+ gate_profile: 'default',
722
+ gates: { plan: 'pass' },
723
+ last_updated: '2026-01-01T00:00:00.000Z'
724
+ });
725
+
726
+ const dashboard = await harness.service.reportDashboard();
727
+ const feat = dashboard.data.features.find((f: AnyRecord) => f.feature_id === 'feature_a');
728
+ expect(feat?.branch).toBe('feature/my-branch');
729
+ });
730
+ });
731
+
732
+ describe('LockService sleep branch coverage', () => {
733
+ const cleanups: Array<() => Promise<void>> = [];
734
+
735
+ afterEach(async () => {
736
+ while (cleanups.length > 0) {
737
+ const cleanup = cleanups.pop();
738
+ if (cleanup) {await cleanup();}
739
+ }
740
+ vi.useRealTimers();
741
+ });
742
+
743
+ it('GIVEN_lock_held_and_wait_mode_WHEN_locksAcquire_called_THEN_sleep_is_called_and_times_out', async () => {
744
+ vi.useFakeTimers();
745
+
746
+ const harness = await makeLockHarness({
747
+ policy: {
748
+ locks: {
749
+ acquire_behavior: 'wait',
750
+ default_wait_timeout_seconds: 10,
751
+ acquire_backoff: {
752
+ initial_ms: 0,
753
+ max_ms: 0,
754
+ multiplier: 1,
755
+ jitter_ms: 0
756
+ }
757
+ }
758
+ },
759
+ states: {
760
+ feature_a: { version: 1, status: STATUS.PLANNING, locks: { held: [] } }
761
+ },
762
+ index: makeIndex({
763
+ locks: { openapi: 'feature_other', db_migrations: null },
764
+ lock_leases: {
765
+ openapi: {
766
+ holder: 'feature_other',
767
+ lease_id: 'lease-sleep-test',
768
+ expires_at: new Date(Date.now() + 60_000).toISOString()
769
+ },
770
+ db_migrations: null
771
+ }
772
+ })
773
+ });
774
+ cleanups.push(harness.cleanup);
775
+
776
+ // Start acquisition in background - it will call sleep() on first blocked iteration
777
+ const acquirePromise = harness.service.locksAcquire('openapi', 'feature_a', 10);
778
+
779
+ // Advance time past the timeout to resolve the sleep and trigger timeout
780
+ vi.advanceTimersByTime(15_000);
781
+
782
+ await expect(acquirePromise).rejects.toMatchObject({
783
+ normalizedResponse: { error: { code: ERROR_CODES.LOCK_CONFLICT } }
784
+ });
785
+ });
786
+ });
@@ -0,0 +1,450 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import { AopKernel } from '../src/index.js';
5
+ import { SendCommandHandler } from '../src/cli/send-command-handler.js';
6
+ import { AttachCommandHandler } from '../src/cli/attach-command-handler.js';
7
+ import { ERROR_CODES } from '../src/core/error-codes.js';
8
+ import { TOOLS } from '../src/core/constants.js';
9
+ import { makeTempRepo, writeFeatureSpec } from './helpers.js';
10
+ import type { WorkerProvider } from '../src/index.js';
11
+
12
+ let repoRoot: string;
13
+
14
+ beforeEach(async () => {
15
+ repoRoot = await makeTempRepo(process.cwd());
16
+ });
17
+
18
+ async function writeFeatureSession(root: string, featureId: string): Promise<void> {
19
+ const leasePath = path.join(root, '.aop', 'runtime', 'default', 'run-lease.json');
20
+ await fs.mkdir(path.dirname(leasePath), { recursive: true });
21
+ let lease: Record<string, unknown> = {};
22
+ try {
23
+ lease = JSON.parse(await fs.readFile(leasePath, 'utf8')) as Record<string, unknown>;
24
+ } catch {
25
+ // file may not exist yet
26
+ }
27
+ lease.orchestrator_session_id = 'orch:session-1';
28
+ lease.feature_sessions = {
29
+ ...((lease.feature_sessions as Record<string, unknown>) ?? {}),
30
+ [featureId]: {
31
+ planner_session_id: 'planner:1',
32
+ builder_session_id: 'builder:1',
33
+ qa_session_id: 'qa:1'
34
+ }
35
+ };
36
+ await fs.writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, 'utf8');
37
+ }
38
+
39
+ function makeProvider(overrides: Partial<WorkerProvider> = {}): WorkerProvider {
40
+ return {
41
+ selection: { provider: 'null', model: 'null-default', provider_config_env: null, provider_config_ref: null },
42
+ createSession: vi.fn(),
43
+ reattachSession: vi.fn(),
44
+ closeSession: vi.fn(),
45
+ runWorker: vi.fn(),
46
+ sendMessage: vi.fn(async () => undefined),
47
+ ...overrides
48
+ } as unknown as WorkerProvider;
49
+ }
50
+
51
+ describe('feature.send_message kernel handler', () => {
52
+ it('GIVEN active session exists WHEN send called THEN message delivered via provider.sendMessage', async () => {
53
+ const kernel = new AopKernel(repoRoot);
54
+ await kernel.ensureLoaded();
55
+ await writeFeatureSpec(repoRoot, 'feature_send_1');
56
+ await kernel.invoke('feature.init', { feature_id: 'feature_send_1' }, { actor_type: 'orchestrator', actor_id: 'test' });
57
+ await writeFeatureSession(repoRoot, 'feature_send_1');
58
+
59
+ const sendMessageMock = vi.fn(async () => undefined);
60
+ const provider = makeProvider({ sendMessage: sendMessageMock });
61
+ kernel.setProvider(provider);
62
+
63
+ const result = await kernel.invoke(
64
+ 'feature.send_message',
65
+ { feature_id: 'feature_send_1', message: 'Hello agent!', operation_id: 'op:send:1' },
66
+ { actor_type: 'orchestrator', actor_id: 'test' }
67
+ );
68
+
69
+ expect(result.ok).toBe(true);
70
+ expect(result.data).toMatchObject({
71
+ feature_id: 'feature_send_1',
72
+ session_id: 'planner:1',
73
+ target_role: 'planner',
74
+ delivered: true
75
+ });
76
+ expect(sendMessageMock).toHaveBeenCalledWith('planner:1', 'Hello agent!');
77
+ });
78
+
79
+ it('GIVEN_provider_exposes_session_info_WHEN_send_called_THEN_waits_until_session_is_active_before_delivery', async () => {
80
+ const kernel = new AopKernel(repoRoot);
81
+ await kernel.ensureLoaded();
82
+ await writeFeatureSpec(repoRoot, 'feature_send_wait');
83
+ await kernel.invoke('feature.init', { feature_id: 'feature_send_wait' }, { actor_type: 'orchestrator', actor_id: 'test' });
84
+ await writeFeatureSession(repoRoot, 'feature_send_wait');
85
+
86
+ const getSessionInfo = vi
87
+ .fn()
88
+ .mockResolvedValueOnce({ active: false, provider: 'codex' })
89
+ .mockResolvedValueOnce({ active: true, provider: 'codex' });
90
+ const sendMessageMock = vi.fn(async () => undefined);
91
+ const provider = makeProvider({ sendMessage: sendMessageMock, getSessionInfo });
92
+ kernel.setProvider(provider);
93
+
94
+ const result = await kernel.invoke(
95
+ 'feature.send_message',
96
+ { feature_id: 'feature_send_wait', message: 'Wait for ready session', operation_id: 'op:send:wait' },
97
+ { actor_type: 'orchestrator', actor_id: 'test' }
98
+ );
99
+
100
+ expect(result.ok).toBe(true);
101
+ expect(getSessionInfo).toHaveBeenCalledTimes(2);
102
+ expect(sendMessageMock).toHaveBeenCalledWith('planner:1', 'Wait for ready session');
103
+ });
104
+
105
+ it('GIVEN no active session WHEN send called THEN error session_not_found returned', async () => {
106
+ const kernel = new AopKernel(repoRoot);
107
+ await kernel.ensureLoaded();
108
+ await writeFeatureSpec(repoRoot, 'feature_send_nosession');
109
+ await kernel.invoke('feature.init', { feature_id: 'feature_send_nosession' }, { actor_type: 'orchestrator', actor_id: 'test' });
110
+
111
+ const provider = makeProvider();
112
+ kernel.setProvider(provider);
113
+
114
+ const result = await kernel.invoke(
115
+ 'feature.send_message',
116
+ { feature_id: 'feature_send_nosession', message: 'Hello agent!', operation_id: 'op:send:2' },
117
+ { actor_type: 'orchestrator', actor_id: 'test' }
118
+ );
119
+
120
+ expect(result.ok).toBe(false);
121
+ expect(result.error).toMatchObject({
122
+ code: 'session_not_found',
123
+ message: 'No active session cluster for feature'
124
+ });
125
+ });
126
+
127
+ it('GIVEN provider does not support sendMessage WHEN send called THEN error provider_unsupported returned', async () => {
128
+ const kernel = new AopKernel(repoRoot);
129
+ await kernel.ensureLoaded();
130
+ await writeFeatureSpec(repoRoot, 'feature_send_noprovider');
131
+ await kernel.invoke('feature.init', { feature_id: 'feature_send_noprovider' }, { actor_type: 'orchestrator', actor_id: 'test' });
132
+ await writeFeatureSession(repoRoot, 'feature_send_noprovider');
133
+
134
+ // Provider without sendMessage
135
+ const provider = makeProvider({ sendMessage: undefined });
136
+ kernel.setProvider(provider);
137
+
138
+ const result = await kernel.invoke(
139
+ 'feature.send_message',
140
+ { feature_id: 'feature_send_noprovider', message: 'Hello agent!', operation_id: 'op:send:3' },
141
+ { actor_type: 'orchestrator', actor_id: 'test' }
142
+ );
143
+
144
+ expect(result.ok).toBe(false);
145
+ expect(result.error).toMatchObject({
146
+ code: 'provider_unsupported',
147
+ message: 'Provider does not support sendMessage'
148
+ });
149
+ });
150
+
151
+ it('GIVEN message is empty WHEN send called THEN validation error returned', async () => {
152
+ const kernel = new AopKernel(repoRoot);
153
+ await kernel.ensureLoaded();
154
+ await writeFeatureSpec(repoRoot, 'feature_send_empty_msg');
155
+ await kernel.invoke('feature.init', { feature_id: 'feature_send_empty_msg' }, { actor_type: 'orchestrator', actor_id: 'test' });
156
+ await writeFeatureSession(repoRoot, 'feature_send_empty_msg');
157
+
158
+ const provider = makeProvider();
159
+ kernel.setProvider(provider);
160
+
161
+ const result = await kernel.invoke(
162
+ 'feature.send_message',
163
+ { feature_id: 'feature_send_empty_msg', message: '', operation_id: 'op:send:4' },
164
+ { actor_type: 'orchestrator', actor_id: 'test' }
165
+ );
166
+
167
+ expect(result.ok).toBe(false);
168
+ });
169
+
170
+ it('GIVEN valid send WHEN operation_id reused THEN operation_id passes through in tool args', async () => {
171
+ const kernel = new AopKernel(repoRoot);
172
+ await kernel.ensureLoaded();
173
+ await writeFeatureSpec(repoRoot, 'feature_send_idem');
174
+ await kernel.invoke('feature.init', { feature_id: 'feature_send_idem' }, { actor_type: 'orchestrator', actor_id: 'test' });
175
+ await writeFeatureSession(repoRoot, 'feature_send_idem');
176
+
177
+ const sendMessageMock = vi.fn(async () => undefined);
178
+ const provider = makeProvider({ sendMessage: sendMessageMock });
179
+ kernel.setProvider(provider);
180
+
181
+ const operationId = 'op:idempotent:1';
182
+ const result1 = await kernel.invoke(
183
+ 'feature.send_message',
184
+ { feature_id: 'feature_send_idem', message: 'idem message', operation_id: operationId },
185
+ { actor_type: 'orchestrator', actor_id: 'test' }
186
+ );
187
+ const result2 = await kernel.invoke(
188
+ 'feature.send_message',
189
+ { feature_id: 'feature_send_idem', message: 'idem message', operation_id: operationId },
190
+ { actor_type: 'orchestrator', actor_id: 'test' }
191
+ );
192
+
193
+ expect(result1.ok).toBe(true);
194
+ expect(result2.ok).toBe(true);
195
+ expect(result1.data.delivered).toBe(true);
196
+ expect(result2.data.delivered).toBe(true);
197
+ });
198
+ });
199
+
200
+ describe('SendCommandHandler', () => {
201
+ it('GIVEN send CLI command WHEN executed THEN tool client called with correct params', async () => {
202
+ const call = vi.fn(async () => ({
203
+ ok: true,
204
+ data: { feature_id: 'feature_a', session_id: 'orch:1', delivered: true }
205
+ }));
206
+ const toolClient = { call };
207
+ const handler = new SendCommandHandler(toolClient as never, 'run:test');
208
+
209
+ const result = await handler.execute({
210
+ command: 'send',
211
+ feature_id: 'feature_a',
212
+ message: 'Do something'
213
+ });
214
+
215
+ expect(call).toHaveBeenCalledWith(
216
+ TOOLS.FEATURE_SEND_MESSAGE,
217
+ expect.objectContaining({
218
+ feature_id: 'feature_a',
219
+ message: 'Do something',
220
+ operation_id: expect.any(String)
221
+ }),
222
+ expect.objectContaining({
223
+ run_id: 'run:test',
224
+ actor_type: 'system'
225
+ })
226
+ );
227
+ expect(result).toMatchObject({
228
+ ok: true,
229
+ data: {
230
+ command: 'send',
231
+ feature_id: 'feature_a',
232
+ delivered: true
233
+ }
234
+ });
235
+ });
236
+
237
+ it('GIVEN feature_id missing WHEN send CLI called THEN validation error thrown', async () => {
238
+ const handler = new SendCommandHandler({ call: vi.fn() } as never, 'run:test');
239
+
240
+ await expect(
241
+ handler.execute({ command: 'send', message: 'Hello' })
242
+ ).rejects.toMatchObject({
243
+ code: ERROR_CODES.INVALID_CLI_ARGS,
244
+ details: expect.objectContaining({
245
+ reason: '--feature-id is required for send'
246
+ })
247
+ });
248
+ });
249
+
250
+ it('GIVEN message missing WHEN send CLI called THEN validation error thrown', async () => {
251
+ const handler = new SendCommandHandler({ call: vi.fn() } as never, 'run:test');
252
+
253
+ await expect(
254
+ handler.execute({ command: 'send', feature_id: 'feature_a' })
255
+ ).rejects.toMatchObject({
256
+ code: ERROR_CODES.INVALID_CLI_ARGS,
257
+ details: expect.objectContaining({
258
+ reason: '--message is required for send'
259
+ })
260
+ });
261
+ });
262
+ });
263
+
264
+ describe('AttachCommandHandler', () => {
265
+ it('GIVEN_feature_in_planning_WHEN_attach_executed_THEN_attaches_to_planner_session', async () => {
266
+ const kernel = new AopKernel(repoRoot);
267
+ await kernel.ensureLoaded();
268
+ await writeFeatureSpec(repoRoot, 'feature_attach_planning');
269
+ await kernel.invoke(
270
+ 'feature.init',
271
+ { feature_id: 'feature_attach_planning' },
272
+ { actor_type: 'orchestrator', actor_id: 'test' }
273
+ );
274
+ await writeFeatureSession(repoRoot, 'feature_attach_planning');
275
+
276
+ const attachToSession = vi.fn(async () => undefined);
277
+ const provider = makeProvider({ attachToSession, reattachSession: undefined });
278
+ const toolClient = {
279
+ call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'planning' } } }))
280
+ };
281
+
282
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
283
+ const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_planning' });
284
+
285
+ expect(attachToSession).toHaveBeenCalledWith('planner:1');
286
+ expect(result).toMatchObject({
287
+ ok: true,
288
+ data: {
289
+ command: 'attach',
290
+ feature_id: 'feature_attach_planning',
291
+ role: 'planner',
292
+ session_id: 'planner:1',
293
+ attached: true
294
+ }
295
+ });
296
+ });
297
+
298
+ it('GIVEN_provider_without_attachToSession_WHEN_attach_executed_THEN_returns_invalid_cli_args_for_unsupported_provider', async () => {
299
+ const kernel = new AopKernel(repoRoot);
300
+ await kernel.ensureLoaded();
301
+ await writeFeatureSpec(repoRoot, 'feature_attach_builder');
302
+ await kernel.invoke(
303
+ 'feature.init',
304
+ { feature_id: 'feature_attach_builder' },
305
+ { actor_type: 'orchestrator', actor_id: 'test' }
306
+ );
307
+ await writeFeatureSession(repoRoot, 'feature_attach_builder');
308
+
309
+ const provider = makeProvider({
310
+ selection: {
311
+ provider: 'custom',
312
+ model: 'custom-default',
313
+ provider_config_env: null,
314
+ provider_config_ref: null
315
+ },
316
+ attachToSession: undefined
317
+ });
318
+ const toolClient = {
319
+ call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'building' } } }))
320
+ };
321
+
322
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
323
+ await expect(handler.execute({ command: 'attach', feature_id: 'feature_attach_builder' })).rejects.toMatchObject({
324
+ code: ERROR_CODES.INVALID_CLI_ARGS,
325
+ details: expect.objectContaining({
326
+ reason: expect.stringContaining('does not support interactive attach'),
327
+ provider: 'custom'
328
+ })
329
+ });
330
+ });
331
+
332
+ it('GIVEN_no_feature_session_cluster_WHEN_attach_executed_THEN_returns_invalid_cli_args', async () => {
333
+ const kernel = new AopKernel(repoRoot);
334
+ await kernel.ensureLoaded();
335
+ await writeFeatureSpec(repoRoot, 'feature_attach_missing');
336
+ await kernel.invoke(
337
+ 'feature.init',
338
+ { feature_id: 'feature_attach_missing' },
339
+ { actor_type: 'orchestrator', actor_id: 'test' }
340
+ );
341
+
342
+ const provider = makeProvider();
343
+ const toolClient = {
344
+ call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'planning' } } }))
345
+ };
346
+
347
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
348
+ await expect(handler.execute({ command: 'attach', feature_id: 'feature_attach_missing' })).rejects.toMatchObject({
349
+ code: ERROR_CODES.INVALID_CLI_ARGS,
350
+ details: expect.objectContaining({
351
+ reason: expect.stringContaining('No active session cluster found')
352
+ })
353
+ });
354
+ });
355
+ });
356
+
357
+ describe('AttachCommandHandler branch coverage', () => {
358
+ it('GIVEN_missing_feature_id_WHEN_attach_executed_THEN_throws_invalid_cli_args', async () => {
359
+ const kernel = { getRuntimeSessions: vi.fn() } as never;
360
+ const provider = makeProvider();
361
+ const toolClient = { call: vi.fn() };
362
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
363
+ await expect(handler.execute({ command: 'attach' })).rejects.toMatchObject({
364
+ code: ERROR_CODES.INVALID_CLI_ARGS
365
+ });
366
+ });
367
+
368
+ it('GIVEN_invalid_feature_id_WHEN_attach_executed_THEN_throws_invalid_cli_args', async () => {
369
+ const kernel = { getRuntimeSessions: vi.fn() } as never;
370
+ const provider = makeProvider();
371
+ const toolClient = { call: vi.fn() };
372
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
373
+ await expect(handler.execute({ command: 'attach', feature_id: 'INVALID Feature ID' })).rejects.toMatchObject({
374
+ code: ERROR_CODES.INVALID_CLI_ARGS
375
+ });
376
+ });
377
+
378
+ it('GIVEN_feature_in_qa_status_WHEN_attach_executed_THEN_attaches_to_qa_session', async () => {
379
+ const kernel = new AopKernel(repoRoot);
380
+ await kernel.ensureLoaded();
381
+ await writeFeatureSpec(repoRoot, 'feature_attach_qa');
382
+ await kernel.invoke('feature.init', { feature_id: 'feature_attach_qa' }, { actor_type: 'orchestrator', actor_id: 'test' });
383
+ await writeFeatureSession(repoRoot, 'feature_attach_qa');
384
+
385
+ const attachToSession = vi.fn(async () => undefined);
386
+ const provider = makeProvider({ attachToSession });
387
+ const toolClient = {
388
+ call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'qa' } } }))
389
+ };
390
+
391
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
392
+ const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_qa' });
393
+ const data = (result as Record<string, unknown>).data as Record<string, unknown>;
394
+ expect(data.role).toBe('qa');
395
+ expect(attachToSession).toHaveBeenCalledWith('qa:1');
396
+ });
397
+
398
+ it('GIVEN_feature_in_building_status_WHEN_attach_executed_THEN_attaches_to_builder_session', async () => {
399
+ const kernel = new AopKernel(repoRoot);
400
+ await kernel.ensureLoaded();
401
+ await writeFeatureSpec(repoRoot, 'feature_attach_building');
402
+ await kernel.invoke('feature.init', { feature_id: 'feature_attach_building' }, { actor_type: 'orchestrator', actor_id: 'test' });
403
+ await writeFeatureSession(repoRoot, 'feature_attach_building');
404
+
405
+ const attachToSession = vi.fn(async () => undefined);
406
+ const provider = makeProvider({ attachToSession });
407
+ const toolClient = {
408
+ call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'building' } } }))
409
+ };
410
+
411
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
412
+ const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_building' });
413
+ const data = (result as Record<string, unknown>).data as Record<string, unknown>;
414
+ expect(data.role).toBe('builder');
415
+ expect(attachToSession).toHaveBeenCalledWith('builder:1');
416
+ });
417
+
418
+ it('GIVEN_feature_session_with_unassigned_specific_role_WHEN_attach_executed_THEN_falls_back_to_orchestrator_session', async () => {
419
+ const kernel = new AopKernel(repoRoot);
420
+ await kernel.ensureLoaded();
421
+ await writeFeatureSpec(repoRoot, 'feature_attach_fallback');
422
+ await kernel.invoke('feature.init', { feature_id: 'feature_attach_fallback' }, { actor_type: 'orchestrator', actor_id: 'test' });
423
+
424
+ // Write session with unassigned planner_session_id but valid orchestrator session
425
+ const leasePath = path.join(repoRoot, '.aop', 'runtime', 'default', 'run-lease.json');
426
+ await fs.mkdir(path.dirname(leasePath), { recursive: true });
427
+ await fs.writeFile(leasePath, JSON.stringify({
428
+ orchestrator_session_id: 'orch:fallback-1',
429
+ feature_sessions: {
430
+ feature_attach_fallback: {
431
+ planner_session_id: 'unassigned',
432
+ builder_session_id: 'unassigned',
433
+ qa_session_id: 'unassigned'
434
+ }
435
+ }
436
+ }), 'utf8');
437
+
438
+ const attachToSession = vi.fn(async () => undefined);
439
+ const provider = makeProvider({ attachToSession });
440
+ const toolClient = {
441
+ call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'planning' } } }))
442
+ };
443
+
444
+ const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
445
+ const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_fallback' });
446
+ const data = (result as Record<string, unknown>).data as Record<string, unknown>;
447
+ expect(data.session_id).toBe('orch:fallback-1');
448
+ expect(attachToSession).toHaveBeenCalledWith('orch:fallback-1');
449
+ });
450
+ });