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
@@ -4,6 +4,7 @@ import { runGit } from '../../core/git.js';
4
4
  import { ERROR_CODES } from '../../core/error-codes.js';
5
5
  import { fail } from '../../core/response.js';
6
6
  import { GATE_RESULT, STATUS } from '../../core/constants.js';
7
+ import { resolveDepBlocked } from './dependency-scheduler-service.js';
7
8
 
8
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
10
  type AnyRecord = Record<string, any>;
@@ -51,12 +52,58 @@ export class MergeService {
51
52
  this.port = port;
52
53
  }
53
54
 
55
+ private async cleanupMergedFeatureArtifacts(featureId: string): Promise<void> {
56
+ const repoRoot = this.port.getRepoRoot();
57
+ const worktreePath = this.port.worktreePath(featureId);
58
+
59
+ const removeWorktreeResult = await runGit(repoRoot, ['worktree', 'remove', '--force', worktreePath]);
60
+ if (removeWorktreeResult.code !== 0) {
61
+ // Best-effort cleanup; merged state must remain intact even if git cleanup fails.
62
+ console.warn(
63
+ `[aop] auto-cleanup worktree removal failed for ${featureId}: ${removeWorktreeResult.stderr.trim() || `exit ${removeWorktreeResult.code}`}`
64
+ );
65
+ }
66
+
67
+ const removeBranchResult = await runGit(repoRoot, ['branch', '-D', featureId]);
68
+ if (removeBranchResult.code !== 0) {
69
+ // Best-effort cleanup; branch may already be removed.
70
+ console.warn(
71
+ `[aop] auto-cleanup branch removal failed for ${featureId}: ${removeBranchResult.stderr.trim() || `exit ${removeBranchResult.code}`}`
72
+ );
73
+ }
74
+
75
+ await fs.rm(this.port.featurePath(featureId), { recursive: true, force: true });
76
+ }
77
+
78
+ private scheduleFeatureCleanup(featureId: string, gracePeriodSeconds: number): void {
79
+ const delayMs = Math.max(0, Math.floor(gracePeriodSeconds * 1000));
80
+ const timeout = setTimeout(() => {
81
+ void this.cleanupMergedFeatureArtifacts(featureId).catch((error) => {
82
+ console.warn(
83
+ `[aop] auto-cleanup failed for ${featureId}: ${error instanceof Error ? error.message : String(error)}`
84
+ );
85
+ });
86
+ }, delayMs);
87
+ if (typeof timeout.unref === 'function') {
88
+ timeout.unref();
89
+ }
90
+ }
91
+
54
92
  async featureReadyToMerge(
55
93
  featureId: string,
56
94
  commitMessage: string,
57
95
  mergeStrategy: string,
58
96
  userApprovalToken: string | null
59
- ): Promise<{ data: { feature_id: string; merge_sha: string; merge_strategy: string; status: string } }> {
97
+ ): Promise<{
98
+ data: {
99
+ feature_id: string;
100
+ merge_sha: string;
101
+ merge_strategy: string;
102
+ status: string;
103
+ retained_for_cleanup?: boolean;
104
+ cleanup_grace_period_seconds?: number;
105
+ };
106
+ }> {
60
107
  const policy = this.port.getPolicySnapshot();
61
108
  const state = await this.port.readState(featureId);
62
109
 
@@ -227,19 +274,36 @@ export class MergeService {
227
274
  runtimeSessions.feature_sessions = featureSessions;
228
275
  index.runtime_sessions = runtimeSessions;
229
276
 
277
+ // N4: Promote features that were blocked waiting for this feature to merge
278
+ resolveDepBlocked(index, featureId);
279
+
230
280
  index.version += 1;
231
281
  index.updated_at = nowIso();
232
282
  await this.port.writeIndex(index);
233
283
  });
234
284
 
235
- await fs.rm(this.port.featurePath(featureId), { recursive: true, force: true });
285
+ const cleanupPolicy = asRecord(policy.cleanup);
286
+ const autoAfterMerge = cleanupPolicy.auto_after_merge === true;
287
+ const gracePeriodSeconds =
288
+ typeof cleanupPolicy.grace_period_seconds === 'number' && Number.isFinite(cleanupPolicy.grace_period_seconds)
289
+ ? cleanupPolicy.grace_period_seconds
290
+ : 3600;
291
+ const shouldCleanupNow = autoAfterMerge && gracePeriodSeconds <= 0;
292
+
293
+ if (shouldCleanupNow) {
294
+ await this.cleanupMergedFeatureArtifacts(featureId);
295
+ } else if (autoAfterMerge) {
296
+ this.scheduleFeatureCleanup(featureId, gracePeriodSeconds);
297
+ }
236
298
 
237
299
  return {
238
300
  data: {
239
301
  feature_id: featureId,
240
302
  merge_sha: mergeSha,
241
303
  merge_strategy: mergeStrategy,
242
- status: STATUS.MERGED
304
+ status: STATUS.MERGED,
305
+ retained_for_cleanup: !shouldCleanupNow,
306
+ cleanup_grace_period_seconds: gracePeriodSeconds
243
307
  }
244
308
  };
245
309
  }
@@ -0,0 +1,295 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+
4
+ const execFileAsync = promisify(execFile);
5
+
6
+ export type NotificationPriority = 'critical' | 'warning' | 'info';
7
+ type CanonicalPriority = NotificationPriority;
8
+ export type NotificationEvent =
9
+ | 'gate_failed'
10
+ | 'collision_detected'
11
+ | 'feature_blocked'
12
+ | 'ready_to_merge'
13
+ | 'feature_merged'
14
+ | 'stale_lease'
15
+ | 'agent_stuck'
16
+ | 'changes_requested'
17
+ | 'budget_exceeded'
18
+ | 'budget_alert';
19
+
20
+ export interface NotificationPayload {
21
+ event: NotificationEvent;
22
+ priority: NotificationPriority;
23
+ feature_id?: string;
24
+ message: string;
25
+ details?: Record<string, unknown>;
26
+ timestamp: string;
27
+ }
28
+
29
+ export interface NotifierChannel {
30
+ name: string;
31
+ send(payload: NotificationPayload): Promise<void>;
32
+ }
33
+
34
+ export interface NotificationChannelConfig {
35
+ desktop?: { enabled?: boolean };
36
+ slack?: { enabled?: boolean; webhook?: string; channel?: string };
37
+ webhook?: { enabled?: boolean; url?: string };
38
+ }
39
+
40
+ export interface NotificationRoutingConfig {
41
+ critical?: string[];
42
+ warning?: string[];
43
+ info?: string[];
44
+ urgent?: string[];
45
+ action?: string[];
46
+ }
47
+
48
+ export interface NotificationConfig {
49
+ enabled: boolean;
50
+ channels?: NotificationChannelConfig;
51
+ routing?: NotificationRoutingConfig;
52
+ }
53
+
54
+ const DEFAULT_ROUTING: Record<NotificationPriority, string[]> = {
55
+ critical: ['desktop', 'slack'],
56
+ warning: ['slack', 'desktop'],
57
+ info: ['slack']
58
+ };
59
+
60
+ const EVENT_PRIORITY: Record<NotificationEvent, CanonicalPriority> = {
61
+ gate_failed: 'critical',
62
+ collision_detected: 'critical',
63
+ feature_blocked: 'warning',
64
+ ready_to_merge: 'info',
65
+ feature_merged: 'info',
66
+ stale_lease: 'warning',
67
+ agent_stuck: 'warning',
68
+ changes_requested: 'warning',
69
+ budget_exceeded: 'critical',
70
+ budget_alert: 'warning'
71
+ };
72
+
73
+ const THROTTLE_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
74
+
75
+ export class DesktopNotifierChannel implements NotifierChannel {
76
+ readonly name = 'desktop';
77
+
78
+ async send(payload: NotificationPayload): Promise<void> {
79
+ const title = `AOP [${payload.priority}]`;
80
+ const body = payload.feature_id
81
+ ? `[${payload.feature_id}] ${payload.message}`
82
+ : payload.message;
83
+
84
+ const platform = process.platform;
85
+ try {
86
+ if (platform === 'linux') {
87
+ await execFileAsync('notify-send', [title, body], { timeout: 3000 });
88
+ } else if (platform === 'darwin') {
89
+ const script = `display notification "${body.replaceAll('"', '\\"')}" with title "${title.replaceAll('"', '\\"')}"`;
90
+ await execFileAsync('osascript', ['-e', script], { timeout: 3000 });
91
+ }
92
+ // Other platforms: silently skip
93
+ } catch {
94
+ // Notification tool not available — silently skip
95
+ }
96
+ }
97
+ }
98
+
99
+ export class SlackNotifierChannel implements NotifierChannel {
100
+ readonly name = 'slack';
101
+ private readonly webhookUrl: string;
102
+ private readonly channel: string;
103
+
104
+ constructor(webhookUrl: string, channel: string) {
105
+ this.webhookUrl = webhookUrl;
106
+ this.channel = channel;
107
+ }
108
+
109
+ async send(payload: NotificationPayload): Promise<void> {
110
+ const details = payload.details ?? {};
111
+ const dashboardFeatureUrl =
112
+ typeof details.dashboard_feature_url === 'string' && details.dashboard_feature_url.length > 0
113
+ ? details.dashboard_feature_url
114
+ : typeof details.dashboard_url === 'string' && details.dashboard_url.length > 0
115
+ ? details.dashboard_url
116
+ : null;
117
+ const detailsJson = JSON.stringify(details);
118
+ const detailsSuffix =
119
+ detailsJson !== '{}' ? `\nContext: \`${detailsJson.length > 800 ? `${detailsJson.slice(0, 800)}…` : detailsJson}\`` : '';
120
+ const dashboardSuffix = dashboardFeatureUrl ? `\nDashboard: ${dashboardFeatureUrl}` : '';
121
+ const text = payload.feature_id
122
+ ? `*[${payload.priority.toUpperCase()}]* \`${payload.feature_id}\`: ${payload.message}`
123
+ : `*[${payload.priority.toUpperCase()}]* ${payload.message}`;
124
+
125
+ const body = JSON.stringify({ channel: this.channel, text: `${text}${dashboardSuffix}${detailsSuffix}` });
126
+ const response = await fetch(this.webhookUrl, {
127
+ method: 'POST',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ body
130
+ });
131
+ if (!response.ok) {
132
+ throw new Error(`Slack webhook returned ${response.status}`);
133
+ }
134
+ }
135
+ }
136
+
137
+ export class WebhookNotifierChannel implements NotifierChannel {
138
+ readonly name = 'webhook';
139
+ private readonly url: string;
140
+
141
+ constructor(url: string) {
142
+ this.url = url;
143
+ }
144
+
145
+ async send(payload: NotificationPayload): Promise<void> {
146
+ const response = await fetch(this.url, {
147
+ method: 'POST',
148
+ headers: { 'Content-Type': 'application/json' },
149
+ body: JSON.stringify(payload)
150
+ });
151
+ if (!response.ok) {
152
+ throw new Error(`Webhook returned ${response.status}`);
153
+ }
154
+ }
155
+ }
156
+
157
+ export class NotifierService {
158
+ private readonly config: NotificationConfig;
159
+ private readonly channels: Map<string, NotifierChannel>;
160
+ private readonly routing: Record<CanonicalPriority, string[]>;
161
+ private readonly throttleCache: Map<string, number>;
162
+ private readonly dashboardUrl: string | null;
163
+
164
+ constructor(config: NotificationConfig, dashboardUrl: string | null = null) {
165
+ this.config = config;
166
+ this.throttleCache = new Map();
167
+ this.routing = this.buildRouting(config.routing);
168
+ this.channels = this.buildChannels(config.channels);
169
+ this.dashboardUrl = dashboardUrl;
170
+ }
171
+
172
+ private buildRouting(routing: NotificationRoutingConfig | undefined): Record<CanonicalPriority, string[]> {
173
+ const critical = routing?.critical ?? routing?.urgent ?? routing?.action ?? DEFAULT_ROUTING.critical;
174
+ return {
175
+ critical,
176
+ warning: routing?.warning ?? DEFAULT_ROUTING.warning,
177
+ info: routing?.info ?? DEFAULT_ROUTING.info
178
+ };
179
+ }
180
+
181
+ private buildChannels(channelsConfig: NotificationChannelConfig | undefined): Map<string, NotifierChannel> {
182
+ const map = new Map<string, NotifierChannel>();
183
+ if (!channelsConfig) {
184
+ return map;
185
+ }
186
+
187
+ if (channelsConfig.desktop?.enabled === true) {
188
+ map.set('desktop', new DesktopNotifierChannel());
189
+ }
190
+
191
+ const slack = channelsConfig.slack;
192
+ if (slack?.enabled === true && slack.webhook) {
193
+ map.set('slack', new SlackNotifierChannel(slack.webhook, slack.channel ?? '#aop-alerts'));
194
+ }
195
+
196
+ const webhook = channelsConfig.webhook;
197
+ if (webhook?.enabled === true && webhook.url) {
198
+ map.set('webhook', new WebhookNotifierChannel(webhook.url));
199
+ }
200
+
201
+ return map;
202
+ }
203
+
204
+ private isThrottled(featureId: string | undefined, event: NotificationEvent): boolean {
205
+ const key = `${featureId ?? '__global__'}:${event}`;
206
+ const last = this.throttleCache.get(key);
207
+ const now = Date.now();
208
+ if (last !== undefined && now - last < THROTTLE_WINDOW_MS) {
209
+ return true;
210
+ }
211
+ this.throttleCache.set(key, now);
212
+ return false;
213
+ }
214
+
215
+ async notify(
216
+ event: NotificationEvent,
217
+ context: { feature_id?: string; message: string; details?: Record<string, unknown> }
218
+ ): Promise<void> {
219
+ if (!this.config.enabled) {
220
+ return;
221
+ }
222
+ if (this.isThrottled(context.feature_id, event)) {
223
+ return;
224
+ }
225
+
226
+ const priority = EVENT_PRIORITY[event];
227
+ const details: Record<string, unknown> = {
228
+ ...(context.details ?? {})
229
+ };
230
+ if (this.dashboardUrl) {
231
+ details.dashboard_url = this.dashboardUrl;
232
+ if (context.feature_id) {
233
+ details.dashboard_feature_url = `${this.dashboardUrl}?feature=${encodeURIComponent(context.feature_id)}`;
234
+ }
235
+ }
236
+ const payload: NotificationPayload = {
237
+ event,
238
+ priority,
239
+ feature_id: context.feature_id,
240
+ message: context.message,
241
+ details,
242
+ timestamp: new Date().toISOString()
243
+ };
244
+
245
+ const targetChannelNames = this.routing[priority] ?? [];
246
+
247
+ for (const channelName of targetChannelNames) {
248
+ const channel = this.channels.get(channelName);
249
+ if (!channel) {
250
+ continue;
251
+ }
252
+ try {
253
+ await channel.send(payload);
254
+ } catch (err) {
255
+ process.stderr.write(`[NotifierService] Channel "${channelName}" failed: ${String(err)}\n`);
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ export function createNotifierService(
262
+ policySnapshot: Record<string, unknown>,
263
+ preferredChannel: string | null = null
264
+ ): NotifierService {
265
+ const raw = policySnapshot.notifications;
266
+ if (!raw || typeof raw !== 'object') {
267
+ return new NotifierService({ enabled: false });
268
+ }
269
+ const n = raw as Record<string, unknown>;
270
+ const config: NotificationConfig = {
271
+ enabled: n.enabled === true,
272
+ channels: (n.channels as NotificationChannelConfig | undefined) ?? {},
273
+ routing: (n.routing as NotificationRoutingConfig | undefined) ?? {}
274
+ };
275
+ if (preferredChannel) {
276
+ const channels = config.channels ?? {};
277
+ const forceEnabled = { enabled: true };
278
+ config.channels = {
279
+ desktop: preferredChannel === 'desktop' ? { ...(channels.desktop ?? {}), ...forceEnabled } : { enabled: false },
280
+ slack: preferredChannel === 'slack' ? { ...(channels.slack ?? {}), ...forceEnabled } : { enabled: false },
281
+ webhook: preferredChannel === 'webhook' ? { ...(channels.webhook ?? {}), ...forceEnabled } : { enabled: false }
282
+ };
283
+ }
284
+ const dashboardRaw =
285
+ policySnapshot.dashboard && typeof policySnapshot.dashboard === 'object'
286
+ ? (policySnapshot.dashboard as Record<string, unknown>)
287
+ : {};
288
+ const dashboardPort =
289
+ typeof dashboardRaw.port === 'number' && Number.isFinite(dashboardRaw.port) ? Math.floor(dashboardRaw.port) : 3000;
290
+ const dashboardUrl =
291
+ typeof process.env.AOP_DASHBOARD_URL === 'string' && process.env.AOP_DASHBOARD_URL.trim().length > 0
292
+ ? process.env.AOP_DASHBOARD_URL.trim()
293
+ : `http://localhost:${dashboardPort}`;
294
+ return new NotifierService(config, dashboardUrl);
295
+ }
@@ -0,0 +1,122 @@
1
+ import { atomicWriteJson, ensureDir, nowIso, readJson } from '../../core/fs.js';
2
+ import path from 'node:path';
3
+
4
+ export interface FeatureOutcome {
5
+ feature_id: string;
6
+ provider: string;
7
+ model: string;
8
+ status: string;
9
+ gate_pass: boolean;
10
+ retry_count: number;
11
+ duration_ms: number;
12
+ cost_usd: number;
13
+ recorded_at: string;
14
+ }
15
+
16
+ export interface ProviderModelStats {
17
+ provider: string;
18
+ model: string;
19
+ total_features: number;
20
+ success_count: number;
21
+ success_rate: number;
22
+ avg_retry_count: number;
23
+ avg_duration_ms: number;
24
+ avg_cost_usd: number;
25
+ last_updated: string;
26
+ }
27
+
28
+ export interface AnalyticsSnapshot {
29
+ outcomes: FeatureOutcome[];
30
+ aggregates: ProviderModelStats[];
31
+ generated_at: string;
32
+ }
33
+
34
+ export interface PerformanceAnalyticsServicePort {
35
+ analyticsPath(): string;
36
+ getPolicySnapshot(): Record<string, unknown>;
37
+ }
38
+
39
+ function computeAggregates(outcomes: FeatureOutcome[]): ProviderModelStats[] {
40
+ const groups = new Map<string, FeatureOutcome[]>();
41
+ for (const o of outcomes) {
42
+ const key = `${o.provider}::${o.model}`;
43
+ const list = groups.get(key);
44
+ if (list) {
45
+ list.push(o);
46
+ } else {
47
+ groups.set(key, [o]);
48
+ }
49
+ }
50
+
51
+ const aggregates: ProviderModelStats[] = [];
52
+ for (const [, group] of groups) {
53
+ const first = group[0];
54
+ const total = group.length;
55
+ const successCount = group.filter((o) => o.gate_pass).length;
56
+ const sumRetry = group.reduce((s, o) => s + o.retry_count, 0);
57
+ const sumDuration = group.reduce((s, o) => s + o.duration_ms, 0);
58
+ const sumCost = group.reduce((s, o) => s + o.cost_usd, 0);
59
+
60
+ aggregates.push({
61
+ provider: first.provider,
62
+ model: first.model,
63
+ total_features: total,
64
+ success_count: successCount,
65
+ success_rate: total > 0 ? successCount / total : 0,
66
+ avg_retry_count: total > 0 ? sumRetry / total : 0,
67
+ avg_duration_ms: total > 0 ? sumDuration / total : 0,
68
+ avg_cost_usd: total > 0 ? sumCost / total : 0,
69
+ last_updated: nowIso()
70
+ });
71
+ }
72
+
73
+ return aggregates;
74
+ }
75
+
76
+ export class PerformanceAnalyticsService {
77
+ private readonly port: PerformanceAnalyticsServicePort;
78
+
79
+ constructor(port: PerformanceAnalyticsServicePort) {
80
+ this.port = port;
81
+ }
82
+
83
+ async recordOutcome(outcome: FeatureOutcome): Promise<AnalyticsSnapshot> {
84
+ const filePath = this.port.analyticsPath();
85
+ await ensureDir(path.dirname(filePath));
86
+
87
+ const current = await this.readSnapshot();
88
+ current.outcomes.push(outcome);
89
+ current.aggregates = computeAggregates(current.outcomes);
90
+ current.generated_at = nowIso();
91
+
92
+ await atomicWriteJson(filePath, current);
93
+ return current;
94
+ }
95
+
96
+ async getAnalytics(): Promise<AnalyticsSnapshot> {
97
+ return await this.readSnapshot();
98
+ }
99
+
100
+ async getProviderStats(provider?: string, model?: string): Promise<ProviderModelStats[]> {
101
+ const snapshot = await this.readSnapshot();
102
+ let stats = snapshot.aggregates;
103
+
104
+ if (provider) {
105
+ stats = stats.filter((s) => s.provider === provider);
106
+ }
107
+ if (model) {
108
+ stats = stats.filter((s) => s.model === model);
109
+ }
110
+
111
+ return stats;
112
+ }
113
+
114
+ private async readSnapshot(): Promise<AnalyticsSnapshot> {
115
+ const existing = await readJson<AnalyticsSnapshot>(this.port.analyticsPath(), null);
116
+ return existing ?? {
117
+ outcomes: [],
118
+ aggregates: [],
119
+ generated_at: nowIso()
120
+ };
121
+ }
122
+ }
@@ -4,6 +4,7 @@ import { sortResourcesDeterministically } from '../../core/path-rules.js';
4
4
  import { ERROR_CODES } from '../../core/error-codes.js';
5
5
  import { fail } from '../../core/response.js';
6
6
  import { GATE_RESULT, STATUS } from '../../core/constants.js';
7
+ import { getUnresolvedDeps, detectCircularDependency, addToDepBlocked } from './dependency-scheduler-service.js';
7
8
 
8
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
10
  type AnyRecord = Record<string, any>;
@@ -227,6 +228,53 @@ export class PlanService {
227
228
  };
228
229
  }
229
230
 
231
+ async checkDependencies(featureId: string | null): Promise<void> {
232
+ if (!featureId) {return;}
233
+ const state = await this.port.readState(featureId);
234
+ const dependsOn: string[] = Array.isArray(state.frontMatter.depends_on)
235
+ ? (state.frontMatter.depends_on as string[]).filter((d): d is string => typeof d === 'string')
236
+ : [];
237
+ if (dependsOn.length === 0) {return;}
238
+
239
+ // Circular detection: traverse the dependency chain
240
+ const depsCache = new Map<string, string[]>([[featureId, dependsOn]]);
241
+ const getDirectDeps = (id: string): string[] => {
242
+ if (depsCache.has(id)) {return depsCache.get(id);}
243
+ return [];
244
+ };
245
+ const cycle = detectCircularDependency(featureId, getDirectDeps);
246
+ if (cycle) {
247
+ throw {
248
+ normalizedResponse: fail(ERROR_CODES.DEPENDENCY_CIRCULAR, `Circular dependency detected: ${cycle}`, {
249
+ feature_id: featureId,
250
+ cycle,
251
+ retryable: false,
252
+ requires_human: true
253
+ })
254
+ };
255
+ }
256
+
257
+ const index = await this.port.readIndex();
258
+ const mergedFeatures: string[] = Array.isArray(index.merged) ? (index.merged as string[]) : [];
259
+ const unresolved = getUnresolvedDeps(dependsOn, mergedFeatures);
260
+ if (unresolved.length > 0) {
261
+ await this.port.withIndexLock(async () => {
262
+ const idx = await this.port.readIndex();
263
+ addToDepBlocked(idx, featureId, unresolved);
264
+ idx.updated_at = nowIso();
265
+ await this.port.writeIndex(idx);
266
+ });
267
+ throw {
268
+ normalizedResponse: fail(ERROR_CODES.DEPENDENCY_UNRESOLVED, 'Plan deferred: dependencies not yet merged', {
269
+ feature_id: featureId,
270
+ depends_on_unresolved: unresolved,
271
+ retryable: true,
272
+ requires_human: false
273
+ })
274
+ };
275
+ }
276
+ }
277
+
230
278
  async planSubmit(
231
279
  featureId: string | null,
232
280
  plan: AnyRecord | null,
@@ -255,6 +303,9 @@ export class PlanService {
255
303
  await this.validatePlanSchema(plan);
256
304
  await this.assertPlanLocksHeld(featureId, plan);
257
305
 
306
+ // N4: Dependency-aware scheduling — check depends_on before accepting plan
307
+ await this.checkDependencies(featureId);
308
+
258
309
  const existing = await readJson(this.port.planPath(featureId), null);
259
310
  this.validatePlanRevisionRules(existing, plan, expectedVersion);
260
311