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
@@ -0,0 +1,247 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ const resolveProjectRootMock = vi.hoisted(() => vi.fn(async () => '/tmp'));
7
+ const readDashboardStatusMock = vi.hoisted(() =>
8
+ vi.fn(async () => ({ index: { active: [], blocked: [], merged: [], blocked_queue: [] }, features: [] }))
9
+ );
10
+ const readFeatureStateMock = vi.hoisted(() => vi.fn(async () => null));
11
+ const getAopRootMock = vi.hoisted(() => vi.fn(() => process.cwd()));
12
+ const approveFeatureReviewMock = vi.hoisted(() => vi.fn(async () => ({ ok: true, data: { merged: true } })));
13
+ const denyFeatureReviewMock = vi.hoisted(() => vi.fn(async () => ({ ok: true, data: { blocked: true } })));
14
+ const requestFeatureChangesMock = vi.hoisted(() =>
15
+ vi.fn(async () => ({ ok: true, data: { delivered: true } }))
16
+ );
17
+ const execFileMock = vi.hoisted(() => vi.fn());
18
+
19
+ vi.mock('../../../packages/web-dashboard/src/lib/aop-client.js', () => ({
20
+ resolveProjectRoot: resolveProjectRootMock,
21
+ readDashboardStatus: readDashboardStatusMock,
22
+ readFeatureState: readFeatureStateMock,
23
+ getAopRoot: getAopRootMock
24
+ }));
25
+
26
+ vi.mock('../../../packages/web-dashboard/src/lib/orchestrator-tools.js', () => ({
27
+ approveFeatureReview: approveFeatureReviewMock,
28
+ denyFeatureReview: denyFeatureReviewMock,
29
+ requestFeatureChanges: requestFeatureChangesMock
30
+ }));
31
+
32
+ vi.mock('node:child_process', () => ({
33
+ execFile: (...args: unknown[]) => {
34
+ const callback = args[args.length - 1] as (err: null | Error, result?: { stdout: string; stderr: string }) => void;
35
+ const call = execFileMock(...args.slice(0, -1)) as Promise<{ stdout: string; stderr: string }>;
36
+ void call.then(
37
+ (result) => callback(null, result),
38
+ (error: Error) => callback(error)
39
+ );
40
+ }
41
+ }));
42
+
43
+ import { POST as actionsPost } from '../../../packages/web-dashboard/src/app/api/actions/route.js';
44
+ import { POST as checkoutPost } from '../../../packages/web-dashboard/src/app/api/features/[id]/checkout/route.js';
45
+ import { GET as statusGet } from '../../../packages/web-dashboard/src/app/api/status/route.js';
46
+ import { GET as projectsGet } from '../../../packages/web-dashboard/src/app/api/projects/route.js';
47
+
48
+ describe('dashboard api integration', () => {
49
+ let repoRoot: string;
50
+
51
+ beforeEach(async () => {
52
+ repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-dashboard-api-'));
53
+ await fs.mkdir(path.join(repoRoot, '.git'), { recursive: true });
54
+ await fs.mkdir(path.join(repoRoot, '.aop', 'runtime'), { recursive: true });
55
+ await fs.mkdir(path.join(repoRoot, '.worktrees', 'feature_checkout'), { recursive: true });
56
+
57
+ resolveProjectRootMock.mockReset();
58
+ readDashboardStatusMock.mockReset();
59
+ readFeatureStateMock.mockReset();
60
+ getAopRootMock.mockReset();
61
+ approveFeatureReviewMock.mockReset();
62
+ denyFeatureReviewMock.mockReset();
63
+ requestFeatureChangesMock.mockReset();
64
+ execFileMock.mockReset();
65
+
66
+ resolveProjectRootMock.mockResolvedValue(repoRoot);
67
+ getAopRootMock.mockReturnValue(repoRoot);
68
+ readDashboardStatusMock.mockResolvedValue({
69
+ index: { active: ['feature_checkout'], blocked: [], merged: [], blocked_queue: [] },
70
+ features: [
71
+ {
72
+ id: 'feature_checkout',
73
+ feature_id: 'feature_checkout',
74
+ status: 'ready_to_merge',
75
+ phase: 'ready_to_merge',
76
+ branch: 'feature/feature_checkout',
77
+ worktree_path: path.join(repoRoot, '.worktrees', 'feature_checkout'),
78
+ pr: null
79
+ }
80
+ ]
81
+ });
82
+ });
83
+
84
+ afterEach(async () => {
85
+ await fs.rm(repoRoot, { recursive: true, force: true });
86
+ });
87
+
88
+ it('GIVEN_review_approve_action_WHEN_posted_THEN_routes_to_feature_ready_to_merge', async () => {
89
+ const response = await actionsPost(
90
+ new Request('http://localhost/api/actions?project=alpha', {
91
+ method: 'POST',
92
+ body: JSON.stringify({
93
+ action: 'review.approve',
94
+ feature_id: 'feature_checkout',
95
+ approval_token: 'approved'
96
+ })
97
+ })
98
+ );
99
+
100
+ expect(response.status).toBe(200);
101
+ expect(approveFeatureReviewMock).toHaveBeenCalledWith(
102
+ 'feature_checkout',
103
+ 'approved',
104
+ 'merge_commit',
105
+ undefined,
106
+ repoRoot
107
+ );
108
+ });
109
+
110
+ it('GIVEN_review_request_changes_WHEN_posted_THEN_routes_to_feature_send_message_flow', async () => {
111
+ const response = await actionsPost(
112
+ new Request('http://localhost/api/actions', {
113
+ method: 'POST',
114
+ body: JSON.stringify({
115
+ action: 'review.request_changes',
116
+ feature_id: 'feature_checkout',
117
+ message: 'Please address CI failures'
118
+ })
119
+ })
120
+ );
121
+
122
+ expect(response.status).toBe(200);
123
+ expect(requestFeatureChangesMock).toHaveBeenCalledWith('feature_checkout', 'Please address CI failures', repoRoot);
124
+ });
125
+
126
+ it('GIVEN_review_deny_without_reason_WHEN_posted_THEN_returns_400', async () => {
127
+ const response = await actionsPost(
128
+ new Request('http://localhost/api/actions', {
129
+ method: 'POST',
130
+ body: JSON.stringify({
131
+ action: 'review.deny',
132
+ feature_id: 'feature_checkout'
133
+ })
134
+ })
135
+ );
136
+
137
+ const body = (await response.json()) as { ok: boolean; error: { code: string } };
138
+ expect(response.status).toBe(400);
139
+ expect(body.ok).toBe(false);
140
+ expect(body.error.code).toBe('reason_required');
141
+ });
142
+
143
+ it('GIVEN_checkout_then_restore_WHEN_feature_branch_exists_THEN_persists_and_restores_checkout_record', async () => {
144
+ readFeatureStateMock.mockResolvedValue({
145
+ id: 'feature_checkout',
146
+ feature_id: 'feature_checkout',
147
+ status: 'ready_to_merge',
148
+ phase: 'ready_to_merge',
149
+ branch: 'feature/feature_checkout',
150
+ worktree_path: path.join(repoRoot, '.worktrees', 'feature_checkout'),
151
+ pr: null
152
+ });
153
+
154
+ execFileMock.mockImplementation(async (_file: string, args: string[]) => {
155
+ const command = args.join(' ');
156
+ if (command.startsWith('show-ref --verify --quiet refs/heads/feature/feature_checkout')) {
157
+ return { stdout: '', stderr: '' };
158
+ }
159
+ if (command === 'status --porcelain') {
160
+ return { stdout: '', stderr: '' };
161
+ }
162
+ if (command === 'rev-parse --abbrev-ref HEAD') {
163
+ return { stdout: 'main\n', stderr: '' };
164
+ }
165
+ if (command === 'checkout feature/feature_checkout') {
166
+ return { stdout: '', stderr: '' };
167
+ }
168
+ if (command === 'checkout main') {
169
+ return { stdout: '', stderr: '' };
170
+ }
171
+ throw new Error(`unexpected git command: ${command}`);
172
+ });
173
+
174
+ const checkoutResponse = await checkoutPost(
175
+ new Request('http://localhost/api/features/feature_checkout/checkout?project=alpha', {
176
+ method: 'POST',
177
+ body: JSON.stringify({ action: 'checkout', stash_changes: false })
178
+ }),
179
+ { params: { id: 'feature_checkout' } }
180
+ );
181
+ const checkoutBody = (await checkoutResponse.json()) as { ok: boolean; data: { previous_branch: string } };
182
+ expect(checkoutResponse.status).toBe(200);
183
+ expect(checkoutBody.ok).toBe(true);
184
+ expect(checkoutBody.data.previous_branch).toBe('main');
185
+
186
+ const restoreRecordPath = path.join(repoRoot, '.aop', 'runtime', 'checkout-restore.json');
187
+ await expect(fs.access(restoreRecordPath)).resolves.toBeUndefined();
188
+
189
+ const restoreResponse = await checkoutPost(
190
+ new Request('http://localhost/api/features/feature_checkout/checkout?project=alpha', {
191
+ method: 'POST',
192
+ body: JSON.stringify({ action: 'restore' })
193
+ }),
194
+ { params: { id: 'feature_checkout' } }
195
+ );
196
+ const restoreBody = (await restoreResponse.json()) as { ok: boolean; data: { restored_to: string } };
197
+ expect(restoreResponse.status).toBe(200);
198
+ expect(restoreBody.ok).toBe(true);
199
+ expect(restoreBody.data.restored_to).toBe('main');
200
+ await expect(fs.access(restoreRecordPath)).rejects.toThrow();
201
+ });
202
+
203
+ it('GIVEN_status_query_with_project_WHEN_requested_THEN_uses_project_specific_root', async () => {
204
+ resolveProjectRootMock.mockResolvedValue(path.join(repoRoot, 'project-beta'));
205
+
206
+ const response = await statusGet(new Request('http://localhost/api/status?project=beta'));
207
+ const body = (await response.json()) as { ok: boolean; data: { features: unknown[] } };
208
+
209
+ expect(response.status).toBe(200);
210
+ expect(body.ok).toBe(true);
211
+ expect(resolveProjectRootMock).toHaveBeenCalledWith('beta');
212
+ expect(readDashboardStatusMock).toHaveBeenCalledWith(path.join(repoRoot, 'project-beta'));
213
+ });
214
+
215
+ it('GIVEN_multi_project_config_WHEN_projects_endpoint_called_THEN_returns_switchable_projects', async () => {
216
+ const projectAlpha = path.join(repoRoot, 'project-alpha');
217
+ const projectBeta = path.join(repoRoot, 'project-beta');
218
+ await fs.mkdir(path.join(repoRoot, 'agentic', 'orchestrator', 'schemas'), { recursive: true });
219
+ await fs.copyFile(
220
+ path.resolve(import.meta.dirname, '../../../agentic/orchestrator/schemas/multi-project.schema.json'),
221
+ path.join(repoRoot, 'agentic', 'orchestrator', 'schemas', 'multi-project.schema.json')
222
+ );
223
+ await fs.writeFile(
224
+ path.join(repoRoot, 'agentic', 'orchestrator', 'multi-project.yaml'),
225
+ [
226
+ 'version: "1.0"',
227
+ 'projects:',
228
+ ` - name: alpha`,
229
+ ` path: ${projectAlpha}`,
230
+ ` - name: beta`,
231
+ ` path: ${projectBeta}`
232
+ ].join('\n'),
233
+ 'utf8'
234
+ );
235
+
236
+ const response = await projectsGet();
237
+ const body = (await response.json()) as {
238
+ ok: boolean;
239
+ data: { projects: Array<{ name: string; path: string }> };
240
+ };
241
+
242
+ expect(response.status).toBe(200);
243
+ expect(body.ok).toBe(true);
244
+ expect(body.data.projects.map((project) => project.name)).toEqual(['alpha', 'beta']);
245
+ expect(body.data.projects.map((project) => project.path)).toEqual([projectAlpha, projectBeta]);
246
+ });
247
+ });
@@ -0,0 +1,116 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { readDashboardStatus } from '../../../packages/web-dashboard/src/lib/aop-client.js';
6
+
7
+ async function writeState(repoRoot: string, featureId: string, frontMatter: string): Promise<void> {
8
+ const featureDir = path.join(repoRoot, '.aop', 'features', featureId);
9
+ await fs.mkdir(featureDir, { recursive: true });
10
+ await fs.writeFile(path.join(featureDir, 'state.md'), `---\n${frontMatter}\n---\n`, 'utf8');
11
+ }
12
+
13
+ describe('dashboard aop client mapping', () => {
14
+ const tempRoots: string[] = [];
15
+
16
+ afterEach(async () => {
17
+ await Promise.all(tempRoots.map(async (root) => fs.rm(root, { recursive: true, force: true })));
18
+ tempRoots.length = 0;
19
+ });
20
+
21
+ it('GIVEN_active_feature_with_unknown_status_WHEN_readDashboardStatus_THEN_phase_falls_back_to_planning', async () => {
22
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-dash-client-'));
23
+ tempRoots.push(repoRoot);
24
+ await fs.mkdir(path.join(repoRoot, '.aop', 'features'), { recursive: true });
25
+ await fs.writeFile(
26
+ path.join(repoRoot, '.aop', 'features', 'index.json'),
27
+ JSON.stringify({ active: ['feature_a'], blocked: [], merged: [], blocked_queue: [] }),
28
+ 'utf8'
29
+ );
30
+ await writeState(
31
+ repoRoot,
32
+ 'feature_a',
33
+ [
34
+ 'feature_id: feature_a',
35
+ 'version: 1',
36
+ 'status: unknown_status',
37
+ 'branch: feature_a',
38
+ 'worktree_path: /tmp/worktrees/feature_a',
39
+ 'gate_profile: default',
40
+ 'last_updated: 2026-03-03T00:00:00Z'
41
+ ].join('\n')
42
+ );
43
+
44
+ const payload = await readDashboardStatus(repoRoot);
45
+ expect(payload.features).toHaveLength(1);
46
+ expect(payload.features[0]).toMatchObject({
47
+ feature_id: 'feature_a',
48
+ status: 'unknown_status',
49
+ phase: 'planning'
50
+ });
51
+ });
52
+
53
+ it('GIVEN_state_with_nested_pr_object_WHEN_readDashboardStatus_THEN_pr_fields_and_merge_score_are_parsed', async () => {
54
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-dash-client-'));
55
+ tempRoots.push(repoRoot);
56
+ await fs.mkdir(path.join(repoRoot, '.aop', 'features'), { recursive: true });
57
+ await fs.writeFile(
58
+ path.join(repoRoot, '.aop', 'features', 'index.json'),
59
+ JSON.stringify({ active: ['feature_pr'], blocked: [], merged: [], blocked_queue: [] }),
60
+ 'utf8'
61
+ );
62
+ await writeState(
63
+ repoRoot,
64
+ 'feature_pr',
65
+ [
66
+ 'feature_id: feature_pr',
67
+ 'version: 1',
68
+ 'status: ready_to_merge',
69
+ 'branch: feature_pr',
70
+ 'worktree_path: /tmp/worktrees/feature_pr',
71
+ 'gate_profile: default',
72
+ 'last_updated: 2026-03-03T00:00:00Z',
73
+ 'pr:',
74
+ ' number: 42',
75
+ ' url: https://github.com/org/repo/pull/42',
76
+ ' ci_status: passing',
77
+ ' review_decision: approved',
78
+ ' merge_ready: true',
79
+ ' pending_review_threads: 0',
80
+ ' has_conflicts: false',
81
+ ' merge_score: 100'
82
+ ].join('\n')
83
+ );
84
+
85
+ const payload = await readDashboardStatus(repoRoot);
86
+ expect(payload.features[0].pr).toMatchObject({
87
+ number: 42,
88
+ ci_status: 'passing',
89
+ review_decision: 'approved',
90
+ merge_score: 100
91
+ });
92
+ });
93
+
94
+ it('GIVEN_blocked_queue_feature_without_state_WHEN_readDashboardStatus_THEN_phase_is_blocked', async () => {
95
+ const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-dash-client-'));
96
+ tempRoots.push(repoRoot);
97
+ await fs.mkdir(path.join(repoRoot, '.aop', 'features'), { recursive: true });
98
+ await fs.writeFile(
99
+ path.join(repoRoot, '.aop', 'features', 'index.json'),
100
+ JSON.stringify({
101
+ active: [],
102
+ blocked: [],
103
+ merged: [],
104
+ blocked_queue: [{ feature_id: 'feature_q', detected_at: '2026-03-03T00:00:00Z', collision_fingerprint: 'abc' }]
105
+ }),
106
+ 'utf8'
107
+ );
108
+
109
+ const payload = await readDashboardStatus(repoRoot);
110
+ expect(payload.features).toHaveLength(1);
111
+ expect(payload.features[0]).toMatchObject({
112
+ feature_id: 'feature_q',
113
+ phase: 'blocked'
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it, vi, afterEach } from 'vitest';
2
+ import { DashboardCommandHandler } from '../src/cli/dashboard-command-handler.js';
3
+ import type { ChildProcess } from 'node:child_process';
4
+
5
+ vi.mock('node:child_process', () => ({
6
+ execFile: vi.fn(),
7
+ spawn: vi.fn(),
8
+ }));
9
+
10
+ describe('DashboardCommandHandler', () => {
11
+ afterEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+
15
+ it('GIVEN_foreground_false_WHEN_dashboard_command_THEN_starts_background_process', async () => {
16
+ const { execFile, spawn } = await import('node:child_process');
17
+ const mockChild = { unref: vi.fn(), on: vi.fn() } as unknown as ChildProcess;
18
+ vi.mocked(execFile).mockImplementation((_file, _args, _opts, cb) => {
19
+ cb?.(null, '', '');
20
+ return {} as never;
21
+ });
22
+ vi.mocked(spawn).mockReturnValue(mockChild);
23
+
24
+ const handler = new DashboardCommandHandler();
25
+ const result = await handler.execute({ port: 3000, foreground: false });
26
+
27
+ expect(execFile).toHaveBeenCalledWith(
28
+ 'npm',
29
+ ['run', '--workspace', '@aop/web-dashboard', 'build'],
30
+ expect.any(Object),
31
+ expect.any(Function)
32
+ );
33
+ expect(spawn).toHaveBeenCalledWith(
34
+ 'npm',
35
+ ['run', '--workspace', '@aop/web-dashboard', 'start'],
36
+ expect.objectContaining({ detached: true })
37
+ );
38
+ expect(mockChild.unref).toHaveBeenCalled();
39
+ expect(result).toMatchObject({ ok: true, data: { port: 3000, background: true, built: true } });
40
+ });
41
+
42
+ it('GIVEN_foreground_true_WHEN_dashboard_command_THEN_runs_in_foreground', async () => {
43
+ const { spawn } = await import('node:child_process');
44
+ const mockChild = {
45
+ on: vi.fn((event: string, cb: () => void) => {
46
+ if (event === 'close') { setTimeout(cb, 0); }
47
+ return mockChild;
48
+ }),
49
+ } as unknown as ChildProcess;
50
+ vi.mocked(spawn).mockReturnValue(mockChild);
51
+
52
+ const handler = new DashboardCommandHandler();
53
+ const result = await handler.execute({ foreground: true });
54
+
55
+ expect(spawn).toHaveBeenCalledWith(
56
+ 'npm',
57
+ ['run', '--workspace', '@aop/web-dashboard', 'start'],
58
+ expect.objectContaining({ stdio: 'inherit' })
59
+ );
60
+ expect(result).toMatchObject({ ok: true, data: { port: 3000, mode: 'production' } });
61
+ });
62
+
63
+ it('GIVEN_custom_port_WHEN_dashboard_command_THEN_uses_provided_port', async () => {
64
+ const { execFile, spawn } = await import('node:child_process');
65
+ const mockChild = { unref: vi.fn(), on: vi.fn() } as unknown as ChildProcess;
66
+ vi.mocked(execFile).mockImplementation((_file, _args, _opts, cb) => {
67
+ cb?.(null, '', '');
68
+ return {} as never;
69
+ });
70
+ vi.mocked(spawn).mockReturnValue(mockChild);
71
+
72
+ const handler = new DashboardCommandHandler();
73
+ const result = await handler.execute({ port: 4000, foreground: false });
74
+
75
+ expect(spawn).toHaveBeenCalledWith(
76
+ 'npm',
77
+ ['run', '--workspace', '@aop/web-dashboard', 'start'],
78
+ expect.objectContaining({ env: expect.objectContaining({ PORT: '4000' }) })
79
+ );
80
+ expect(result).toMatchObject({ ok: true, data: { port: 4000 } });
81
+ });
82
+
83
+ it('GIVEN_dev_mode_WHEN_dashboard_command_THEN_runs_next_dev_in_foreground', async () => {
84
+ const { spawn } = await import('node:child_process');
85
+ const mockChild = {
86
+ on: vi.fn((event: string, cb: () => void) => {
87
+ if (event === 'close') { setTimeout(cb, 0); }
88
+ return mockChild;
89
+ }),
90
+ } as unknown as ChildProcess;
91
+ vi.mocked(spawn).mockReturnValue(mockChild);
92
+
93
+ const handler = new DashboardCommandHandler();
94
+ const result = await handler.execute({ dev: true });
95
+
96
+ expect(spawn).toHaveBeenCalledWith(
97
+ 'npm',
98
+ ['run', '--workspace', '@aop/web-dashboard', 'dev'],
99
+ expect.objectContaining({ stdio: 'inherit' })
100
+ );
101
+ expect(result).toMatchObject({ ok: true, data: { mode: 'dev' } });
102
+ });
103
+ });
@@ -0,0 +1,189 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { AopKernel } from '../src/index.js';
3
+ import {
4
+ getUnresolvedDeps,
5
+ detectCircularDependency,
6
+ addToDepBlocked,
7
+ resolveDepBlocked
8
+ } from '../src/application/services/dependency-scheduler-service.js';
9
+ import { makeTempRepo, writeFeatureSpec } from './helpers.js';
10
+
11
+ let repoRoot: string;
12
+
13
+ const ORCH_CTX = { actor_type: 'orchestrator', actor_id: 'test' };
14
+
15
+ beforeEach(async () => {
16
+ repoRoot = await makeTempRepo(process.cwd());
17
+ });
18
+
19
+ describe('N4: Dependency-Aware Feature Scheduling', () => {
20
+ describe('getUnresolvedDeps', () => {
21
+ it('returns deps not yet merged', () => {
22
+ expect(getUnresolvedDeps(['feat-a', 'feat-b'], ['feat-a'])).toEqual(['feat-b']);
23
+ });
24
+
25
+ it('returns empty when all deps are merged', () => {
26
+ expect(getUnresolvedDeps(['feat-a', 'feat-b'], ['feat-a', 'feat-b'])).toEqual([]);
27
+ });
28
+
29
+ it('returns all deps when nothing is merged', () => {
30
+ expect(getUnresolvedDeps(['feat-a', 'feat-b'], [])).toEqual(['feat-a', 'feat-b']);
31
+ });
32
+ });
33
+
34
+ describe('detectCircularDependency', () => {
35
+ it('returns null for linear dependency chain', () => {
36
+ const deps: Record<string, string[]> = { a: ['b'], b: ['c'], c: [] };
37
+ expect(detectCircularDependency('a', (id) => deps[id] ?? [])).toBeNull();
38
+ });
39
+
40
+ it('detects a direct cycle', () => {
41
+ const deps: Record<string, string[]> = { a: ['b'], b: ['a'] };
42
+ expect(detectCircularDependency('a', (id) => deps[id] ?? [])).not.toBeNull();
43
+ });
44
+
45
+ it('detects an indirect cycle', () => {
46
+ const deps: Record<string, string[]> = { a: ['b'], b: ['c'], c: ['a'] };
47
+ expect(detectCircularDependency('a', (id) => deps[id] ?? [])).not.toBeNull();
48
+ });
49
+
50
+ it('returns null for feature with no deps', () => {
51
+ expect(detectCircularDependency('solo', (_id) => [])).toBeNull();
52
+ });
53
+ });
54
+
55
+ describe('addToDepBlocked / resolveDepBlocked', () => {
56
+ it('adds a new dep_blocked entry', () => {
57
+ const index: Record<string, unknown> = { dep_blocked: [] };
58
+ addToDepBlocked(index, 'feat-child', ['feat-parent']);
59
+ expect(index.dep_blocked).toEqual([{ feature_id: 'feat-child', depends_on_unresolved: ['feat-parent'] }]);
60
+ });
61
+
62
+ it('updates existing dep_blocked entry for the same feature', () => {
63
+ const index: Record<string, unknown> = { dep_blocked: [{ feature_id: 'feat-child', depends_on_unresolved: ['feat-a', 'feat-b'] }] };
64
+ addToDepBlocked(index, 'feat-child', ['feat-b']);
65
+ const entries = index.dep_blocked as Array<{ feature_id: string; depends_on_unresolved: string[] }>;
66
+ expect(entries).toHaveLength(1);
67
+ expect(entries[0].depends_on_unresolved).toEqual(['feat-b']);
68
+ });
69
+
70
+ it('resolveDepBlocked removes entries that are now fully resolved', () => {
71
+ const index: Record<string, unknown> = {
72
+ dep_blocked: [{ feature_id: 'feat-child', depends_on_unresolved: ['feat-parent'] }]
73
+ };
74
+ const promoted = resolveDepBlocked(index, 'feat-parent');
75
+ expect(promoted).toEqual(['feat-child']);
76
+ expect(index.dep_blocked).toEqual([]);
77
+ });
78
+
79
+ it('resolveDepBlocked reduces unresolved list when multiple deps exist', () => {
80
+ const index: Record<string, unknown> = {
81
+ dep_blocked: [{ feature_id: 'feat-child', depends_on_unresolved: ['feat-a', 'feat-b'] }]
82
+ };
83
+ const promoted = resolveDepBlocked(index, 'feat-a');
84
+ expect(promoted).toEqual([]);
85
+ const entries = index.dep_blocked as Array<{ feature_id: string; depends_on_unresolved: string[] }>;
86
+ expect(entries[0].depends_on_unresolved).toEqual(['feat-b']);
87
+ });
88
+ });
89
+
90
+ describe('Kernel integration: plan.submit dependency enforcement', () => {
91
+ const validPlan = (featureId: string) => ({
92
+ feature_id: featureId,
93
+ plan_version: 1,
94
+ summary: 'test plan',
95
+ allowed_areas: ['src'],
96
+ forbidden_areas: [],
97
+ base_ref: 'HEAD',
98
+ files: { create: [], modify: [], delete: [] },
99
+ contracts: { openapi: 'none', events: 'none', db: 'none' },
100
+ acceptance_criteria: ['works'],
101
+ gate_profile: 'default'
102
+ });
103
+
104
+ it('rejects plan when dependency is not merged', async () => {
105
+ const kernel = new AopKernel(repoRoot);
106
+ await kernel.ensureLoaded();
107
+ await writeFeatureSpec(repoRoot, 'feat-parent');
108
+ await writeFeatureSpec(repoRoot, 'feat-child');
109
+ await kernel.invoke('feature.init', { feature_id: 'feat-parent' }, ORCH_CTX);
110
+ await kernel.invoke('feature.init', { feature_id: 'feat-child' }, ORCH_CTX);
111
+
112
+ // Set depends_on in feat-child's state
113
+ await kernel.updateState('feat-child', null, async (fm: Record<string, unknown>) => ({
114
+ frontMatter: { ...fm, depends_on: ['feat-parent'] }
115
+ }));
116
+
117
+ const result = await kernel.invoke('plan.submit', { feature_id: 'feat-child', plan_json: validPlan('feat-child') }, ORCH_CTX);
118
+ expect(result.ok).toBe(false);
119
+ expect((result as { ok: false; error: { code: string } }).error.code).toBe('dependency_unresolved');
120
+ });
121
+
122
+ it('accepts plan when dependency is already merged', async () => {
123
+ const kernel = new AopKernel(repoRoot);
124
+ await kernel.ensureLoaded();
125
+ await writeFeatureSpec(repoRoot, 'feat-merged-dep');
126
+ await writeFeatureSpec(repoRoot, 'feat-child2');
127
+ await kernel.invoke('feature.init', { feature_id: 'feat-merged-dep' }, ORCH_CTX);
128
+ await kernel.invoke('feature.init', { feature_id: 'feat-child2' }, ORCH_CTX);
129
+
130
+ // Mark feat-merged-dep as merged directly in index
131
+ await kernel.withIndexLock(async () => {
132
+ const idx = await kernel.readIndex() as Record<string, unknown>;
133
+ idx.merged = [...(Array.isArray(idx.merged) ? idx.merged : []), 'feat-merged-dep'];
134
+ await kernel.writeIndex(idx);
135
+ });
136
+
137
+ // Set depends_on in feat-child2's state
138
+ await kernel.updateState('feat-child2', null, async (fm: Record<string, unknown>) => ({
139
+ frontMatter: { ...fm, depends_on: ['feat-merged-dep'] }
140
+ }));
141
+
142
+ const result = await kernel.invoke('plan.submit', { feature_id: 'feat-child2', plan_json: validPlan('feat-child2') }, ORCH_CTX);
143
+ expect(result.ok).toBe(true);
144
+ });
145
+
146
+ it('adds feature to dep_blocked in index when dependency is unresolved', async () => {
147
+ const kernel = new AopKernel(repoRoot);
148
+ await kernel.ensureLoaded();
149
+ await writeFeatureSpec(repoRoot, 'feat-blocker');
150
+ await writeFeatureSpec(repoRoot, 'feat-waiter');
151
+ await kernel.invoke('feature.init', { feature_id: 'feat-blocker' }, ORCH_CTX);
152
+ await kernel.invoke('feature.init', { feature_id: 'feat-waiter' }, ORCH_CTX);
153
+
154
+ await kernel.updateState('feat-waiter', null, async (fm: Record<string, unknown>) => ({
155
+ frontMatter: { ...fm, depends_on: ['feat-blocker'] }
156
+ }));
157
+
158
+ await kernel.invoke('plan.submit', { feature_id: 'feat-waiter', plan_json: validPlan('feat-waiter') }, ORCH_CTX);
159
+
160
+ const index = await kernel.readIndex() as Record<string, unknown>;
161
+ const depBlocked = index.dep_blocked as Array<{ feature_id: string; depends_on_unresolved: string[] }>;
162
+ expect(depBlocked.some((e) => e.feature_id === 'feat-waiter')).toBe(true);
163
+ });
164
+ });
165
+ });
166
+
167
+ describe('DependencySchedulerService branch coverage', () => {
168
+ it('GIVEN_index_without_dep_blocked_WHEN_addToDepBlocked_called_THEN_creates_new_array', () => {
169
+ const index: Record<string, unknown> = {};
170
+ addToDepBlocked(index, 'feat-a', ['dep-1']);
171
+ expect(index.dep_blocked).toEqual([{ feature_id: 'feat-a', depends_on_unresolved: ['dep-1'] }]);
172
+ });
173
+
174
+ it('GIVEN_index_without_dep_blocked_WHEN_resolveDepBlocked_called_THEN_returns_empty', () => {
175
+ const index: Record<string, unknown> = {};
176
+ const promoted = resolveDepBlocked(index, 'some-feature');
177
+ expect(promoted).toEqual([]);
178
+ });
179
+
180
+ it('GIVEN_cycle_with_only_one_node_self_referencing_WHEN_detectCircular_called_THEN_detects_cycle', () => {
181
+ const result = detectCircularDependency('self', (id) => id === 'self' ? ['self'] : []);
182
+ expect(result).not.toBeNull();
183
+ });
184
+
185
+ it('GIVEN_empty_index_merged_WHEN_getUnresolvedDeps_called_THEN_returns_all_deps', () => {
186
+ const result = getUnresolvedDeps(['dep-a', 'dep-b'], []);
187
+ expect(result).toEqual(['dep-a', 'dep-b']);
188
+ });
189
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
- import { AopKernel } from '../src/core/kernel.js';
2
+ import { AopKernel } from '../src/index.js';
3
3
  import { makeTempRepo } from './helpers.js';
4
4
 
5
5
  describe('Orchestrator Epoch Tracking and Recovery', () => {
@@ -102,9 +102,9 @@ describe('Orchestrator Epoch Tracking and Recovery', () => {
102
102
  increment_epoch: true
103
103
  });
104
104
 
105
- const index = await kernel.readIndex();
106
- index.runtime_sessions.lease_expires_at = new Date(Date.now() - 60000).toISOString();
107
- await kernel.writeIndex(index);
105
+ const lease = await kernel.readRunLease();
106
+ lease.lease_expires_at = new Date(Date.now() - 60000).toISOString();
107
+ await kernel.writeRunLease(lease);
108
108
 
109
109
  const takeoverResult = await kernel.acquireRunLease({
110
110
  run_id: 'test-run-5-takeover',