@agile-vibe-coding/avc 0.1.0 → 0.2.3

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 (290) hide show
  1. package/README.md +2 -0
  2. package/cli/agent-loader.js +21 -0
  3. package/cli/agents/agent-selector.md +129 -0
  4. package/cli/agents/architecture-recommender.md +418 -0
  5. package/cli/agents/database-deep-dive.md +470 -0
  6. package/cli/agents/database-recommender.md +634 -0
  7. package/cli/agents/doc-distributor.md +176 -0
  8. package/cli/agents/documentation-updater.md +203 -0
  9. package/cli/agents/epic-story-decomposer.md +280 -0
  10. package/cli/agents/feature-context-generator.md +91 -0
  11. package/cli/agents/gap-checker-epic.md +52 -0
  12. package/cli/agents/impact-checker-story.md +51 -0
  13. package/cli/agents/migration-guide-generator.md +305 -0
  14. package/cli/agents/mission-scope-generator.md +79 -0
  15. package/cli/agents/mission-scope-validator.md +112 -0
  16. package/cli/agents/project-context-extractor.md +107 -0
  17. package/cli/agents/project-documentation-creator.json +226 -0
  18. package/cli/agents/project-documentation-creator.md +595 -0
  19. package/cli/agents/question-prefiller.md +269 -0
  20. package/cli/agents/refiner-epic.md +39 -0
  21. package/cli/agents/refiner-story.md +42 -0
  22. package/cli/agents/solver-epic-api.json +15 -0
  23. package/cli/agents/solver-epic-api.md +39 -0
  24. package/cli/agents/solver-epic-backend.json +15 -0
  25. package/cli/agents/solver-epic-backend.md +39 -0
  26. package/cli/agents/solver-epic-cloud.json +15 -0
  27. package/cli/agents/solver-epic-cloud.md +39 -0
  28. package/cli/agents/solver-epic-data.json +15 -0
  29. package/cli/agents/solver-epic-data.md +39 -0
  30. package/cli/agents/solver-epic-database.json +15 -0
  31. package/cli/agents/solver-epic-database.md +39 -0
  32. package/cli/agents/solver-epic-developer.json +15 -0
  33. package/cli/agents/solver-epic-developer.md +39 -0
  34. package/cli/agents/solver-epic-devops.json +15 -0
  35. package/cli/agents/solver-epic-devops.md +39 -0
  36. package/cli/agents/solver-epic-frontend.json +15 -0
  37. package/cli/agents/solver-epic-frontend.md +39 -0
  38. package/cli/agents/solver-epic-mobile.json +15 -0
  39. package/cli/agents/solver-epic-mobile.md +39 -0
  40. package/cli/agents/solver-epic-qa.json +15 -0
  41. package/cli/agents/solver-epic-qa.md +39 -0
  42. package/cli/agents/solver-epic-security.json +15 -0
  43. package/cli/agents/solver-epic-security.md +39 -0
  44. package/cli/agents/solver-epic-solution-architect.json +15 -0
  45. package/cli/agents/solver-epic-solution-architect.md +39 -0
  46. package/cli/agents/solver-epic-test-architect.json +15 -0
  47. package/cli/agents/solver-epic-test-architect.md +39 -0
  48. package/cli/agents/solver-epic-ui.json +15 -0
  49. package/cli/agents/solver-epic-ui.md +39 -0
  50. package/cli/agents/solver-epic-ux.json +15 -0
  51. package/cli/agents/solver-epic-ux.md +39 -0
  52. package/cli/agents/solver-story-api.json +15 -0
  53. package/cli/agents/solver-story-api.md +39 -0
  54. package/cli/agents/solver-story-backend.json +15 -0
  55. package/cli/agents/solver-story-backend.md +39 -0
  56. package/cli/agents/solver-story-cloud.json +15 -0
  57. package/cli/agents/solver-story-cloud.md +39 -0
  58. package/cli/agents/solver-story-data.json +15 -0
  59. package/cli/agents/solver-story-data.md +39 -0
  60. package/cli/agents/solver-story-database.json +15 -0
  61. package/cli/agents/solver-story-database.md +39 -0
  62. package/cli/agents/solver-story-developer.json +15 -0
  63. package/cli/agents/solver-story-developer.md +39 -0
  64. package/cli/agents/solver-story-devops.json +15 -0
  65. package/cli/agents/solver-story-devops.md +39 -0
  66. package/cli/agents/solver-story-frontend.json +15 -0
  67. package/cli/agents/solver-story-frontend.md +39 -0
  68. package/cli/agents/solver-story-mobile.json +15 -0
  69. package/cli/agents/solver-story-mobile.md +39 -0
  70. package/cli/agents/solver-story-qa.json +15 -0
  71. package/cli/agents/solver-story-qa.md +39 -0
  72. package/cli/agents/solver-story-security.json +15 -0
  73. package/cli/agents/solver-story-security.md +39 -0
  74. package/cli/agents/solver-story-solution-architect.json +15 -0
  75. package/cli/agents/solver-story-solution-architect.md +39 -0
  76. package/cli/agents/solver-story-test-architect.json +15 -0
  77. package/cli/agents/solver-story-test-architect.md +39 -0
  78. package/cli/agents/solver-story-ui.json +15 -0
  79. package/cli/agents/solver-story-ui.md +39 -0
  80. package/cli/agents/solver-story-ux.json +15 -0
  81. package/cli/agents/solver-story-ux.md +39 -0
  82. package/cli/agents/story-doc-enricher.md +133 -0
  83. package/cli/agents/suggestion-business-analyst.md +88 -0
  84. package/cli/agents/suggestion-deployment-architect.md +263 -0
  85. package/cli/agents/suggestion-product-manager.md +129 -0
  86. package/cli/agents/suggestion-security-specialist.md +156 -0
  87. package/cli/agents/suggestion-technical-architect.md +269 -0
  88. package/cli/agents/suggestion-ux-researcher.md +93 -0
  89. package/cli/agents/task-subtask-decomposer.md +188 -0
  90. package/cli/agents/validator-documentation.json +152 -0
  91. package/cli/agents/validator-documentation.md +453 -0
  92. package/cli/agents/validator-epic-api.json +93 -0
  93. package/cli/agents/validator-epic-api.md +137 -0
  94. package/cli/agents/validator-epic-backend.json +93 -0
  95. package/cli/agents/validator-epic-backend.md +130 -0
  96. package/cli/agents/validator-epic-cloud.json +93 -0
  97. package/cli/agents/validator-epic-cloud.md +137 -0
  98. package/cli/agents/validator-epic-data.json +93 -0
  99. package/cli/agents/validator-epic-data.md +130 -0
  100. package/cli/agents/validator-epic-database.json +93 -0
  101. package/cli/agents/validator-epic-database.md +137 -0
  102. package/cli/agents/validator-epic-developer.json +74 -0
  103. package/cli/agents/validator-epic-developer.md +153 -0
  104. package/cli/agents/validator-epic-devops.json +74 -0
  105. package/cli/agents/validator-epic-devops.md +153 -0
  106. package/cli/agents/validator-epic-frontend.json +74 -0
  107. package/cli/agents/validator-epic-frontend.md +153 -0
  108. package/cli/agents/validator-epic-mobile.json +93 -0
  109. package/cli/agents/validator-epic-mobile.md +130 -0
  110. package/cli/agents/validator-epic-qa.json +93 -0
  111. package/cli/agents/validator-epic-qa.md +130 -0
  112. package/cli/agents/validator-epic-security.json +74 -0
  113. package/cli/agents/validator-epic-security.md +154 -0
  114. package/cli/agents/validator-epic-solution-architect.json +74 -0
  115. package/cli/agents/validator-epic-solution-architect.md +156 -0
  116. package/cli/agents/validator-epic-test-architect.json +93 -0
  117. package/cli/agents/validator-epic-test-architect.md +130 -0
  118. package/cli/agents/validator-epic-ui.json +93 -0
  119. package/cli/agents/validator-epic-ui.md +130 -0
  120. package/cli/agents/validator-epic-ux.json +93 -0
  121. package/cli/agents/validator-epic-ux.md +130 -0
  122. package/cli/agents/validator-selector.md +211 -0
  123. package/cli/agents/validator-story-api.json +104 -0
  124. package/cli/agents/validator-story-api.md +152 -0
  125. package/cli/agents/validator-story-backend.json +104 -0
  126. package/cli/agents/validator-story-backend.md +152 -0
  127. package/cli/agents/validator-story-cloud.json +104 -0
  128. package/cli/agents/validator-story-cloud.md +152 -0
  129. package/cli/agents/validator-story-data.json +104 -0
  130. package/cli/agents/validator-story-data.md +152 -0
  131. package/cli/agents/validator-story-database.json +104 -0
  132. package/cli/agents/validator-story-database.md +152 -0
  133. package/cli/agents/validator-story-developer.json +104 -0
  134. package/cli/agents/validator-story-developer.md +152 -0
  135. package/cli/agents/validator-story-devops.json +104 -0
  136. package/cli/agents/validator-story-devops.md +152 -0
  137. package/cli/agents/validator-story-frontend.json +104 -0
  138. package/cli/agents/validator-story-frontend.md +152 -0
  139. package/cli/agents/validator-story-mobile.json +104 -0
  140. package/cli/agents/validator-story-mobile.md +152 -0
  141. package/cli/agents/validator-story-qa.json +104 -0
  142. package/cli/agents/validator-story-qa.md +152 -0
  143. package/cli/agents/validator-story-security.json +104 -0
  144. package/cli/agents/validator-story-security.md +152 -0
  145. package/cli/agents/validator-story-solution-architect.json +104 -0
  146. package/cli/agents/validator-story-solution-architect.md +152 -0
  147. package/cli/agents/validator-story-test-architect.json +104 -0
  148. package/cli/agents/validator-story-test-architect.md +152 -0
  149. package/cli/agents/validator-story-ui.json +104 -0
  150. package/cli/agents/validator-story-ui.md +152 -0
  151. package/cli/agents/validator-story-ux.json +104 -0
  152. package/cli/agents/validator-story-ux.md +152 -0
  153. package/cli/ansi-colors.js +21 -0
  154. package/cli/build-docs.js +298 -0
  155. package/cli/ceremony-history.js +369 -0
  156. package/cli/command-logger.js +245 -0
  157. package/cli/components/static-output.js +63 -0
  158. package/cli/console-output-manager.js +94 -0
  159. package/cli/docs-sync.js +306 -0
  160. package/cli/epic-story-validator.js +1174 -0
  161. package/cli/evaluation-prompts.js +1008 -0
  162. package/cli/execution-context.js +195 -0
  163. package/cli/generate-summary-table.js +340 -0
  164. package/cli/index.js +3 -25
  165. package/cli/init-model-config.js +697 -0
  166. package/cli/init.js +1765 -100
  167. package/cli/kanban-server-manager.js +228 -0
  168. package/cli/llm-claude.js +109 -0
  169. package/cli/llm-gemini.js +115 -0
  170. package/cli/llm-mock.js +233 -0
  171. package/cli/llm-openai.js +233 -0
  172. package/cli/llm-provider.js +300 -0
  173. package/cli/llm-token-limits.js +102 -0
  174. package/cli/llm-verifier.js +454 -0
  175. package/cli/logger.js +32 -5
  176. package/cli/message-constants.js +58 -0
  177. package/cli/message-manager.js +334 -0
  178. package/cli/message-types.js +96 -0
  179. package/cli/messaging-api.js +297 -0
  180. package/cli/model-pricing.js +169 -0
  181. package/cli/model-query-engine.js +468 -0
  182. package/cli/model-recommendation-analyzer.js +495 -0
  183. package/cli/model-selector.js +269 -0
  184. package/cli/output-buffer.js +107 -0
  185. package/cli/process-manager.js +332 -0
  186. package/cli/repl-ink.js +5840 -504
  187. package/cli/repl-old.js +4 -4
  188. package/cli/seed-processor.js +792 -0
  189. package/cli/sprint-planning-processor.js +1813 -0
  190. package/cli/template-processor.js +2306 -108
  191. package/cli/templates/project.md +25 -8
  192. package/cli/templates/vitepress-config.mts.template +34 -0
  193. package/cli/token-tracker.js +520 -0
  194. package/cli/tools/generate-story-validators.js +317 -0
  195. package/cli/tools/generate-validators.js +669 -0
  196. package/cli/update-checker.js +19 -17
  197. package/cli/update-notifier.js +4 -4
  198. package/cli/validation-router.js +605 -0
  199. package/cli/verification-tracker.js +563 -0
  200. package/kanban/README.md +386 -0
  201. package/kanban/client/README.md +205 -0
  202. package/kanban/client/components.json +20 -0
  203. package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
  204. package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
  205. package/kanban/client/dist/index.html +16 -0
  206. package/kanban/client/dist/vite.svg +1 -0
  207. package/kanban/client/index.html +15 -0
  208. package/kanban/client/package-lock.json +9442 -0
  209. package/kanban/client/package.json +44 -0
  210. package/kanban/client/postcss.config.js +6 -0
  211. package/kanban/client/public/vite.svg +1 -0
  212. package/kanban/client/src/App.jsx +622 -0
  213. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  214. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
  215. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
  216. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
  217. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  218. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
  219. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
  220. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  221. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
  222. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  223. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  224. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  225. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
  226. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
  227. package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
  228. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  229. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  230. package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
  231. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  232. package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
  233. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  234. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
  235. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  236. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  237. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  238. package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
  239. package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
  240. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
  241. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
  242. package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
  243. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  244. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  245. package/kanban/client/src/components/stats/CostModal.jsx +353 -0
  246. package/kanban/client/src/components/ui/badge.jsx +27 -0
  247. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  248. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  249. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  250. package/kanban/client/src/hooks/useGrouping.js +118 -0
  251. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  252. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  253. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  254. package/kanban/client/src/lib/api.js +401 -0
  255. package/kanban/client/src/lib/status-grouping.js +144 -0
  256. package/kanban/client/src/lib/utils.js +11 -0
  257. package/kanban/client/src/main.jsx +10 -0
  258. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  259. package/kanban/client/src/store/ceremonyStore.js +172 -0
  260. package/kanban/client/src/store/filterStore.js +201 -0
  261. package/kanban/client/src/store/kanbanStore.js +115 -0
  262. package/kanban/client/src/store/processStore.js +65 -0
  263. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  264. package/kanban/client/src/styles/globals.css +59 -0
  265. package/kanban/client/tailwind.config.js +77 -0
  266. package/kanban/client/vite.config.js +28 -0
  267. package/kanban/client/vitest.config.js +28 -0
  268. package/kanban/dev-start.sh +47 -0
  269. package/kanban/package.json +12 -0
  270. package/kanban/server/index.js +516 -0
  271. package/kanban/server/routes/ceremony.js +305 -0
  272. package/kanban/server/routes/costs.js +157 -0
  273. package/kanban/server/routes/processes.js +50 -0
  274. package/kanban/server/routes/settings.js +303 -0
  275. package/kanban/server/routes/websocket.js +276 -0
  276. package/kanban/server/routes/work-items.js +347 -0
  277. package/kanban/server/services/CeremonyService.js +1190 -0
  278. package/kanban/server/services/FileSystemScanner.js +95 -0
  279. package/kanban/server/services/FileWatcher.js +144 -0
  280. package/kanban/server/services/HierarchyBuilder.js +196 -0
  281. package/kanban/server/services/ProcessRegistry.js +122 -0
  282. package/kanban/server/services/WorkItemReader.js +123 -0
  283. package/kanban/server/services/WorkItemRefineService.js +510 -0
  284. package/kanban/server/start.js +49 -0
  285. package/kanban/server/utils/kanban-logger.js +132 -0
  286. package/kanban/server/utils/markdown.js +91 -0
  287. package/kanban/server/utils/status-grouping.js +107 -0
  288. package/kanban/server/workers/sponsor-call-worker.js +84 -0
  289. package/kanban/server/workers/sprint-planning-worker.js +130 -0
  290. package/package.json +34 -7
@@ -0,0 +1,946 @@
1
+ import { useState } from 'react';
2
+ import { X, Pencil } from 'lucide-react';
3
+ import { AgentEditorPopup } from '../settings/AgentEditorPopup';
4
+
5
+ // Human-readable labels for agent slugs
6
+ const AGENT_LABELS = {
7
+ // Sponsor Call
8
+ 'mission-scope-generator': 'Mission Scope Generator',
9
+ 'mission-scope-validator': 'Mission Scope Validator',
10
+ 'suggestion-ux-researcher': 'UX Researcher',
11
+ 'suggestion-product-manager': 'Product Manager',
12
+ 'suggestion-deployment-architect': 'Deployment Architect',
13
+ 'suggestion-technical-architect': 'Technical Architect',
14
+ 'suggestion-security-specialist': 'Security Specialist',
15
+ 'architecture-recommender': 'Architecture Recommender',
16
+ 'question-prefiller': 'Question Prefiller',
17
+ 'project-documentation-creator': 'Documentation Creator',
18
+ 'validator-documentation': 'Documentation Validator',
19
+ // Sprint Planning + Seed (shared)
20
+ 'doc-distributor': 'Doc Distributor',
21
+ 'feature-context-generator': 'Feature Context Generator',
22
+ // Sprint Planning
23
+ 'epic-story-decomposer': 'Epic/Story Decomposer',
24
+ 'project-context-extractor': 'Project Context Extractor',
25
+ 'agent-selector': 'Agent Selector',
26
+ 'story-doc-enricher': 'Story Doc Enricher',
27
+ 'validator-selector': 'Validator Selector',
28
+ 'validator-epic-solution-architect': 'Solution Architect (Epic)',
29
+ 'validator-epic-developer': 'Developer (Epic)',
30
+ 'validator-epic-security': 'Security (Epic)',
31
+ 'validator-epic-backend': 'Backend (Epic)',
32
+ 'validator-epic-frontend': 'Frontend (Epic)',
33
+ 'validator-epic-ux': 'UX (Epic)',
34
+ 'validator-story-developer': 'Developer (Story)',
35
+ 'validator-story-qa': 'QA (Story)',
36
+ 'validator-story-test-architect': 'Test Architect (Story)',
37
+ 'validator-story-solution-architect': 'Solution Architect (Story)',
38
+ 'validator-story-security': 'Security (Story)',
39
+ 'validator-story-backend': 'Backend (Story)',
40
+ 'validator-story-frontend': 'Frontend (Story)',
41
+ 'validator-story-ux': 'UX (Story)',
42
+ // Sprint Planning — Epic Solvers
43
+ 'solver-epic-solution-architect': 'Solver: Solution Architect (Epic)',
44
+ 'solver-epic-developer': 'Solver: Developer (Epic)',
45
+ 'solver-epic-security': 'Solver: Security (Epic)',
46
+ 'solver-epic-devops': 'Solver: DevOps (Epic)',
47
+ 'solver-epic-cloud': 'Solver: Cloud (Epic)',
48
+ 'solver-epic-backend': 'Solver: Backend (Epic)',
49
+ 'solver-epic-database': 'Solver: Database (Epic)',
50
+ 'solver-epic-api': 'Solver: API (Epic)',
51
+ 'solver-epic-frontend': 'Solver: Frontend (Epic)',
52
+ 'solver-epic-ui': 'Solver: UI (Epic)',
53
+ 'solver-epic-ux': 'Solver: UX (Epic)',
54
+ 'solver-epic-mobile': 'Solver: Mobile (Epic)',
55
+ 'solver-epic-data': 'Solver: Data (Epic)',
56
+ 'solver-epic-qa': 'Solver: QA (Epic)',
57
+ 'solver-epic-test-architect': 'Solver: Test Architect (Epic)',
58
+ // Sprint Planning — Story Solvers
59
+ 'solver-story-solution-architect': 'Solver: Solution Architect (Story)',
60
+ 'solver-story-developer': 'Solver: Developer (Story)',
61
+ 'solver-story-security': 'Solver: Security (Story)',
62
+ 'solver-story-devops': 'Solver: DevOps (Story)',
63
+ 'solver-story-cloud': 'Solver: Cloud (Story)',
64
+ 'solver-story-backend': 'Solver: Backend (Story)',
65
+ 'solver-story-database': 'Solver: Database (Story)',
66
+ 'solver-story-api': 'Solver: API (Story)',
67
+ 'solver-story-frontend': 'Solver: Frontend (Story)',
68
+ 'solver-story-ui': 'Solver: UI (Story)',
69
+ 'solver-story-ux': 'Solver: UX (Story)',
70
+ 'solver-story-mobile': 'Solver: Mobile (Story)',
71
+ 'solver-story-data': 'Solver: Data (Story)',
72
+ 'solver-story-qa': 'Solver: QA (Story)',
73
+ 'solver-story-test-architect': 'Solver: Test Architect (Story)',
74
+ };
75
+
76
+ // ── Step type config ──────────────────────────────────────────────────────────
77
+
78
+ const STEP_TYPE_CONFIG = {
79
+ generate: { label: 'Generate', cls: 'bg-blue-50 text-blue-700 border-blue-200' },
80
+ validate: { label: 'Validate', cls: 'bg-amber-50 text-amber-700 border-amber-200' },
81
+ refine: { label: 'Refine', cls: 'bg-orange-50 text-orange-700 border-orange-200' },
82
+ input: { label: 'User Input', cls: 'bg-purple-50 text-purple-700 border-purple-200' },
83
+ cross: { label: 'Cross-validate', cls: 'bg-indigo-50 text-indigo-700 border-indigo-200' },
84
+ output: { label: 'Write', cls: 'bg-emerald-50 text-emerald-700 border-emerald-200' },
85
+ read: { label: 'Read', cls: 'bg-sky-50 text-sky-700 border-sky-200' },
86
+ process: { label: 'Process', cls: 'bg-slate-50 text-slate-600 border-slate-200' },
87
+ };
88
+
89
+ const PHASE_COLOR_CONFIG = {
90
+ blue: { dot: 'bg-blue-500', label: 'text-blue-600' },
91
+ purple: { dot: 'bg-purple-500', label: 'text-purple-600' },
92
+ amber: { dot: 'bg-amber-500', label: 'text-amber-600' },
93
+ green: { dot: 'bg-green-500', label: 'text-green-600' },
94
+ emerald: { dot: 'bg-emerald-500', label: 'text-emerald-600' },
95
+ };
96
+
97
+ // Loop group color palettes
98
+ const LOOP_C = {
99
+ amber: {
100
+ border: 'border-amber-400',
101
+ bg: 'bg-amber-50',
102
+ hdr: 'bg-amber-100',
103
+ hdrBorder: 'border-amber-300',
104
+ text: 'text-amber-800',
105
+ subtext: 'text-amber-600',
106
+ line: 'bg-amber-400',
107
+ chip: 'bg-amber-200 text-amber-900',
108
+ arrow: 'text-amber-500',
109
+ condLine: 'bg-amber-300',
110
+ },
111
+ indigo: {
112
+ border: 'border-indigo-400',
113
+ bg: 'bg-indigo-50',
114
+ hdr: 'bg-indigo-100',
115
+ hdrBorder: 'border-indigo-300',
116
+ text: 'text-indigo-800',
117
+ subtext: 'text-indigo-600',
118
+ line: 'bg-indigo-400',
119
+ chip: 'bg-indigo-200 text-indigo-900',
120
+ arrow: 'text-indigo-500',
121
+ condLine: 'bg-indigo-300',
122
+ },
123
+ };
124
+
125
+ // ── Phase builders ────────────────────────────────────────────────────────────
126
+ //
127
+ // Step metadata fields (in addition to type / label / agent / agents):
128
+ // stageKey — key in ceremony.stages to read/write the model
129
+ // validationKey — 'top' (ceremony.validation.model) or area name ('documentation', 'context')
130
+ // sharedWith — string shown instead of a select; marks a secondary reference to the same key
131
+ // loopParamType — 'missionGen' | 'docContext' | 'crossValidation'
132
+ // loopParamReadOnly — true: show values read-only (params already editable in a sibling loop)
133
+
134
+ function buildSponsorCallPhases(ceremony, missionGenValidation) {
135
+ return [
136
+ {
137
+ id: 'mission',
138
+ label: 'Mission & Scope',
139
+ color: 'blue',
140
+ steps: [
141
+ {
142
+ type: 'generate',
143
+ label: 'Generate mission statement & initial scope',
144
+ model: ceremony.stages?.suggestions?.model,
145
+ stageKey: 'suggestions',
146
+ agent: 'mission-scope-generator',
147
+ },
148
+ {
149
+ type: 'loop-group',
150
+ loopParamType: 'missionGen',
151
+ loop: {
152
+ max: missionGenValidation?.maxIterations ?? 3,
153
+ threshold: missionGenValidation?.acceptanceThreshold ?? 95,
154
+ },
155
+ steps: [
156
+ {
157
+ type: 'validate',
158
+ label: 'Validate quality against acceptance threshold',
159
+ model: ceremony.validation?.model,
160
+ validationKey: 'top',
161
+ agent: 'mission-scope-validator',
162
+ },
163
+ {
164
+ type: 'refine',
165
+ label: 'Refine based on validation issues',
166
+ model: ceremony.stages?.suggestions?.model,
167
+ stageKey: 'suggestions',
168
+ sharedWith: 'Mission generator',
169
+ agent: 'mission-scope-generator',
170
+ },
171
+ ],
172
+ },
173
+ ],
174
+ },
175
+ {
176
+ id: 'questionnaire',
177
+ label: 'Questionnaire',
178
+ color: 'purple',
179
+ steps: [
180
+ {
181
+ type: 'input',
182
+ label: '5 project definition questions (user-provided or skipped)',
183
+ },
184
+ {
185
+ type: 'generate',
186
+ label: 'Auto-fill any skipped questions',
187
+ model: ceremony.stages?.suggestions?.model,
188
+ stageKey: 'suggestions',
189
+ sharedWith: 'Mission generator',
190
+ agents: [
191
+ { slug: 'suggestion-product-manager', note: 'fills Initial Scope' },
192
+ { slug: 'suggestion-ux-researcher', note: 'fills Target Users' },
193
+ { slug: 'suggestion-deployment-architect', note: 'fills Deployment Target' },
194
+ { slug: 'suggestion-technical-architect', note: 'fills Technical Considerations' },
195
+ { slug: 'suggestion-security-specialist', note: 'fills Security & Compliance' },
196
+ ],
197
+ },
198
+ {
199
+ type: 'generate',
200
+ label: 'Architecture recommendation',
201
+ model: ceremony.stages?.['architecture-recommendation']?.model,
202
+ stageKey: 'architecture-recommendation',
203
+ agent: 'architecture-recommender',
204
+ },
205
+ {
206
+ type: 'generate',
207
+ label: 'Pre-fill answers from architecture analysis',
208
+ model: ceremony.stages?.['question-prefilling']?.model,
209
+ stageKey: 'question-prefilling',
210
+ agent: 'question-prefiller',
211
+ },
212
+ ],
213
+ },
214
+ {
215
+ id: 'documentation',
216
+ label: 'Documentation',
217
+ color: 'amber',
218
+ steps: [
219
+ {
220
+ type: 'generate',
221
+ label: 'Generate project documentation (doc.md)',
222
+ model: ceremony.stages?.documentation?.model,
223
+ stageKey: 'documentation',
224
+ agent: 'project-documentation-creator',
225
+ },
226
+ {
227
+ type: 'loop-group',
228
+ loopParamType: 'docContext',
229
+ loop: {
230
+ max: ceremony.validation?.maxIterations ?? 100,
231
+ threshold: ceremony.validation?.acceptanceThreshold ?? 95,
232
+ },
233
+ steps: [
234
+ {
235
+ type: 'validate',
236
+ label: 'Validate documentation quality',
237
+ model: ceremony.validation?.documentation?.model ?? ceremony.validation?.model,
238
+ validationKey: 'documentation',
239
+ agent: 'validator-documentation',
240
+ },
241
+ {
242
+ type: 'refine',
243
+ label: 'Improve documentation based on issues',
244
+ model: ceremony.stages?.documentation?.model,
245
+ stageKey: 'documentation',
246
+ sharedWith: 'Documentation generator',
247
+ agent: 'project-documentation-creator',
248
+ },
249
+ ],
250
+ },
251
+ ],
252
+ },
253
+ {
254
+ id: 'output',
255
+ label: 'Output',
256
+ color: 'emerald',
257
+ steps: [
258
+ { type: 'output', label: '.avc/project/doc.md written', files: [{ name: 'project/doc.md', direction: 'out' }] },
259
+ ],
260
+ },
261
+ ];
262
+ }
263
+
264
+ function buildSprintPlanningPhases(ceremony) {
265
+ // Stages that aren't explicitly configured fall back to ceremony.defaultModel
266
+ const fallbackModel = ceremony.defaultModel;
267
+ const solverMaxIter = ceremony.stages?.solver?.maxIterations ?? 3;
268
+ const solverThreshold = ceremony.stages?.solver?.acceptanceThreshold ?? 95;
269
+
270
+ return [
271
+ {
272
+ id: 'scope',
273
+ label: 'Scope Collection',
274
+ color: 'blue',
275
+ steps: [
276
+ {
277
+ type: 'read',
278
+ label: 'Read project scope',
279
+ files: [{ name: 'project/doc.md', direction: 'in', note: '.avc/project/doc.md — scope section extracted and sent to decomposer' }],
280
+ },
281
+ { type: 'read', label: 'Analyse existing Epics & Stories (deduplication baseline)' },
282
+ ],
283
+ },
284
+ {
285
+ id: 'decomposition',
286
+ label: 'Decomposition',
287
+ color: 'purple',
288
+ steps: [
289
+ {
290
+ type: 'generate',
291
+ label: 'Decompose scope into Epics and Stories',
292
+ model: ceremony.stages?.decomposition?.model ?? fallbackModel,
293
+ stageKey: 'decomposition',
294
+ agent: 'epic-story-decomposer',
295
+ files: [
296
+ { name: 'project/doc.md', direction: 'in', note: 'scope text extracted from doc.md' },
297
+ ],
298
+ },
299
+ ],
300
+ },
301
+ {
302
+ id: 'validation',
303
+ label: 'Multi-Agent Validation (Iterative)',
304
+ color: 'amber',
305
+ // Per-validator iteration loop: each validator runs, if issues found a paired
306
+ // solver improves the epic/story, then the same validator re-validates.
307
+ // Validators run sequentially so each one sees improvements from previous pairs.
308
+ steps: [
309
+ {
310
+ type: 'generate',
311
+ label: 'Extract project context (once) — infers deployment type, tech stack, cloud/mobile presence to filter validators',
312
+ model: ceremony.stages?.validation?.model ?? fallbackModel,
313
+ stageKey: 'validation',
314
+ agent: 'project-context-extractor',
315
+ },
316
+ {
317
+ type: 'generate',
318
+ label: 'AI-select relevant validators per Epic / Story — excludes inapplicable roles (e.g. cloud if no cloud services). Runs once per Epic, once per Story.',
319
+ model: ceremony.stages?.validation?.model ?? fallbackModel,
320
+ stageKey: 'validation',
321
+ sharedWith: 'Validation',
322
+ agent: 'agent-selector',
323
+ },
324
+ {
325
+ type: 'loop-group',
326
+ loopParamType: 'sprintSolver',
327
+ loop: {
328
+ max: solverMaxIter,
329
+ threshold: solverThreshold,
330
+ },
331
+ steps: [
332
+ {
333
+ type: 'validate',
334
+ label: 'Validate each Epic with domain expert validators (sequential)',
335
+ model: ceremony.stages?.validation?.model ?? fallbackModel,
336
+ stageKey: 'validation',
337
+ agents: [
338
+ { slug: 'validator-epic-solution-architect', note: 'always runs' },
339
+ { slug: 'validator-epic-developer', note: 'always runs' },
340
+ { slug: 'validator-epic-security', note: 'always runs' },
341
+ { slug: 'validator-epic-backend', note: '+ domain validators selected per project' },
342
+ ],
343
+ },
344
+ {
345
+ type: 'refine',
346
+ label: 'Solve issues — improve Epic description, features, dependencies',
347
+ model: ceremony.stages?.solver?.model ?? fallbackModel,
348
+ stageKey: 'solver',
349
+ agents: [
350
+ { slug: 'solver-epic-solution-architect', note: 'paired with validator-epic-solution-architect' },
351
+ { slug: 'solver-epic-developer', note: 'paired with validator-epic-developer' },
352
+ { slug: 'solver-epic-security', note: 'paired with validator-epic-security' },
353
+ { slug: 'solver-epic-backend', note: 'paired with each domain validator' },
354
+ ],
355
+ },
356
+ ],
357
+ },
358
+ {
359
+ type: 'loop-group',
360
+ loopParamType: 'sprintSolver',
361
+ loopParamReadOnly: true,
362
+ loop: {
363
+ max: solverMaxIter,
364
+ threshold: solverThreshold,
365
+ },
366
+ steps: [
367
+ {
368
+ type: 'validate',
369
+ label: 'Validate each Story with domain expert validators (sequential)',
370
+ model: ceremony.stages?.validation?.model ?? fallbackModel,
371
+ stageKey: 'validation',
372
+ agents: [
373
+ { slug: 'validator-story-developer', note: 'always runs' },
374
+ { slug: 'validator-story-qa', note: 'always runs' },
375
+ { slug: 'validator-story-test-architect', note: 'always runs' },
376
+ { slug: 'validator-story-backend', note: '+ domain validators selected per project' },
377
+ ],
378
+ },
379
+ {
380
+ type: 'refine',
381
+ label: 'Solve issues — improve Story description, acceptance criteria, dependencies',
382
+ model: ceremony.stages?.solver?.model ?? fallbackModel,
383
+ stageKey: 'solver',
384
+ agents: [
385
+ { slug: 'solver-story-developer', note: 'paired with validator-story-developer' },
386
+ { slug: 'solver-story-qa', note: 'paired with validator-story-qa' },
387
+ { slug: 'solver-story-test-architect', note: 'paired with validator-story-test-architect' },
388
+ { slug: 'solver-story-backend', note: 'paired with each domain validator' },
389
+ ],
390
+ },
391
+ ],
392
+ },
393
+ ],
394
+ },
395
+ {
396
+ id: 'output',
397
+ label: 'Documentation & Output',
398
+ color: 'emerald',
399
+ steps: [
400
+ { type: 'process', label: 'Renumber hierarchy IDs' },
401
+ {
402
+ type: 'generate',
403
+ label: 'Distribute documentation — extract Epic-specific content from project/doc.md into each Epic\'s doc.md (moves content, doesn\'t copy)',
404
+ model: ceremony.stages?.['doc-distribution']?.model ?? fallbackModel,
405
+ stageKey: 'doc-distribution',
406
+ agent: 'doc-distributor',
407
+ files: [
408
+ { name: 'project/doc.md', direction: 'in', note: 'source — progressively lightened as epics extract their content' },
409
+ { name: '{epic}/doc.md', direction: 'out', note: 'receives content extracted from project doc' },
410
+ ],
411
+ },
412
+ {
413
+ type: 'generate',
414
+ label: 'Distribute documentation — extract Story-specific content from each Epic\'s doc.md into each Story\'s doc.md',
415
+ model: ceremony.stages?.['doc-distribution']?.model ?? fallbackModel,
416
+ stageKey: 'doc-distribution',
417
+ agent: 'doc-distributor',
418
+ files: [
419
+ { name: '{epic}/doc.md', direction: 'in', note: 'source — progressively lightened as stories extract their content' },
420
+ { name: '{story}/doc.md', direction: 'out', note: 'receives content extracted from epic doc' },
421
+ ],
422
+ },
423
+ {
424
+ type: 'generate',
425
+ label: 'Enrich Story docs with missing implementation detail — API contracts, error tables, DB fields, business rules. Runs once per Story.',
426
+ model: ceremony.stages?.enrichment?.model ?? fallbackModel,
427
+ stageKey: 'enrichment',
428
+ agent: 'story-doc-enricher',
429
+ files: [
430
+ { name: '{story}/doc.md', direction: 'inout', note: 'read then overwritten with enriched content' },
431
+ ],
432
+ },
433
+ {
434
+ type: 'output',
435
+ label: 'Write Epic & Story work.json files',
436
+ files: [
437
+ { name: '{epic}/work.json', direction: 'out' },
438
+ { name: '{story}/work.json', direction: 'out' },
439
+ ],
440
+ },
441
+ ],
442
+ },
443
+ ];
444
+ }
445
+ function buildSeedPhases(_c) { return null; }
446
+
447
+ const CEREMONY_WORKFLOWS = {
448
+ 'sponsor-call': buildSponsorCallPhases,
449
+ 'sprint-planning': (c) => buildSprintPlanningPhases(c),
450
+ 'seed': (_c) => buildSeedPhases(_c),
451
+ };
452
+
453
+ // ── Helpers ───────────────────────────────────────────────────────────────────
454
+
455
+ function resolveModelName(modelId, models) {
456
+ if (!modelId) return '—';
457
+ return models.find((m) => m.modelId === modelId)?.displayName || modelId;
458
+ }
459
+
460
+ // ── Inline model select ───────────────────────────────────────────────────────
461
+
462
+ function ModelSelectInline({ value, models, onChange }) {
463
+ const providers = [...new Set(models.map((m) => m.provider))];
464
+ return (
465
+ <select
466
+ value={value || ''}
467
+ onChange={(e) => onChange(e.target.value)}
468
+ onClick={(e) => e.stopPropagation()}
469
+ className="text-xs border border-slate-300 rounded px-1.5 py-0.5 bg-white text-slate-800 focus:outline-none focus:ring-1 focus:ring-blue-400 max-w-[200px]"
470
+ >
471
+ {!value && <option value="">— select —</option>}
472
+ {providers.map((p) => (
473
+ <optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
474
+ {models.filter((m) => m.provider === p).map((m) => (
475
+ <option key={m.modelId} value={m.modelId}>{m.displayName || m.modelId}</option>
476
+ ))}
477
+ </optgroup>
478
+ ))}
479
+ </select>
480
+ );
481
+ }
482
+
483
+ // ── Agent link ────────────────────────────────────────────────────────────────
484
+
485
+ function AgentLink({ slug, onOpen }) {
486
+ const label = AGENT_LABELS[slug] || slug;
487
+ return (
488
+ <button
489
+ type="button"
490
+ onClick={() => onOpen(slug)}
491
+ className="inline-flex items-center gap-1 text-[10px] font-medium text-blue-600 hover:text-blue-800 bg-blue-50 hover:bg-blue-100 border border-blue-200 hover:border-blue-300 px-1.5 py-0.5 rounded transition-colors whitespace-nowrap"
492
+ title={`Edit ${label} agent instructions`}
493
+ >
494
+ {label}
495
+ <Pencil className="w-2.5 h-2.5 flex-shrink-0" />
496
+ </button>
497
+ );
498
+ }
499
+
500
+ function AgentLinks({ step, onOpenAgent }) {
501
+ const items = step.agents
502
+ ? step.agents.map((a) => (typeof a === 'string' ? { slug: a, note: null } : a))
503
+ : step.agent
504
+ ? [{ slug: step.agent, note: null }]
505
+ : [];
506
+
507
+ if (items.length === 0) return null;
508
+
509
+ return (
510
+ <div className="mt-1.5">
511
+ <span className="text-[10px] text-slate-400 font-medium">Agent(s)</span>
512
+ <div className="mt-1 flex flex-col gap-1">
513
+ {items.map(({ slug, note }) => (
514
+ <div key={slug} className="flex items-center gap-2 flex-wrap">
515
+ <AgentLink slug={slug} onOpen={onOpenAgent} />
516
+ {note && <span className="text-[10px] text-slate-400 italic">{note}</span>}
517
+ </div>
518
+ ))}
519
+ </div>
520
+ </div>
521
+ );
522
+ }
523
+
524
+ // ── File tags ─────────────────────────────────────────────────────────────────
525
+ // Renders small ← filename (input) / → filename (output) badges on step cards.
526
+ // Steps can carry a `files` array: [{ name, direction: 'in'|'out', note? }]
527
+
528
+ function FileTags({ files, className = '' }) {
529
+ if (!files || files.length === 0) return null;
530
+ return (
531
+ <div className={`flex items-center gap-1 flex-wrap ${className}`}>
532
+ {files.map((f, i) => {
533
+ const isIn = f.direction === 'in';
534
+ const isInOut = f.direction === 'inout';
535
+ const cls = isInOut
536
+ ? 'bg-purple-50 text-purple-700 border-purple-200'
537
+ : isIn
538
+ ? 'bg-sky-50 text-sky-700 border-sky-200'
539
+ : 'bg-emerald-50 text-emerald-700 border-emerald-200';
540
+ const arrow = isInOut ? '↕' : isIn ? '←' : '→';
541
+ return (
542
+ <span
543
+ key={i}
544
+ className={`inline-flex items-center gap-0.5 text-[10px] font-mono font-medium px-1.5 py-0.5 rounded border whitespace-nowrap ${cls}`}
545
+ title={f.note}
546
+ >
547
+ <span className="font-sans mr-0.5">{arrow}</span>
548
+ {f.name}
549
+ </span>
550
+ );
551
+ })}
552
+ </div>
553
+ );
554
+ }
555
+
556
+ // ── Step badge ────────────────────────────────────────────────────────────────
557
+
558
+ function StepBadge({ type }) {
559
+ const cfg = STEP_TYPE_CONFIG[type] || { label: type, cls: 'bg-slate-50 text-slate-600 border-slate-200' };
560
+ return (
561
+ <span className={`inline-block text-xs font-medium px-2 py-0.5 rounded border whitespace-nowrap ${cfg.cls}`}>
562
+ {cfg.label}
563
+ </span>
564
+ );
565
+ }
566
+
567
+ // ── Plain step card ───────────────────────────────────────────────────────────
568
+
569
+ function StepCard({ step, models, editable, onStageModelChange, onValidationModelChange, onOpenAgent }) {
570
+ const showModel = !['input', 'output', 'read', 'process'].includes(step.type);
571
+ const canEdit = editable && !step.sharedWith && (step.stageKey || step.validationKey);
572
+
573
+ return (
574
+ <div className="bg-white border border-slate-200 rounded-lg px-3 py-2.5 shadow-sm">
575
+
576
+ {/* Row 1: badge + file tags (flex-1) + model — all vertically centred */}
577
+ <div className="flex items-center gap-2">
578
+ <div className="flex-shrink-0">
579
+ <StepBadge type={step.type} />
580
+ </div>
581
+ <FileTags files={step.files} className="flex-1 min-w-0" />
582
+ {showModel && (
583
+ step.sharedWith ? (
584
+ <p className="text-[10px] text-slate-400 italic flex-shrink-0">↑ {step.sharedWith}</p>
585
+ ) : canEdit ? (
586
+ <ModelSelectInline
587
+ value={step.model}
588
+ models={models}
589
+ onChange={(modelId) => {
590
+ if (step.stageKey) onStageModelChange(step.stageKey, modelId);
591
+ else onValidationModelChange(step.validationKey, modelId);
592
+ }}
593
+ />
594
+ ) : (
595
+ <p className="text-[10px] text-slate-400 flex-shrink-0">
596
+ {step.note ? step.note : resolveModelName(step.model, models)}
597
+ </p>
598
+ )
599
+ )}
600
+ </div>
601
+
602
+ {/* Row 2: description + agents — tiny left indent */}
603
+ <p className="text-xs text-slate-700 leading-snug text-left mt-2 pl-1">{step.label}</p>
604
+
605
+ <div className="pl-1">
606
+ <AgentLinks step={step} onOpenAgent={onOpenAgent} />
607
+ </div>
608
+ </div>
609
+ );
610
+ }
611
+
612
+ // ── Loop group card ───────────────────────────────────────────────────────────
613
+
614
+ function LoopGroupCard({ group, models, editable, onStageModelChange, onValidationModelChange, onLoopParamChange, onOpenAgent }) {
615
+ const { loop, steps } = group;
616
+ const isIndigo = steps.some((s) => s.type === 'cross');
617
+ const c = isIndigo ? LOOP_C.indigo : LOOP_C.amber;
618
+ const hasMulti = steps.length > 1;
619
+
620
+ const canEditLoop = editable && group.loopParamType && !group.loopParamReadOnly;
621
+
622
+ return (
623
+ <div className={`rounded-xl border-2 border-dashed ${c.border} overflow-hidden`}>
624
+
625
+ {/* ── Header ── */}
626
+ <div className={`${c.hdr} border-b-2 border-dashed ${c.hdrBorder} px-3 py-2 flex items-center gap-2 flex-wrap`}>
627
+ <span className={`text-xl font-black leading-none ${c.text}`}>↺</span>
628
+ <span className={`text-xs font-bold ${c.text} tracking-wide`}>Iteration Loop</span>
629
+ <div className="ml-auto flex items-center gap-1.5 flex-wrap">
630
+ {loop.max != null && (
631
+ canEditLoop ? (
632
+ <label className={`text-xs font-semibold ${c.chip} px-2 py-0.5 rounded-full flex items-center gap-1`}>
633
+ up to
634
+ <input
635
+ type="number" min="1" max="200" value={loop.max}
636
+ onChange={(e) => onLoopParamChange(group.loopParamType, 'maxIterations', Number(e.target.value))}
637
+ onClick={(e) => e.stopPropagation()}
638
+ className="w-10 bg-transparent border-b border-current text-center focus:outline-none"
639
+ />
640
+ iter
641
+ </label>
642
+ ) : (
643
+ <span className={`text-xs font-semibold ${c.chip} px-2 py-0.5 rounded-full`}>
644
+ {group.loopParamReadOnly ? `↑ ${loop.max} iter` : `up to ${loop.max} iter`}
645
+ </span>
646
+ )
647
+ )}
648
+ {loop.threshold != null && (
649
+ canEditLoop ? (
650
+ <label className={`text-xs font-semibold ${c.chip} px-2 py-0.5 rounded-full flex items-center gap-1`}>
651
+
652
+ <input
653
+ type="number" min="0" max="100" value={loop.threshold}
654
+ onChange={(e) => onLoopParamChange(group.loopParamType, 'acceptanceThreshold', Number(e.target.value))}
655
+ onClick={(e) => e.stopPropagation()}
656
+ className="w-10 bg-transparent border-b border-current text-center focus:outline-none"
657
+ />
658
+ /100
659
+ </label>
660
+ ) : (
661
+ <span className={`text-xs font-semibold ${c.chip} px-2 py-0.5 rounded-full`}>
662
+ {group.loopParamReadOnly ? `↑ ≥${loop.threshold}/100` : `≥${loop.threshold}/100`}
663
+ </span>
664
+ )
665
+ )}
666
+ </div>
667
+ </div>
668
+
669
+ {/* ── Steps + right-side loop arrow ── */}
670
+ <div className={`${c.bg} p-3 flex gap-2 items-stretch`}>
671
+ <div className="flex-1 flex flex-col">
672
+ {steps.map((step, i) => (
673
+ <div key={i}>
674
+ <StepCard
675
+ step={step}
676
+ models={models}
677
+ editable={editable}
678
+ onStageModelChange={onStageModelChange}
679
+ onValidationModelChange={onValidationModelChange}
680
+ onOpenAgent={onOpenAgent}
681
+ />
682
+ {i < steps.length - 1 && (
683
+ <div className="flex items-center gap-2 my-1.5 ml-3">
684
+ <div className={`w-px h-4 ${c.condLine}`} />
685
+ <span className={`text-xs ${c.subtext} italic`}>score &lt; threshold → retry</span>
686
+ </div>
687
+ )}
688
+ </div>
689
+ ))}
690
+ </div>
691
+
692
+ {/* Right-side loop-back arrow */}
693
+ {hasMulti && (
694
+ <div className={`flex flex-col items-center flex-shrink-0 ${c.arrow}`} style={{ width: 22 }}>
695
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
696
+ <path d="M2 7 L7 1 L12 7" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
697
+ </svg>
698
+ <div className={`w-0.5 flex-1 ${c.line} my-1 rounded-full`} style={{ minHeight: 36 }} />
699
+ <span
700
+ className={`text-[9px] font-bold tracking-widest uppercase ${c.text} opacity-70`}
701
+ style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
702
+ >
703
+ retry
704
+ </span>
705
+ </div>
706
+ )}
707
+ </div>
708
+ </div>
709
+ );
710
+ }
711
+
712
+ // ── Phase header ──────────────────────────────────────────────────────────────
713
+
714
+ function PhaseHeader({ label, color }) {
715
+ const cfg = PHASE_COLOR_CONFIG[color] || { dot: 'bg-slate-400', label: 'text-slate-600' };
716
+ return (
717
+ <div className="flex items-center gap-2 mt-5 mb-2">
718
+ <div className={`w-2 h-2 rounded-full flex-shrink-0 ${cfg.dot}`} />
719
+ <span className={`text-xs font-semibold uppercase tracking-wide ${cfg.label}`}>{label}</span>
720
+ </div>
721
+ );
722
+ }
723
+
724
+ function Connector() {
725
+ return <div className="w-px h-4 bg-slate-200 ml-4" />;
726
+ }
727
+
728
+ // ── Main export ───────────────────────────────────────────────────────────────
729
+
730
+ export function CeremonyWorkflowModal({
731
+ ceremony,
732
+ models = [],
733
+ missionGenValidation = null,
734
+ onClose,
735
+ onSave,
736
+ readOnly = false,
737
+ }) {
738
+ const [draft, setDraft] = useState(() => JSON.parse(JSON.stringify(ceremony || {})));
739
+ const [missionGenDraft, setMissionGenDraft] = useState(() =>
740
+ JSON.parse(JSON.stringify(missionGenValidation || { maxIterations: 3, acceptanceThreshold: 95 }))
741
+ );
742
+ const [saving, setSaving] = useState(false);
743
+ const [saveError, setSaveError] = useState(null);
744
+ const [openAgentSlug, setOpenAgentSlug] = useState(null);
745
+
746
+ // Phases rebuild from draft every render — always reflect current edits
747
+ const buildPhases = ceremony?.name ? CEREMONY_WORKFLOWS[ceremony.name] : null;
748
+ const phases = buildPhases ? buildPhases(draft, missionGenDraft) : null;
749
+
750
+ const isEditable = !readOnly && !!onSave;
751
+
752
+ // ── Update helpers ──────────────────────────────────────────────────────────
753
+
754
+ const updateStageModel = (stageKey, modelId) => {
755
+ const found = models.find((m) => m.modelId === modelId);
756
+ setDraft((prev) => ({
757
+ ...prev,
758
+ stages: {
759
+ ...prev.stages,
760
+ [stageKey]: {
761
+ ...prev.stages?.[stageKey],
762
+ model: modelId,
763
+ provider: found?.provider || prev.stages?.[stageKey]?.provider || '',
764
+ },
765
+ },
766
+ }));
767
+ };
768
+
769
+ const updateValidationModel = (area, modelId) => {
770
+ const found = models.find((m) => m.modelId === modelId);
771
+ if (area === 'top') {
772
+ setDraft((prev) => ({
773
+ ...prev,
774
+ validation: {
775
+ ...prev.validation,
776
+ model: modelId,
777
+ provider: found?.provider || prev.validation?.provider || '',
778
+ },
779
+ }));
780
+ } else {
781
+ setDraft((prev) => ({
782
+ ...prev,
783
+ validation: {
784
+ ...prev.validation,
785
+ [area]: {
786
+ ...prev.validation?.[area],
787
+ model: modelId,
788
+ provider: found?.provider || prev.validation?.[area]?.provider || '',
789
+ },
790
+ },
791
+ }));
792
+ }
793
+ };
794
+
795
+ const updateLoopParam = (loopParamType, field, value) => {
796
+ if (loopParamType === 'missionGen') {
797
+ setMissionGenDraft((prev) => ({ ...prev, [field]: value }));
798
+ } else if (loopParamType === 'docContext') {
799
+ setDraft((prev) => ({ ...prev, validation: { ...prev.validation, [field]: value } }));
800
+ } else if (loopParamType === 'sprintSolver') {
801
+ setDraft((prev) => ({
802
+ ...prev,
803
+ stages: {
804
+ ...prev.stages,
805
+ solver: { ...(prev.stages?.solver || {}), [field]: value },
806
+ },
807
+ }));
808
+ }
809
+ };
810
+
811
+ // ── Save / cancel ───────────────────────────────────────────────────────────
812
+
813
+ const handleSave = async () => {
814
+ setSaving(true);
815
+ setSaveError(null);
816
+ try {
817
+ await onSave(draft, missionGenDraft);
818
+ onClose();
819
+ } catch {
820
+ setSaveError('Failed to save. Please try again.');
821
+ } finally {
822
+ setSaving(false);
823
+ }
824
+ };
825
+
826
+ const handleCancel = () => {
827
+ setDraft(JSON.parse(JSON.stringify(ceremony || {})));
828
+ setMissionGenDraft(JSON.parse(JSON.stringify(missionGenValidation || { maxIterations: 3, acceptanceThreshold: 95 })));
829
+ onClose();
830
+ };
831
+
832
+ return (
833
+ <div className="fixed inset-0 z-[70] flex items-center justify-center">
834
+ {/* Backdrop: in edit mode clicking outside does nothing to prevent accidental data loss */}
835
+ <div
836
+ className="absolute inset-0 bg-black/40"
837
+ onClick={isEditable ? undefined : onClose}
838
+ />
839
+
840
+ <div
841
+ className="relative bg-slate-50 rounded-2xl shadow-2xl w-full max-w-xl mx-4 flex flex-col"
842
+ style={{ height: '85vh' }}
843
+ >
844
+ {/* Header */}
845
+ <div className="flex items-center justify-between px-5 pt-4 pb-3 border-b border-slate-200 flex-shrink-0 bg-white rounded-t-2xl">
846
+ <div>
847
+ <h2 className="text-sm font-semibold text-slate-900">
848
+ {isEditable ? 'Configure Models' : 'Ceremony Workflow'}
849
+ </h2>
850
+ <p className="text-xs text-slate-500 mt-0.5">
851
+ {ceremony?.displayName || ceremony?.name || 'Unknown ceremony'}
852
+ </p>
853
+ </div>
854
+ <button
855
+ type="button"
856
+ onClick={isEditable ? handleCancel : onClose}
857
+ className="text-slate-400 hover:text-slate-600 transition-colors"
858
+ >
859
+ <X className="w-4 h-4" />
860
+ </button>
861
+ </div>
862
+
863
+ {/* Scrollable body */}
864
+ <div className="flex-1 overflow-y-auto px-5 pb-5">
865
+ {phases === null ? (
866
+ <div className="flex items-center justify-center h-32">
867
+ <p className="text-sm text-slate-400">Workflow diagram coming soon for this ceremony.</p>
868
+ </div>
869
+ ) : (
870
+ phases.map((phase, pi) => (
871
+ <div key={phase.id}>
872
+ <PhaseHeader label={phase.label} color={phase.color} />
873
+ {phase.steps.map((step, si) => (
874
+ <div key={si}>
875
+ {step.type === 'loop-group' ? (
876
+ <LoopGroupCard
877
+ group={step}
878
+ models={models}
879
+ editable={isEditable}
880
+ onStageModelChange={updateStageModel}
881
+ onValidationModelChange={updateValidationModel}
882
+ onLoopParamChange={updateLoopParam}
883
+ onOpenAgent={setOpenAgentSlug}
884
+ />
885
+ ) : (
886
+ <StepCard
887
+ step={step}
888
+ models={models}
889
+ editable={isEditable}
890
+ onStageModelChange={updateStageModel}
891
+ onValidationModelChange={updateValidationModel}
892
+ onOpenAgent={setOpenAgentSlug}
893
+ />
894
+ )}
895
+ {si < phase.steps.length - 1 && <Connector />}
896
+ </div>
897
+ ))}
898
+ {pi < phases.length - 1 && (
899
+ <div className="mt-3 border-t border-dashed border-slate-200" />
900
+ )}
901
+ </div>
902
+ ))
903
+ )}
904
+ </div>
905
+
906
+ {/* Footer — edit mode only */}
907
+ {isEditable && (
908
+ <div className="flex-shrink-0 border-t border-slate-200 bg-white rounded-b-2xl px-5 py-3 flex items-center justify-between">
909
+ <div>
910
+ {saveError && <p className="text-xs text-red-600">{saveError}</p>}
911
+ </div>
912
+ <div className="flex items-center gap-2">
913
+ <button
914
+ type="button"
915
+ onClick={handleCancel}
916
+ className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 transition-colors"
917
+ >
918
+ Cancel
919
+ </button>
920
+ <button
921
+ type="button"
922
+ onClick={handleSave}
923
+ disabled={saving}
924
+ className="px-4 py-1.5 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors disabled:opacity-40 flex items-center gap-2"
925
+ >
926
+ {saving ? (
927
+ <>
928
+ <span className="w-3.5 h-3.5 border border-white/40 border-t-white rounded-full animate-spin" />
929
+ Saving…
930
+ </>
931
+ ) : 'Save Changes'}
932
+ </button>
933
+ </div>
934
+ </div>
935
+ )}
936
+ </div>
937
+
938
+ {openAgentSlug && (
939
+ <AgentEditorPopup
940
+ agentName={`${openAgentSlug}.md`}
941
+ onClose={() => setOpenAgentSlug(null)}
942
+ />
943
+ )}
944
+ </div>
945
+ );
946
+ }