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,393 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react';
4
+ import type { DashboardStatusPayload, FeatureDetail, FeatureSummary, SSEEvent } from '@/lib/types';
5
+
6
+ type ReviewAction = 'review.approve' | 'review.deny' | 'review.request_changes';
7
+ type ProjectEntry = { name: string; path: string };
8
+
9
+ const COLUMN_ORDER: Array<{ title: string; phase: FeatureSummary['phase'] }> = [
10
+ { title: 'Planning', phase: 'planning' },
11
+ { title: 'Building', phase: 'building' },
12
+ { title: 'QA', phase: 'qa' },
13
+ { title: 'Ready To Merge', phase: 'ready_to_merge' },
14
+ { title: 'Blocked', phase: 'blocked' },
15
+ { title: 'Merged', phase: 'merged' }
16
+ ];
17
+
18
+ function columnColor(phase: FeatureSummary['phase']): string {
19
+ switch (phase) {
20
+ case 'planning':
21
+ return '#3b82f6';
22
+ case 'building':
23
+ return '#f97316';
24
+ case 'qa':
25
+ return '#facc15';
26
+ case 'ready_to_merge':
27
+ return '#10b981';
28
+ case 'blocked':
29
+ return '#ef4444';
30
+ case 'merged':
31
+ return '#64748b';
32
+ default:
33
+ return '#334155';
34
+ }
35
+ }
36
+
37
+ function projectQuery(project: string): string {
38
+ return project.length > 0 ? `?project=${encodeURIComponent(project)}` : '';
39
+ }
40
+
41
+ async function jsonFetch<T>(input: string, init?: RequestInit): Promise<T> {
42
+ const response = await fetch(input, init);
43
+ return (await response.json()) as T;
44
+ }
45
+
46
+ function StatusBadge({ status }: { status: string }) {
47
+ return (
48
+ <span
49
+ style={{
50
+ fontSize: 11,
51
+ textTransform: 'uppercase',
52
+ color: '#cbd5e1',
53
+ background: '#0f172a',
54
+ padding: '2px 6px',
55
+ borderRadius: 6,
56
+ border: '1px solid #334155'
57
+ }}
58
+ >
59
+ {status}
60
+ </span>
61
+ );
62
+ }
63
+
64
+ function ActivityBadge({ activity }: { activity?: FeatureSummary['activity_state'] }) {
65
+ const label = activity ?? 'unknown';
66
+ return (
67
+ <span
68
+ style={{
69
+ fontSize: 11,
70
+ textTransform: 'uppercase',
71
+ color: '#93c5fd',
72
+ background: '#0b2239',
73
+ padding: '2px 6px',
74
+ borderRadius: 6,
75
+ border: '1px solid #1d4ed8'
76
+ }}
77
+ >
78
+ {label}
79
+ </span>
80
+ );
81
+ }
82
+
83
+ export default function Dashboard() {
84
+ const [payload, setPayload] = useState<DashboardStatusPayload>({
85
+ index: { active: [], blocked: [], merged: [], blocked_queue: [] },
86
+ features: []
87
+ });
88
+ const [connected, setConnected] = useState(false);
89
+ const [selectedFeatureId, setSelectedFeatureId] = useState<string | null>(null);
90
+ const [detail, setDetail] = useState<FeatureDetail | null>(null);
91
+ const [approvalToken, setApprovalToken] = useState('approved');
92
+ const [reviewMessage, setReviewMessage] = useState('');
93
+ const [actionResult, setActionResult] = useState<string>('');
94
+ const [projects, setProjects] = useState<ProjectEntry[]>([]);
95
+ const [project, setProject] = useState('');
96
+
97
+ const refreshDetail = useCallback(
98
+ async (featureId: string) => {
99
+ const res = await jsonFetch<{ ok: boolean; data?: FeatureDetail }>(
100
+ `/api/features/${featureId}${projectQuery(project)}`
101
+ );
102
+ if (res.ok && res.data) {
103
+ setDetail(res.data);
104
+ }
105
+ },
106
+ [project]
107
+ );
108
+
109
+ const refreshStatus = useCallback(async () => {
110
+ const response = await jsonFetch<{ ok: boolean; data?: DashboardStatusPayload }>(
111
+ `/api/status${projectQuery(project)}`
112
+ );
113
+ if (response.ok && response.data) {
114
+ setPayload(response.data);
115
+ }
116
+ }, [project]);
117
+
118
+ useEffect(() => {
119
+ void (async () => {
120
+ const response = await jsonFetch<{ ok: boolean; data?: { projects: ProjectEntry[] } }>('/api/projects');
121
+ if (response.ok && response.data) {
122
+ setProjects(response.data.projects ?? []);
123
+ }
124
+ })();
125
+ }, []);
126
+
127
+ useEffect(() => {
128
+ const params = new URLSearchParams(window.location.search);
129
+ const queryProject = params.get('project');
130
+ if (queryProject) {
131
+ setProject(queryProject);
132
+ }
133
+ }, []);
134
+
135
+ useEffect(() => {
136
+ void refreshStatus();
137
+ const es = new EventSource(`/api/events${projectQuery(project)}`);
138
+ es.onopen = () => setConnected(true);
139
+ es.onmessage = (event: MessageEvent<string>) => {
140
+ try {
141
+ const parsed = JSON.parse(event.data) as SSEEvent;
142
+ if (parsed.type === 'snapshot') {
143
+ if (parsed.payload) {
144
+ setPayload(parsed.payload);
145
+ }
146
+ }
147
+ } catch {
148
+ // ignore malformed events
149
+ }
150
+ };
151
+ es.onerror = () => setConnected(false);
152
+ return () => es.close();
153
+ }, [project, refreshStatus]);
154
+
155
+ useEffect(() => {
156
+ if (selectedFeatureId) {
157
+ void refreshDetail(selectedFeatureId);
158
+ }
159
+ }, [selectedFeatureId, refreshDetail]);
160
+
161
+ const featuresByPhase = useMemo(() => {
162
+ const byPhase = new Map<FeatureSummary['phase'], FeatureSummary[]>();
163
+ for (const column of COLUMN_ORDER) {
164
+ byPhase.set(column.phase, []);
165
+ }
166
+ for (const feature of payload.features) {
167
+ const phase = feature.phase ?? 'unknown';
168
+ if (!byPhase.has(phase)) {
169
+ byPhase.set(phase, []);
170
+ }
171
+ byPhase.get(phase)?.push(feature);
172
+ }
173
+ for (const [phase, items] of byPhase.entries()) {
174
+ byPhase.set(
175
+ phase,
176
+ [...items].sort((a, b) => a.feature_id.localeCompare(b.feature_id))
177
+ );
178
+ }
179
+ return byPhase;
180
+ }, [payload.features]);
181
+
182
+ const triggerReview = useCallback(
183
+ async (action: ReviewAction) => {
184
+ if (!selectedFeatureId) {
185
+ return;
186
+ }
187
+ const body: Record<string, unknown> = {
188
+ action,
189
+ feature_id: selectedFeatureId,
190
+ message: reviewMessage
191
+ };
192
+ if (action === 'review.approve') {
193
+ body.approval_token = approvalToken;
194
+ }
195
+ if (action === 'review.deny') {
196
+ body.reason = reviewMessage || 'Denied in dashboard review panel.';
197
+ }
198
+
199
+ const result = await jsonFetch<{ ok: boolean; error?: { message?: string } }>(
200
+ `/api/actions${projectQuery(project)}`,
201
+ {
202
+ method: 'POST',
203
+ headers: { 'Content-Type': 'application/json' },
204
+ body: JSON.stringify(body)
205
+ }
206
+ );
207
+
208
+ if (result.ok) {
209
+ setActionResult(`${action} completed`);
210
+ await refreshStatus();
211
+ await refreshDetail(selectedFeatureId);
212
+ } else {
213
+ setActionResult(result.error?.message ?? `${action} failed`);
214
+ }
215
+ },
216
+ [approvalToken, project, refreshDetail, refreshStatus, reviewMessage, selectedFeatureId]
217
+ );
218
+
219
+ const triggerCheckout = useCallback(
220
+ async (action: 'checkout' | 'restore') => {
221
+ if (!selectedFeatureId) {
222
+ return;
223
+ }
224
+ const result = await jsonFetch<{ ok: boolean; error?: { message?: string } }>(
225
+ `/api/features/${selectedFeatureId}/checkout${projectQuery(project)}`,
226
+ {
227
+ method: 'POST',
228
+ headers: { 'Content-Type': 'application/json' },
229
+ body: JSON.stringify({ action, stash_changes: true })
230
+ }
231
+ );
232
+ setActionResult(result.ok ? `${action} completed` : result.error?.message ?? `${action} failed`);
233
+ },
234
+ [project, selectedFeatureId]
235
+ );
236
+
237
+ return (
238
+ <div style={{ maxWidth: 1600, margin: '0 auto', padding: 24 }}>
239
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16, alignItems: 'center' }}>
240
+ <div>
241
+ <h1 style={{ fontSize: 28, fontWeight: 700, color: '#f8fafc' }}>AOP Dashboard</h1>
242
+ <p style={{ fontSize: 12, color: '#94a3b8' }}>
243
+ {connected ? 'Live updates connected' : 'Disconnected from event stream'}
244
+ </p>
245
+ </div>
246
+ <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
247
+ <label style={{ fontSize: 12, color: '#94a3b8' }}>Project</label>
248
+ <select
249
+ value={project}
250
+ onChange={(event) => {
251
+ const next = event.target.value;
252
+ setProject(next);
253
+ const url = new URL(window.location.href);
254
+ if (next) {
255
+ url.searchParams.set('project', next);
256
+ } else {
257
+ url.searchParams.delete('project');
258
+ }
259
+ window.history.replaceState(null, '', url.toString());
260
+ }}
261
+ style={{ background: '#0f172a', color: '#e2e8f0', border: '1px solid #334155', padding: '6px 8px' }}
262
+ >
263
+ <option value="">Current Repo</option>
264
+ {projects.map((entry) => (
265
+ <option key={entry.name} value={entry.name}>
266
+ {entry.name}
267
+ </option>
268
+ ))}
269
+ </select>
270
+ <div style={{ width: 10, height: 10, borderRadius: '50%', background: connected ? '#10b981' : '#ef4444' }} />
271
+ </div>
272
+ </div>
273
+
274
+ <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 16 }}>
275
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(220px, 1fr))', gap: 12 }}>
276
+ {COLUMN_ORDER.map((column) => {
277
+ const features = featuresByPhase.get(column.phase) ?? [];
278
+ return (
279
+ <section key={column.title} style={{ background: '#1e293b', borderRadius: 12, padding: 12 }}>
280
+ <header style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
281
+ <strong style={{ color: '#f1f5f9', fontSize: 13 }}>{column.title}</strong>
282
+ <span style={{ color: '#94a3b8', fontSize: 11 }}>{features.length}</span>
283
+ </header>
284
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
285
+ {features.map((feature) => (
286
+ <button
287
+ key={feature.feature_id}
288
+ onClick={() => setSelectedFeatureId(feature.feature_id)}
289
+ style={{
290
+ textAlign: 'left',
291
+ borderRadius: 10,
292
+ border: selectedFeatureId === feature.feature_id ? '1px solid #e2e8f0' : '1px solid #334155',
293
+ background: '#0f172a',
294
+ padding: 10,
295
+ borderLeft: `4px solid ${columnColor(feature.phase)}`,
296
+ cursor: 'pointer'
297
+ }}
298
+ >
299
+ <div style={{ color: '#f8fafc', fontSize: 13, marginBottom: 4 }}>{feature.feature_id}</div>
300
+ <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
301
+ <StatusBadge status={feature.status} />
302
+ <ActivityBadge activity={feature.activity_state} />
303
+ </div>
304
+ </button>
305
+ ))}
306
+ </div>
307
+ </section>
308
+ );
309
+ })}
310
+ </div>
311
+
312
+ <aside style={{ background: '#1e293b', borderRadius: 12, padding: 14 }}>
313
+ {!detail ? (
314
+ <div style={{ color: '#94a3b8', fontSize: 13 }}>Select a feature to inspect details, evidence, and review actions.</div>
315
+ ) : (
316
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
317
+ <h2 style={{ color: '#f8fafc', fontSize: 16 }}>{detail.feature.feature_id}</h2>
318
+ <StatusBadge status={detail.feature.status} />
319
+ <div style={{ color: '#cbd5e1', fontSize: 12 }}>
320
+ Branch: {detail.feature.branch ?? 'unknown'}
321
+ </div>
322
+ <div style={{ color: '#cbd5e1', fontSize: 12 }}>
323
+ Activity: {detail.feature.activity_state ?? 'unknown'}
324
+ </div>
325
+ <div style={{ color: '#cbd5e1', fontSize: 12 }}>
326
+ Plan version: {detail.plan?.plan_version != null ? String(detail.plan.plan_version) : 'n/a'}
327
+ </div>
328
+ {detail.feature.pr ? (
329
+ <div style={{ color: '#cbd5e1', fontSize: 12 }}>
330
+ PR: #{detail.feature.pr.number ?? 'n/a'} | CI: {detail.feature.pr.ci_status ?? 'none'} | Review:{' '}
331
+ {detail.feature.pr.review_decision ?? 'none'} | Merge Score: {detail.feature.pr.merge_score ?? 0}
332
+ </div>
333
+ ) : null}
334
+
335
+ <textarea
336
+ value={reviewMessage}
337
+ onChange={(event) => setReviewMessage(event.target.value)}
338
+ placeholder="Review feedback"
339
+ rows={4}
340
+ style={{ background: '#0f172a', color: '#e2e8f0', border: '1px solid #334155', padding: 8, borderRadius: 8 }}
341
+ />
342
+ <input
343
+ value={approvalToken}
344
+ onChange={(event) => setApprovalToken(event.target.value)}
345
+ placeholder="approval token"
346
+ style={{ background: '#0f172a', color: '#e2e8f0', border: '1px solid #334155', padding: 8, borderRadius: 8 }}
347
+ />
348
+
349
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
350
+ <button onClick={() => void triggerReview('review.approve')}>Approve</button>
351
+ <button onClick={() => void triggerReview('review.deny')}>Deny</button>
352
+ <button onClick={() => void triggerReview('review.request_changes')}>Request Changes</button>
353
+ <button onClick={() => void triggerCheckout('checkout')}>Checkout Branch</button>
354
+ <button onClick={() => void triggerCheckout('restore')}>Restore Branch</button>
355
+ </div>
356
+
357
+ {actionResult ? <div style={{ color: '#93c5fd', fontSize: 12 }}>{actionResult}</div> : null}
358
+
359
+ <details>
360
+ <summary style={{ cursor: 'pointer', color: '#e2e8f0' }}>Diff</summary>
361
+ <pre
362
+ style={{
363
+ marginTop: 8,
364
+ maxHeight: 180,
365
+ overflow: 'auto',
366
+ background: '#0f172a',
367
+ border: '1px solid #334155',
368
+ padding: 8
369
+ }}
370
+ >
371
+ {detail.diff || 'No diff artifact available.'}
372
+ </pre>
373
+ </details>
374
+
375
+ <details>
376
+ <summary style={{ cursor: 'pointer', color: '#e2e8f0' }}>Evidence</summary>
377
+ <ul style={{ marginTop: 8, paddingLeft: 18 }}>
378
+ {detail.evidence.map((artifact) => (
379
+ <li key={artifact.name} style={{ marginBottom: 4 }}>
380
+ <a href={`/api/features/${detail.feature.feature_id}/evidence/${artifact.name}${projectQuery(project)}`}>
381
+ {artifact.name}
382
+ </a>
383
+ </li>
384
+ ))}
385
+ </ul>
386
+ </details>
387
+ </div>
388
+ )}
389
+ </aside>
390
+ </div>
391
+ </div>
392
+ );
393
+ }
@@ -0,0 +1,244 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import YAML from 'yaml';
4
+ import { getProjectByName, readMultiProjectConfig } from './multi-project-config';
5
+ import type {
6
+ DashboardStatusPayload,
7
+ EvidenceArtifact,
8
+ FeatureDetail,
9
+ FeaturesIndex,
10
+ FeatureSummary
11
+ } from './types.js';
12
+
13
+ const AOP_ROOT = process.env.AOP_ROOT ?? process.cwd();
14
+
15
+ interface ParsedFrontMatter {
16
+ frontMatter: Record<string, unknown>;
17
+ body: string;
18
+ }
19
+
20
+ const STATUS_TO_PHASE: Record<string, FeatureSummary['phase']> = {
21
+ planning: 'planning',
22
+ building: 'building',
23
+ qa: 'qa',
24
+ ready_to_merge: 'ready_to_merge',
25
+ merged: 'merged',
26
+ blocked: 'blocked',
27
+ failed: 'blocked',
28
+ paused_budget: 'blocked'
29
+ };
30
+
31
+ function parseFrontMatter(raw: string): ParsedFrontMatter {
32
+ const normalized = raw.replace(/\r\n/g, '\n');
33
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?/);
34
+ if (!match) {
35
+ return { frontMatter: {}, body: normalized };
36
+ }
37
+ const frontMatter = (YAML.parse(match[1]) ?? {}) as Record<string, unknown>;
38
+ return { frontMatter, body: normalized.slice(match[0].length) };
39
+ }
40
+
41
+ function normalizeFeatureSummary(
42
+ featureId: string,
43
+ frontMatter: Record<string, unknown>,
44
+ index: FeaturesIndex,
45
+ repoRoot = AOP_ROOT
46
+ ): FeatureSummary {
47
+ const status = typeof frontMatter.status === 'string' ? frontMatter.status : 'unknown';
48
+ const blockedQueue = Array.isArray(index.blocked_queue) ? index.blocked_queue.map((entry) => entry.feature_id) : [];
49
+ const phase =
50
+ index.merged?.includes(featureId)
51
+ ? 'merged'
52
+ : index.blocked?.includes(featureId) || blockedQueue.includes(featureId)
53
+ ? 'blocked'
54
+ : STATUS_TO_PHASE[status] ?? (index.active?.includes(featureId) ? 'planning' : 'unknown');
55
+ const branchRaw =
56
+ typeof frontMatter.worktree_branch === 'string'
57
+ ? frontMatter.worktree_branch
58
+ : typeof frontMatter.branch === 'string'
59
+ ? frontMatter.branch
60
+ : null;
61
+ return {
62
+ id: featureId,
63
+ feature_id: featureId,
64
+ status,
65
+ phase,
66
+ plan_version: typeof frontMatter.plan_version === 'number' ? frontMatter.plan_version : undefined,
67
+ locks_held: Array.isArray(frontMatter.locks) ? (frontMatter.locks as string[]) : undefined,
68
+ gate_profile: typeof frontMatter.gate_profile === 'string' ? frontMatter.gate_profile : undefined,
69
+ branch: branchRaw,
70
+ worktree_path:
71
+ typeof frontMatter.worktree_path === 'string'
72
+ ? frontMatter.worktree_path
73
+ : path.join(repoRoot, '.worktrees', featureId),
74
+ last_updated: typeof frontMatter.last_updated === 'string' ? frontMatter.last_updated : undefined,
75
+ status_reason: typeof frontMatter.status_reason === 'string' ? frontMatter.status_reason : undefined,
76
+ activity_state:
77
+ typeof frontMatter.activity_state === 'string' ? (frontMatter.activity_state as FeatureSummary['activity_state']) : undefined,
78
+ activity_last_event_at:
79
+ typeof frontMatter.activity_last_event_at === 'string' ? frontMatter.activity_last_event_at : undefined,
80
+ activity_detected_via:
81
+ typeof frontMatter.activity_detected_via === 'string'
82
+ ? (frontMatter.activity_detected_via as FeatureSummary['activity_detected_via'])
83
+ : undefined,
84
+ pr:
85
+ frontMatter.pr && typeof frontMatter.pr === 'object'
86
+ ? (frontMatter.pr as FeatureSummary['pr'])
87
+ : null
88
+ };
89
+ }
90
+
91
+ export function getAopRoot(): string {
92
+ return AOP_ROOT;
93
+ }
94
+
95
+ export async function resolveProjectRoot(projectName?: string | null): Promise<string> {
96
+ const normalized = typeof projectName === 'string' ? projectName.trim() : '';
97
+ if (!normalized) {
98
+ return AOP_ROOT;
99
+ }
100
+
101
+ const config = await readMultiProjectConfig(AOP_ROOT);
102
+ if (!config) {
103
+ return AOP_ROOT;
104
+ }
105
+ const project = getProjectByName(config, normalized);
106
+ return project?.path ?? AOP_ROOT;
107
+ }
108
+
109
+ export function getFeatureRoot(featureId: string, repoRoot = AOP_ROOT): string {
110
+ return path.join(repoRoot, '.aop', 'features', featureId);
111
+ }
112
+
113
+ export async function readFeaturesIndex(repoRoot = AOP_ROOT): Promise<FeaturesIndex> {
114
+ const indexPath = path.join(repoRoot, '.aop', 'features', 'index.json');
115
+ try {
116
+ const raw = await readFile(indexPath, 'utf-8');
117
+ return JSON.parse(raw) as FeaturesIndex;
118
+ } catch {
119
+ return { active: [], blocked: [], merged: [], blocked_queue: [] };
120
+ }
121
+ }
122
+
123
+ export async function readFeatureState(featureId: string, repoRoot = AOP_ROOT): Promise<FeatureSummary | null> {
124
+ const statePath = path.join(getFeatureRoot(featureId, repoRoot), 'state.md');
125
+ try {
126
+ const raw = await readFile(statePath, 'utf-8');
127
+ const { frontMatter } = parseFrontMatter(raw);
128
+ return normalizeFeatureSummary(featureId, frontMatter, { active: [], blocked: [], merged: [], blocked_queue: [] }, repoRoot);
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ export async function readFeaturePlan(featureId: string, repoRoot = AOP_ROOT): Promise<Record<string, unknown> | null> {
135
+ const planPath = path.join(getFeatureRoot(featureId, repoRoot), 'plan.json');
136
+ try {
137
+ const raw = await readFile(planPath, 'utf-8');
138
+ return JSON.parse(raw) as Record<string, unknown>;
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ export async function readFeatureDiff(featureId: string, repoRoot = AOP_ROOT): Promise<string> {
145
+ const diffPath = path.join(getFeatureRoot(featureId, repoRoot), 'evidence', 'diff.patch');
146
+ try {
147
+ return await readFile(diffPath, 'utf-8');
148
+ } catch {
149
+ return '';
150
+ }
151
+ }
152
+
153
+ export async function listEvidenceArtifacts(featureId: string, repoRoot = AOP_ROOT): Promise<EvidenceArtifact[]> {
154
+ const evidenceDir = path.join(getFeatureRoot(featureId, repoRoot), 'evidence');
155
+ try {
156
+ const files = await readdir(evidenceDir);
157
+ const artifacts: EvidenceArtifact[] = [];
158
+ for (const file of files.sort((a, b) => a.localeCompare(b))) {
159
+ const fullPath = path.join(evidenceDir, file);
160
+ const fileStat = await stat(fullPath);
161
+ if (!fileStat.isFile()) {
162
+ continue;
163
+ }
164
+ artifacts.push({
165
+ name: file,
166
+ path: fullPath,
167
+ updated_at: fileStat.mtime.toISOString()
168
+ });
169
+ }
170
+ return artifacts;
171
+ } catch {
172
+ return [];
173
+ }
174
+ }
175
+
176
+ export async function readEvidenceArtifact(
177
+ featureId: string,
178
+ artifactName: string,
179
+ repoRoot = AOP_ROOT
180
+ ): Promise<string | null> {
181
+ const sanitized = path.basename(artifactName);
182
+ if (sanitized !== artifactName) {
183
+ return null;
184
+ }
185
+ const artifactPath = path.join(getFeatureRoot(featureId, repoRoot), 'evidence', sanitized);
186
+ try {
187
+ return await readFile(artifactPath, 'utf-8');
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ export async function readDashboardStatus(repoRoot = AOP_ROOT): Promise<DashboardStatusPayload> {
194
+ const index = await readFeaturesIndex(repoRoot);
195
+ const allFeatureIds = new Set<string>([
196
+ ...(index.active ?? []),
197
+ ...(index.blocked ?? []),
198
+ ...(index.merged ?? []),
199
+ ...((index.blocked_queue ?? []).map((entry) => entry.feature_id))
200
+ ]);
201
+
202
+ const features: FeatureSummary[] = [];
203
+ for (const featureId of [...allFeatureIds].sort((a, b) => a.localeCompare(b))) {
204
+ const statePath = path.join(getFeatureRoot(featureId, repoRoot), 'state.md');
205
+ try {
206
+ const raw = await readFile(statePath, 'utf-8');
207
+ const { frontMatter } = parseFrontMatter(raw);
208
+ features.push(normalizeFeatureSummary(featureId, frontMatter, index, repoRoot));
209
+ } catch {
210
+ const blockedQueue = Array.isArray(index.blocked_queue) ? index.blocked_queue.map((entry) => entry.feature_id) : [];
211
+ features.push({
212
+ id: featureId,
213
+ feature_id: featureId,
214
+ status: index.merged?.includes(featureId) ? 'merged' : index.blocked?.includes(featureId) ? 'blocked' : 'unknown',
215
+ phase:
216
+ index.merged?.includes(featureId)
217
+ ? 'merged'
218
+ : index.blocked?.includes(featureId) || blockedQueue.includes(featureId)
219
+ ? 'blocked'
220
+ : index.active?.includes(featureId)
221
+ ? 'planning'
222
+ : 'unknown',
223
+ branch: null,
224
+ worktree_path: null,
225
+ pr: null
226
+ });
227
+ }
228
+ }
229
+
230
+ return { index, features };
231
+ }
232
+
233
+ export async function readFeatureDetail(featureId: string, repoRoot = AOP_ROOT): Promise<FeatureDetail | null> {
234
+ const feature = await readFeatureState(featureId, repoRoot);
235
+ if (!feature) {
236
+ return null;
237
+ }
238
+ return {
239
+ feature,
240
+ plan: await readFeaturePlan(featureId, repoRoot),
241
+ diff: await readFeatureDiff(featureId, repoRoot),
242
+ evidence: await listEvidenceArtifacts(featureId, repoRoot)
243
+ };
244
+ }