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,312 @@
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
+ import { buildExecutionWaves } from '../src/core/gates.js';
6
+ import type { GateStep, PolicyShape, GateProfile } from '../src/core/gates.js';
7
+
8
+ const runCommandMock = vi.hoisted(() => vi.fn());
9
+
10
+ vi.mock('../src/core/git.js', () => ({
11
+ runCommand: runCommandMock
12
+ }));
13
+
14
+ // Re-import after mock
15
+ const { GateExecutionService, CoverageThresholdPolicy } = await import('../src/core/gates.js');
16
+
17
+ describe('parallel gate execution', () => {
18
+ let tempDir: string;
19
+ let logDir: string;
20
+ let evidenceDir: string;
21
+
22
+ const defaultPolicy: PolicyShape = {
23
+ execution: {
24
+ retry_policy: { transient_max_retries: 0, transient_error_codes: [] },
25
+ env_allowlist: [],
26
+ default_step_timeout_seconds: 60
27
+ },
28
+ testing: {
29
+ coverage: {
30
+ minimums: { line: 0, branch: 0 },
31
+ targets: { line: 1, branch: 1 }
32
+ }
33
+ }
34
+ };
35
+
36
+ beforeEach(async () => {
37
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'aop-parallel-gates-'));
38
+ logDir = path.join(tempDir, 'logs');
39
+ evidenceDir = path.join(tempDir, 'evidence');
40
+ await fs.mkdir(logDir, { recursive: true });
41
+ await fs.mkdir(evidenceDir, { recursive: true });
42
+ runCommandMock.mockReset();
43
+ });
44
+
45
+ afterEach(async () => {
46
+ await fs.rm(tempDir, { recursive: true });
47
+ vi.restoreAllMocks();
48
+ });
49
+
50
+ describe('buildExecutionWaves', () => {
51
+ it('GIVEN steps with same parallel_group THEN they appear in the same wave', () => {
52
+ const steps: GateStep[] = [
53
+ { name: 'lint', cmd: ['eslint', '.'], parallel_group: 'static' },
54
+ { name: 'typecheck', cmd: ['tsc', '--noEmit'], parallel_group: 'static' },
55
+ { name: 'test', cmd: ['vitest', 'run'], depends_on: ['lint', 'typecheck'] }
56
+ ];
57
+
58
+ const waves = buildExecutionWaves(steps);
59
+ expect(waves).toHaveLength(2);
60
+ expect(waves[0].map((s) => s.name)).toEqual(expect.arrayContaining(['lint', 'typecheck']));
61
+ expect(waves[1].map((s) => s.name)).toEqual(['test']);
62
+ });
63
+
64
+ it('GIVEN no parallel config THEN each step is its own wave', () => {
65
+ const steps: GateStep[] = [
66
+ { name: 'lint', cmd: ['eslint', '.'] },
67
+ { name: 'test', cmd: ['vitest', 'run'] }
68
+ ];
69
+
70
+ const waves = buildExecutionWaves(steps);
71
+ // Without depends_on, all steps have their deps met immediately — they go into wave 1
72
+ expect(waves).toHaveLength(1);
73
+ expect(waves[0]).toHaveLength(2);
74
+ });
75
+
76
+ it('GIVEN chain of depends_on THEN steps form sequential waves', () => {
77
+ const steps: GateStep[] = [
78
+ { name: 'step1', cmd: ['echo', '1'] },
79
+ { name: 'step2', cmd: ['echo', '2'], depends_on: ['step1'] },
80
+ { name: 'step3', cmd: ['echo', '3'], depends_on: ['step2'] }
81
+ ];
82
+
83
+ const waves = buildExecutionWaves(steps);
84
+ expect(waves).toHaveLength(3);
85
+ expect(waves[0][0].name).toBe('step1');
86
+ expect(waves[1][0].name).toBe('step2');
87
+ expect(waves[2][0].name).toBe('step3');
88
+ });
89
+
90
+ it('GIVEN mixed parallel and sequential steps THEN correct wave ordering', () => {
91
+ const steps: GateStep[] = [
92
+ { name: 'lint', cmd: ['eslint'], parallel_group: 'checks' },
93
+ { name: 'typecheck', cmd: ['tsc'], parallel_group: 'checks' },
94
+ { name: 'unit_test', cmd: ['vitest'], depends_on: ['lint', 'typecheck'] },
95
+ { name: 'integration', cmd: ['vitest', '--integration'], depends_on: ['lint', 'typecheck'] }
96
+ ];
97
+
98
+ const waves = buildExecutionWaves(steps);
99
+ expect(waves).toHaveLength(2);
100
+ expect(waves[0].map((s) => s.name).sort()).toEqual(['lint', 'typecheck']);
101
+ expect(waves[1].map((s) => s.name).sort()).toEqual(['integration', 'unit_test']);
102
+ });
103
+ });
104
+
105
+ describe('GateExecutionService.runMode — parallel behavior', () => {
106
+ it('GIVEN steps with parallel_group WHEN gate runs THEN steps in same group execute concurrently', async () => {
107
+ const executionOrder: string[] = [];
108
+ runCommandMock.mockImplementation(async (cmd: string) => {
109
+ executionOrder.push(cmd);
110
+ return { code: 0, signal: null, stdout: '', stderr: '', timeout: false };
111
+ });
112
+
113
+ const profile: GateProfile = {
114
+ modes: {
115
+ fast: [
116
+ { name: 'lint', cmd: ['lint-cmd'], parallel_group: 'static' },
117
+ { name: 'typecheck', cmd: ['typecheck-cmd'], parallel_group: 'static' },
118
+ { name: 'test', cmd: ['test-cmd'], depends_on: ['lint', 'typecheck'] }
119
+ ]
120
+ }
121
+ };
122
+
123
+ const service = new GateExecutionService(new CoverageThresholdPolicy());
124
+ const result = await service.runMode({
125
+ featureId: 'feat-1',
126
+ mode: 'fast',
127
+ profileName: 'default',
128
+ profile,
129
+ policy: defaultPolicy,
130
+ worktreePath: tempDir,
131
+ logDirectory: logDir,
132
+ evidenceDirectory: evidenceDir
133
+ });
134
+
135
+ expect(result['overall']).toBe('pass');
136
+ const stepResults = result['step_results'] as Array<Record<string, unknown>>;
137
+ expect(stepResults).toHaveLength(3);
138
+ // test-cmd must run after lint-cmd and typecheck-cmd
139
+ const testIdx = executionOrder.indexOf('test-cmd');
140
+ expect(testIdx).toBe(2);
141
+ });
142
+
143
+ it('GIVEN step with depends_on WHEN dependency fails THEN dependent step is skipped', async () => {
144
+ runCommandMock.mockImplementation(async (cmd: string) => {
145
+ if (cmd === 'lint-cmd') {
146
+ return { code: 1, signal: null, stdout: '', stderr: 'lint error', timeout: false };
147
+ }
148
+ return { code: 0, signal: null, stdout: '', stderr: '', timeout: false };
149
+ });
150
+
151
+ const profile: GateProfile = {
152
+ modes: {
153
+ fast: [
154
+ { name: 'lint', cmd: ['lint-cmd'], parallel_group: 'static' },
155
+ { name: 'typecheck', cmd: ['typecheck-cmd'], parallel_group: 'static' },
156
+ { name: 'test', cmd: ['test-cmd'], depends_on: ['lint', 'typecheck'] }
157
+ ]
158
+ }
159
+ };
160
+
161
+ const service = new GateExecutionService(new CoverageThresholdPolicy());
162
+ const result = await service.runMode({
163
+ featureId: 'feat-1',
164
+ mode: 'fast',
165
+ profileName: 'default',
166
+ profile,
167
+ policy: defaultPolicy,
168
+ worktreePath: tempDir,
169
+ logDirectory: logDir,
170
+ evidenceDirectory: evidenceDir
171
+ });
172
+
173
+ expect(result['overall']).toBe('fail');
174
+ const stepResults = result['step_results'] as Array<Record<string, unknown>>;
175
+ const testStep = stepResults.find((s) => s['name'] === 'test');
176
+ expect(testStep).toBeDefined();
177
+ expect(testStep?.['skipped']).toBe(true);
178
+ expect(testStep?.['skip_reason']).toBe('dependency_failed');
179
+ });
180
+
181
+ it('GIVEN no parallel config WHEN gate runs THEN sequential execution preserved (backward compat)', async () => {
182
+ const executionOrder: string[] = [];
183
+ runCommandMock.mockImplementation(async (cmd: string) => {
184
+ executionOrder.push(cmd);
185
+ return { code: 0, signal: null, stdout: '', stderr: '', timeout: false };
186
+ });
187
+
188
+ const profile: GateProfile = {
189
+ modes: {
190
+ fast: [
191
+ { name: 'lint', cmd: ['lint-cmd'] },
192
+ { name: 'test', cmd: ['test-cmd'] }
193
+ ]
194
+ }
195
+ };
196
+
197
+ const service = new GateExecutionService(new CoverageThresholdPolicy());
198
+ const result = await service.runMode({
199
+ featureId: 'feat-1',
200
+ mode: 'fast',
201
+ profileName: 'default',
202
+ profile,
203
+ policy: defaultPolicy,
204
+ worktreePath: tempDir,
205
+ logDirectory: logDir,
206
+ evidenceDirectory: evidenceDir
207
+ });
208
+
209
+ expect(result['overall']).toBe('pass');
210
+ expect(executionOrder).toEqual(['lint-cmd', 'test-cmd']);
211
+ });
212
+
213
+ it('GIVEN no parallel config and first step fails WHEN gate runs THEN second step does not execute', async () => {
214
+ const executionOrder: string[] = [];
215
+ runCommandMock.mockImplementation(async (cmd: string) => {
216
+ executionOrder.push(cmd);
217
+ if (cmd === 'lint-cmd') {
218
+ return { code: 1, signal: null, stdout: '', stderr: 'fail', timeout: false };
219
+ }
220
+ return { code: 0, signal: null, stdout: '', stderr: '', timeout: false };
221
+ });
222
+
223
+ const profile: GateProfile = {
224
+ modes: {
225
+ fast: [
226
+ { name: 'lint', cmd: ['lint-cmd'] },
227
+ { name: 'test', cmd: ['test-cmd'] }
228
+ ]
229
+ }
230
+ };
231
+
232
+ const service = new GateExecutionService(new CoverageThresholdPolicy());
233
+ const result = await service.runMode({
234
+ featureId: 'feat-1',
235
+ mode: 'fast',
236
+ profileName: 'default',
237
+ profile,
238
+ policy: defaultPolicy,
239
+ worktreePath: tempDir,
240
+ logDirectory: logDir,
241
+ evidenceDirectory: evidenceDir
242
+ });
243
+
244
+ expect(result['overall']).toBe('fail');
245
+ expect(executionOrder).toEqual(['lint-cmd']);
246
+ });
247
+
248
+ it('GIVEN all parallel steps fail WHEN gate runs THEN all failures captured in evidence', async () => {
249
+ runCommandMock.mockResolvedValue({ code: 1, signal: null, stdout: '', stderr: 'fail', timeout: false });
250
+
251
+ const profile: GateProfile = {
252
+ modes: {
253
+ fast: [
254
+ { name: 'lint', cmd: ['lint-cmd'], parallel_group: 'static' },
255
+ { name: 'typecheck', cmd: ['typecheck-cmd'], parallel_group: 'static' }
256
+ ]
257
+ }
258
+ };
259
+
260
+ const service = new GateExecutionService(new CoverageThresholdPolicy());
261
+ const result = await service.runMode({
262
+ featureId: 'feat-1',
263
+ mode: 'fast',
264
+ profileName: 'default',
265
+ profile,
266
+ policy: defaultPolicy,
267
+ worktreePath: tempDir,
268
+ logDirectory: logDir,
269
+ evidenceDirectory: evidenceDir
270
+ });
271
+
272
+ expect(result['overall']).toBe('fail');
273
+ const stepResults = result['step_results'] as Array<Record<string, unknown>>;
274
+ expect(stepResults).toHaveLength(2);
275
+ expect(stepResults.every((s) => s['exit_code'] === 1)).toBe(true);
276
+ });
277
+
278
+ it('GIVEN parallel steps WHEN evidence written THEN timing metadata present', async () => {
279
+ runCommandMock.mockResolvedValue({ code: 0, signal: null, stdout: '', stderr: '', timeout: false });
280
+
281
+ const profile: GateProfile = {
282
+ modes: {
283
+ fast: [
284
+ { name: 'lint', cmd: ['lint-cmd'], parallel_group: 'static' },
285
+ { name: 'typecheck', cmd: ['typecheck-cmd'], parallel_group: 'static' }
286
+ ]
287
+ }
288
+ };
289
+
290
+ const service = new GateExecutionService(new CoverageThresholdPolicy());
291
+ const result = await service.runMode({
292
+ featureId: 'feat-1',
293
+ mode: 'fast',
294
+ profileName: 'default',
295
+ profile,
296
+ policy: defaultPolicy,
297
+ worktreePath: tempDir,
298
+ logDirectory: logDir,
299
+ evidenceDirectory: evidenceDir
300
+ });
301
+
302
+ const stepResults = result['step_results'] as Array<Record<string, unknown>>;
303
+ for (const step of stepResults) {
304
+ expect(step['started_at']).toBeDefined();
305
+ expect(step['finished_at']).toBeDefined();
306
+ expect(step['parallel_group']).toBe('static');
307
+ expect(typeof step['started_at']).toBe('string');
308
+ expect(typeof step['finished_at']).toBe('string');
309
+ }
310
+ });
311
+ });
312
+ });
@@ -0,0 +1,253 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { PatchService } from '../src/application/services/patch-service.js';
3
+
4
+ vi.mock('../src/core/git.js', () => ({
5
+ runGit: vi.fn()
6
+ }));
7
+ vi.mock('../src/core/patch.js', () => ({
8
+ parseUnifiedDiff: vi.fn(() => []),
9
+ touchedPathsFromDiff: vi.fn(() => [])
10
+ }));
11
+ vi.mock('../src/core/path-rules.js', () => ({
12
+ normalizeRepoPath: vi.fn(async (_root: string, p: string) => p),
13
+ anyAreaMatch: vi.fn(() => false),
14
+ sortResourcesDeterministically: vi.fn((r: string[]) => [...new Set(r)].sort())
15
+ }));
16
+ vi.mock('../src/core/qa-index.js', () => ({
17
+ buildQaIndex: vi.fn(() => ({ version: 1, tests: [] })),
18
+ makeRequiredTests: vi.fn(() => [])
19
+ }));
20
+ vi.mock('../src/core/fs.js', () => ({
21
+ readJson: vi.fn(async () => null),
22
+ atomicWriteJson: vi.fn(async () => undefined)
23
+ }));
24
+
25
+ import { runGit } from '../src/core/git.js';
26
+ import { parseUnifiedDiff, touchedPathsFromDiff } from '../src/core/patch.js';
27
+ import { anyAreaMatch, normalizeRepoPath } from '../src/core/path-rules.js';
28
+ import { readJson } from '../src/core/fs.js';
29
+
30
+ function makePort(overrides: Partial<{
31
+ plan: unknown;
32
+ enforcePlan: boolean;
33
+ enforceAllowedAreas: boolean;
34
+ allowedAreas: string[];
35
+ forbiddenAreas: string[];
36
+ protectedAreas: string[];
37
+ heldLocks: string[];
38
+ openapi: string;
39
+ events: string;
40
+ db: string;
41
+ }> = {}) {
42
+ const o = {
43
+ plan: null as unknown,
44
+ enforcePlan: true,
45
+ enforceAllowedAreas: false,
46
+ allowedAreas: [] as string[],
47
+ forbiddenAreas: [] as string[],
48
+ protectedAreas: [] as string[],
49
+ heldLocks: [] as string[],
50
+ openapi: 'none',
51
+ events: 'none',
52
+ db: 'none',
53
+ ...overrides
54
+ };
55
+
56
+ return {
57
+ getRepoRoot: vi.fn(() => '/repo'),
58
+ getPolicySnapshot: vi.fn(() => ({
59
+ patch_policy: { enforce_plan: o.enforcePlan, enforce_allowed_areas: o.enforceAllowedAreas },
60
+ path_rules: { matching: 'repo_prefix', allow_symlink_traversal: false },
61
+ locks: {
62
+ contract_to_resource: { openapi: 'openapi', events: 'events', db: 'db' }
63
+ },
64
+ protected_areas: o.protectedAreas
65
+ })),
66
+ getGatesConfig: vi.fn(() => ({ profiles: { default: { modes: { fast: [], full: [], merge: [] } } } })),
67
+ planPath: vi.fn(() => '/repo/.aop/features/f1/plan.json'),
68
+ qaIndexPath: vi.fn(() => '/repo/.aop/features/f1/qa_test_index.json'),
69
+ worktreePath: vi.fn(() => '/worktree/f1'),
70
+ readState: vi.fn(async () => ({ frontMatter: { locks: { held: o.heldLocks } } })),
71
+ validateSchema: vi.fn(async () => ({ valid: true })),
72
+ updateState: vi.fn(async (_id: string, _v: unknown, cb: (fm: object, body: string) => Promise<{ frontMatter?: object; body?: string }>) => {
73
+ return await cb({}, '');
74
+ }),
75
+ __plan: o.plan,
76
+ __openapi: o.openapi,
77
+ __events: o.events,
78
+ __db: o.db
79
+ };
80
+ }
81
+
82
+ describe('PatchService', () => {
83
+ beforeEach(() => { vi.clearAllMocks(); });
84
+
85
+ describe('loadAcceptedPlan', () => {
86
+ it('GIVEN_enforce_plan_true_and_no_plan_file_WHEN_loadAcceptedPlan_called_THEN_throws_PLAN_REQUIRED', async () => {
87
+ vi.mocked(readJson).mockResolvedValue(null);
88
+ const port = makePort({ enforcePlan: true });
89
+ const svc = new PatchService(port);
90
+ await expect(svc.loadAcceptedPlan('f1')).rejects.toMatchObject({
91
+ normalizedResponse: expect.objectContaining({ ok: false })
92
+ });
93
+ });
94
+
95
+ it('GIVEN_enforce_plan_false_and_no_plan_file_WHEN_loadAcceptedPlan_called_THEN_returns_null', async () => {
96
+ vi.mocked(readJson).mockResolvedValue(null);
97
+ const port = makePort({ enforcePlan: false });
98
+ const svc = new PatchService(port);
99
+ const result = await svc.loadAcceptedPlan('f1');
100
+ expect(result).toBeNull();
101
+ });
102
+
103
+ it('GIVEN_plan_file_exists_WHEN_loadAcceptedPlan_called_THEN_returns_plan', async () => {
104
+ const plan = { plan_version: 1, allowed_areas: [] };
105
+ vi.mocked(readJson).mockResolvedValue(plan);
106
+ const port = makePort();
107
+ const svc = new PatchService(port);
108
+ const result = await svc.loadAcceptedPlan('f1');
109
+ expect(result).toEqual(plan);
110
+ });
111
+ });
112
+
113
+ describe('requiredResourcesForPlan', () => {
114
+ it('GIVEN_openapi_modify_WHEN_requiredResourcesForPlan_THEN_includes_openapi_resource', () => {
115
+ const port = makePort();
116
+ const svc = new PatchService(port);
117
+ const result = svc.requiredResourcesForPlan({ contracts: { openapi: 'modify', events: 'none', db: 'none' } });
118
+ expect(result).toContain('openapi');
119
+ });
120
+
121
+ it('GIVEN_events_modify_WHEN_requiredResourcesForPlan_THEN_includes_events_resource', () => {
122
+ const port = makePort();
123
+ const svc = new PatchService(port);
124
+ const result = svc.requiredResourcesForPlan({ contracts: { openapi: 'none', events: 'modify', db: 'none' } });
125
+ expect(result).toContain('events');
126
+ });
127
+
128
+ it('GIVEN_db_migration_WHEN_requiredResourcesForPlan_THEN_includes_db_resource', () => {
129
+ const port = makePort();
130
+ const svc = new PatchService(port);
131
+ const result = svc.requiredResourcesForPlan({ contracts: { openapi: 'none', events: 'none', db: 'migration' } });
132
+ expect(result).toContain('db');
133
+ });
134
+
135
+ it('GIVEN_no_contracts_WHEN_requiredResourcesForPlan_THEN_returns_empty', () => {
136
+ const port = makePort();
137
+ const svc = new PatchService(port);
138
+ const result = svc.requiredResourcesForPlan({ contracts: { openapi: 'none', events: 'none', db: 'none' } });
139
+ expect(result).toHaveLength(0);
140
+ });
141
+ });
142
+
143
+ describe('assertPlanLocksHeld', () => {
144
+ it('GIVEN_all_required_locks_held_WHEN_assertPlanLocksHeld_THEN_resolves_without_error', async () => {
145
+ const port = makePort({ heldLocks: ['openapi'] });
146
+ const svc = new PatchService(port);
147
+ await expect(svc.assertPlanLocksHeld('f1', { contracts: { openapi: 'modify', events: 'none', db: 'none' } })).resolves.toBeUndefined();
148
+ });
149
+
150
+ it('GIVEN_required_lock_not_held_WHEN_assertPlanLocksHeld_THEN_throws_LOCK_NOT_HELD', async () => {
151
+ const port = makePort({ heldLocks: [] });
152
+ const svc = new PatchService(port);
153
+ await expect(
154
+ svc.assertPlanLocksHeld('f1', { contracts: { openapi: 'modify', events: 'none', db: 'none' } })
155
+ ).rejects.toMatchObject({ normalizedResponse: expect.objectContaining({ ok: false }) });
156
+ });
157
+ });
158
+
159
+ describe('validatePatchPaths', () => {
160
+ it('GIVEN_file_in_forbidden_area_WHEN_validatePatchPaths_THEN_throws_violation', async () => {
161
+ vi.mocked(touchedPathsFromDiff).mockReturnValue(['src/evil.ts']);
162
+ vi.mocked(normalizeRepoPath).mockResolvedValue('src/evil.ts');
163
+ vi.mocked(anyAreaMatch).mockReturnValue(true);
164
+ const port = makePort({ forbiddenAreas: ['src'] });
165
+ const svc = new PatchService(port);
166
+ const plan = { allowed_areas: [], forbidden_areas: ['src'], files: { create: ['src/evil.ts'], modify: [], delete: [] } };
167
+ await expect(svc.validatePatchPaths('f1', [] as never, plan)).rejects.toMatchObject({
168
+ normalizedResponse: expect.objectContaining({ ok: false })
169
+ });
170
+ });
171
+
172
+ it('GIVEN_file_outside_allowed_areas_with_enforcement_WHEN_validatePatchPaths_THEN_throws', async () => {
173
+ vi.mocked(touchedPathsFromDiff).mockReturnValue(['src/file.ts']);
174
+ vi.mocked(normalizeRepoPath).mockResolvedValue('src/file.ts');
175
+ vi.mocked(anyAreaMatch).mockReturnValue(false);
176
+ const port = makePort({ enforceAllowedAreas: true, allowedAreas: ['lib'] });
177
+ const svc = new PatchService(port);
178
+ await expect(svc.validatePatchPaths('f1', [] as never, { allowed_areas: ['lib'], forbidden_areas: [], files: { create: [], modify: [], delete: [] } })).rejects.toMatchObject({
179
+ normalizedResponse: expect.objectContaining({ ok: false })
180
+ });
181
+ });
182
+
183
+ it('GIVEN_file_not_in_plan_file_set_WHEN_validatePatchPaths_THEN_throws', async () => {
184
+ vi.mocked(touchedPathsFromDiff).mockReturnValue(['src/unplanned.ts']);
185
+ vi.mocked(normalizeRepoPath).mockResolvedValue('src/unplanned.ts');
186
+ vi.mocked(anyAreaMatch).mockReturnValue(false);
187
+ const port = makePort();
188
+ const svc = new PatchService(port);
189
+ const plan = { allowed_areas: [], forbidden_areas: [], files: { create: ['src/other.ts'], modify: [], delete: [] } };
190
+ await expect(svc.validatePatchPaths('f1', [] as never, plan)).rejects.toMatchObject({
191
+ normalizedResponse: expect.objectContaining({ ok: false })
192
+ });
193
+ });
194
+
195
+ it('GIVEN_no_violations_WHEN_validatePatchPaths_THEN_returns_normalized_paths', async () => {
196
+ vi.mocked(touchedPathsFromDiff).mockReturnValue(['src/file.ts']);
197
+ vi.mocked(normalizeRepoPath).mockResolvedValue('src/file.ts');
198
+ vi.mocked(anyAreaMatch).mockReturnValue(false);
199
+ const port = makePort();
200
+ const svc = new PatchService(port);
201
+ const plan = { allowed_areas: [], forbidden_areas: [], files: { create: ['src/file.ts'], modify: [], delete: [] } };
202
+ const result = await svc.validatePatchPaths('f1', [] as never, plan);
203
+ expect(result).toEqual(['src/file.ts']);
204
+ });
205
+ });
206
+
207
+ describe('repoApplyPatch', () => {
208
+ it('GIVEN_git_apply_fails_WHEN_repoApplyPatch_THEN_throws_GIT_FAILURE', async () => {
209
+ vi.mocked(readJson).mockResolvedValue({ allowed_areas: [], forbidden_areas: [], files: { create: [], modify: [], delete: [] }, contracts: { openapi: 'none', events: 'none', db: 'none' }, gate_profile: 'default' });
210
+ vi.mocked(parseUnifiedDiff).mockReturnValue([]);
211
+ vi.mocked(touchedPathsFromDiff).mockReturnValue([]);
212
+ vi.mocked(runGit).mockResolvedValue({ code: 1, signal: null, stdout: '', stderr: 'apply failed', timeout: false });
213
+ const port = makePort({ heldLocks: [] });
214
+ const svc = new PatchService(port);
215
+ await expect(svc.repoApplyPatch('f1', 'diff --git a/f b/f\n')).rejects.toMatchObject({
216
+ normalizedResponse: expect.objectContaining({ ok: false })
217
+ });
218
+ });
219
+
220
+ it('GIVEN_git_apply_succeeds_WHEN_repoApplyPatch_THEN_returns_changed_files', async () => {
221
+ vi.mocked(readJson).mockResolvedValue({ allowed_areas: [], forbidden_areas: [], files: { create: ['src/a.ts'], modify: [], delete: [] }, contracts: { openapi: 'none', events: 'none', db: 'none' }, gate_profile: 'default' });
222
+ vi.mocked(parseUnifiedDiff).mockReturnValue([]);
223
+ vi.mocked(touchedPathsFromDiff).mockReturnValue(['src/a.ts']);
224
+ vi.mocked(normalizeRepoPath).mockResolvedValue('src/a.ts');
225
+ vi.mocked(anyAreaMatch).mockReturnValue(false);
226
+ vi.mocked(runGit)
227
+ .mockResolvedValueOnce({ code: 0, signal: null, stdout: '', stderr: '', timeout: false }) // apply
228
+ .mockResolvedValueOnce({ code: 0, signal: null, stdout: 'M src/a.ts\n', stderr: '', timeout: false }) // status
229
+ .mockResolvedValueOnce({ code: 0, signal: null, stdout: 'src/a.ts\n', stderr: '', timeout: false }); // diff
230
+ const port = makePort({ heldLocks: [] });
231
+ const svc = new PatchService(port);
232
+ const result = await svc.repoApplyPatch('f1', 'diff --git a/src/a.ts b/src/a.ts\n');
233
+ expect(result.data.changed_files).toContain('src/a.ts');
234
+ expect(result.data.feature_id).toBe('f1');
235
+ });
236
+
237
+ it('GIVEN_qa_index_validation_fails_WHEN_repoApplyPatch_THEN_throws_INTERNAL_ERROR', async () => {
238
+ vi.mocked(readJson).mockResolvedValue({ allowed_areas: [], forbidden_areas: [], files: { create: [], modify: [], delete: [] }, contracts: { openapi: 'none', events: 'none', db: 'none' }, gate_profile: 'default' });
239
+ vi.mocked(parseUnifiedDiff).mockReturnValue([]);
240
+ vi.mocked(touchedPathsFromDiff).mockReturnValue([]);
241
+ vi.mocked(runGit)
242
+ .mockResolvedValueOnce({ code: 0, signal: null, stdout: '', stderr: '', timeout: false })
243
+ .mockResolvedValueOnce({ code: 0, signal: null, stdout: '', stderr: '', timeout: false })
244
+ .mockResolvedValueOnce({ code: 0, signal: null, stdout: '', stderr: '', timeout: false });
245
+ const port = makePort({ heldLocks: [] });
246
+ port.validateSchema.mockResolvedValue({ valid: false });
247
+ const svc = new PatchService(port);
248
+ await expect(svc.repoApplyPatch('f1', '')).rejects.toMatchObject({
249
+ normalizedResponse: expect.objectContaining({ ok: false })
250
+ });
251
+ });
252
+ });
253
+ });