agentic-orchestrator 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/CLAUDE.md +126 -0
  3. package/README.md +166 -25
  4. package/agentic/orchestrator/adapters.yaml +3 -0
  5. package/agentic/orchestrator/gates.yaml +47 -0
  6. package/agentic/orchestrator/policy.yaml +89 -0
  7. package/agentic/orchestrator/schemas/adapters.schema.json +12 -0
  8. package/agentic/orchestrator/schemas/gates.schema.json +6 -1
  9. package/agentic/orchestrator/schemas/index.schema.json +14 -0
  10. package/agentic/orchestrator/schemas/multi-project.schema.json +41 -0
  11. package/agentic/orchestrator/schemas/policy.schema.json +449 -52
  12. package/agentic/orchestrator/schemas/state.schema.json +16 -0
  13. package/agentic/orchestrator/tools/catalog.json +68 -0
  14. package/agentic/orchestrator/tools/schemas/input/cost.get.input.schema.json +10 -0
  15. package/agentic/orchestrator/tools/schemas/input/cost.record.input.schema.json +13 -0
  16. package/agentic/orchestrator/tools/schemas/input/feature.send_message.input.schema.json +11 -0
  17. package/agentic/orchestrator/tools/schemas/input/performance.get_analytics.input.schema.json +10 -0
  18. package/agentic/orchestrator/tools/schemas/input/performance.record_outcome.input.schema.json +18 -0
  19. package/agentic/orchestrator/tools/schemas/output/cost.get.output.schema.json +13 -0
  20. package/agentic/orchestrator/tools/schemas/output/cost.record.output.schema.json +13 -0
  21. package/agentic/orchestrator/tools/schemas/output/feature.ready_to_merge.output.schema.json +7 -0
  22. package/agentic/orchestrator/tools/schemas/output/feature.send_message.output.schema.json +23 -0
  23. package/agentic/orchestrator/tools/schemas/output/performance.get_analytics.output.schema.json +46 -0
  24. package/agentic/orchestrator/tools/schemas/output/performance.record_outcome.output.schema.json +10 -0
  25. package/agentic/orchestrator/tools.md +5 -0
  26. package/apps/control-plane/scripts/validate-architecture-rules.mjs +28 -2
  27. package/apps/control-plane/scripts/validate-docker-mcp-contract.mjs +12 -0
  28. package/apps/control-plane/scripts/validate-mcp-contracts.ts +92 -0
  29. package/apps/control-plane/src/application/adapters/adapter-registry.ts +169 -0
  30. package/apps/control-plane/src/application/multi-project-loader.ts +119 -0
  31. package/apps/control-plane/src/application/services/activity-monitor-service.ts +199 -0
  32. package/apps/control-plane/src/application/services/cost-tracking-service.ts +82 -0
  33. package/apps/control-plane/src/application/services/dependency-scheduler-service.ts +86 -0
  34. package/apps/control-plane/src/application/services/feature-deletion-service.ts +8 -7
  35. package/apps/control-plane/src/application/services/gate-interpolation-service.ts +15 -0
  36. package/apps/control-plane/src/application/services/gate-service.ts +38 -2
  37. package/apps/control-plane/src/application/services/instance-isolation-service.ts +18 -0
  38. package/apps/control-plane/src/application/services/issue-tracker-service.ts +469 -0
  39. package/apps/control-plane/src/application/services/merge-service.ts +67 -3
  40. package/apps/control-plane/src/application/services/notifier-service.ts +295 -0
  41. package/apps/control-plane/src/application/services/performance-analytics-service.ts +122 -0
  42. package/apps/control-plane/src/application/services/plan-service.ts +51 -0
  43. package/apps/control-plane/src/application/services/pr-monitor-service.ts +262 -0
  44. package/apps/control-plane/src/application/services/reactions-service.ts +175 -0
  45. package/apps/control-plane/src/application/services/reporting-service.ts +17 -2
  46. package/apps/control-plane/src/application/services/run-lease-service.ts +16 -38
  47. package/apps/control-plane/src/application/tools/tool-metadata.ts +4 -1
  48. package/apps/control-plane/src/cli/aop.ts +1 -1
  49. package/apps/control-plane/src/cli/attach-command-handler.ts +120 -0
  50. package/apps/control-plane/src/cli/cleanup-command-handler.ts +190 -0
  51. package/apps/control-plane/src/cli/cli-argument-parser.ts +69 -3
  52. package/apps/control-plane/src/cli/dashboard-command-handler.ts +57 -0
  53. package/apps/control-plane/src/cli/help-command-handler.ts +163 -0
  54. package/apps/control-plane/src/cli/init-command-handler.ts +609 -0
  55. package/apps/control-plane/src/cli/resume-command-handler.ts +1 -0
  56. package/apps/control-plane/src/cli/retry-command-handler.ts +138 -0
  57. package/apps/control-plane/src/cli/run-command-handler.ts +115 -3
  58. package/apps/control-plane/src/cli/send-command-handler.ts +65 -0
  59. package/apps/control-plane/src/cli/status-command-handler.ts +102 -2
  60. package/apps/control-plane/src/cli/types.ts +26 -1
  61. package/apps/control-plane/src/core/constants.ts +8 -2
  62. package/apps/control-plane/src/core/error-codes.ts +3 -1
  63. package/apps/control-plane/src/core/gates.ts +170 -50
  64. package/apps/control-plane/src/core/kernel.ts +280 -5
  65. package/apps/control-plane/src/core/path-layout.ts +12 -0
  66. package/apps/control-plane/src/core/tool-caller.ts +36 -0
  67. package/apps/control-plane/src/core/workspace-hooks.ts +87 -0
  68. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +258 -9
  69. package/apps/control-plane/src/providers/providers.ts +235 -14
  70. package/apps/control-plane/src/supervisor/build-wave-executor.ts +129 -8
  71. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +123 -5
  72. package/apps/control-plane/src/supervisor/run-coordinator.ts +143 -6
  73. package/apps/control-plane/src/supervisor/runtime.ts +135 -6
  74. package/apps/control-plane/src/supervisor/types.ts +12 -21
  75. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +8 -0
  76. package/apps/control-plane/test/activity-monitor.spec.ts +294 -0
  77. package/apps/control-plane/test/adapter-registry.spec.ts +132 -0
  78. package/apps/control-plane/test/batch-operations.spec.ts +112 -0
  79. package/apps/control-plane/test/bootstrap-attach.spec.ts +102 -0
  80. package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +252 -0
  81. package/apps/control-plane/test/bootstrap.spec.ts +560 -0
  82. package/apps/control-plane/test/cleanup-command.spec.ts +301 -0
  83. package/apps/control-plane/test/cli-helpers.spec.ts +404 -1
  84. package/apps/control-plane/test/cli.unit.spec.ts +182 -1
  85. package/apps/control-plane/test/collision-queue.spec.ts +104 -1
  86. package/apps/control-plane/test/core-utils.spec.ts +175 -2
  87. package/apps/control-plane/test/cost-tracking.spec.ts +143 -0
  88. package/apps/control-plane/test/dashboard-api.integration.spec.ts +247 -0
  89. package/apps/control-plane/test/dashboard-client.spec.ts +116 -0
  90. package/apps/control-plane/test/dashboard-command.spec.ts +103 -0
  91. package/apps/control-plane/test/dependency-scheduler.spec.ts +189 -0
  92. package/apps/control-plane/test/epoch-tracking.spec.ts +4 -4
  93. package/apps/control-plane/test/feature-deletion-service.spec.ts +422 -0
  94. package/apps/control-plane/test/feature-lifecycle.spec.ts +202 -0
  95. package/apps/control-plane/test/git-spawn-error.spec.ts +24 -0
  96. package/apps/control-plane/test/incremental-gates.spec.ts +137 -0
  97. package/apps/control-plane/test/init-wizard.spec.ts +506 -0
  98. package/apps/control-plane/test/instance-isolation.spec.ts +83 -0
  99. package/apps/control-plane/test/issue-tracker.spec.ts +890 -0
  100. package/apps/control-plane/test/kernel.coverage.spec.ts +3 -5
  101. package/apps/control-plane/test/kernel.coverage2.spec.ts +871 -0
  102. package/apps/control-plane/test/kernel.spec.ts +13 -11
  103. package/apps/control-plane/test/lock-service.spec.ts +508 -0
  104. package/apps/control-plane/test/mcp-helpers.spec.ts +176 -0
  105. package/apps/control-plane/test/mcp.spec.ts +50 -15
  106. package/apps/control-plane/test/merge-service.spec.ts +67 -4
  107. package/apps/control-plane/test/multi-project.spec.ts +372 -0
  108. package/apps/control-plane/test/notifier-service.spec.ts +388 -0
  109. package/apps/control-plane/test/parallel-gates.spec.ts +312 -0
  110. package/apps/control-plane/test/patch-service.spec.ts +253 -0
  111. package/apps/control-plane/test/performance-analytics.spec.ts +338 -0
  112. package/apps/control-plane/test/planning-wave-executor.spec.ts +168 -0
  113. package/apps/control-plane/test/pr-monitor.spec.ts +385 -0
  114. package/apps/control-plane/test/providers.spec.ts +344 -1
  115. package/apps/control-plane/test/reactions.spec.ts +392 -0
  116. package/apps/control-plane/test/resume-command.spec.ts +390 -0
  117. package/apps/control-plane/test/run-coordinator.spec.ts +481 -2
  118. package/apps/control-plane/test/schema-date-time.spec.ts +46 -0
  119. package/apps/control-plane/test/service-retry-paths.spec.ts +30 -0
  120. package/apps/control-plane/test/services.spec.ts +95 -2
  121. package/apps/control-plane/test/session-management.spec.ts +450 -0
  122. package/apps/control-plane/test/spec-ingestion.spec.ts +190 -0
  123. package/apps/control-plane/test/supervisor-collaborators.spec.ts +699 -2
  124. package/apps/control-plane/test/supervisor.spec.ts +36 -30
  125. package/apps/control-plane/test/supervisor.unit.spec.ts +405 -0
  126. package/apps/control-plane/test/worker-decision-loop.spec.ts +57 -0
  127. package/apps/control-plane/test/workspace-hooks.spec.ts +177 -0
  128. package/apps/control-plane/vitest.config.ts +21 -5
  129. package/dist/apps/control-plane/application/adapters/adapter-registry.d.ts +44 -0
  130. package/dist/apps/control-plane/application/adapters/adapter-registry.js +76 -0
  131. package/dist/apps/control-plane/application/adapters/adapter-registry.js.map +1 -0
  132. package/dist/apps/control-plane/application/multi-project-loader.d.ts +31 -0
  133. package/dist/apps/control-plane/application/multi-project-loader.js +82 -0
  134. package/dist/apps/control-plane/application/multi-project-loader.js.map +1 -0
  135. package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +43 -0
  136. package/dist/apps/control-plane/application/services/activity-monitor-service.js +132 -0
  137. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -0
  138. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +28 -0
  139. package/dist/apps/control-plane/application/services/cost-tracking-service.js +48 -0
  140. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -0
  141. package/dist/apps/control-plane/application/services/dependency-scheduler-service.d.ts +26 -0
  142. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js +75 -0
  143. package/dist/apps/control-plane/application/services/dependency-scheduler-service.js.map +1 -0
  144. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +2 -0
  145. package/dist/apps/control-plane/application/services/feature-deletion-service.js +6 -7
  146. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  147. package/dist/apps/control-plane/application/services/gate-interpolation-service.d.ts +7 -0
  148. package/dist/apps/control-plane/application/services/gate-interpolation-service.js +7 -0
  149. package/dist/apps/control-plane/application/services/gate-interpolation-service.js.map +1 -0
  150. package/dist/apps/control-plane/application/services/gate-service.js +32 -2
  151. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  152. package/dist/apps/control-plane/application/services/instance-isolation-service.d.ts +11 -0
  153. package/dist/apps/control-plane/application/services/instance-isolation-service.js +17 -0
  154. package/dist/apps/control-plane/application/services/instance-isolation-service.js.map +1 -0
  155. package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +65 -0
  156. package/dist/apps/control-plane/application/services/issue-tracker-service.js +358 -0
  157. package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -0
  158. package/dist/apps/control-plane/application/services/merge-service.d.ts +4 -0
  159. package/dist/apps/control-plane/application/services/merge-service.js +44 -2
  160. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  161. package/dist/apps/control-plane/application/services/notifier-service.d.ts +74 -0
  162. package/dist/apps/control-plane/application/services/notifier-service.js +212 -0
  163. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -0
  164. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +39 -0
  165. package/dist/apps/control-plane/application/services/performance-analytics-service.js +75 -0
  166. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -0
  167. package/dist/apps/control-plane/application/services/plan-service.d.ts +1 -0
  168. package/dist/apps/control-plane/application/services/plan-service.js +53 -0
  169. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  170. package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +44 -0
  171. package/dist/apps/control-plane/application/services/pr-monitor-service.js +192 -0
  172. package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -0
  173. package/dist/apps/control-plane/application/services/reactions-service.d.ts +67 -0
  174. package/dist/apps/control-plane/application/services/reactions-service.js +114 -0
  175. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -0
  176. package/dist/apps/control-plane/application/services/reporting-service.d.ts +1 -0
  177. package/dist/apps/control-plane/application/services/reporting-service.js +13 -2
  178. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  179. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +2 -0
  180. package/dist/apps/control-plane/application/services/run-lease-service.js +14 -38
  181. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  182. package/dist/apps/control-plane/application/tools/tool-metadata.js +3 -1
  183. package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
  184. package/dist/apps/control-plane/cli/aop.d.ts +1 -1
  185. package/dist/apps/control-plane/cli/aop.js +1 -1
  186. package/dist/apps/control-plane/cli/attach-command-handler.d.ts +12 -0
  187. package/dist/apps/control-plane/cli/attach-command-handler.js +98 -0
  188. package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -0
  189. package/dist/apps/control-plane/cli/cleanup-command-handler.d.ts +12 -0
  190. package/dist/apps/control-plane/cli/cleanup-command-handler.js +162 -0
  191. package/dist/apps/control-plane/cli/cleanup-command-handler.js.map +1 -0
  192. package/dist/apps/control-plane/cli/cli-argument-parser.js +73 -3
  193. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  194. package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +7 -0
  195. package/dist/apps/control-plane/cli/dashboard-command-handler.js +45 -0
  196. package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -0
  197. package/dist/apps/control-plane/cli/help-command-handler.d.ts +8 -0
  198. package/dist/apps/control-plane/cli/help-command-handler.js +146 -0
  199. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -0
  200. package/dist/apps/control-plane/cli/init-command-handler.d.ts +26 -0
  201. package/dist/apps/control-plane/cli/init-command-handler.js +517 -0
  202. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -0
  203. package/dist/apps/control-plane/cli/resume-command-handler.js +1 -1
  204. package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
  205. package/dist/apps/control-plane/cli/retry-command-handler.d.ts +8 -0
  206. package/dist/apps/control-plane/cli/retry-command-handler.js +111 -0
  207. package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -0
  208. package/dist/apps/control-plane/cli/run-command-handler.d.ts +5 -0
  209. package/dist/apps/control-plane/cli/run-command-handler.js +82 -3
  210. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  211. package/dist/apps/control-plane/cli/send-command-handler.d.ts +8 -0
  212. package/dist/apps/control-plane/cli/send-command-handler.js +55 -0
  213. package/dist/apps/control-plane/cli/send-command-handler.js.map +1 -0
  214. package/dist/apps/control-plane/cli/status-command-handler.d.ts +12 -1
  215. package/dist/apps/control-plane/cli/status-command-handler.js +55 -2
  216. package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
  217. package/dist/apps/control-plane/cli/types.d.ts +26 -1
  218. package/dist/apps/control-plane/cli/types.js +15 -1
  219. package/dist/apps/control-plane/cli/types.js.map +1 -1
  220. package/dist/apps/control-plane/core/constants.d.ts +6 -0
  221. package/dist/apps/control-plane/core/constants.js +8 -2
  222. package/dist/apps/control-plane/core/constants.js.map +1 -1
  223. package/dist/apps/control-plane/core/error-codes.d.ts +2 -0
  224. package/dist/apps/control-plane/core/error-codes.js +3 -1
  225. package/dist/apps/control-plane/core/error-codes.js.map +1 -1
  226. package/dist/apps/control-plane/core/gates.d.ts +4 -0
  227. package/dist/apps/control-plane/core/gates.js +140 -43
  228. package/dist/apps/control-plane/core/gates.js.map +1 -1
  229. package/dist/apps/control-plane/core/kernel.d.ts +50 -1
  230. package/dist/apps/control-plane/core/kernel.js +220 -7
  231. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  232. package/dist/apps/control-plane/core/path-layout.d.ts +3 -0
  233. package/dist/apps/control-plane/core/path-layout.js +9 -0
  234. package/dist/apps/control-plane/core/path-layout.js.map +1 -1
  235. package/dist/apps/control-plane/core/tool-caller.d.ts +32 -0
  236. package/dist/apps/control-plane/core/tool-caller.js +2 -0
  237. package/dist/apps/control-plane/core/tool-caller.js.map +1 -0
  238. package/dist/apps/control-plane/core/workspace-hooks.d.ts +20 -0
  239. package/dist/apps/control-plane/core/workspace-hooks.js +69 -0
  240. package/dist/apps/control-plane/core/workspace-hooks.js.map +1 -0
  241. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +245 -9
  242. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  243. package/dist/apps/control-plane/providers/providers.d.ts +42 -3
  244. package/dist/apps/control-plane/providers/providers.js +216 -5
  245. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  246. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
  247. package/dist/apps/control-plane/supervisor/build-wave-executor.js +115 -6
  248. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  249. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
  250. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +109 -5
  251. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  252. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +15 -0
  253. package/dist/apps/control-plane/supervisor/run-coordinator.js +132 -6
  254. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  255. package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
  256. package/dist/apps/control-plane/supervisor/runtime.js +110 -6
  257. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  258. package/dist/apps/control-plane/supervisor/types.d.ts +9 -16
  259. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  260. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +3 -0
  261. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +5 -0
  262. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  263. package/eslint.config.mjs +2 -1
  264. package/package.json +12 -2
  265. package/packages/web-dashboard/next-env.d.ts +5 -0
  266. package/packages/web-dashboard/next.config.js +7 -0
  267. package/packages/web-dashboard/package.json +26 -0
  268. package/packages/web-dashboard/src/app/api/actions/route.ts +64 -0
  269. package/packages/web-dashboard/src/app/api/events/route.ts +51 -0
  270. package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +256 -0
  271. package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +10 -0
  272. package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +25 -0
  273. package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +63 -0
  274. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +16 -0
  275. package/packages/web-dashboard/src/app/api/projects/route.ts +31 -0
  276. package/packages/web-dashboard/src/app/api/status/route.ts +15 -0
  277. package/packages/web-dashboard/src/app/globals.css +2 -0
  278. package/packages/web-dashboard/src/app/layout.tsx +15 -0
  279. package/packages/web-dashboard/src/app/page.tsx +393 -0
  280. package/packages/web-dashboard/src/lib/aop-client.ts +244 -0
  281. package/packages/web-dashboard/src/lib/multi-project-config.ts +116 -0
  282. package/packages/web-dashboard/src/lib/orchestrator-tools.ts +284 -0
  283. package/packages/web-dashboard/src/lib/types.ts +58 -0
  284. package/packages/web-dashboard/tsconfig.json +40 -0
  285. package/packages/web-dashboard/vitest.config.ts +6 -0
  286. package/spec-files/completed/agentic_orchestrator_feature_gaps_closure_spec.md +1764 -0
  287. package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +348 -0
  288. package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +344 -0
  289. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +374 -0
  290. package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +1059 -0
  291. package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +466 -0
  292. package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +198 -0
  293. package/spec-files/outstanding/agentic_orchestrator_validator_hardening_spec.md +365 -0
  294. package/spec-files/progress.md +481 -52
  295. /package/spec-files/{agentic_orchestrator_cli_delete_command_spec.md → completed/agentic_orchestrator_cli_delete_command_spec.md} +0 -0
  296. /package/spec-files/{agentic_orchestrator_dot_aop_generated_artifacts_spec.md → completed/agentic_orchestrator_dot_aop_generated_artifacts_spec.md} +0 -0
  297. /package/spec-files/{agentic_orchestrator_mcp_formalization_spec.md → completed/agentic_orchestrator_mcp_formalization_spec.md} +0 -0
  298. /package/spec-files/{agentic_orchestrator_oop_refactor_spec.md → completed/agentic_orchestrator_oop_refactor_spec.md} +0 -0
  299. /package/spec-files/{agentic_orchestrator_single_global_orchestrator_spec.md → completed/agentic_orchestrator_single_global_orchestrator_spec.md} +0 -0
  300. /package/spec-files/{agentic_orchestrator_spec.md → completed/agentic_orchestrator_spec.md} +0 -0
@@ -0,0 +1,169 @@
1
+ import { ERROR_CODES } from '../../core/error-codes.js';
2
+
3
+ export interface AdapterSlot<TContract> {
4
+ readonly name: string;
5
+ readonly _contract?: TContract; // phantom type — never at runtime
6
+ }
7
+
8
+ export interface AdapterManifest {
9
+ readonly slot: string;
10
+ readonly name: string;
11
+ readonly configSchema?: Record<string, unknown>;
12
+ }
13
+
14
+ export interface AdapterRegistry {
15
+ register<T>(slot: AdapterSlot<T>, manifest: AdapterManifest, factory: (config: unknown) => T): void;
16
+ resolve<T>(slot: AdapterSlot<T>, name: string, config: unknown): T;
17
+ list(slot: string): ReadonlyArray<AdapterManifest>;
18
+ has(slot: string, name: string): boolean;
19
+ }
20
+
21
+ interface AdapterEntry<T> {
22
+ manifest: AdapterManifest;
23
+ factory: (config: unknown) => T;
24
+ }
25
+
26
+ export interface AdapterRegistryError extends Error {
27
+ code: string;
28
+ details?: Record<string, unknown>;
29
+ }
30
+
31
+ function makeRegistryError(message: string, code: string, details?: Record<string, unknown>): AdapterRegistryError {
32
+ const error = new Error(message) as AdapterRegistryError;
33
+ error.code = code;
34
+ if (details !== undefined) {
35
+ error.details = details;
36
+ }
37
+ return error;
38
+ }
39
+
40
+ export class DefaultAdapterRegistry implements AdapterRegistry {
41
+ // slot name → adapter name → entry
42
+ private readonly _entries = new Map<string, Map<string, AdapterEntry<unknown>>>();
43
+
44
+ register<T>(slot: AdapterSlot<T>, manifest: AdapterManifest, factory: (config: unknown) => T): void {
45
+ let byName = this._entries.get(slot.name);
46
+ if (!byName) {
47
+ byName = new Map();
48
+ this._entries.set(slot.name, byName);
49
+ } byName.set(manifest.name, { manifest, factory: factory as (config: unknown) => unknown });
50
+ }
51
+
52
+ resolve<T>(slot: AdapterSlot<T>, name: string, config: unknown): T {
53
+ const byName = this._entries.get(slot.name);
54
+ const entry = byName?.get(name) as AdapterEntry<T> | undefined;
55
+
56
+ if (!entry) {
57
+ throw makeRegistryError(
58
+ ERROR_CODES.UNSUPPORTED_AGENT_PROVIDER,
59
+ ERROR_CODES.UNSUPPORTED_AGENT_PROVIDER,
60
+ { slot: slot.name, name }
61
+ );
62
+ }
63
+
64
+ return entry.factory(config);
65
+ }
66
+
67
+ list(slot: string): ReadonlyArray<AdapterManifest> {
68
+ const byName = this._entries.get(slot);
69
+ if (!byName) {
70
+ return [];
71
+ }
72
+ return Array.from(byName.values()).map((e) => e.manifest);
73
+ }
74
+
75
+ has(slot: string, name: string): boolean {
76
+ return this._entries.get(slot)?.has(name) ?? false;
77
+ }
78
+ }
79
+
80
+ // ---- Built-in agent-provider slot ----
81
+
82
+ export interface AgentProviderContract {
83
+ readonly name: string;
84
+ }
85
+
86
+ export const AGENT_PROVIDER_SLOT: AdapterSlot<AgentProviderContract> = {
87
+ name: 'agent-provider'
88
+ };
89
+
90
+ // ---- Activity detector slot ----
91
+
92
+ export interface ActivityDetectorContract {
93
+ readonly name: string;
94
+ }
95
+
96
+ export const ACTIVITY_DETECTOR_SLOT: AdapterSlot<ActivityDetectorContract> = {
97
+ name: 'activity-detector'
98
+ };
99
+
100
+ // ---- Notification channel slot ----
101
+
102
+ export interface NotificationChannelContract {
103
+ readonly name: string;
104
+ }
105
+
106
+ export const NOTIFICATION_CHANNEL_SLOT: AdapterSlot<NotificationChannelContract> = {
107
+ name: 'notification-channel'
108
+ };
109
+
110
+ // ---- SCM provider slot ----
111
+
112
+ export interface ScmProviderContract {
113
+ readonly name: string;
114
+ }
115
+
116
+ export const SCM_PROVIDER_SLOT: AdapterSlot<ScmProviderContract> = {
117
+ name: 'scm-provider'
118
+ };
119
+
120
+ const BUILT_IN_PROVIDER_NAMES = ['codex', 'claude', 'gemini', 'kiro-cli', 'copilot', 'custom'] as const;
121
+ const BUILT_IN_ACTIVITY_DETECTORS = ['claude-jsonl', 'codex-rpc', 'process-heuristic'] as const;
122
+ const BUILT_IN_NOTIFICATION_CHANNELS = ['desktop', 'slack', 'webhook'] as const;
123
+ const BUILT_IN_SCM_PROVIDERS = ['github'] as const;
124
+
125
+ function makeProviderManifest(name: string): AdapterManifest {
126
+ return { slot: AGENT_PROVIDER_SLOT.name, name };
127
+ }
128
+
129
+ function makeProviderFactory(name: string): (config: unknown) => AgentProviderContract {
130
+ return (_config: unknown) => ({ name });
131
+ }
132
+
133
+ export const globalAdapterRegistry = new DefaultAdapterRegistry();
134
+
135
+ for (const providerName of BUILT_IN_PROVIDER_NAMES) {
136
+ globalAdapterRegistry.register(
137
+ AGENT_PROVIDER_SLOT,
138
+ makeProviderManifest(providerName),
139
+ makeProviderFactory(providerName)
140
+ );
141
+ }
142
+
143
+ for (const detectorName of BUILT_IN_ACTIVITY_DETECTORS) {
144
+ globalAdapterRegistry.register(
145
+ ACTIVITY_DETECTOR_SLOT,
146
+ { slot: ACTIVITY_DETECTOR_SLOT.name, name: detectorName },
147
+ () => ({ name: detectorName })
148
+ );
149
+ }
150
+
151
+ for (const channelName of BUILT_IN_NOTIFICATION_CHANNELS) {
152
+ globalAdapterRegistry.register(
153
+ NOTIFICATION_CHANNEL_SLOT,
154
+ { slot: NOTIFICATION_CHANNEL_SLOT.name, name: channelName },
155
+ () => ({ name: channelName })
156
+ );
157
+ }
158
+
159
+ for (const scmProviderName of BUILT_IN_SCM_PROVIDERS) {
160
+ globalAdapterRegistry.register(
161
+ SCM_PROVIDER_SLOT,
162
+ { slot: SCM_PROVIDER_SLOT.name, name: scmProviderName },
163
+ () => ({ name: scmProviderName })
164
+ );
165
+ }
166
+
167
+ export const REGISTERED_PROVIDER_NAMES: ReadonlySet<string> = new Set(
168
+ globalAdapterRegistry.list(AGENT_PROVIDER_SLOT.name).map((m) => m.name)
169
+ );
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import YAML from 'yaml';
5
+ import { SchemaRegistry } from '../core/schemas.js';
6
+ import { ERROR_CODES } from '../core/error-codes.js';
7
+ import type { AppError } from '../providers/providers.js';
8
+
9
+ export interface ProjectConfig {
10
+ name: string;
11
+ path: string;
12
+ repo?: string;
13
+ branch?: string;
14
+ policy?: string;
15
+ gates?: string;
16
+ dashboard_port?: number;
17
+ }
18
+
19
+ export interface MultiProjectConfig {
20
+ version: string;
21
+ defaults?: {
22
+ max_active_features?: number;
23
+ max_parallel_gate_runs?: number;
24
+ dashboard_port?: number;
25
+ default_project?: string;
26
+ policy?: string;
27
+ gates?: string;
28
+ notifications?: { enabled?: boolean };
29
+ };
30
+ projects: ProjectConfig[];
31
+ }
32
+
33
+ const MULTI_PROJECT_YAML = 'multi-project.yaml';
34
+ const SCHEMA_NAME = 'multi-project.schema.json';
35
+
36
+ function resolveHomePath(rawPath: string): string {
37
+ if (rawPath.startsWith('~/') || rawPath === '~') {
38
+ return path.join(os.homedir(), rawPath.slice(1));
39
+ }
40
+ return rawPath;
41
+ }
42
+
43
+ function resolveConfiguredPath(rawPath: string, baseDir: string): string {
44
+ const expanded = resolveHomePath(rawPath);
45
+ if (path.isAbsolute(expanded)) {
46
+ return expanded;
47
+ }
48
+ return path.resolve(baseDir, expanded);
49
+ }
50
+
51
+ export class MultiProjectLoader {
52
+ async load(baseDir: string): Promise<MultiProjectConfig | null> {
53
+ const yamlPath = path.join(baseDir, 'agentic', 'orchestrator', MULTI_PROJECT_YAML);
54
+
55
+ let raw: string;
56
+ try {
57
+ raw = await fs.readFile(yamlPath, 'utf8');
58
+ } catch {
59
+ return null;
60
+ }
61
+
62
+ const parsed = YAML.parse(raw) as MultiProjectConfig;
63
+
64
+ const schemaRegistry = new SchemaRegistry(baseDir);
65
+ const result = await schemaRegistry.validate(SCHEMA_NAME, parsed);
66
+ if (!result.valid) {
67
+ const error = new Error(
68
+ `multi-project.yaml validation failed: ${result.errors.map((e) => e.message).join(', ')}`
69
+ ) as AppError;
70
+ error.code = ERROR_CODES.INVALID_ARGUMENT;
71
+ error.details = {
72
+ retryable: false,
73
+ requires_human: true,
74
+ validation_errors: result.errors
75
+ };
76
+ throw error;
77
+ }
78
+
79
+ // Resolve project paths and per-project override paths to absolute paths.
80
+ const projects: ProjectConfig[] = parsed.projects.map((p) => ({
81
+ ...p,
82
+ path: resolveConfiguredPath(p.path, baseDir),
83
+ policy: typeof p.policy === 'string' ? resolveConfiguredPath(p.policy, resolveConfiguredPath(p.path, baseDir)) : undefined,
84
+ gates: typeof p.gates === 'string' ? resolveConfiguredPath(p.gates, resolveConfiguredPath(p.path, baseDir)) : undefined
85
+ }));
86
+
87
+ return { ...parsed, projects };
88
+ }
89
+
90
+ getProject(config: MultiProjectConfig, name: string): ProjectConfig | null {
91
+ return config.projects.find((p) => p.name === name) ?? null;
92
+ }
93
+
94
+ resolveProjectPolicyPath(config: MultiProjectConfig, project: ProjectConfig): string | undefined {
95
+ const override = project.policy ?? config.defaults?.policy;
96
+ if (typeof override !== 'string' || override.length === 0) {
97
+ return undefined;
98
+ }
99
+ return resolveConfiguredPath(override, project.path);
100
+ }
101
+
102
+ resolveProjectGatesPath(config: MultiProjectConfig, project: ProjectConfig): string | undefined {
103
+ const override = project.gates ?? config.defaults?.gates;
104
+ if (typeof override !== 'string' || override.length === 0) {
105
+ return undefined;
106
+ }
107
+ return resolveConfiguredPath(override, project.path);
108
+ }
109
+
110
+ async exists(baseDir: string): Promise<boolean> {
111
+ const yamlPath = path.join(baseDir, 'agentic', 'orchestrator', MULTI_PROJECT_YAML);
112
+ try {
113
+ await fs.access(yamlPath);
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,199 @@
1
+ import { TOOLS } from '../../core/constants.js';
2
+ import type { FeatureStateFrontMatter, FeatureStatePayload, ToolCaller as SupervisorToolCaller } from '../../core/tool-caller.js';
3
+ import type { NotifierService } from './notifier-service.js';
4
+
5
+ export type ActivityState = 'active' | 'idle' | 'waiting_input' | 'blocked' | 'exited' | 'unknown';
6
+
7
+ export interface ActivitySnapshot {
8
+ featureId: string;
9
+ state: ActivityState;
10
+ lastEventAt: string | null; // ISO8601
11
+ detectedVia: 'jsonl' | 'ledger' | 'process' | 'unknown';
12
+ }
13
+
14
+ export interface AgentStuckReaction {
15
+ enabled: boolean;
16
+ action: string;
17
+ idle_threshold_ms: number;
18
+ escalate_after: number;
19
+ }
20
+
21
+ export interface ActivityMonitorServiceDependencies {
22
+ toolCaller: SupervisorToolCaller;
23
+ operationLedger?: { getLastOperationAt(featureId: string): string | null };
24
+ idleThresholdMs?: number;
25
+ notifier?: NotifierService;
26
+ reactionsPolicy?: { agent_stuck?: AgentStuckReaction };
27
+ detectorName?: string;
28
+ }
29
+
30
+ type ActivityFrontMatter = FeatureStateFrontMatter & {
31
+ status_reason?: string;
32
+ last_heartbeat_at?: string;
33
+ last_run_at?: string;
34
+ activity_state?: string;
35
+ activity_last_event_at?: string | null;
36
+ activity_detected_via?: string;
37
+ };
38
+
39
+ const DEFAULT_IDLE_THRESHOLD_MS = 300_000;
40
+
41
+ export class ActivityMonitorService {
42
+ private readonly toolCaller: SupervisorToolCaller;
43
+ private readonly operationLedger: { getLastOperationAt(featureId: string): string | null } | undefined;
44
+ private readonly idleThresholdMs: number;
45
+ private readonly notifier: NotifierService | undefined;
46
+ private readonly reactionsPolicy: { agent_stuck?: AgentStuckReaction } | undefined;
47
+ private readonly detectorName: string;
48
+
49
+ constructor(dependencies: ActivityMonitorServiceDependencies) {
50
+ this.toolCaller = dependencies.toolCaller;
51
+ this.operationLedger = dependencies.operationLedger;
52
+ this.idleThresholdMs = dependencies.idleThresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS;
53
+ this.notifier = dependencies.notifier;
54
+ this.reactionsPolicy = dependencies.reactionsPolicy;
55
+ this.detectorName = dependencies.detectorName ?? 'claude-jsonl';
56
+ }
57
+
58
+ private detectedVia(featureId: string): ActivitySnapshot['detectedVia'] {
59
+ if (this.detectorName === 'codex-rpc' && this.operationLedger?.getLastOperationAt(featureId)) {
60
+ return 'ledger';
61
+ }
62
+ if (this.detectorName === 'codex-rpc' || this.detectorName === 'process-heuristic') {
63
+ return 'process';
64
+ }
65
+ return this.detectorName === 'claude-jsonl' ? 'jsonl' : 'unknown';
66
+ }
67
+
68
+ private resolveLastEvent(featureId: string, frontMatter: ActivityFrontMatter): string | null {
69
+ const statusReason = typeof frontMatter.status_reason === 'string' ? frontMatter.status_reason.toLowerCase() : '';
70
+ if (statusReason.includes('waiting_input') || statusReason.includes('awaiting_input')) {
71
+ return frontMatter.last_heartbeat_at ?? frontMatter.last_run_at ?? null;
72
+ }
73
+
74
+ if (this.detectorName === 'codex-rpc') {
75
+ const ledgerTs = this.operationLedger?.getLastOperationAt(featureId) ?? null;
76
+ return ledgerTs ?? frontMatter.last_run_at ?? frontMatter.last_heartbeat_at ?? null;
77
+ }
78
+
79
+ if (this.detectorName === 'process-heuristic') {
80
+ return frontMatter.last_run_at ?? frontMatter.last_heartbeat_at ?? null;
81
+ }
82
+
83
+ return frontMatter.last_heartbeat_at ?? frontMatter.last_run_at ?? null;
84
+ }
85
+
86
+ private deriveState(frontMatter: ActivityFrontMatter, lastEventAt: string | null): ActivityState {
87
+ const status = frontMatter.status;
88
+ const statusReason = typeof frontMatter.status_reason === 'string' ? frontMatter.status_reason.toLowerCase() : '';
89
+
90
+ if (status === 'failed' || status === 'merged') {
91
+ return 'exited';
92
+ }
93
+ if (status === 'blocked') {
94
+ return 'blocked';
95
+ }
96
+ if (statusReason.includes('waiting_input') || statusReason.includes('awaiting_input')) {
97
+ return 'waiting_input';
98
+ }
99
+ if (!lastEventAt) {
100
+ return 'unknown';
101
+ }
102
+
103
+ const elapsed = Date.now() - new Date(lastEventAt).getTime();
104
+ return elapsed <= this.idleThresholdMs ? 'active' : 'idle';
105
+ }
106
+
107
+ private async persistSnapshot(featureId: string, snapshot: ActivitySnapshot): Promise<void> {
108
+ try {
109
+ const current = await this.toolCaller.callTool<FeatureStatePayload>('orchestrator', TOOLS.FEATURE_STATE_GET, {
110
+ feature_id: featureId
111
+ });
112
+ const frontMatter = current.data.front_matter as ActivityFrontMatter & {
113
+ version: number;
114
+ activity_state?: string;
115
+ activity_last_event_at?: string | null;
116
+ activity_detected_via?: string;
117
+ };
118
+ const unchanged =
119
+ frontMatter.activity_state === snapshot.state &&
120
+ (frontMatter.activity_last_event_at ?? null) === snapshot.lastEventAt &&
121
+ frontMatter.activity_detected_via === snapshot.detectedVia;
122
+ if (unchanged) {
123
+ return;
124
+ }
125
+
126
+ await this.toolCaller.callTool('orchestrator', TOOLS.FEATURE_STATE_PATCH, {
127
+ feature_id: featureId,
128
+ expected_version: frontMatter.version,
129
+ patch: {
130
+ front_matter: {
131
+ activity_state: snapshot.state,
132
+ activity_last_event_at: snapshot.lastEventAt,
133
+ activity_detected_via: snapshot.detectedVia,
134
+ activity_detector: this.detectorName
135
+ }
136
+ }
137
+ });
138
+ } catch {
139
+ // Activity persistence is best-effort and should not block orchestration.
140
+ }
141
+ }
142
+
143
+ async getActivityState(featureId: string): Promise<ActivitySnapshot> {
144
+ const response = await this.toolCaller.callTool<FeatureStatePayload>(
145
+ 'orchestrator',
146
+ TOOLS.FEATURE_STATE_GET,
147
+ { feature_id: featureId }
148
+ );
149
+
150
+ const frontMatter = response.data.front_matter as ActivityFrontMatter;
151
+ const lastEventAt = this.resolveLastEvent(featureId, frontMatter);
152
+ return {
153
+ featureId,
154
+ state: this.deriveState(frontMatter, lastEventAt),
155
+ lastEventAt,
156
+ detectedVia: this.detectedVia(featureId)
157
+ };
158
+ }
159
+
160
+ async checkAndNotifyStuck(featureId: string): Promise<boolean> {
161
+ const snapshot = await this.getActivityState(featureId);
162
+ await this.persistSnapshot(featureId, snapshot);
163
+
164
+ if (snapshot.state !== 'idle') {
165
+ return false;
166
+ }
167
+
168
+ const reaction = this.reactionsPolicy?.agent_stuck;
169
+ if (!reaction?.enabled) {
170
+ return true;
171
+ }
172
+
173
+ if (reaction.action.includes('notify') && this.notifier) {
174
+ await this.notifier.notify('agent_stuck', {
175
+ feature_id: featureId,
176
+ message: `Agent for feature '${featureId}' appears stuck (idle since ${snapshot.lastEventAt ?? 'unknown'}).`,
177
+ details: {
178
+ state: snapshot.state,
179
+ last_event_at: snapshot.lastEventAt,
180
+ idle_threshold_ms: this.idleThresholdMs
181
+ }
182
+ });
183
+ }
184
+
185
+ return true;
186
+ }
187
+
188
+ formatForStatus(snapshot: ActivitySnapshot): string {
189
+ if (!snapshot.lastEventAt) {
190
+ return snapshot.state;
191
+ }
192
+
193
+ const elapsed = Date.now() - new Date(snapshot.lastEventAt).getTime();
194
+ const minutes = Math.floor(elapsed / 60_000);
195
+ const timeStr = minutes < 1 ? 'just now' : `${minutes}m ago`;
196
+
197
+ return `${snapshot.state} (${timeStr})`;
198
+ }
199
+ }
@@ -0,0 +1,82 @@
1
+ import { atomicWriteJson, nowIso, readJson } from '../../core/fs.js';
2
+
3
+ export interface FeatureCost {
4
+ feature_id: string;
5
+ tokens_used: number;
6
+ estimated_cost_usd: number;
7
+ last_updated: string;
8
+ }
9
+
10
+ export interface BudgetPolicy {
11
+ per_feature_limit_usd?: number;
12
+ alert_threshold?: number;
13
+ }
14
+
15
+ export interface BudgetCheckResult {
16
+ over_budget: boolean;
17
+ alert_threshold_reached: boolean;
18
+ current_cost_usd: number;
19
+ limit_usd: number;
20
+ alert_threshold: number;
21
+ }
22
+
23
+ export interface CostTrackingServicePort {
24
+ featureCostPath(featureId: string): string;
25
+ getPolicySnapshot(): Record<string, unknown>;
26
+ }
27
+
28
+ export class CostTrackingService {
29
+ private readonly port: CostTrackingServicePort;
30
+
31
+ constructor(port: CostTrackingServicePort) {
32
+ this.port = port;
33
+ }
34
+
35
+ async getFeatureCost(featureId: string): Promise<FeatureCost> {
36
+ const existing = await readJson<FeatureCost>(this.port.featureCostPath(featureId), null);
37
+ return existing ?? {
38
+ feature_id: featureId,
39
+ tokens_used: 0,
40
+ estimated_cost_usd: 0.0,
41
+ last_updated: nowIso()
42
+ };
43
+ }
44
+
45
+ async recordCost(featureId: string, tokensDelta: number, costUsdDelta: number): Promise<FeatureCost> {
46
+ const current = await this.getFeatureCost(featureId);
47
+ const updated: FeatureCost = {
48
+ feature_id: featureId,
49
+ tokens_used: current.tokens_used + tokensDelta,
50
+ estimated_cost_usd: current.estimated_cost_usd + costUsdDelta,
51
+ last_updated: nowIso()
52
+ };
53
+ await atomicWriteJson(this.port.featureCostPath(featureId), updated);
54
+ return updated;
55
+ }
56
+
57
+ async checkBudget(featureId: string): Promise<BudgetCheckResult> {
58
+ const policy = this.port.getPolicySnapshot();
59
+ const budgetPolicy = (
60
+ policy.budget && typeof policy.budget === 'object' ? policy.budget : {}
61
+ ) as BudgetPolicy;
62
+
63
+ const limitUsd = typeof budgetPolicy.per_feature_limit_usd === 'number'
64
+ ? budgetPolicy.per_feature_limit_usd
65
+ : Infinity;
66
+ const alertThreshold = typeof budgetPolicy.alert_threshold === 'number'
67
+ ? budgetPolicy.alert_threshold
68
+ : 0.8;
69
+
70
+ const cost = await this.getFeatureCost(featureId);
71
+ const overBudget = Number.isFinite(limitUsd) && cost.estimated_cost_usd >= limitUsd;
72
+ const alertThresholdReached = Number.isFinite(limitUsd) && cost.estimated_cost_usd >= limitUsd * alertThreshold;
73
+
74
+ return {
75
+ over_budget: overBudget,
76
+ alert_threshold_reached: alertThresholdReached,
77
+ current_cost_usd: cost.estimated_cost_usd,
78
+ limit_usd: Number.isFinite(limitUsd) ? limitUsd : -1,
79
+ alert_threshold: alertThreshold
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,86 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ type AnyRecord = Record<string, any>;
3
+
4
+ export interface DepBlockedEntry {
5
+ feature_id: string;
6
+ depends_on_unresolved: string[];
7
+ }
8
+
9
+ /**
10
+ * Returns the subset of `dependsOn` IDs not present in `mergedFeatures`.
11
+ */
12
+ export function getUnresolvedDeps(dependsOn: string[], mergedFeatures: string[]): string[] {
13
+ const mergedSet = new Set(mergedFeatures);
14
+ return dependsOn.filter((dep) => !mergedSet.has(dep));
15
+ }
16
+
17
+ /**
18
+ * DFS cycle detection starting from `startId`.
19
+ * `getDirectDeps` returns the direct `depends_on` list for a given feature ID.
20
+ * Returns a cycle description string if a cycle is found, or `null` if none.
21
+ */
22
+ export function detectCircularDependency(
23
+ startId: string,
24
+ getDirectDeps: (id: string) => string[]
25
+ ): string | null {
26
+ const visited = new Set<string>();
27
+ const stack: string[] = [];
28
+
29
+ function dfs(id: string): boolean {
30
+ if (stack.includes(id)) {
31
+ return true; // cycle found
32
+ }
33
+ if (visited.has(id)) {return false;}
34
+ visited.add(id);
35
+ stack.push(id);
36
+ for (const dep of getDirectDeps(id)) {
37
+ if (dfs(dep)) {return true;}
38
+ }
39
+ stack.pop();
40
+ return false;
41
+ }
42
+
43
+ if (dfs(startId)) {
44
+ const cycleStart = stack.indexOf(startId) === -1 ? 0 : stack.indexOf(startId);
45
+ return [...stack.slice(cycleStart), startId].join(' → ');
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Adds (or updates) a feature's entry in `index.dep_blocked`.
52
+ */
53
+ export function addToDepBlocked(index: AnyRecord, featureId: string, unresolvedDeps: string[]): void {
54
+ const depBlocked: DepBlockedEntry[] = Array.isArray(index.dep_blocked) ? index.dep_blocked : [];
55
+ const existing = depBlocked.findIndex((e) => e.feature_id === featureId);
56
+ const entry: DepBlockedEntry = { feature_id: featureId, depends_on_unresolved: unresolvedDeps };
57
+ if (existing >= 0) {
58
+ depBlocked[existing] = entry;
59
+ } else {
60
+ depBlocked.push(entry);
61
+ }
62
+ index.dep_blocked = depBlocked;
63
+ }
64
+
65
+ /**
66
+ * After `mergedFeatureId` is merged, remove it from each entry's `depends_on_unresolved`.
67
+ * If an entry's `depends_on_unresolved` becomes empty, the entry is removed (feature is unblocked).
68
+ * Returns the list of feature IDs that are now fully unblocked.
69
+ */
70
+ export function resolveDepBlocked(index: AnyRecord, mergedFeatureId: string): string[] {
71
+ const depBlocked: DepBlockedEntry[] = Array.isArray(index.dep_blocked) ? index.dep_blocked : [];
72
+ const promoted: string[] = [];
73
+ const remaining: DepBlockedEntry[] = [];
74
+
75
+ for (const entry of depBlocked) {
76
+ const stillUnresolved = entry.depends_on_unresolved.filter((dep) => dep !== mergedFeatureId);
77
+ if (stillUnresolved.length === 0) {
78
+ promoted.push(entry.feature_id);
79
+ } else {
80
+ remaining.push({ ...entry, depends_on_unresolved: stillUnresolved });
81
+ }
82
+ }
83
+
84
+ index.dep_blocked = remaining;
85
+ return promoted;
86
+ }