@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,616 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { AlertTriangle, Settings as SettingsIcon } from 'lucide-react';
3
+ import { getModels, getSettings, generateMission, refineMission } from '../../lib/api';
4
+ import { useCeremonyStore } from '../../store/ceremonyStore';
5
+
6
+ // Maps model provider → apiKeys key returned by getSettings()
7
+ function normalizeProvider(p = '') {
8
+ if (p === 'claude') return 'anthropic';
9
+ return p;
10
+ }
11
+
12
+ const KEY_LABELS = {
13
+ claude: 'Anthropic API Key (ANTHROPIC_API_KEY)',
14
+ anthropic: 'Anthropic API Key (ANTHROPIC_API_KEY)',
15
+ gemini: 'Google Gemini API Key (GEMINI_API_KEY)',
16
+ openai: 'OpenAI API Key (OPENAI_API_KEY)',
17
+ };
18
+
19
+ /**
20
+ * IssuesPopup
21
+ * Sub-modal that displays all validation issues with full detail.
22
+ * Supports selecting issues and triggering a targeted refine.
23
+ */
24
+ function IssuesPopup({ issues, onClose, onRefineWithIssues }) {
25
+ const [selectedIssueIndices, setSelectedIssueIndices] = useState(new Set());
26
+
27
+ const severityColor = {
28
+ critical: 'text-red-600',
29
+ major: 'text-orange-600',
30
+ minor: 'text-amber-600',
31
+ };
32
+ const severityBg = {
33
+ critical: 'bg-red-50 border-red-100',
34
+ major: 'bg-orange-50 border-orange-100',
35
+ minor: 'bg-amber-50 border-amber-100',
36
+ };
37
+
38
+ function toggleIssue(i) {
39
+ setSelectedIssueIndices(prev => {
40
+ const next = new Set(prev);
41
+ if (next.has(i)) next.delete(i);
42
+ else next.add(i);
43
+ return next;
44
+ });
45
+ }
46
+
47
+ return (
48
+ <div
49
+ className="fixed inset-0 z-[70] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4"
50
+ onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
51
+ >
52
+ <div className="w-full max-w-lg bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden max-h-[70vh]">
53
+ {/* Header */}
54
+ <div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
55
+ <div>
56
+ <h3 className="text-sm font-semibold text-slate-900">Validation Issues</h3>
57
+ <p className="text-xs text-slate-400 mt-0.5">{issues.length} issue{issues.length !== 1 ? 's' : ''} found during validation</p>
58
+ </div>
59
+ <button
60
+ type="button"
61
+ onClick={onClose}
62
+ className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
63
+ aria-label="Close"
64
+ >
65
+ ×
66
+ </button>
67
+ </div>
68
+
69
+ {/* Issues list */}
70
+ <ul className="flex-1 overflow-y-auto px-4 py-3 space-y-2">
71
+ {issues.map((issue, i) => (
72
+ <li
73
+ key={i}
74
+ onClick={() => toggleIssue(i)}
75
+ className={`rounded-lg border px-3 py-2.5 cursor-pointer flex items-start gap-2.5 ${selectedIssueIndices.has(i) ? 'ring-2 ring-blue-500' : ''} ${severityBg[issue.severity] ?? 'bg-slate-50 border-slate-100'}`}
76
+ >
77
+ <input
78
+ type="checkbox"
79
+ checked={selectedIssueIndices.has(i)}
80
+ onChange={() => toggleIssue(i)}
81
+ onClick={(e) => e.stopPropagation()}
82
+ className="mt-0.5 flex-shrink-0 accent-blue-600"
83
+ />
84
+ <div className="flex-1 min-w-0">
85
+ <div className="flex items-center gap-2 mb-1">
86
+ <span className={`text-xs font-semibold uppercase ${severityColor[issue.severity] ?? 'text-slate-600'}`}>
87
+ {issue.severity}
88
+ </span>
89
+ </div>
90
+ <p className="text-sm text-slate-800 leading-snug">{issue.description}</p>
91
+ {issue.suggestion && (
92
+ <p className="text-xs text-slate-500 mt-1 leading-snug">
93
+ <span className="font-medium">Suggestion:</span> {issue.suggestion}
94
+ </p>
95
+ )}
96
+ </div>
97
+ </li>
98
+ ))}
99
+ </ul>
100
+
101
+ {/* Footer */}
102
+ <div className="px-5 py-3 border-t border-slate-100 flex-shrink-0 flex items-center justify-between gap-2">
103
+ <button
104
+ type="button"
105
+ onClick={() => {
106
+ onRefineWithIssues(issues.filter((_, i) => selectedIssueIndices.has(i)));
107
+ onClose();
108
+ }}
109
+ disabled={selectedIssueIndices.size === 0}
110
+ className="px-4 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-40"
111
+ >
112
+ Refine with selected
113
+ </button>
114
+ <button
115
+ type="button"
116
+ onClick={onClose}
117
+ className="px-4 py-1.5 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
118
+ >
119
+ Close
120
+ </button>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ );
125
+ }
126
+
127
+ /**
128
+ * RefinePopup
129
+ * Sub-modal for requesting targeted refinements to the generated mission & scope.
130
+ */
131
+ function RefinePopup({ onSubmit, onClose }) {
132
+ const [refinementRequest, setRefinementRequest] = useState('');
133
+
134
+ return (
135
+ <div
136
+ className="fixed inset-0 z-[70] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4"
137
+ onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
138
+ >
139
+ <div className="w-full max-w-md bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden">
140
+ {/* Header */}
141
+ <div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
142
+ <div>
143
+ <h3 className="text-sm font-semibold text-slate-900">Refine Mission & Scope</h3>
144
+ <p className="text-xs text-slate-400 mt-0.5">Describe what you'd like to change or improve.</p>
145
+ </div>
146
+ <button
147
+ type="button"
148
+ onClick={onClose}
149
+ className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
150
+ aria-label="Close"
151
+ >
152
+ ×
153
+ </button>
154
+ </div>
155
+
156
+ {/* Body */}
157
+ <div className="px-5 py-4 flex flex-col gap-3">
158
+ {/* Refinement textarea */}
159
+ <div>
160
+ <label className="block text-xs font-medium text-slate-500 mb-1">
161
+ What would you like to change or improve?
162
+ </label>
163
+ <textarea
164
+ value={refinementRequest}
165
+ onChange={(e) => setRefinementRequest(e.target.value)}
166
+ rows={4}
167
+ placeholder="E.g. Focus more on enterprise teams, make the mission mention mobile specifically…"
168
+ autoFocus
169
+ className="w-full rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
170
+ />
171
+ </div>
172
+ </div>
173
+
174
+ {/* Footer */}
175
+ <div className="px-5 py-3 border-t border-slate-100 flex-shrink-0 flex items-center justify-end gap-2">
176
+ <button
177
+ type="button"
178
+ onClick={onClose}
179
+ className="px-4 py-1.5 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
180
+ >
181
+ Cancel
182
+ </button>
183
+ <button
184
+ type="button"
185
+ onClick={() => onSubmit(refinementRequest)}
186
+ disabled={!refinementRequest.trim()}
187
+ className="px-4 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-40"
188
+ >
189
+ Refine
190
+ </button>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ /**
198
+ * AskModelPopup
199
+ * Sub-popup for generating a mission statement and initial scope via an LLM.
200
+ * Two-column layout: left = controls/buttons, right = result output.
201
+ *
202
+ * Props:
203
+ * onUse(mission, scope) — called when the user accepts the generated result
204
+ * onClose() — called when the user cancels / closes
205
+ */
206
+ export function AskModelPopup({ onUse, onClose, onOpenSettings }) {
207
+ const [models, setModels] = useState([]);
208
+ const [selectedModelId, setSelectedModelId] = useState('');
209
+ const [selectedValidatorModelId, setSelectedValidatorModelId] = useState('');
210
+ const [description, setDescription] = useState('');
211
+ const [generating, setGenerating] = useState(false);
212
+ const [result, setResult] = useState(null);
213
+ const [error, setError] = useState('');
214
+ const [showIssuesPopup, setShowIssuesPopup] = useState(false);
215
+ const [showRefinePopup, setShowRefinePopup] = useState(false);
216
+
217
+ const { missionProgressLog, clearMissionProgress } = useCeremonyStore();
218
+ const latestProgress = missionProgressLog[missionProgressLog.length - 1] ?? null;
219
+
220
+ const [apiKeyStatus, setApiKeyStatus] = useState(null); // { anthropic: { isSet }, gemini: { isSet }, openai: { isSet } }
221
+
222
+ useEffect(() => {
223
+ Promise.all([getModels(), getSettings()])
224
+ .then(([data, settings]) => {
225
+ setModels(data);
226
+ setApiKeyStatus(settings.apiKeys ?? {});
227
+ // Auto-select first model whose provider has an API key
228
+ const firstReady = data.find((m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet);
229
+ const generatorId = firstReady ? firstReady.modelId : (data.length > 0 ? data[0].modelId : '');
230
+ setSelectedModelId(generatorId);
231
+ const otherReady = data.find(
232
+ (m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet && m.modelId !== generatorId
233
+ );
234
+ setSelectedValidatorModelId(otherReady ? otherReady.modelId : generatorId);
235
+ })
236
+ .catch(() => setError('Failed to load available models.'));
237
+ }, []);
238
+
239
+ function recheckKeys() {
240
+ getSettings()
241
+ .then((s) => setApiKeyStatus(s.apiKeys ?? {}))
242
+ .catch(() => {});
243
+ }
244
+
245
+ const selectedModel = models.find((m) => m.modelId === selectedModelId);
246
+ const selectedValidatorModel = models.find((m) => m.modelId === selectedValidatorModelId);
247
+
248
+ // Derive missing providers from currently selected models using settings key status
249
+ const missingKeyProviders = (() => {
250
+ if (!apiKeyStatus) return [];
251
+ const missing = new Set();
252
+ if (selectedModel && !apiKeyStatus[normalizeProvider(selectedModel.provider)]?.isSet) {
253
+ missing.add(selectedModel.provider);
254
+ }
255
+ if (selectedValidatorModel && !apiKeyStatus[normalizeProvider(selectedValidatorModel.provider)]?.isSet) {
256
+ missing.add(selectedValidatorModel.provider);
257
+ }
258
+ return [...missing];
259
+ })();
260
+
261
+ async function handleGenerate() {
262
+ if (!description.trim() || !selectedModelId || !selectedModel || !selectedValidatorModelId || !selectedValidatorModel) return;
263
+ setGenerating(true);
264
+ setError('');
265
+ setResult(null);
266
+ setShowIssuesPopup(false);
267
+ clearMissionProgress();
268
+ try {
269
+ const data = await generateMission(
270
+ description.trim(),
271
+ selectedModelId,
272
+ selectedModel.provider,
273
+ selectedValidatorModelId,
274
+ selectedValidatorModel.provider,
275
+ );
276
+ setResult(data);
277
+ } catch (err) {
278
+ setError(err.message || 'Generation failed. Please try again.');
279
+ } finally {
280
+ setGenerating(false);
281
+ }
282
+ }
283
+
284
+ async function handleRefine(refinementRequest) {
285
+ const { missionStatement, initialScope } = result;
286
+ setShowRefinePopup(false);
287
+ setResult(null);
288
+ setGenerating(true);
289
+ setError('');
290
+ setShowIssuesPopup(false);
291
+ clearMissionProgress();
292
+ try {
293
+ const data = await refineMission(
294
+ missionStatement, initialScope, refinementRequest,
295
+ selectedModelId, selectedModel.provider,
296
+ selectedValidatorModelId, selectedValidatorModel.provider,
297
+ );
298
+ setResult(data);
299
+ } catch (err) {
300
+ setError(err.message || 'Refinement failed. Please try again.');
301
+ } finally {
302
+ setGenerating(false);
303
+ }
304
+ }
305
+
306
+ function handleRefineFromIssues(selectedIssues) {
307
+ setShowIssuesPopup(false);
308
+ const request = 'Fix the following validation issues:\n' +
309
+ selectedIssues.map(i =>
310
+ `- [${i.severity.toUpperCase()}] ${i.field}: ${i.description} → ${i.suggestion}`
311
+ ).join('\n');
312
+ handleRefine(request);
313
+ }
314
+
315
+ function handleUse() {
316
+ if (!result) return;
317
+ onUse(result.missionStatement, result.initialScope);
318
+ }
319
+
320
+ function handleTryAgain() {
321
+ setResult(null);
322
+ setError('');
323
+ setShowIssuesPopup(false);
324
+ }
325
+
326
+ const providers = [...new Set(models.map((m) => m.provider))];
327
+ const scorePassed = result?.validationScore != null && result.validationScore >= 75;
328
+
329
+ const canGenerate =
330
+ !generating &&
331
+ description.trim().length > 0 &&
332
+ !!selectedModelId &&
333
+ !!selectedValidatorModelId &&
334
+ missingKeyProviders.length === 0;
335
+
336
+ // Shared model dropdown markup
337
+ function ModelSelect({ label, value, onChange }) {
338
+ const chosen = models.find((m) => m.modelId === value);
339
+ return (
340
+ <div>
341
+ <label className="block text-xs font-medium text-slate-500 mb-1">{label}</label>
342
+ <select
343
+ value={value}
344
+ onChange={(e) => onChange(e.target.value)}
345
+ disabled={generating || models.length === 0}
346
+ className="w-full rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-60 bg-white"
347
+ >
348
+ {models.length === 0 && <option value="">Loading…</option>}
349
+ {providers.map((p) => (
350
+ <optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
351
+ {models.filter((m) => m.provider === p).map((m) => (
352
+ <option key={m.modelId} value={m.modelId}>
353
+ {m.displayName}{!m.hasApiKey ? ' (no key)' : ''}
354
+ </option>
355
+ ))}
356
+ </optgroup>
357
+ ))}
358
+ </select>
359
+ {chosen && !chosen.hasApiKey && (
360
+ <p className="text-xs text-amber-600 mt-0.5">
361
+ ⚠ No API key — add to <code>.env</code>
362
+ </p>
363
+ )}
364
+ </div>
365
+ );
366
+ }
367
+
368
+ return (
369
+ <>
370
+ <div
371
+ className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
372
+ onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
373
+ >
374
+ {/* Card — fixed height two-column */}
375
+ <div className="w-full max-w-5xl bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden h-[540px]">
376
+
377
+ {/* Header */}
378
+ <div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
379
+ <div>
380
+ <h3 className="text-sm font-semibold text-slate-900">✨ Ask a Model</h3>
381
+ <p className="text-xs text-slate-400 mt-0.5">Describe your idea — an LLM generates and validates your mission &amp; scope.</p>
382
+ </div>
383
+ <button
384
+ type="button"
385
+ onClick={onClose}
386
+ className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
387
+ aria-label="Close"
388
+ >
389
+ ×
390
+ </button>
391
+ </div>
392
+
393
+ {/* Body — two columns */}
394
+ <div className="flex flex-1 min-h-0">
395
+
396
+ {/* ── LEFT column: controls ────────────────────────────────────────── */}
397
+ <div className="w-2/5 min-w-0 border-r border-slate-100 flex flex-col px-4 py-4 gap-3">
398
+
399
+ <ModelSelect
400
+ label="Generator Model"
401
+ value={selectedModelId}
402
+ onChange={setSelectedModelId}
403
+ />
404
+
405
+ <ModelSelect
406
+ label="Validator Model"
407
+ value={selectedValidatorModelId}
408
+ onChange={setSelectedValidatorModelId}
409
+ />
410
+
411
+ {missingKeyProviders.length > 0 && (
412
+ <div className="flex flex-col gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
413
+ <div className="flex items-start gap-2">
414
+ <AlertTriangle className="w-3.5 h-3.5 text-amber-500 flex-shrink-0 mt-0.5" />
415
+ <div>
416
+ <p className="text-xs font-semibold text-amber-900">API Key Missing</p>
417
+ <ul className="mt-1 space-y-0.5">
418
+ {missingKeyProviders.map((p) => (
419
+ <li key={p} className="flex items-center gap-1.5 text-xs text-amber-800">
420
+ <span className="w-1 h-1 rounded-full bg-amber-400 flex-shrink-0" />
421
+ {KEY_LABELS[p] || p}
422
+ </li>
423
+ ))}
424
+ </ul>
425
+ </div>
426
+ </div>
427
+ <div className="flex items-center gap-2">
428
+ {onOpenSettings && (
429
+ <button
430
+ type="button"
431
+ onClick={onOpenSettings}
432
+ className="flex items-center gap-1 text-xs font-medium bg-slate-900 text-white px-2.5 py-1 rounded-md hover:bg-slate-700 transition-colors"
433
+ >
434
+ <SettingsIcon className="w-3 h-3" />
435
+ Settings
436
+ </button>
437
+ )}
438
+ <button
439
+ type="button"
440
+ onClick={recheckKeys}
441
+ className="text-xs text-slate-500 hover:text-slate-800 transition-colors"
442
+ >
443
+ Re-check
444
+ </button>
445
+ </div>
446
+ </div>
447
+ )}
448
+
449
+ <div>
450
+ <label className="block text-xs font-medium text-slate-500 mb-1">
451
+ What are you building?
452
+ </label>
453
+ <textarea
454
+ value={description}
455
+ onChange={(e) => setDescription(e.target.value)}
456
+ rows={4}
457
+ placeholder="E.g. A recipe sharing app for home cooks…"
458
+ disabled={generating}
459
+ className="w-full rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none disabled:opacity-60"
460
+ />
461
+ </div>
462
+
463
+ {/* Error */}
464
+ {error && (
465
+ <p className="text-xs text-red-600 bg-red-50 rounded-md px-2 py-1.5">{error}</p>
466
+ )}
467
+
468
+ {/* Score badge + issues button */}
469
+ {result?.validationScore != null && (
470
+ <div className="flex items-center gap-2 flex-wrap">
471
+ <span
472
+ className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${
473
+ scorePassed
474
+ ? 'bg-green-50 text-green-700 border border-green-200'
475
+ : 'bg-amber-50 text-amber-700 border border-amber-200'
476
+ }`}
477
+ >
478
+ {scorePassed ? '✓' : '⚠'} {result.validationScore}/100
479
+ &nbsp;·&nbsp;
480
+ {result.iterations} iter{result.iterations !== 1 ? 's' : ''}
481
+ </span>
482
+ {result.issues?.length > 0 && (
483
+ <button
484
+ type="button"
485
+ onClick={() => setShowIssuesPopup(true)}
486
+ className="text-xs text-slate-400 hover:text-slate-600 underline underline-offset-2 transition-colors"
487
+ >
488
+ Issues ({result.issues.length})
489
+ </button>
490
+ )}
491
+ </div>
492
+ )}
493
+
494
+ {/* Spacer pushes buttons to bottom */}
495
+ <div className="flex-1" />
496
+
497
+ {/* Buttons */}
498
+ <div className="flex flex-col gap-2">
499
+ {result ? (
500
+ <>
501
+ <button
502
+ type="button"
503
+ onClick={handleUse}
504
+ className="w-full px-3 py-2 bg-slate-900 text-white text-xs font-medium rounded-lg hover:bg-slate-700 transition-colors"
505
+ >
506
+ Use This
507
+ </button>
508
+ <button
509
+ type="button"
510
+ onClick={() => setShowRefinePopup(true)}
511
+ className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
512
+ >
513
+ Refine
514
+ </button>
515
+ <button
516
+ type="button"
517
+ onClick={handleTryAgain}
518
+ className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
519
+ >
520
+ Try Again
521
+ </button>
522
+ </>
523
+ ) : (
524
+ <button
525
+ type="button"
526
+ onClick={handleGenerate}
527
+ disabled={!canGenerate}
528
+ className="w-full px-3 py-2 bg-blue-600 text-white text-xs font-medium rounded-lg disabled:opacity-40 hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
529
+ >
530
+ {generating ? (
531
+ <>
532
+ <span className="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
533
+ Generating…
534
+ </>
535
+ ) : 'Generate'}
536
+ </button>
537
+ )}
538
+ <button
539
+ type="button"
540
+ onClick={onClose}
541
+ className="w-full px-3 py-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors"
542
+ >
543
+ Cancel
544
+ </button>
545
+ </div>
546
+ </div>
547
+
548
+ {/* ── RIGHT column: output ─────────────────────────────────────────── */}
549
+ <div className="flex-1 flex flex-col min-w-0">
550
+
551
+ {/* Generating state */}
552
+ {generating && (
553
+ <div className="flex-1 flex flex-col items-center justify-center gap-3 px-8 text-center">
554
+ <span className="w-8 h-8 border-3 border-blue-200 border-t-blue-600 rounded-full animate-spin" style={{ borderWidth: 3 }} />
555
+ <p className="text-sm font-medium text-slate-700">
556
+ {latestProgress ? latestProgress.message : 'Starting…'}
557
+ </p>
558
+ <p className="text-xs text-slate-400">This may take a moment while the models validate and refine the output.</p>
559
+ </div>
560
+ )}
561
+
562
+ {/* Idle / no result yet */}
563
+ {!generating && !result && (
564
+ <div className="flex-1 flex flex-col items-center justify-center gap-2 px-8 text-center text-slate-300">
565
+ <svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
566
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
567
+ d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
568
+ </svg>
569
+ <p className="text-sm font-medium text-slate-400">Generated mission &amp; scope will appear here</p>
570
+ <p className="text-xs text-slate-300">Fill in the form and click Generate</p>
571
+ </div>
572
+ )}
573
+
574
+ {/* Result */}
575
+ {!generating && result && (
576
+ <div className="flex-1 flex flex-col gap-4 px-5 py-4 overflow-y-auto">
577
+ <div>
578
+ <p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Mission Statement</p>
579
+ <p className="text-sm text-slate-800 bg-slate-50 rounded-lg px-3 py-2.5 leading-relaxed">
580
+ {result.missionStatement}
581
+ </p>
582
+ </div>
583
+ <div className="flex-1 flex flex-col min-h-0">
584
+ <p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Initial Scope</p>
585
+ <div className="flex-1 text-sm text-slate-800 bg-slate-50 rounded-lg px-3 py-2.5 leading-relaxed overflow-y-auto min-h-0">
586
+ {result.initialScope.split('\n').map((line, i) => (
587
+ <p key={i} className="mb-1 last:mb-0">{line}</p>
588
+ ))}
589
+ </div>
590
+ </div>
591
+ </div>
592
+ )}
593
+ </div>
594
+ </div>
595
+ </div>
596
+ </div>
597
+
598
+ {/* Issues detail popup */}
599
+ {showIssuesPopup && result?.issues?.length > 0 && (
600
+ <IssuesPopup
601
+ issues={result.issues}
602
+ onClose={() => setShowIssuesPopup(false)}
603
+ onRefineWithIssues={handleRefineFromIssues}
604
+ />
605
+ )}
606
+
607
+ {/* Refine popup */}
608
+ {showRefinePopup && result && (
609
+ <RefinePopup
610
+ onSubmit={handleRefine}
611
+ onClose={() => setShowRefinePopup(false)}
612
+ />
613
+ )}
614
+ </>
615
+ );
616
+ }