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,137 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ const runGateModeMock = vi.hoisted(() => vi.fn());
7
+
8
+ vi.mock('../src/core/gates.js', () => ({
9
+ runGateMode: runGateModeMock
10
+ }));
11
+
12
+ import { GateService } from '../src/application/services/gate-service.js';
13
+ import type { GateServicePort } from '../src/application/services/gate-service.js';
14
+ import { interpolateGateCommands, isIncrementalMode } from '../src/application/services/gate-interpolation-service.js';
15
+
16
+ describe('incremental gate execution', () => {
17
+ let tempDir: string;
18
+
19
+ beforeEach(async () => {
20
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-incremental-gates-'));
21
+ runGateModeMock.mockReset();
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await fs.rm(tempDir, { recursive: true });
26
+ vi.restoreAllMocks();
27
+ });
28
+
29
+ it('GIVEN fast mode with {base_branch} command WHEN interpolateGateCommands called THEN base_branch substituted', () => {
30
+ const cmd = ['npx', 'vitest', 'run', '--changed', '{base_branch}'];
31
+ const result = interpolateGateCommands(cmd, { base_branch: 'main', feature_id: 'feat-1' });
32
+ expect(result).toEqual(['npx', 'vitest', 'run', '--changed', 'main']);
33
+ });
34
+
35
+ it('GIVEN full mode with {base_branch} command WHEN gatesRun is called THEN command passed through unchanged', async () => {
36
+ runGateModeMock.mockResolvedValue({
37
+ overall: 'pass',
38
+ coverage_status: 'na',
39
+ evidence_path: path.join(tempDir, 'evidence.json'),
40
+ latest_path: path.join(tempDir, 'latest.json'),
41
+ coverage: null,
42
+ thresholds: {}
43
+ });
44
+
45
+ const profile = {
46
+ modes: {
47
+ full: [{ name: 'test_full', cmd: ['npx', 'vitest', 'run', '--changed', '{base_branch}'], timeout_seconds: 60 }]
48
+ },
49
+ thresholds: {}
50
+ };
51
+
52
+ const port: GateServicePort = buildPort(tempDir, { profiles: { default: profile } });
53
+ const service = new GateService(port);
54
+ await service.gatesRun('feature-abc', 'default', 'full');
55
+
56
+ const callArgs = runGateModeMock.mock.calls[0][0] as { profile: { modes: { full: Array<{ cmd: string[] }> } } };
57
+ expect(callArgs.profile.modes.full[0].cmd).toEqual(['npx', 'vitest', 'run', '--changed', '{base_branch}']);
58
+ });
59
+
60
+ it('GIVEN feature_id in command WHEN interpolateGateCommands called THEN feature_id substituted correctly', () => {
61
+ const cmd = ['scripts/test.sh', '--feature', '{feature_id}'];
62
+ const result = interpolateGateCommands(cmd, { base_branch: 'main', feature_id: 'my-feature' });
63
+ expect(result).toEqual(['scripts/test.sh', '--feature', 'my-feature']);
64
+ });
65
+
66
+ it('GIVEN isIncrementalMode with fast THEN returns true', () => {
67
+ expect(isIncrementalMode('fast')).toBe(true);
68
+ });
69
+
70
+ it('GIVEN isIncrementalMode with full THEN returns false', () => {
71
+ expect(isIncrementalMode('full')).toBe(false);
72
+ });
73
+
74
+ it('GIVEN isIncrementalMode with merge THEN returns false', () => {
75
+ expect(isIncrementalMode('merge')).toBe(false);
76
+ });
77
+
78
+ it('GIVEN gate run in fast mode with base_branch command WHEN GateService runs THEN command has base_branch interpolated', async () => {
79
+ const evidenceFile = path.join(tempDir, 'gate-fast-123.json');
80
+ const latestFile = path.join(tempDir, 'latest-fast.json');
81
+ const initialEvidence = JSON.stringify({ feature_id: 'feature-123', mode: 'fast', overall: 'pass' });
82
+ await fs.writeFile(evidenceFile, initialEvidence, 'utf8');
83
+ await fs.writeFile(latestFile, initialEvidence, 'utf8');
84
+
85
+ runGateModeMock.mockResolvedValue({
86
+ overall: 'pass',
87
+ coverage_status: 'na',
88
+ evidence_path: evidenceFile,
89
+ latest_path: latestFile,
90
+ coverage: null,
91
+ thresholds: {}
92
+ });
93
+
94
+ const profile = {
95
+ modes: {
96
+ fast: [{ name: 'test_incremental', cmd: ['npx', 'vitest', 'run', '--changed', '{base_branch}'], timeout_seconds: 60 }]
97
+ },
98
+ thresholds: {}
99
+ };
100
+
101
+ const port: GateServicePort = buildPort(tempDir, { profiles: { default: profile } });
102
+ const service = new GateService(port);
103
+ await service.gatesRun('feature-123', 'default', 'fast');
104
+
105
+ expect(runGateModeMock).toHaveBeenCalledOnce();
106
+ const callArgs = runGateModeMock.mock.calls[0][0] as { profile: { modes: { fast: Array<{ cmd: string[] }> } } };
107
+ expect(callArgs.profile.modes.fast[0].cmd).toEqual(['npx', 'vitest', 'run', '--changed', 'main']);
108
+
109
+ // Verify evidence files were augmented with incremental metadata
110
+ const updatedEvidence = JSON.parse(await fs.readFile(evidenceFile, 'utf8')) as Record<string, unknown>;
111
+ expect(updatedEvidence['skipped_reason']).toBe('incremental_mode');
112
+ expect(updatedEvidence['base_branch']).toBe('main');
113
+ });
114
+ });
115
+
116
+ function buildPort(tempDir: string, gatesConfig: Record<string, unknown>): GateServicePort {
117
+ return {
118
+ getRepoRoot: () => '/repo',
119
+ getGatesConfig: () => gatesConfig,
120
+ getPolicySnapshot: () => ({
121
+ worktree: { base_branch: 'main' },
122
+ execution: {
123
+ retry_policy: { transient_max_retries: 0, transient_error_codes: [] },
124
+ env_allowlist: [],
125
+ default_step_timeout_seconds: 60
126
+ },
127
+ testing: { coverage: { minimums: { line: 0, branch: 0 }, targets: { line: 1, branch: 1 } } }
128
+ }),
129
+ readState: (_featureId: string) => Promise.resolve({
130
+ frontMatter: { status: 'building', version: 1, gate_profile: 'default', gates: {} }
131
+ }),
132
+ updateState: vi.fn().mockResolvedValue({}),
133
+ worktreePath: () => tempDir,
134
+ logsPath: () => tempDir,
135
+ evidencePath: () => tempDir
136
+ };
137
+ }
@@ -0,0 +1,506 @@
1
+ import fs from 'node:fs/promises';
2
+ import type * as FsPromises from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
+
7
+ const execFileMock = vi.hoisted(() => vi.fn());
8
+
9
+ vi.mock('node:child_process', () => ({
10
+ execFile: (...args: unknown[]) => {
11
+ const cb = args[args.length - 1] as (err: null | Error, result?: { stdout: string; stderr: string }) => void;
12
+ const promise = execFileMock(...args.slice(0, -1)) as Promise<{ stdout: string; stderr: string }>;
13
+ void promise.then(
14
+ (result) => cb(null, result),
15
+ (err: Error) => cb(err)
16
+ );
17
+ }
18
+ }));
19
+
20
+ import { InitCommandHandler } from '../src/cli/init-command-handler.js';
21
+
22
+ function makePromptFactory(responses: string[]) {
23
+ const question = vi.fn(async () => responses.shift() ?? '');
24
+ const close = vi.fn();
25
+ const factory = vi.fn(() => ({ question, close }));
26
+ return { factory, question, close };
27
+ }
28
+
29
+ describe('InitCommandHandler', () => {
30
+ let cwd: string;
31
+
32
+ beforeEach(async () => {
33
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-init-'));
34
+ execFileMock.mockReset();
35
+ });
36
+
37
+ afterEach(async () => {
38
+ await fs.rm(cwd, { recursive: true, force: true });
39
+ vi.restoreAllMocks();
40
+ });
41
+
42
+ it('GIVEN_git_repo_with_remote_WHEN_auto_init_THEN_generates_valid_config_files', async () => {
43
+ // Create .git/config with a remote URL
44
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
45
+ await fs.writeFile(
46
+ path.join(cwd, '.git', 'config'),
47
+ `[core]\n repositoryformatversion = 0\n[remote "origin"]\n url = https://github.com/example/repo.git\n fetch = +refs/heads/*:refs/remotes/origin/*\n`,
48
+ 'utf8'
49
+ );
50
+
51
+ // Mock git symbolic-ref to return origin/main
52
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
53
+
54
+ const handler = new InitCommandHandler(cwd);
55
+ const result = await handler.execute({ auto: true });
56
+
57
+ expect(result.ok).toBe(true);
58
+ expect(result.data.command).toBe('init');
59
+ expect(result.data.created).toContain('agentic/orchestrator/policy.yaml');
60
+ expect(result.data.created).toContain('agentic/orchestrator/gates.yaml');
61
+ expect(result.data.created).toContain('agentic/orchestrator/agents.yaml');
62
+ expect(result.data.created).toContain('agentic/orchestrator/adapters.yaml');
63
+ expect(result.data.created).toContain('agentic/orchestrator/prompts/planner.system.md');
64
+ expect(result.data.created).toContain('agentic/orchestrator/schemas/state.schema.json');
65
+ expect(result.data.skipped).toHaveLength(0);
66
+ expect(result.data.updated).toHaveLength(0);
67
+ expect(result.data.validation_warnings).toHaveLength(0);
68
+
69
+ // Verify files were created
70
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
71
+ expect(policyContent).toContain('base_branch: main');
72
+ expect(policyContent).toContain('framework: vitest');
73
+ expect(policyContent).toContain('notifications:');
74
+
75
+ const gatesContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'gates.yaml'), 'utf8');
76
+ expect(gatesContent).toContain('version: 1');
77
+ expect(gatesContent).toContain('profiles:');
78
+
79
+ const agentsContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'agents.yaml'), 'utf8');
80
+ expect(agentsContent).toContain('version: 1');
81
+ expect(agentsContent).toContain('roles:');
82
+
83
+ await expect(
84
+ fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'prompts', 'planner.system.md'), 'utf8')
85
+ ).resolves.toContain('planner');
86
+ await expect(
87
+ fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'schemas', 'state.schema.json'), 'utf8')
88
+ ).resolves.toContain('"$schema"');
89
+ });
90
+
91
+ it('GIVEN_existing_config_WHEN_init_without_force_THEN_skips_existing_files', async () => {
92
+ // Pre-create the orchestrator directory and policy.yaml
93
+ const orchestratorDir = path.join(cwd, 'agentic', 'orchestrator');
94
+ await fs.mkdir(orchestratorDir, { recursive: true });
95
+ await fs.writeFile(path.join(orchestratorDir, 'policy.yaml'), 'version: 1\n# existing\n', 'utf8');
96
+
97
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
98
+
99
+ const handler = new InitCommandHandler(cwd);
100
+ const result = await handler.execute({ auto: true });
101
+
102
+ expect(result.ok).toBe(true);
103
+ expect(result.data.skipped).toContain('agentic/orchestrator/policy.yaml');
104
+ expect(result.data.created).not.toContain('agentic/orchestrator/policy.yaml');
105
+ expect(result.data.updated).not.toContain('agentic/orchestrator/policy.yaml');
106
+
107
+ // Verify file was NOT overwritten
108
+ const content = await fs.readFile(path.join(orchestratorDir, 'policy.yaml'), 'utf8');
109
+ expect(content).toContain('# existing');
110
+ });
111
+
112
+ it('GIVEN_existing_config_WHEN_init_with_force_THEN_overwrites_files', async () => {
113
+ // Pre-create the orchestrator directory and policy.yaml
114
+ const orchestratorDir = path.join(cwd, 'agentic', 'orchestrator');
115
+ await fs.mkdir(orchestratorDir, { recursive: true });
116
+ await fs.writeFile(path.join(orchestratorDir, 'policy.yaml'), 'version: 1\n# existing\n', 'utf8');
117
+
118
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
119
+
120
+ const handler = new InitCommandHandler(cwd);
121
+ const result = await handler.execute({ auto: true, force: true });
122
+
123
+ expect(result.ok).toBe(true);
124
+ expect(result.data.updated).toContain('agentic/orchestrator/policy.yaml');
125
+ expect(result.data.skipped).not.toContain('agentic/orchestrator/policy.yaml');
126
+
127
+ // Verify file WAS overwritten with generated content
128
+ const content = await fs.readFile(path.join(orchestratorDir, 'policy.yaml'), 'utf8');
129
+ expect(content).not.toContain('# existing');
130
+ expect(content).toContain('framework: vitest');
131
+ });
132
+
133
+ it('GIVEN_non_git_directory_WHEN_init_THEN_uses_defaults', async () => {
134
+ // No .git directory — git detection should fail gracefully
135
+ execFileMock.mockRejectedValue(new Error('not a git repository'));
136
+
137
+ const handler = new InitCommandHandler(cwd);
138
+ const result = await handler.execute({ auto: true });
139
+
140
+ expect(result.ok).toBe(true);
141
+ expect(result.data.created).toContain('agentic/orchestrator/policy.yaml');
142
+ expect(result.data.validation_warnings).toHaveLength(0);
143
+
144
+ // Should default to main branch
145
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
146
+ expect(policyContent).toContain('base_branch: main');
147
+ });
148
+
149
+ it('GIVEN_jest_dependency_in_package_json_WHEN_init_THEN_generates_jest_gate_commands', async () => {
150
+ await fs.writeFile(
151
+ path.join(cwd, 'package.json'),
152
+ JSON.stringify({ devDependencies: { jest: '^29.0.0' } }),
153
+ 'utf8'
154
+ );
155
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
156
+
157
+ const handler = new InitCommandHandler(cwd);
158
+ const result = await handler.execute({ auto: true });
159
+
160
+ expect(result.ok).toBe(true);
161
+ const gatesContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'gates.yaml'), 'utf8');
162
+ expect(gatesContent).toContain('"npx"');
163
+ expect(gatesContent).toContain('"jest"');
164
+ });
165
+
166
+ it('GIVEN_pyproject_toml_WHEN_init_THEN_generates_pytest_gate_commands', async () => {
167
+ await fs.writeFile(path.join(cwd, 'pyproject.toml'), '[tool.poetry]\nname = "myproject"\n', 'utf8');
168
+ execFileMock.mockRejectedValue(new Error('not git'));
169
+
170
+ const handler = new InitCommandHandler(cwd);
171
+ const result = await handler.execute({ auto: true });
172
+
173
+ expect(result.ok).toBe(true);
174
+ const gatesContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'gates.yaml'), 'utf8');
175
+ expect(gatesContent).toContain('"pytest"');
176
+ });
177
+
178
+ it('GIVEN_pom_xml_WHEN_init_THEN_generates_maven_gate_commands', async () => {
179
+ await fs.writeFile(path.join(cwd, 'pom.xml'), '<project></project>', 'utf8');
180
+ execFileMock.mockRejectedValue(new Error('not git'));
181
+
182
+ const handler = new InitCommandHandler(cwd);
183
+ const result = await handler.execute({ auto: true });
184
+
185
+ expect(result.ok).toBe(true);
186
+ const gatesContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'gates.yaml'), 'utf8');
187
+ expect(gatesContent).toContain('"mvn"');
188
+ });
189
+
190
+ it('GIVEN_build_gradle_WHEN_init_THEN_generates_gradle_gate_commands', async () => {
191
+ await fs.writeFile(path.join(cwd, 'build.gradle'), 'plugins {}', 'utf8');
192
+ execFileMock.mockRejectedValue(new Error('not git'));
193
+
194
+ const handler = new InitCommandHandler(cwd);
195
+ const result = await handler.execute({ auto: true });
196
+
197
+ expect(result.ok).toBe(true);
198
+ const gatesContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'gates.yaml'), 'utf8');
199
+ expect(gatesContent).toContain('"./gradlew"');
200
+ });
201
+
202
+ it('GIVEN_git_config_without_remote_section_WHEN_init_THEN_uses_null_remote_url', async () => {
203
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
204
+ await fs.writeFile(
205
+ path.join(cwd, '.git', 'config'),
206
+ '[core]\n repositoryformatversion = 0\n',
207
+ 'utf8'
208
+ );
209
+ execFileMock.mockResolvedValue({ stdout: 'origin/develop\n', stderr: '' });
210
+
211
+ const handler = new InitCommandHandler(cwd);
212
+ const result = await handler.execute({ auto: true });
213
+
214
+ expect(result.ok).toBe(true);
215
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
216
+ expect(policyContent).toContain('base_branch: develop');
217
+ });
218
+
219
+ it('GIVEN_git_symbolic_ref_returns_no_slash_WHEN_init_THEN_uses_full_ref_as_branch', async () => {
220
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
221
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
222
+ execFileMock.mockResolvedValue({ stdout: 'master\n', stderr: '' });
223
+
224
+ const handler = new InitCommandHandler(cwd);
225
+ const result = await handler.execute({ auto: true });
226
+
227
+ expect(result.ok).toBe(true);
228
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
229
+ expect(policyContent).toContain('base_branch: master');
230
+ });
231
+
232
+ it('GIVEN_auto_mode_WHEN_init_THEN_does_not_open_interactive_prompt', async () => {
233
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
234
+ const prompts = makePromptFactory([]);
235
+ const handler = new InitCommandHandler(cwd, prompts.factory);
236
+ const result = await handler.execute({ auto: true });
237
+
238
+ expect(result.ok).toBe(true);
239
+ expect(prompts.factory).not.toHaveBeenCalled();
240
+ expect(prompts.question).not.toHaveBeenCalled();
241
+ expect(prompts.close).not.toHaveBeenCalled();
242
+ });
243
+
244
+ it('GIVEN_non_auto_mode_WHEN_init_THEN_uses_prompt_values_for_policy_and_gates', async () => {
245
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
246
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
247
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
248
+
249
+ const prompts = makePromptFactory([
250
+ 'release',
251
+ '4',
252
+ '4500',
253
+ 'desktop,slack',
254
+ 'jest',
255
+ 'https://hooks.slack.example/webhook'
256
+ ]);
257
+ const handler = new InitCommandHandler(cwd, prompts.factory);
258
+ const result = await handler.execute({ auto: false });
259
+
260
+ expect(result.ok).toBe(true);
261
+ expect(prompts.factory).toHaveBeenCalledTimes(1);
262
+ expect(prompts.close).toHaveBeenCalledTimes(1);
263
+
264
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
265
+ expect(policyContent).toContain('base_branch: release');
266
+ expect(policyContent).toContain('max_parallel_gate_runs: 4');
267
+ expect(policyContent).toContain('port: 4500');
268
+ expect(policyContent).toContain('notifications:');
269
+ expect(policyContent).toContain('enabled: true');
270
+ expect(policyContent).toContain('webhook: "https://hooks.slack.example/webhook"');
271
+
272
+ const gatesContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'gates.yaml'), 'utf8');
273
+ expect(gatesContent).toContain('"npx"');
274
+ expect(gatesContent).toContain('"jest"');
275
+ });
276
+ });
277
+
278
+ describe('InitCommandHandler validation branches', () => {
279
+ let cwd: string;
280
+
281
+ beforeEach(async () => {
282
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-init-val-'));
283
+ execFileMock.mockReset();
284
+ });
285
+
286
+ afterEach(async () => {
287
+ await fs.rm(cwd, { recursive: true, force: true });
288
+ vi.restoreAllMocks();
289
+ });
290
+
291
+ it('GIVEN_skipped_file_WHEN_validation_loop_runs_THEN_skipped_rel_is_not_validated', async () => {
292
+ const orchestratorDir = path.join(cwd, 'agentic', 'orchestrator');
293
+ await fs.mkdir(orchestratorDir, { recursive: true });
294
+ // Pre-create policy.yaml so it gets skipped (no --force)
295
+ await fs.writeFile(path.join(orchestratorDir, 'policy.yaml'), '# existing\n', 'utf8');
296
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
297
+
298
+ const handler = new InitCommandHandler(cwd);
299
+ const result = await handler.execute({ auto: true });
300
+
301
+ expect(result.ok).toBe(true);
302
+ // policy.yaml was skipped, so the validation loop skipped it too (no error about it)
303
+ expect(result.data.skipped).toContain('agentic/orchestrator/policy.yaml');
304
+ // Validation warnings should be empty (skipped file is not validated)
305
+ expect(result.data.validation_warnings).toHaveLength(0);
306
+ });
307
+
308
+ it('GIVEN_schema_registry_validates_to_invalid_WHEN_init_runs_THEN_adds_validation_warning', async () => {
309
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
310
+ const { SchemaRegistry } = await import('../src/core/schemas.js');
311
+ const validateSpy = vi.spyOn(SchemaRegistry.prototype, 'validate').mockResolvedValue({
312
+ valid: false,
313
+ errors: [{ message: 'test-schema-error' } as any]
314
+ });
315
+
316
+ const handler = new InitCommandHandler(cwd);
317
+ const result = await handler.execute({ auto: true });
318
+
319
+ expect(result.ok).toBe(true);
320
+ expect(result.data.validation_warnings.length).toBeGreaterThan(0);
321
+ expect(result.data.validation_warnings[0]).toContain('test-schema-error');
322
+ validateSpy.mockRestore();
323
+ });
324
+
325
+ it('GIVEN_schema_registry_throws_WHEN_init_runs_THEN_adds_catch_validation_warning', async () => {
326
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
327
+ const { SchemaRegistry } = await import('../src/core/schemas.js');
328
+ const validateSpy = vi.spyOn(SchemaRegistry.prototype, 'validate').mockRejectedValue(
329
+ new Error('schema-load-failure')
330
+ );
331
+
332
+ const handler = new InitCommandHandler(cwd);
333
+ const result = await handler.execute({ auto: true });
334
+
335
+ expect(result.ok).toBe(true);
336
+ expect(result.data.validation_warnings.length).toBeGreaterThan(0);
337
+ expect(result.data.validation_warnings[0]).toContain('Failed to validate');
338
+ validateSpy.mockRestore();
339
+ });
340
+ });
341
+
342
+ describe('InitCommandHandler webhook channel branch', () => {
343
+ let cwd: string;
344
+
345
+ beforeEach(async () => {
346
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-init-webhook-'));
347
+ execFileMock.mockReset();
348
+ });
349
+
350
+ afterEach(async () => {
351
+ await fs.rm(cwd, { recursive: true, force: true });
352
+ vi.restoreAllMocks();
353
+ });
354
+
355
+ it('GIVEN_auto_false_with_webhook_channel_WHEN_init_THEN_prompts_for_webhook_url', async () => {
356
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
357
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
358
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
359
+
360
+ const prompts = makePromptFactory([
361
+ 'main',
362
+ '3',
363
+ '3000',
364
+ 'webhook',
365
+ 'jest',
366
+ 'https://example.com/my-webhook'
367
+ ]);
368
+ const handler = new InitCommandHandler(cwd, prompts.factory);
369
+ const result = await handler.execute({ auto: false });
370
+
371
+ expect(result.ok).toBe(true);
372
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
373
+ expect(policyContent).toContain('url: "https://example.com/my-webhook"');
374
+ });
375
+ });
376
+
377
+ describe('InitCommandHandler parseNotificationChannels none branch', () => {
378
+ let cwd: string;
379
+
380
+ beforeEach(async () => {
381
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-init-none-'));
382
+ execFileMock.mockReset();
383
+ });
384
+
385
+ afterEach(async () => {
386
+ await fs.rm(cwd, { recursive: true, force: true });
387
+ vi.restoreAllMocks();
388
+ });
389
+
390
+ it('GIVEN_auto_false_with_none_channel_WHEN_init_THEN_no_channels_enabled', async () => {
391
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
392
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
393
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
394
+
395
+ const prompts = makePromptFactory([
396
+ 'main',
397
+ '3',
398
+ '3000',
399
+ 'none',
400
+ 'jest'
401
+ ]);
402
+ const handler = new InitCommandHandler(cwd, prompts.factory);
403
+ const result = await handler.execute({ auto: false });
404
+
405
+ expect(result.ok).toBe(true);
406
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
407
+ expect(policyContent).toContain('notifications:');
408
+ });
409
+ });
410
+
411
+ describe('InitCommandHandler loadTemplateFiles non-file entry', () => {
412
+ let cwd: string;
413
+
414
+ beforeEach(async () => {
415
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-init-tmpl-'));
416
+ execFileMock.mockReset();
417
+ });
418
+
419
+ afterEach(async () => {
420
+ await fs.rm(cwd, { recursive: true, force: true });
421
+ vi.restoreAllMocks();
422
+ });
423
+
424
+ it('GIVEN_template_dir_contains_directory_entry_WHEN_auto_init_THEN_skips_non_file_entries', async () => {
425
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
426
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
427
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
428
+
429
+ const readdirSpy = vi.spyOn(fs, 'readdir').mockImplementation(async (dirPath, opts) => {
430
+ const originalReaddir = (await vi.importActual<typeof FsPromises>('node:fs/promises')).readdir;
431
+ const entries = await (originalReaddir)(dirPath, opts as never);
432
+ const fakeDir = {
433
+ name: 'subdir',
434
+ isFile: () => false,
435
+ isDirectory: () => true,
436
+ isBlockDevice: () => false,
437
+ isCharacterDevice: () => false,
438
+ isFIFO: () => false,
439
+ isSocket: () => false,
440
+ isSymbolicLink: () => false,
441
+ parentPath: String(dirPath),
442
+ path: String(dirPath)
443
+ };
444
+ return [...entries, fakeDir] as never;
445
+ });
446
+
447
+ const handler = new InitCommandHandler(cwd);
448
+ const result = await handler.execute({ auto: true });
449
+
450
+ readdirSpy.mockRestore();
451
+ expect(result.ok).toBe(true);
452
+ });
453
+ });
454
+
455
+ describe('InitCommandHandler parseInteger out-of-range and parseFramework fallback', () => {
456
+ let cwd: string;
457
+
458
+ beforeEach(async () => {
459
+ cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-init-parse-'));
460
+ execFileMock.mockReset();
461
+ });
462
+
463
+ afterEach(async () => {
464
+ await fs.rm(cwd, { recursive: true, force: true });
465
+ vi.restoreAllMocks();
466
+ });
467
+
468
+ it('GIVEN_out_of_range_maxParallelGateRuns_WHEN_init_THEN_falls_back_to_default', async () => {
469
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
470
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
471
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
472
+
473
+ const prompts = makePromptFactory([
474
+ 'main',
475
+ '0', // out of range (min=1) → parseInteger returns fallback 3
476
+ '3000',
477
+ 'none',
478
+ 'vitest'
479
+ ]);
480
+ const handler = new InitCommandHandler(cwd, prompts.factory);
481
+ const result = await handler.execute({ auto: false });
482
+
483
+ expect(result.ok).toBe(true);
484
+ const policyContent = await fs.readFile(path.join(cwd, 'agentic', 'orchestrator', 'policy.yaml'), 'utf8');
485
+ // Falls back to default 3
486
+ expect(policyContent).toContain('max_parallel_gate_runs: 3');
487
+ });
488
+
489
+ it('GIVEN_unrecognized_framework_WHEN_init_THEN_falls_back_to_default_framework', async () => {
490
+ await fs.mkdir(path.join(cwd, '.git'), { recursive: true });
491
+ await fs.writeFile(path.join(cwd, '.git', 'config'), '[core]\n', 'utf8');
492
+ execFileMock.mockResolvedValue({ stdout: 'origin/main\n', stderr: '' });
493
+
494
+ const prompts = makePromptFactory([
495
+ 'main',
496
+ '3',
497
+ '3000',
498
+ 'none',
499
+ 'unknown_framework' // not vitest/jest/pytest/maven/gradle → parseFramework returns fallback
500
+ ]);
501
+ const handler = new InitCommandHandler(cwd, prompts.factory);
502
+ const result = await handler.execute({ auto: false });
503
+
504
+ expect(result.ok).toBe(true);
505
+ });
506
+ });