@agile-vibe-coding/avc 0.1.1 → 0.3.1

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 (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,381 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { ChevronDown, ChevronRight, Pencil } from 'lucide-react';
3
+ import { getAgentList, getCheckList } from '../../lib/api';
4
+ import { AgentEditorPopup } from './AgentEditorPopup';
5
+ import { CheckEditorPopup } from './CheckEditorPopup';
6
+
7
+ const CEREMONY_STRUCTURE = [
8
+ {
9
+ ceremony: 'Sponsor Call',
10
+ color: 'blue',
11
+ phases: [
12
+ { phase: 'Mission & Scope', agents: [
13
+ { slug: 'mission-scope-generator', label: 'Mission Scope Generator', note: 'Generates mission & initial scope' },
14
+ { slug: 'mission-scope-validator', label: 'Mission Scope Validator', note: 'Validates mission quality' },
15
+ ]},
16
+ { phase: 'Questionnaire', agents: [
17
+ { slug: 'suggestion-product-manager', label: 'Product Manager', note: 'Fills Initial Scope' },
18
+ { slug: 'suggestion-ux-researcher', label: 'UX Researcher', note: 'Fills Target Users' },
19
+ { slug: 'suggestion-deployment-architect', label: 'Deployment Architect', note: 'Fills Deployment Target' },
20
+ { slug: 'suggestion-technical-architect', label: 'Technical Architect', note: 'Fills Technical Considerations' },
21
+ { slug: 'suggestion-security-specialist', label: 'Security Specialist', note: 'Fills Security & Compliance' },
22
+ { slug: 'architecture-recommender', label: 'Architecture Recommender', note: 'Recommends deployment architectures' },
23
+ { slug: 'database-recommender', label: 'Database Recommender', note: 'Recommends database type' },
24
+ { slug: 'database-deep-dive', label: 'Database Deep Dive', note: 'Detailed database analysis' },
25
+ { slug: 'question-prefiller', label: 'Question Prefiller', note: 'Pre-fills answers from architecture' },
26
+ ]},
27
+ { phase: 'Documentation', agents: [
28
+ { slug: 'project-documentation-creator', label: 'Documentation Creator', note: 'Creates project documentation' },
29
+ { slug: 'validator-documentation', label: 'Documentation Validator', note: 'Validates documentation quality' },
30
+ ]},
31
+ { phase: 'Context', agents: [
32
+ { slug: 'migration-guide-generator', label: 'Migration Guide Generator', note: 'Generates cloud migration guide' },
33
+ ]},
34
+ ],
35
+ },
36
+ {
37
+ ceremony: 'Sprint Planning',
38
+ color: 'purple',
39
+ phases: [
40
+ { phase: 'Decomposition', agents: [
41
+ { slug: 'epic-story-decomposer', label: 'Epic Story Decomposer', note: 'Breaks scope into epics & stories' },
42
+ ]},
43
+ { phase: 'Contextual Selection', agents: [
44
+ { slug: 'project-context-extractor', label: 'Project Context Extractor', note: 'Extracts project traits (once per run) to inform validator selection' },
45
+ { slug: 'validator-selector', label: 'Validator Selector', note: 'Selects appropriate domain validators' },
46
+ { slug: 'agent-selector', label: 'Agent Selector', note: 'Selects relevant validators per Epic/Story based on project context' },
47
+ ]},
48
+ { phase: 'Context Generation', agents: [
49
+ { slug: 'context-writer-epic', label: 'Context Writer (Epic)', note: 'Writes complete canonical context.md — Purpose, Scope, NFRs, Success Criteria' },
50
+ { slug: 'context-reviewer-epic', label: 'Context Reviewer (Epic)', note: 'Independent accuracy audit: checks all features present, no hallucinations, sections complete' },
51
+ { slug: 'context-writer-story', label: 'Context Writer (Story)', note: 'Writes complete canonical context.md — User Story framing, Technical Notes, Scope boundaries' },
52
+ { slug: 'context-reviewer-story', label: 'Context Reviewer (Story)', note: 'Independent accuracy audit: checks all ACs present, no hallucinations, sections complete' },
53
+ ]},
54
+ { phase: 'Documentation & Enrichment', agents: [
55
+ { slug: 'doc-writer-epic', label: 'Doc Writer (Epic)', note: 'Generates narrative epic doc.md from canonical context.md' },
56
+ { slug: 'doc-writer-story', label: 'Doc Writer (Story)', note: 'Generates narrative story doc.md from canonical context.md' },
57
+ { slug: 'story-doc-enricher', label: 'Story Doc Enricher', note: 'Enriches story docs with API contracts, error tables, DB fields, business rules' },
58
+ ]},
59
+ { phase: 'Micro-Check Definitions — Epic', agents: [
60
+ { slug: 'checks/epic/solution-architect', label: 'Solution Architect', note: 'JSON', isCheck: true },
61
+ { slug: 'checks/epic/developer', label: 'Developer', note: 'JSON', isCheck: true },
62
+ { slug: 'checks/epic/security', label: 'Security', note: 'JSON', isCheck: true },
63
+ { slug: 'checks/epic/devops', label: 'DevOps', note: 'JSON', isCheck: true },
64
+ { slug: 'checks/epic/cloud', label: 'Cloud', note: 'JSON', isCheck: true },
65
+ { slug: 'checks/epic/backend', label: 'Backend', note: 'JSON', isCheck: true },
66
+ { slug: 'checks/epic/database', label: 'Database', note: 'JSON', isCheck: true },
67
+ { slug: 'checks/epic/api', label: 'API', note: 'JSON', isCheck: true },
68
+ { slug: 'checks/epic/frontend', label: 'Frontend', note: 'JSON', isCheck: true },
69
+ { slug: 'checks/epic/ui', label: 'UI', note: 'JSON', isCheck: true },
70
+ { slug: 'checks/epic/ux', label: 'UX', note: 'JSON', isCheck: true },
71
+ { slug: 'checks/epic/mobile', label: 'Mobile', note: 'JSON', isCheck: true },
72
+ { slug: 'checks/epic/data', label: 'Data', note: 'JSON', isCheck: true },
73
+ { slug: 'checks/epic/qa', label: 'QA', note: 'JSON', isCheck: true },
74
+ { slug: 'checks/epic/test-architect', label: 'Test Architect', note: 'JSON', isCheck: true },
75
+ ]},
76
+ { phase: 'Micro-Check Definitions — Story', agents: [
77
+ { slug: 'checks/story/solution-architect', label: 'Solution Architect', note: 'JSON', isCheck: true },
78
+ { slug: 'checks/story/developer', label: 'Developer', note: 'JSON', isCheck: true },
79
+ { slug: 'checks/story/security', label: 'Security', note: 'JSON', isCheck: true },
80
+ { slug: 'checks/story/devops', label: 'DevOps', note: 'JSON', isCheck: true },
81
+ { slug: 'checks/story/cloud', label: 'Cloud', note: 'JSON', isCheck: true },
82
+ { slug: 'checks/story/backend', label: 'Backend', note: 'JSON', isCheck: true },
83
+ { slug: 'checks/story/database', label: 'Database', note: 'JSON', isCheck: true },
84
+ { slug: 'checks/story/api', label: 'API', note: 'JSON', isCheck: true },
85
+ { slug: 'checks/story/frontend', label: 'Frontend', note: 'JSON', isCheck: true },
86
+ { slug: 'checks/story/ui', label: 'UI', note: 'JSON', isCheck: true },
87
+ { slug: 'checks/story/ux', label: 'UX', note: 'JSON', isCheck: true },
88
+ { slug: 'checks/story/mobile', label: 'Mobile', note: 'JSON', isCheck: true },
89
+ { slug: 'checks/story/data', label: 'Data', note: 'JSON', isCheck: true },
90
+ { slug: 'checks/story/qa', label: 'QA', note: 'JSON', isCheck: true },
91
+ { slug: 'checks/story/test-architect', label: 'Test Architect', note: 'JSON', isCheck: true },
92
+ { slug: 'story-splitter', label: 'Story Splitter', note: 'Splits oversized stories (15+ ACs) into 2-3 focused stories' },
93
+ ]},
94
+ { phase: 'Cross-Reference Checks', agents: [
95
+ { slug: 'checks/cross-refs/epic', label: 'Epic Cross-Refs', note: 'JSON', isCheck: true },
96
+ { slug: 'checks/cross-refs/story', label: 'Story Cross-Refs', note: 'JSON', isCheck: true },
97
+ ]},
98
+ ],
99
+ },
100
+ {
101
+ ceremony: 'Seed',
102
+ color: 'amber',
103
+ phases: [
104
+ { phase: 'Decomposition', agents: [
105
+ { slug: 'task-subtask-decomposer', label: 'Task Decomposer', note: 'Breaks stories into tasks & subtasks' },
106
+ ]},
107
+ { phase: 'Documentation', agents: [
108
+ { slug: 'doc-distributor', label: 'Doc Distributor', note: 'Moves content from story doc into task/subtask docs' },
109
+ { slug: 'feature-context-generator', label: 'Feature Context Generator', note: 'Generates implementation context.md for each task/subtask' },
110
+ ]},
111
+ ],
112
+ },
113
+ ];
114
+
115
+ const CEREMONY_COLORS = {
116
+ blue: { border: 'border-blue-200', header: 'bg-blue-50', accent: 'border-l-blue-400', text: 'text-blue-800' },
117
+ purple: { border: 'border-purple-200', header: 'bg-purple-50', accent: 'border-l-purple-400', text: 'text-purple-800' },
118
+ amber: { border: 'border-amber-200', header: 'bg-amber-50', accent: 'border-l-amber-400', text: 'text-amber-800' },
119
+ green: { border: 'border-green-200', header: 'bg-green-50', accent: 'border-l-green-400', text: 'text-green-800' },
120
+ slate: { border: 'border-slate-200', header: 'bg-slate-50', accent: 'border-l-slate-400', text: 'text-slate-800' },
121
+ };
122
+
123
+ // Flat set of all known slugs for computing "Other" group
124
+ const KNOWN_SLUGS = new Set(
125
+ CEREMONY_STRUCTURE.flatMap(c => c.phases.flatMap(p => p.agents.map(a => a.slug)))
126
+ );
127
+
128
+ export function AgentsTab() {
129
+ const [agentStatus, setAgentStatus] = useState({}); // { slug: isCustomized }
130
+ const [checkStatus, setCheckStatus] = useState({}); // { 'scope/perspective': { isCustomized, checkCount } }
131
+ const [openAgent, setOpenAgent] = useState(null); // slug | null
132
+ const [openCheck, setOpenCheck] = useState(null); // { scope, perspective } | null
133
+ const [search, setSearch] = useState('');
134
+ const [error, setError] = useState(null);
135
+ // collapsed state: absence = collapsed (default), true = open
136
+ const [collapsed, setCollapsed] = useState({});
137
+
138
+ useEffect(() => {
139
+ getAgentList()
140
+ .then(r => {
141
+ const status = {};
142
+ r.agents.forEach(a => {
143
+ const slug = a.name.replace(/\.md$/, '');
144
+ status[slug] = a.isCustomized;
145
+ });
146
+ setAgentStatus(status);
147
+ })
148
+ .catch(err => setError(err.message));
149
+
150
+ getCheckList()
151
+ .then(r => {
152
+ const status = {};
153
+ r.checks.forEach(c => {
154
+ const key = `checks/${c.scope}/${c.perspective}`;
155
+ status[key] = { isCustomized: c.isCustomized, checkCount: c.checkCount };
156
+ });
157
+ setCheckStatus(status);
158
+ })
159
+ .catch(() => {}); // silently ignore if check API not available
160
+ }, []);
161
+
162
+ const toggle = (key) => setCollapsed(prev => ({ ...prev, [key]: !prev[key] }));
163
+ const isOpen = (key) => collapsed[key] === true; // default collapsed when key absent
164
+
165
+ // Build "Other" group from agents returned by the API that aren't in any ceremony structure
166
+ const otherAgents = Object.keys(agentStatus)
167
+ .filter(slug => !KNOWN_SLUGS.has(slug))
168
+ .map(slug => ({ slug, label: slug, note: null }));
169
+
170
+ const allCeremonies = [
171
+ ...CEREMONY_STRUCTURE,
172
+ ...(otherAgents.length > 0
173
+ ? [{ ceremony: 'Other', color: 'slate', phases: [{ phase: 'Other', agents: otherAgents }] }]
174
+ : []),
175
+ ];
176
+
177
+ // Filter by search query — when searching, force everything open
178
+ const q = search.toLowerCase();
179
+ const filteredCeremonies = q
180
+ ? allCeremonies
181
+ .map(c => ({
182
+ ...c,
183
+ phases: c.phases
184
+ .map(p => ({
185
+ ...p,
186
+ agents: p.agents.filter(a =>
187
+ a.label.toLowerCase().includes(q) ||
188
+ a.slug.toLowerCase().includes(q) ||
189
+ c.ceremony.toLowerCase().includes(q) ||
190
+ p.phase.toLowerCase().includes(q)
191
+ ),
192
+ }))
193
+ .filter(p => p.agents.length > 0),
194
+ }))
195
+ .filter(c => c.phases.length > 0)
196
+ : allCeremonies;
197
+
198
+ const hasAgentsLoaded = Object.keys(agentStatus).length > 0 || Object.keys(checkStatus).length > 0;
199
+ const forceOpen = q.length > 0;
200
+
201
+ // Count customized agents per ceremony for the badge
202
+ const countCustomized = (ceremony) =>
203
+ ceremony.phases
204
+ .flatMap(p => p.agents)
205
+ .filter(a => a.isCheck ? checkStatus[a.slug]?.isCustomized : agentStatus[a.slug])
206
+ .length;
207
+
208
+ return (
209
+ <div>
210
+ {/* Sticky search bar */}
211
+ <div className="sticky top-0 bg-white z-10 px-4 py-2.5 border-b border-slate-100 flex items-center justify-between gap-3">
212
+ <input
213
+ type="search"
214
+ placeholder="Search agents…"
215
+ value={search}
216
+ onChange={e => setSearch(e.target.value)}
217
+ className="w-48 rounded border border-slate-200 px-3 py-1.5 text-xs text-slate-700 focus:outline-none focus:ring-1 focus:ring-blue-500"
218
+ />
219
+ <span className="text-xs text-slate-400 italic flex-shrink-0">Click any agent to edit its prompt</span>
220
+ </div>
221
+
222
+ {/* Agent hierarchy list */}
223
+ <div className="px-4 py-4 flex flex-col gap-3">
224
+ {error && (
225
+ <p className="text-xs text-red-500">{error}</p>
226
+ )}
227
+ {filteredCeremonies.length === 0 && (
228
+ <p className="text-sm text-slate-400 py-4">No agents match your search.</p>
229
+ )}
230
+
231
+ {filteredCeremonies.map(ceremony => {
232
+ const colors = CEREMONY_COLORS[ceremony.color] || CEREMONY_COLORS.slate;
233
+ const ceremonyOpen = forceOpen || isOpen(ceremony.ceremony);
234
+ const customCount = countCustomized(ceremony);
235
+
236
+ return (
237
+ <div
238
+ key={ceremony.ceremony}
239
+ className={`rounded-xl border ${colors.border} overflow-hidden`}
240
+ >
241
+ {/* Ceremony header — clickable to collapse/expand */}
242
+ <button
243
+ type="button"
244
+ onClick={() => !forceOpen && toggle(ceremony.ceremony)}
245
+ className={`w-full flex items-center gap-2 px-4 py-2.5 border-l-4 ${colors.header} ${colors.accent} text-left`}
246
+ >
247
+ {ceremonyOpen
248
+ ? <ChevronDown className="w-3.5 h-3.5 text-slate-400 flex-shrink-0" />
249
+ : <ChevronRight className="w-3.5 h-3.5 text-slate-400 flex-shrink-0" />
250
+ }
251
+ <span className={`text-sm font-semibold flex-1 ${colors.text}`}>
252
+ {ceremony.ceremony}
253
+ </span>
254
+ {customCount > 0 && (
255
+ <span className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-amber-100 text-amber-700">
256
+ {customCount} custom
257
+ </span>
258
+ )}
259
+ </button>
260
+
261
+ {/* Ceremony body — collapsible */}
262
+ {ceremonyOpen && (
263
+ <div className="divide-y divide-slate-100">
264
+ {ceremony.phases.map(phase => {
265
+ const phaseKey = `${ceremony.ceremony}::${phase.phase}`;
266
+ const phaseOpen = forceOpen || isOpen(phaseKey);
267
+ const visibleAgents = phase.agents.filter(
268
+ a => !hasAgentsLoaded || a.isCheck || a.slug in agentStatus
269
+ );
270
+ if (visibleAgents.length === 0) return null;
271
+
272
+ return (
273
+ <div key={phase.phase}>
274
+ {/* Phase sub-header — indented, highlighted as non-leaf node */}
275
+ <button
276
+ type="button"
277
+ onClick={() => !forceOpen && toggle(phaseKey)}
278
+ className="w-full flex items-center gap-2 pl-6 pr-4 py-2 bg-slate-50 hover:bg-slate-100 transition-colors text-left"
279
+ >
280
+ {phaseOpen
281
+ ? <ChevronDown className="w-3 h-3 text-slate-400 flex-shrink-0" />
282
+ : <ChevronRight className="w-3 h-3 text-slate-400 flex-shrink-0" />
283
+ }
284
+ <span className="text-xs font-semibold text-slate-600 uppercase tracking-wide">
285
+ {phase.phase}
286
+ </span>
287
+ <span className="text-xs text-slate-400 ml-1">
288
+ {visibleAgents.length}
289
+ </span>
290
+ </button>
291
+
292
+ {/* Agent rows — further indented */}
293
+ {phaseOpen && (
294
+ <div className="pb-1">
295
+ {visibleAgents.map(agent => {
296
+ const isCheck = agent.isCheck;
297
+ const isCustomized = isCheck
298
+ ? (checkStatus[agent.slug]?.isCustomized ?? false)
299
+ : (agentStatus[agent.slug] ?? false);
300
+ const checkCount = isCheck ? (checkStatus[agent.slug]?.checkCount ?? 0) : 0;
301
+ const handleClick = () => {
302
+ if (isCheck) {
303
+ // Parse slug: "checks/scope/perspective"
304
+ const parts = agent.slug.split('/');
305
+ setOpenCheck({ scope: parts[1], perspective: parts[2] });
306
+ } else {
307
+ setOpenAgent(agent.slug);
308
+ }
309
+ };
310
+ return (
311
+ <button
312
+ key={`${ceremony.ceremony}-${agent.slug}`}
313
+ type="button"
314
+ onClick={handleClick}
315
+ className="w-full text-left pl-10 pr-4 py-1.5 hover:bg-slate-50 transition-colors flex items-center gap-3 group"
316
+ >
317
+ <div className="flex-1 min-w-0">
318
+ <span className="text-sm text-slate-700 group-hover:text-slate-900">
319
+ {agent.label}
320
+ </span>
321
+ {isCheck && checkCount > 0 && (
322
+ <span className="text-xs text-blue-500 ml-2">
323
+ {checkCount} checks
324
+ </span>
325
+ )}
326
+ {agent.note && !isCheck && (
327
+ <span className="text-xs text-slate-400 italic ml-2">
328
+ {agent.note}
329
+ </span>
330
+ )}
331
+ </div>
332
+ {isCustomized && (
333
+ <span className="flex-shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-amber-100 text-amber-700">
334
+ Custom
335
+ </span>
336
+ )}
337
+ <Pencil className="w-3.5 h-3.5 text-slate-300 group-hover:text-slate-500 flex-shrink-0 transition-colors" />
338
+ </button>
339
+ );
340
+ })}
341
+ </div>
342
+ )}
343
+ </div>
344
+ );
345
+ })}
346
+ </div>
347
+ )}
348
+ </div>
349
+ );
350
+ })}
351
+ </div>
352
+
353
+ {/* Agent editor popup */}
354
+ {openAgent && (
355
+ <AgentEditorPopup
356
+ agentName={`${openAgent}.md`}
357
+ onClose={() => setOpenAgent(null)}
358
+ onSaved={() => setAgentStatus(prev => ({ ...prev, [openAgent]: true }))}
359
+ onReset={() => setAgentStatus(prev => ({ ...prev, [openAgent]: false }))}
360
+ />
361
+ )}
362
+
363
+ {/* Check editor popup */}
364
+ {openCheck && (
365
+ <CheckEditorPopup
366
+ scope={openCheck.scope}
367
+ perspective={openCheck.perspective}
368
+ onClose={() => setOpenCheck(null)}
369
+ onSaved={() => {
370
+ const key = `checks/${openCheck.scope}/${openCheck.perspective}`;
371
+ setCheckStatus(prev => ({ ...prev, [key]: { ...prev[key], isCustomized: true } }));
372
+ }}
373
+ onReset={() => {
374
+ const key = `checks/${openCheck.scope}/${openCheck.perspective}`;
375
+ setCheckStatus(prev => ({ ...prev, [key]: { ...prev[key], isCustomized: false } }));
376
+ }}
377
+ />
378
+ )}
379
+ </div>
380
+ );
381
+ }
@@ -0,0 +1,142 @@
1
+ import { useState } from 'react';
2
+ import { Eye, EyeOff } from 'lucide-react';
3
+ import { saveApiKeys } from '../../lib/api';
4
+ import { OpenAIAuthSection } from './OpenAIAuthSection';
5
+
6
+ const PROVIDERS = [
7
+ { key: 'anthropic', label: 'Anthropic', envKey: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-…' },
8
+ { key: 'gemini', label: 'Google (Gemini)', envKey: 'GEMINI_API_KEY', placeholder: 'AIza…' },
9
+ { key: 'xiaomi', label: 'Xiaomi (MiMo)', envKey: 'XIAOMI_API_KEY', placeholder: 'sk-…' },
10
+ ];
11
+
12
+ function ApiKeyRow({ provider, apiKeyInfo, onSaved }) {
13
+ const [value, setValue] = useState('');
14
+ const [showKey, setShowKey] = useState(false);
15
+ const [status, setStatus] = useState(null); // null | 'saving' | 'saved' | 'error' | 'clearing'
16
+
17
+ const handleSave = async () => {
18
+ setStatus('saving');
19
+ try {
20
+ await saveApiKeys({ [provider.key]: value });
21
+ setStatus('saved');
22
+ setValue('');
23
+ onSaved();
24
+ setTimeout(() => setStatus(null), 2000);
25
+ } catch {
26
+ setStatus('error');
27
+ setTimeout(() => setStatus(null), 2000);
28
+ }
29
+ };
30
+
31
+ const handleClear = async () => {
32
+ setStatus('clearing');
33
+ try {
34
+ await saveApiKeys({ [provider.key]: '' });
35
+ setStatus('saved');
36
+ onSaved();
37
+ setTimeout(() => setStatus(null), 2000);
38
+ } catch {
39
+ setStatus('error');
40
+ setTimeout(() => setStatus(null), 2000);
41
+ }
42
+ };
43
+
44
+ return (
45
+ <div className="flex items-center gap-3 py-3 border-b border-slate-100 last:border-0">
46
+ {/* Provider name */}
47
+ <div className="w-36 flex-shrink-0">
48
+ <p className="text-sm font-medium text-slate-800">{provider.label}</p>
49
+ <p className="text-xs text-slate-400">{provider.envKey}</p>
50
+ </div>
51
+
52
+ {/* Status badge */}
53
+ <div className="w-16 flex-shrink-0">
54
+ {apiKeyInfo.isSet ? (
55
+ <span className="inline-flex items-center gap-1 text-xs font-medium text-green-700 bg-green-50 border border-green-200 rounded-full px-2 py-0.5">
56
+ ✓ Set
57
+ </span>
58
+ ) : (
59
+ <span className="inline-flex items-center text-xs font-medium text-slate-400 bg-slate-50 border border-slate-200 rounded-full px-2 py-0.5">
60
+ Not set
61
+ </span>
62
+ )}
63
+ </div>
64
+
65
+ {/* Preview */}
66
+ {apiKeyInfo.isSet && !value && (
67
+ <p className="text-xs text-slate-400 font-mono flex-shrink-0">{apiKeyInfo.preview}</p>
68
+ )}
69
+
70
+ {/* Key input */}
71
+ <div className="flex-1 flex items-center gap-2 min-w-0">
72
+ <div className="relative flex-1">
73
+ <input
74
+ type={showKey ? 'text' : 'password'}
75
+ value={value}
76
+ onChange={(e) => setValue(e.target.value)}
77
+ placeholder={apiKeyInfo.isSet ? 'Enter new key to update…' : provider.placeholder}
78
+ className="w-full rounded-md border border-slate-300 px-2 py-1.5 pr-8 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono"
79
+ />
80
+ <button
81
+ type="button"
82
+ onClick={() => setShowKey((v) => !v)}
83
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
84
+ tabIndex={-1}
85
+ >
86
+ {showKey ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
87
+ </button>
88
+ </div>
89
+
90
+ {apiKeyInfo.isSet && !value && (
91
+ <button
92
+ type="button"
93
+ onClick={handleClear}
94
+ disabled={status === 'clearing'}
95
+ className="px-3 py-1.5 text-xs font-medium border border-red-200 text-red-600 rounded-md hover:bg-red-50 transition-colors disabled:opacity-40 flex-shrink-0"
96
+ >
97
+ {status === 'clearing' ? '…' : 'Reset'}
98
+ </button>
99
+ )}
100
+
101
+ <button
102
+ type="button"
103
+ onClick={handleSave}
104
+ disabled={!value.trim() || status === 'saving'}
105
+ className="px-3 py-1.5 text-xs font-medium bg-slate-900 text-white rounded-md hover:bg-slate-700 transition-colors disabled:opacity-40 flex-shrink-0"
106
+ >
107
+ {status === 'saving' ? (
108
+ <span className="inline-flex items-center gap-1">
109
+ <span className="w-3 h-3 border border-white/40 border-t-white rounded-full animate-spin" />
110
+ Saving
111
+ </span>
112
+ ) : status === 'saved' ? '✓ Saved' : status === 'error' ? '✗ Error' : 'Save'}
113
+ </button>
114
+ </div>
115
+ </div>
116
+ );
117
+ }
118
+
119
+ export function ApiKeysTab({ settings, onSaved }) {
120
+ return (
121
+ <div className="px-5 py-4">
122
+ <p className="text-xs text-slate-500 mb-4">
123
+ API keys are stored in your project's <code className="font-mono bg-slate-100 px-1 rounded">.env</code> file.
124
+ Enter a new key and click Save to update. Clear the field and save to remove a key.
125
+ </p>
126
+ <div>
127
+ {PROVIDERS.map((provider) => (
128
+ <ApiKeyRow
129
+ key={provider.key}
130
+ provider={provider}
131
+ apiKeyInfo={settings.apiKeys[provider.key]}
132
+ onSaved={onSaved}
133
+ />
134
+ ))}
135
+ <OpenAIAuthSection
136
+ apiKeyInfo={settings.apiKeys.openai}
137
+ onSaved={onSaved}
138
+ />
139
+ </div>
140
+ </div>
141
+ );
142
+ }
@@ -0,0 +1,105 @@
1
+ import { useState } from 'react';
2
+ import { Workflow } from 'lucide-react';
3
+ import { saveCeremonies } from '../../lib/api';
4
+ import { CeremonyWorkflowModal } from '../ceremony/CeremonyWorkflowModal';
5
+
6
+ function humanize(str) {
7
+ return str
8
+ .replace(/-/g, ' ')
9
+ .replace(/\b\w/g, (c) => c.toUpperCase());
10
+ }
11
+
12
+ const CEREMONY_DESCRIPTIONS = {
13
+ 'sponsor-call': 'Generates mission statement and project documentation through a structured AI-guided interview.',
14
+ 'sprint-planning': 'Decomposes project scope into epics and stories with multi-agent validation and context generation.',
15
+ 'seed': 'Decomposes a story into implementable tasks and subtasks with documentation and context files.',
16
+ 'run': 'Implements task code in a git worktree using AI agents with traceability rules validation.',
17
+ };
18
+
19
+ export function CeremonyModelsTab({ settings, models, onSaved }) {
20
+ const [ceremonies, setCeremonies] = useState(
21
+ () => JSON.parse(JSON.stringify(settings.ceremonies || []))
22
+ );
23
+ const [missionGenValidation, setMissionGenValidation] = useState(
24
+ () => JSON.parse(JSON.stringify(
25
+ settings.missionGenerator?.validation || { maxIterations: 3, acceptanceThreshold: 95 }
26
+ ))
27
+ );
28
+ const [activeWorkflow, setActiveWorkflow] = useState(null);
29
+
30
+ const handleCeremonySave = async (updatedCeremony, updatedMG) => {
31
+ const next = ceremonies.map((c) =>
32
+ c.name === updatedCeremony.name ? updatedCeremony : c
33
+ );
34
+ // Always pass missionGen params; use updated value for sponsor-call, current for others
35
+ const missionGenArg = updatedCeremony.name === 'sponsor-call'
36
+ ? { validation: updatedMG || missionGenValidation }
37
+ : { validation: missionGenValidation };
38
+ await saveCeremonies(next, missionGenArg);
39
+ setCeremonies(next);
40
+ if (updatedCeremony.name === 'sponsor-call' && updatedMG) {
41
+ setMissionGenValidation(updatedMG);
42
+ }
43
+ onSaved();
44
+ };
45
+
46
+ if (!ceremonies.length) {
47
+ return (
48
+ <div className="px-5 py-8 text-center">
49
+ <p className="text-sm text-slate-500">
50
+ No ceremony configurations found yet. Run your first ceremony from the kanban board
51
+ to populate settings here.
52
+ </p>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ const activeWorkflowCeremony = ceremonies.find((c) => c.name === activeWorkflow);
58
+
59
+ return (
60
+ <div className="px-5 py-4 flex flex-col gap-3">
61
+ {ceremonies.map((ceremony) => {
62
+ const description = CEREMONY_DESCRIPTIONS[ceremony.name];
63
+ return (
64
+ <div
65
+ key={ceremony.name}
66
+ className="border border-slate-200 rounded-lg px-4 py-3 flex items-center justify-between gap-4"
67
+ >
68
+ <div className="min-w-0">
69
+ <p className="text-sm font-semibold text-slate-800">
70
+ {ceremony.displayName || humanize(ceremony.name || '')}
71
+ </p>
72
+ {description && (
73
+ <p className="text-xs text-slate-500 mt-0.5 leading-relaxed">{description}</p>
74
+ )}
75
+ </div>
76
+ <button
77
+ type="button"
78
+ onClick={() => setActiveWorkflow(ceremony.name)}
79
+ className="flex items-center gap-1.5 text-xs font-medium text-blue-600 hover:text-blue-700 bg-blue-50 hover:bg-blue-100 border border-blue-200 rounded-md px-3 py-1.5 transition-colors flex-shrink-0"
80
+ >
81
+ <Workflow className="w-3.5 h-3.5" />
82
+ Configure Models
83
+ </button>
84
+ </div>
85
+ );
86
+ })}
87
+
88
+ {activeWorkflow && activeWorkflowCeremony && (
89
+ <CeremonyWorkflowModal
90
+ ceremony={activeWorkflowCeremony}
91
+ allCeremonies={ceremonies}
92
+ apiKeys={settings.apiKeys}
93
+ models={models}
94
+ missionGenValidation={activeWorkflow === 'sponsor-call' ? missionGenValidation : null}
95
+ onClose={() => setActiveWorkflow(null)}
96
+ onSave={handleCeremonySave}
97
+ onCeremoniesUpdated={(updated) => {
98
+ setCeremonies(updated);
99
+ onSaved();
100
+ }}
101
+ />
102
+ )}
103
+ </div>
104
+ );
105
+ }