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
@@ -27,6 +27,8 @@ export interface GateStep {
27
27
  cwd?: string;
28
28
  env?: Record<string, string>;
29
29
  timeout_seconds?: number;
30
+ parallel_group?: string;
31
+ depends_on?: string[];
30
32
  }
31
33
 
32
34
  export interface GateProfile {
@@ -172,6 +174,37 @@ export async function parseCoverage(parserConfig: ParserConfig | undefined, work
172
174
  return defaultCoverageParserRegistry.parse(parserConfig, content);
173
175
  }
174
176
 
177
+ export function buildExecutionWaves(steps: GateStep[]): GateStep[][] {
178
+ const stepMap = new Map(steps.map((s) => [s.name, s]));
179
+ const placed = new Set<string>();
180
+ const waves: GateStep[][] = [];
181
+
182
+ let remaining = [...steps];
183
+ while (remaining.length > 0) {
184
+ const wave: GateStep[] = [];
185
+ for (const step of remaining) {
186
+ const deps = step.depends_on ?? [];
187
+ if (deps.every((dep) => placed.has(dep) || !stepMap.has(dep))) {
188
+ wave.push(step);
189
+ }
190
+ }
191
+
192
+ if (wave.length === 0) {
193
+ // Circular dependency — force remaining into final wave
194
+ waves.push(remaining);
195
+ break;
196
+ }
197
+
198
+ waves.push(wave);
199
+ for (const step of wave) {
200
+ placed.add(step.name);
201
+ }
202
+ remaining = remaining.filter((s) => !placed.has(s.name));
203
+ }
204
+
205
+ return waves;
206
+ }
207
+
175
208
  export class GateExecutionService {
176
209
  private readonly thresholdPolicy: CoverageThresholdPolicy;
177
210
 
@@ -179,6 +212,70 @@ export class GateExecutionService {
179
212
  this.thresholdPolicy = thresholdPolicy;
180
213
  }
181
214
 
215
+ private async executeStep(
216
+ step: GateStep,
217
+ ctx: {
218
+ featureId: string;
219
+ mode: string;
220
+ policy: PolicyShape;
221
+ env: NodeJS.ProcessEnv;
222
+ worktreePath: string;
223
+ logDirectory: string;
224
+ }
225
+ ): Promise<Record<string, unknown>> {
226
+ const timeoutSeconds = Number(step.timeout_seconds ?? ctx.policy.execution.default_step_timeout_seconds);
227
+ const stepLogPath = path.join(
228
+ ctx.logDirectory,
229
+ `${Date.now()}-${ctx.mode}-${step.name.replaceAll(/[^a-zA-Z0-9_-]/g, '_')}.log`
230
+ );
231
+
232
+ const retryPolicy = ctx.policy.execution.retry_policy;
233
+ let attempts = 0;
234
+ let lastResult: CommandResult = { code: 1, signal: null, stdout: '', stderr: '', timeout: false };
235
+ const maxAttempts = retryPolicy.transient_max_retries + 1;
236
+
237
+ while (attempts < maxAttempts) {
238
+ attempts += 1;
239
+ const result = await runCommand(step.cmd[0], step.cmd.slice(1), {
240
+ cwd: step.cwd ? path.join(ctx.worktreePath, step.cwd) : ctx.worktreePath,
241
+ env: { ...ctx.env, ...step.env },
242
+ timeoutMs: timeoutSeconds * 1000
243
+ });
244
+
245
+ lastResult = result;
246
+ const logText = [
247
+ `# feature=${ctx.featureId} mode=${ctx.mode} step=${step.name} attempt=${attempts}`,
248
+ `# cmd=${JSON.stringify(step.cmd)}`,
249
+ `# exit=${result.code} timeout=${result.timeout}`,
250
+ '',
251
+ '## stdout',
252
+ result.stdout,
253
+ '',
254
+ '## stderr',
255
+ result.stderr
256
+ ].join('\n');
257
+ await fs.writeFile(stepLogPath, `${logText}\n`, 'utf8');
258
+
259
+ if (result.timeout || result.code === 0) {
260
+ break;
261
+ }
262
+
263
+ const isTransient = retryPolicy.transient_error_codes.includes(result.code);
264
+ if (!isTransient) {
265
+ break;
266
+ }
267
+ }
268
+
269
+ return {
270
+ name: step.name,
271
+ cmd: step.cmd,
272
+ attempts,
273
+ exit_code: lastResult.code,
274
+ timeout: lastResult.timeout,
275
+ log_path: stepLogPath
276
+ };
277
+ }
278
+
182
279
  async runMode({
183
280
  featureId,
184
281
  mode,
@@ -206,68 +303,91 @@ export class GateExecutionService {
206
303
  await ensureDir(logDirectory);
207
304
  await ensureDir(evidenceDirectory);
208
305
 
209
- const retryPolicy = policy.execution.retry_policy;
210
306
  const env = pickAllowedEnv(policy.execution.env_allowlist);
211
307
  const stepResults: Array<Record<string, unknown>> = [];
212
308
  let overall = 'pass';
213
309
 
214
- for (const step of modeSteps) {
215
- const timeoutSeconds = Number(step.timeout_seconds ?? policy.execution.default_step_timeout_seconds);
216
- const stepLogPath = path.join(
217
- logDirectory,
218
- `${Date.now()}-${mode}-${step.name.replaceAll(/[^a-zA-Z0-9_-]/g, '_')}.log`
219
- );
220
-
221
- let attempts = 0;
222
- let lastResult: CommandResult = { code: 1, signal: null, stdout: '', stderr: '', timeout: false };
223
- const maxAttempts = retryPolicy.transient_max_retries + 1;
224
-
225
- while (attempts < maxAttempts) {
226
- attempts += 1;
227
- const result = await runCommand(step.cmd[0], step.cmd.slice(1), {
228
- cwd: step.cwd ? path.join(worktreePath, step.cwd) : worktreePath,
229
- env: { ...env, ...step.env },
230
- timeoutMs: timeoutSeconds * 1000
310
+ const hasParallelConfig = modeSteps.some((s) => s.parallel_group || s.depends_on?.length);
311
+
312
+ if (hasParallelConfig) {
313
+ const waves = buildExecutionWaves(modeSteps);
314
+ const completedSteps = new Set<string>();
315
+ const failedSteps = new Set<string>();
316
+
317
+ for (const wave of waves) {
318
+ const runnableSteps = wave.filter((step) => {
319
+ if (!step.depends_on?.length) {
320
+ return true;
321
+ }
322
+ return step.depends_on.every((dep) => completedSteps.has(dep) && !failedSteps.has(dep));
231
323
  });
232
324
 
233
- lastResult = result;
234
- const logText = [
235
- `# feature=${featureId} mode=${mode} step=${step.name} attempt=${attempts}`,
236
- `# cmd=${JSON.stringify(step.cmd)}`,
237
- `# exit=${result.code} timeout=${result.timeout}`,
238
- '',
239
- '## stdout',
240
- result.stdout,
241
- '',
242
- '## stderr',
243
- result.stderr
244
- ].join('\n');
245
- await fs.writeFile(stepLogPath, `${logText}\n`, 'utf8');
246
-
247
- if (result.timeout || result.code === 0) {
248
- break;
325
+ const skippedSteps = wave.filter((step) => !runnableSteps.includes(step));
326
+ for (const step of skippedSteps) {
327
+ failedSteps.add(step.name);
328
+ stepResults.push({
329
+ name: step.name,
330
+ cmd: step.cmd,
331
+ attempts: 0,
332
+ exit_code: -1,
333
+ timeout: false,
334
+ skipped: true,
335
+ skip_reason: 'dependency_failed',
336
+ parallel_group: step.parallel_group,
337
+ started_at: nowIso(),
338
+ finished_at: nowIso()
339
+ });
249
340
  }
250
341
 
251
- const isTransient = retryPolicy.transient_error_codes.includes(result.code);
252
- if (!isTransient) {
253
- break;
342
+ const results = await Promise.allSettled(
343
+ runnableSteps.map(async (step) => {
344
+ const startedAt = nowIso();
345
+ const stepResult = await this.executeStep(step, {
346
+ featureId,
347
+ mode,
348
+ policy,
349
+ env,
350
+ worktreePath,
351
+ logDirectory
352
+ });
353
+ stepResult['parallel_group'] = step.parallel_group;
354
+ stepResult['started_at'] = startedAt;
355
+ stepResult['finished_at'] = nowIso();
356
+ return stepResult;
357
+ })
358
+ );
359
+
360
+ for (const result of results) {
361
+ if (result.status === 'fulfilled') {
362
+ const val = result.value;
363
+ stepResults.push(val);
364
+ if (val['exit_code'] === 0 && !val['timeout']) {
365
+ completedSteps.add(val['name'] as string);
366
+ } else {
367
+ overall = 'fail';
368
+ failedSteps.add(val['name'] as string);
369
+ }
370
+ }
254
371
  }
255
372
  }
373
+ } else {
374
+ for (const step of modeSteps) {
375
+ const startedAt = nowIso();
376
+ const result = await this.executeStep(step, {
377
+ featureId,
378
+ mode,
379
+ policy,
380
+ env,
381
+ worktreePath,
382
+ logDirectory
383
+ });
256
384
 
257
- const stepState = {
258
- name: step.name,
259
- cmd: step.cmd,
260
- attempts,
261
- exit_code: lastResult.code,
262
- timeout: lastResult.timeout,
263
- log_path: stepLogPath
264
- };
265
-
266
- stepResults.push(stepState);
385
+ stepResults.push({ ...result, started_at: startedAt, finished_at: nowIso() });
267
386
 
268
- if (lastResult.timeout || lastResult.code !== 0) {
269
- overall = 'fail';
270
- break;
387
+ if (result.timeout || result.exit_code !== 0) {
388
+ overall = 'fail';
389
+ break;
390
+ }
271
391
  }
272
392
  }
273
393
 
@@ -9,6 +9,12 @@ import { ERROR_CODES } from './error-codes.js';
9
9
  import { ok, fail, withSuggestedActions, type ToolResponse } from './response.js';
10
10
  import { ALLOWED_ACTORS, DEFAULT_CLUSTER, DEFAULT_ROLE_STATUS, GATE_RESULT, STATUS, TOOLS } from './constants.js';
11
11
  import { AopPathLayout, ensureAopRuntimeLayout } from './path-layout.js';
12
+ import {
13
+ applyWorktreeSymlinks,
14
+ formatWorkspaceHookWarning,
15
+ runWorktreePostCreate,
16
+ type WorkspaceHookWarning
17
+ } from './workspace-hooks.js';
12
18
  import type { RuntimeSessionsSnapshot } from './runtime-sessions.js';
13
19
  import { ToolRegistryLoader } from '../mcp/tool-registry-loader.js';
14
20
  import { ToolHandlerRegistry, ToolRouter, type ToolHandlerContext } from '../application/tools/tool-router.js';
@@ -24,6 +30,15 @@ import { QaIndexService } from '../application/services/qa-index-service.js';
24
30
  import { MergeService } from '../application/services/merge-service.js';
25
31
  import { CollisionQueueService } from '../application/services/collision-queue-service.js';
26
32
  import { FeatureDeletionService, type FeatureDeleteResult } from '../application/services/feature-deletion-service.js';
33
+ import { CostTrackingService } from '../application/services/cost-tracking-service.js';
34
+ import { PerformanceAnalyticsService, type FeatureOutcome } from '../application/services/performance-analytics-service.js';
35
+ import {
36
+ ACTIVITY_DETECTOR_SLOT,
37
+ NOTIFICATION_CHANNEL_SLOT,
38
+ SCM_PROVIDER_SLOT,
39
+ globalAdapterRegistry
40
+ } from '../application/adapters/adapter-registry.js';
41
+ import type { WorkerProvider } from '../providers/providers.js';
27
42
 
28
43
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
44
  type AnyRecord = Record<string, any>;
@@ -33,6 +48,13 @@ interface KernelContext {
33
48
  actor_id?: string;
34
49
  }
35
50
 
51
+ interface KernelConfigOverrides {
52
+ policyPath?: string;
53
+ gatesPath?: string;
54
+ agentsPath?: string;
55
+ adaptersPath?: string;
56
+ }
57
+
36
58
  export interface AgentsRoleConfig {
37
59
  system_prompt_path?: string;
38
60
  }
@@ -91,6 +113,7 @@ export class AopKernel {
91
113
  gatesConfig: AnyRecord;
92
114
  policy: PolicyConfigSnapshot;
93
115
  agentsConfig: AgentsConfigSnapshot;
116
+ adaptersConfig: Record<string, unknown>;
94
117
  toolRegistry: AnyRecord | null;
95
118
  toolHandlers: ToolHandlerRegistry;
96
119
  toolRouter: ToolRouter;
@@ -106,16 +129,24 @@ export class AopKernel {
106
129
  private readonly qaIndexService: QaIndexService;
107
130
  private readonly mergeService: MergeService;
108
131
  private readonly featureDeletionService: FeatureDeletionService;
132
+ private readonly costTrackingService: CostTrackingService;
133
+ private readonly performanceAnalyticsService: PerformanceAnalyticsService;
109
134
  private readonly pathLayout: AopPathLayout;
135
+ readonly instanceId: string;
136
+ private provider: WorkerProvider | null = null;
137
+ private readonly configOverrides: KernelConfigOverrides;
110
138
 
111
- constructor(repoRoot: string) {
139
+ constructor(repoRoot: string, instanceId = 'default', configOverrides: KernelConfigOverrides = {}) {
112
140
  this.repoRoot = repoRoot;
141
+ this.instanceId = instanceId;
142
+ this.configOverrides = configOverrides;
113
143
  this.pathLayout = new AopPathLayout(repoRoot);
114
144
  this.schemaRegistry = new SchemaRegistry(repoRoot);
115
145
  this.loaded = false;
116
146
  this.gatesConfig = {};
117
147
  this.policy = {};
118
148
  this.agentsConfig = {};
149
+ this.adaptersConfig = {};
119
150
  this.toolRegistry = null;
120
151
  this.toolHandlers = new ToolHandlerRegistry();
121
152
  this.registerToolHandlers();
@@ -131,6 +162,8 @@ export class AopKernel {
131
162
  this.qaIndexService = new QaIndexService(this);
132
163
  this.mergeService = new MergeService(this);
133
164
  this.featureDeletionService = new FeatureDeletionService(this);
165
+ this.costTrackingService = new CostTrackingService(this);
166
+ this.performanceAnalyticsService = new PerformanceAnalyticsService(this);
134
167
  this.toolRouter = new ToolRouter(this.toolHandlers, (toolName) =>
135
168
  Promise.resolve(
136
169
  fail(ERROR_CODES.INVALID_ARGUMENT, `Unknown tool ${toolName}`, {
@@ -145,6 +178,10 @@ export class AopKernel {
145
178
  return this.repoRoot;
146
179
  }
147
180
 
181
+ setProvider(provider: WorkerProvider): void {
182
+ this.provider = provider;
183
+ }
184
+
148
185
  getAgentsConfig(): AgentsConfigSnapshot {
149
186
  return this.agentsConfig;
150
187
  }
@@ -153,6 +190,10 @@ export class AopKernel {
153
190
  return this.policy.rbac ?? {};
154
191
  }
155
192
 
193
+ getAdaptersConfig(): Record<string, unknown> {
194
+ return this.adaptersConfig;
195
+ }
196
+
156
197
  getPolicySnapshot(): AnyRecord {
157
198
  return this.policy;
158
199
  }
@@ -276,6 +317,7 @@ export class AopKernel {
276
317
  locks: source.locks && typeof source.locks === 'object' ? source.locks : {},
277
318
  lock_leases: source.lock_leases && typeof source.lock_leases === 'object' ? source.lock_leases : {},
278
319
  blocked_queue: asArray(source.blocked_queue).filter((item) => item && typeof item === 'object'),
320
+ dep_blocked: asArray(source.dep_blocked).filter((item) => item && typeof item === 'object'),
279
321
  updated_at: typeof source.updated_at === 'string' && source.updated_at ? source.updated_at : now,
280
322
  runtime_sessions: this.normalizeRuntimeSessions(source.runtime_sessions, now)
281
323
  };
@@ -292,9 +334,10 @@ export class AopKernel {
292
334
  async load() {
293
335
  await ensureAopRuntimeLayout(this.pathLayout);
294
336
 
295
- const gatesPath = path.join(this.orchestratorDir, 'gates.yaml');
296
- const policyPath = path.join(this.orchestratorDir, 'policy.yaml');
297
- const agentsPath = path.join(this.orchestratorDir, 'agents.yaml');
337
+ const gatesPath = this.configOverrides.gatesPath ?? path.join(this.orchestratorDir, 'gates.yaml');
338
+ const policyPath = this.configOverrides.policyPath ?? path.join(this.orchestratorDir, 'policy.yaml');
339
+ const agentsPath = this.configOverrides.agentsPath ?? path.join(this.orchestratorDir, 'agents.yaml');
340
+ const adaptersPath = this.configOverrides.adaptersPath ?? path.join(this.orchestratorDir, 'adapters.yaml');
298
341
 
299
342
  const gates = await loadAndValidateYaml(this.schemaRegistry, 'gates.schema.json', gatesPath);
300
343
  if (!gates.validation.valid) {
@@ -326,9 +369,44 @@ export class AopKernel {
326
369
  }
327
370
  }
328
371
 
372
+ const adaptersExists = await pathExists(adaptersPath);
373
+ let adapters = { parsed: {}, validation: { valid: true, errors: [] as unknown[] } };
374
+ if (adaptersExists) {
375
+ adapters = await loadAndValidateYaml(this.schemaRegistry, 'adapters.schema.json', adaptersPath);
376
+ if (!adapters.validation.valid) {
377
+ throw new Error(`invalid_adapters_yaml:${JSON.stringify(adapters.validation.errors)}`);
378
+ }
379
+ const parsedAdapters = readObjectField(adapters, 'parsed');
380
+ const notificationChannel = readStringField(parsedAdapters, NOTIFICATION_CHANNEL_SLOT.name);
381
+ if (notificationChannel) {
382
+ try {
383
+ globalAdapterRegistry.resolve(NOTIFICATION_CHANNEL_SLOT, notificationChannel, {});
384
+ } catch {
385
+ throw new Error(`adapter_not_found:${NOTIFICATION_CHANNEL_SLOT.name}:${notificationChannel}`);
386
+ }
387
+ }
388
+ const activityDetector = readStringField(parsedAdapters, ACTIVITY_DETECTOR_SLOT.name);
389
+ if (activityDetector) {
390
+ try {
391
+ globalAdapterRegistry.resolve(ACTIVITY_DETECTOR_SLOT, activityDetector, {});
392
+ } catch {
393
+ throw new Error(`adapter_not_found:${ACTIVITY_DETECTOR_SLOT.name}:${activityDetector}`);
394
+ }
395
+ }
396
+ const scmProvider = readStringField(parsedAdapters, SCM_PROVIDER_SLOT.name);
397
+ if (scmProvider) {
398
+ try {
399
+ globalAdapterRegistry.resolve(SCM_PROVIDER_SLOT, scmProvider, {});
400
+ } catch {
401
+ throw new Error(`adapter_not_found:${SCM_PROVIDER_SLOT.name}:${scmProvider}`);
402
+ }
403
+ }
404
+ }
405
+
329
406
  this.gatesConfig = gates.parsed;
330
407
  this.policy = parsedPolicy;
331
408
  this.agentsConfig = agents.parsed as AgentsConfigSnapshot;
409
+ this.adaptersConfig = readObjectField(adapters, 'parsed');
332
410
  const registryLoader = new ToolRegistryLoader(this.repoRoot);
333
411
  this.toolRegistry = await registryLoader.load();
334
412
  this.loaded = true;
@@ -530,6 +608,25 @@ export class AopKernel {
530
608
  this.toolHandlers.register(TOOLS.REPORT_FEATURE_SUMMARY, async (args) =>
531
609
  await this.reportFeatureSummary(readStringField(args, 'feature_id'))
532
610
  );
611
+ this.toolHandlers.register(TOOLS.FEATURE_SEND_MESSAGE, async (args) =>
612
+ await this.featureSendMessage(readStringField(args, 'feature_id'), readStringField(args, 'message'))
613
+ );
614
+ this.toolHandlers.register(TOOLS.COST_RECORD, async (args) =>
615
+ await this.costRecord(
616
+ readStringField(args, 'feature_id'),
617
+ typeof args.tokens_used_delta === 'number' ? args.tokens_used_delta : 0,
618
+ typeof args.estimated_cost_usd_delta === 'number' ? args.estimated_cost_usd_delta : 0
619
+ )
620
+ );
621
+ this.toolHandlers.register(TOOLS.COST_GET, async (args) =>
622
+ await this.costGet(readStringField(args, 'feature_id'))
623
+ );
624
+ this.toolHandlers.register(TOOLS.PERFORMANCE_RECORD_OUTCOME, async (args) =>
625
+ await this.performanceRecordOutcome(args)
626
+ );
627
+ this.toolHandlers.register(TOOLS.PERFORMANCE_GET_ANALYTICS, async (args) =>
628
+ await this.performanceGetAnalytics(readStringField(args, 'provider'), readStringField(args, 'model'))
629
+ );
533
630
  }
534
631
 
535
632
  featurePath(featureId) {
@@ -548,6 +645,14 @@ export class AopKernel {
548
645
  return this.pathLayout.planPath(featureId);
549
646
  }
550
647
 
648
+ featureCostPath(featureId: string): string {
649
+ return this.pathLayout.featureCostPath(featureId);
650
+ }
651
+
652
+ analyticsPath(): string {
653
+ return path.join(this.pathLayout.runtimeRoot, 'analytics', 'performance.json');
654
+ }
655
+
551
656
  qaIndexPath(featureId) {
552
657
  return this.pathLayout.qaIndexPath(featureId);
553
658
  }
@@ -681,8 +786,27 @@ export class AopKernel {
681
786
  }
682
787
 
683
788
  async getRuntimeSessions(): Promise<RuntimeSessionsSnapshot> {
789
+ return this.normalizeRuntimeSessions(await this.readRunLease());
790
+ }
791
+
792
+ async readRunLease(): Promise<RuntimeSessionsSnapshot> {
793
+ const runLeasePath = this.pathLayout.runLeaseFilePath(this.instanceId);
794
+ const existing = await readJson(runLeasePath, null);
795
+ if (existing !== null) {
796
+ return this.normalizeRuntimeSessions(existing);
797
+ }
798
+ // Migration: fall back to index.json runtime_sessions for existing repos
684
799
  const index = await this.readIndex();
685
- return this.normalizeRuntimeSessions(index.runtime_sessions);
800
+ if (index.runtime_sessions && index.runtime_sessions.run_id !== 'none') {
801
+ return this.normalizeRuntimeSessions(index.runtime_sessions);
802
+ }
803
+ return this.emptyRuntimeSessions();
804
+ }
805
+
806
+ async writeRunLease(data: RuntimeSessionsSnapshot): Promise<void> {
807
+ const runLeasePath = this.pathLayout.runLeaseFilePath(this.instanceId);
808
+ await ensureDir(path.dirname(runLeasePath));
809
+ await atomicWriteJson(runLeasePath, data);
686
810
  }
687
811
 
688
812
  async acquireRunLease(input: AcquireRunLeaseInput): Promise<{ data: { runtime_sessions: RuntimeSessionsSnapshot; took_over_stale: boolean; reused_existing_owner: boolean } }> {
@@ -846,6 +970,29 @@ export class AopKernel {
846
970
  };
847
971
  }
848
972
 
973
+ const worktreeConfig = this.policy.worktree as {
974
+ base_branch: string;
975
+ symlinks?: string[];
976
+ post_create?: string[];
977
+ } | undefined;
978
+ const hookWarnings: WorkspaceHookWarning[] = [];
979
+ const collectHookWarning = (warning: WorkspaceHookWarning) => {
980
+ hookWarnings.push(warning);
981
+ };
982
+
983
+ if (worktreeConfig?.symlinks?.length) {
984
+ await applyWorktreeSymlinks(this.repoRoot, worktree, worktreeConfig.symlinks, collectHookWarning);
985
+ }
986
+
987
+ if (worktreeConfig?.post_create?.length) {
988
+ await runWorktreePostCreate(worktree, worktreeConfig.post_create, collectHookWarning);
989
+ }
990
+
991
+ for (const warning of hookWarnings) {
992
+ // Preserve non-fatal behavior while making hook failures observable.
993
+ console.warn(`[aop] workspace hook warning: ${formatWorkspaceHookWarning(warning)}`);
994
+ }
995
+
849
996
  return {
850
997
  data: {
851
998
  feature_id: featureId,
@@ -1059,6 +1206,134 @@ export class AopKernel {
1059
1206
  async recoverFromState() {
1060
1207
  return await this.lockService.recoverFromState();
1061
1208
  }
1209
+
1210
+ private async waitForSessionToBecomeActive(sessionId: string): Promise<void> {
1211
+ if (!this.provider?.getSessionInfo) {
1212
+ return;
1213
+ }
1214
+
1215
+ const timeoutMs = 5000;
1216
+ const pollIntervalMs = 250;
1217
+ const deadline = Date.now() + timeoutMs;
1218
+
1219
+ while (Date.now() <= deadline) {
1220
+ const sessionInfo = await this.provider.getSessionInfo(sessionId).catch(() => null);
1221
+ if (sessionInfo?.active) {
1222
+ return;
1223
+ }
1224
+ await new Promise<void>((resolve) => {
1225
+ setTimeout(resolve, pollIntervalMs);
1226
+ });
1227
+ }
1228
+ }
1229
+
1230
+ async featureSendMessage(
1231
+ featureId: string | null,
1232
+ message: string | null
1233
+ ): Promise<unknown> {
1234
+ if (!featureId) {
1235
+ throw { normalizedResponse: fail(ERROR_CODES.INVALID_ARGUMENT, 'feature_id is required', { retryable: false, requires_human: false }) };
1236
+ }
1237
+ if (!message) {
1238
+ throw { normalizedResponse: fail(ERROR_CODES.INVALID_ARGUMENT, 'message is required and must not be empty', { retryable: false, requires_human: false }) };
1239
+ }
1240
+
1241
+ const runtimeSessions = await this.getRuntimeSessions();
1242
+ const featureSession = runtimeSessions.feature_sessions?.[featureId];
1243
+ if (!featureSession) {
1244
+ throw { normalizedResponse: { ok: false, error: { code: 'session_not_found', message: 'No active session cluster for feature' } } };
1245
+ }
1246
+
1247
+ const state = await this.readState(featureId);
1248
+ const status = typeof state.frontMatter.status === 'string' ? state.frontMatter.status : '';
1249
+ const gates = readObjectField(state.frontMatter, 'gates');
1250
+
1251
+ let targetRole = 'orchestrator';
1252
+ let targetSessionId = runtimeSessions.orchestrator_session_id;
1253
+
1254
+ if (status === STATUS.PLANNING) {
1255
+ targetRole = 'planner';
1256
+ targetSessionId = featureSession.planner_session_id;
1257
+ } else if (status === STATUS.BUILDING) {
1258
+ targetRole = 'builder';
1259
+ targetSessionId = featureSession.builder_session_id;
1260
+ } else if (status === STATUS.QA || status === STATUS.READY_TO_MERGE) {
1261
+ targetRole = 'qa';
1262
+ targetSessionId = featureSession.qa_session_id;
1263
+ } else if (status === STATUS.BLOCKED) {
1264
+ const fastGate = readStringField(gates, 'fast');
1265
+ const fullGate = readStringField(gates, 'full');
1266
+ if (fastGate === GATE_RESULT.FAIL && fullGate !== GATE_RESULT.FAIL) {
1267
+ targetRole = 'builder';
1268
+ targetSessionId = featureSession.builder_session_id;
1269
+ } else {
1270
+ targetRole = 'qa';
1271
+ targetSessionId = featureSession.qa_session_id;
1272
+ }
1273
+ }
1274
+
1275
+ if (!targetSessionId || targetSessionId === 'unassigned' || targetSessionId === 'unknown') {
1276
+ targetRole = 'orchestrator';
1277
+ targetSessionId = runtimeSessions.orchestrator_session_id;
1278
+ }
1279
+
1280
+ if (!this.provider?.sendMessage) {
1281
+ throw { normalizedResponse: { ok: false, error: { code: 'provider_unsupported', message: 'Provider does not support sendMessage' } } };
1282
+ }
1283
+
1284
+ await this.waitForSessionToBecomeActive(targetSessionId);
1285
+ await this.provider.sendMessage(targetSessionId, message);
1286
+ return {
1287
+ feature_id: featureId,
1288
+ session_id: targetSessionId,
1289
+ target_role: targetRole,
1290
+ status,
1291
+ delivered: true
1292
+ };
1293
+ }
1294
+
1295
+ async costRecord(featureId: string, tokensDelta: number, costUsdDelta: number) {
1296
+ const updated = await this.costTrackingService.recordCost(featureId, tokensDelta, costUsdDelta);
1297
+ return { ok: true as const, data: updated };
1298
+ }
1299
+
1300
+ async costGet(featureId: string) {
1301
+ const cost = await this.costTrackingService.getFeatureCost(featureId);
1302
+ return { ok: true as const, data: cost };
1303
+ }
1304
+
1305
+ async checkBudget(featureId: string) {
1306
+ return await this.costTrackingService.checkBudget(featureId);
1307
+ }
1308
+
1309
+ async performanceRecordOutcome(args: AnyRecord) {
1310
+ const outcome: FeatureOutcome = {
1311
+ feature_id: typeof args.feature_id === 'string' ? args.feature_id : '',
1312
+ provider: typeof args.provider === 'string' ? args.provider : '',
1313
+ model: typeof args.model === 'string' ? args.model : '',
1314
+ status: typeof args.status === 'string' ? args.status : '',
1315
+ gate_pass: typeof args.gate_pass === 'boolean' ? args.gate_pass : false,
1316
+ retry_count: typeof args.retry_count === 'number' ? args.retry_count : 0,
1317
+ duration_ms: typeof args.duration_ms === 'number' ? args.duration_ms : 0,
1318
+ cost_usd: typeof args.cost_usd === 'number' ? args.cost_usd : 0,
1319
+ recorded_at: nowIso()
1320
+ };
1321
+ const snapshot = await this.performanceAnalyticsService.recordOutcome(outcome);
1322
+ return { ok: true as const, data: { total_outcomes: snapshot.outcomes.length } };
1323
+ }
1324
+
1325
+ async performanceGetAnalytics(provider: string | null, model: string | null) {
1326
+ if (provider || model) {
1327
+ const stats = await this.performanceAnalyticsService.getProviderStats(
1328
+ provider ?? undefined,
1329
+ model ?? undefined
1330
+ );
1331
+ const snapshot = await this.performanceAnalyticsService.getAnalytics();
1332
+ return { ok: true as const, data: { outcomes: snapshot.outcomes, aggregates: stats, generated_at: snapshot.generated_at } };
1333
+ }
1334
+ const snapshot = await this.performanceAnalyticsService.getAnalytics();
1335
+ return { ok: true as const, data: snapshot };
1336
+ }
1062
1337
  }
1063
1338
 
1064
1339
  function normalizeRepoPathForState(repoRoot: string, absolutePath: string) {
@@ -29,6 +29,14 @@ export class AopPathLayout {
29
29
  return path.join(this.runtimeDataRoot, 'operation-ledger');
30
30
  }
31
31
 
32
+ runLeaseFilePath(instanceId: string): string {
33
+ return path.join(this.runtimeDataRoot, instanceId, 'run-lease.json');
34
+ }
35
+
36
+ runLeaseLockPath(instanceId: string): string {
37
+ return path.join(this.runtimeDataRoot, instanceId, '.run-lease.lock');
38
+ }
39
+
32
40
  get legacyFeaturesRoot(): string {
33
41
  return path.join(this.repoRoot, 'agentic', 'features');
34
42
  }
@@ -61,6 +69,10 @@ export class AopPathLayout {
61
69
  return path.join(this.featureRoot(featureId), 'plan.json');
62
70
  }
63
71
 
72
+ featureCostPath(featureId: string): string {
73
+ return path.join(this.featureRoot(featureId), 'cost.json');
74
+ }
75
+
64
76
  qaIndexPath(featureId: string): string {
65
77
  return path.join(this.featureRoot(featureId), 'qa_test_index.json');
66
78
  }