@agile-vibe-coding/avc 0.2.3 → 0.3.2

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 (262) hide show
  1. package/README.md +475 -3
  2. package/cli/agents/agent-selector.md +23 -0
  3. package/cli/agents/code-implementer.md +117 -0
  4. package/cli/agents/code-validator.md +80 -0
  5. package/cli/agents/context-reviewer-epic.md +101 -0
  6. package/cli/agents/context-reviewer-story.md +92 -0
  7. package/cli/agents/context-writer-epic.md +145 -0
  8. package/cli/agents/context-writer-story.md +111 -0
  9. package/cli/agents/doc-writer-epic.md +42 -0
  10. package/cli/agents/doc-writer-story.md +43 -0
  11. package/cli/agents/duplicate-detector.md +110 -0
  12. package/cli/agents/epic-story-decomposer.md +318 -39
  13. package/cli/agents/mission-scope-generator.md +68 -4
  14. package/cli/agents/mission-scope-validator.md +40 -6
  15. package/cli/agents/project-context-extractor.md +21 -6
  16. package/cli/agents/scaffolding-generator.md +99 -0
  17. package/cli/agents/seed-validator.md +71 -0
  18. package/cli/agents/story-scope-reviewer.md +147 -0
  19. package/cli/agents/story-splitter.md +83 -0
  20. package/cli/agents/validator-documentation.json +31 -0
  21. package/cli/agents/validator-documentation.md +3 -1
  22. package/cli/api-reference-tool.js +368 -0
  23. package/cli/checks/catalog.json +76 -0
  24. package/cli/checks/code/quality.json +26 -0
  25. package/cli/checks/code/testing.json +14 -0
  26. package/cli/checks/code/traceability.json +26 -0
  27. package/cli/checks/cross-refs/epic.json +171 -0
  28. package/cli/checks/cross-refs/story.json +149 -0
  29. package/cli/checks/epic/api.json +114 -0
  30. package/cli/checks/epic/backend.json +126 -0
  31. package/cli/checks/epic/cloud.json +126 -0
  32. package/cli/checks/epic/data.json +102 -0
  33. package/cli/checks/epic/database.json +114 -0
  34. package/cli/checks/epic/developer.json +182 -0
  35. package/cli/checks/epic/devops.json +174 -0
  36. package/cli/checks/epic/frontend.json +162 -0
  37. package/cli/checks/epic/mobile.json +102 -0
  38. package/cli/checks/epic/qa.json +90 -0
  39. package/cli/checks/epic/security.json +184 -0
  40. package/cli/checks/epic/solution-architect.json +192 -0
  41. package/cli/checks/epic/test-architect.json +90 -0
  42. package/cli/checks/epic/ui.json +102 -0
  43. package/cli/checks/epic/ux.json +90 -0
  44. package/cli/checks/fixes/epic-fix-template.md +10 -0
  45. package/cli/checks/fixes/story-fix-template.md +10 -0
  46. package/cli/checks/story/api.json +186 -0
  47. package/cli/checks/story/backend.json +102 -0
  48. package/cli/checks/story/cloud.json +102 -0
  49. package/cli/checks/story/data.json +210 -0
  50. package/cli/checks/story/database.json +102 -0
  51. package/cli/checks/story/developer.json +168 -0
  52. package/cli/checks/story/devops.json +102 -0
  53. package/cli/checks/story/frontend.json +174 -0
  54. package/cli/checks/story/mobile.json +102 -0
  55. package/cli/checks/story/qa.json +210 -0
  56. package/cli/checks/story/security.json +198 -0
  57. package/cli/checks/story/solution-architect.json +230 -0
  58. package/cli/checks/story/test-architect.json +210 -0
  59. package/cli/checks/story/ui.json +102 -0
  60. package/cli/checks/story/ux.json +102 -0
  61. package/cli/coding-order.js +401 -0
  62. package/cli/dependency-checker.js +72 -0
  63. package/cli/epic-story-validator.js +284 -799
  64. package/cli/index.js +0 -0
  65. package/cli/init-model-config.js +17 -10
  66. package/cli/init.js +514 -92
  67. package/cli/kanban-server-manager.js +1 -2
  68. package/cli/llm-claude.js +98 -31
  69. package/cli/llm-gemini.js +29 -5
  70. package/cli/llm-local.js +493 -0
  71. package/cli/llm-openai.js +262 -41
  72. package/cli/llm-provider.js +147 -8
  73. package/cli/llm-token-limits.js +113 -4
  74. package/cli/llm-verifier.js +209 -1
  75. package/cli/llm-xiaomi.js +143 -0
  76. package/cli/message-constants.js +3 -12
  77. package/cli/messaging-api.js +6 -12
  78. package/cli/micro-check-fixer.js +335 -0
  79. package/cli/micro-check-runner.js +449 -0
  80. package/cli/micro-check-scorer.js +148 -0
  81. package/cli/micro-check-validator.js +538 -0
  82. package/cli/model-pricing.js +23 -0
  83. package/cli/model-selector.js +3 -2
  84. package/cli/prompt-logger.js +57 -0
  85. package/cli/repl-ink.js +106 -346
  86. package/cli/repl-old.js +1 -2
  87. package/cli/seed-processor.js +194 -24
  88. package/cli/sprint-planning-processor.js +2638 -289
  89. package/cli/template-processor.js +50 -3
  90. package/cli/token-tracker.js +50 -23
  91. package/cli/tools/generate-story-validators.js +1 -1
  92. package/cli/validation-router.js +70 -8
  93. package/cli/worktree-runner.js +654 -0
  94. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  95. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  96. package/kanban/client/dist/index.html +2 -2
  97. package/kanban/client/src/App.jsx +43 -14
  98. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
  99. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
  100. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
  101. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  102. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
  103. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
  104. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
  105. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
  106. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
  107. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
  108. package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
  109. package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
  110. package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
  111. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
  112. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  113. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  114. package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
  115. package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
  116. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
  117. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  118. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
  119. package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
  120. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  121. package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
  122. package/kanban/client/src/components/stats/CostModal.jsx +34 -3
  123. package/kanban/client/src/hooks/useGrouping.js +59 -0
  124. package/kanban/client/src/lib/api.js +118 -4
  125. package/kanban/client/src/lib/status-grouping.js +10 -0
  126. package/kanban/client/src/store/kanbanStore.js +8 -0
  127. package/kanban/server/index.js +23 -2
  128. package/kanban/server/routes/ceremony.js +153 -4
  129. package/kanban/server/routes/costs.js +9 -3
  130. package/kanban/server/routes/openai-oauth.js +366 -0
  131. package/kanban/server/routes/settings.js +447 -14
  132. package/kanban/server/routes/websocket.js +7 -2
  133. package/kanban/server/routes/work-items.js +141 -1
  134. package/kanban/server/services/CeremonyService.js +275 -24
  135. package/kanban/server/services/TaskRunnerService.js +261 -0
  136. package/kanban/server/workers/run-task-worker.js +121 -0
  137. package/kanban/server/workers/seed-worker.js +94 -0
  138. package/kanban/server/workers/sponsor-call-worker.js +14 -6
  139. package/kanban/server/workers/sprint-planning-worker.js +94 -12
  140. package/package.json +2 -3
  141. package/cli/agents/solver-epic-api.json +0 -15
  142. package/cli/agents/solver-epic-api.md +0 -39
  143. package/cli/agents/solver-epic-backend.json +0 -15
  144. package/cli/agents/solver-epic-backend.md +0 -39
  145. package/cli/agents/solver-epic-cloud.json +0 -15
  146. package/cli/agents/solver-epic-cloud.md +0 -39
  147. package/cli/agents/solver-epic-data.json +0 -15
  148. package/cli/agents/solver-epic-data.md +0 -39
  149. package/cli/agents/solver-epic-database.json +0 -15
  150. package/cli/agents/solver-epic-database.md +0 -39
  151. package/cli/agents/solver-epic-developer.json +0 -15
  152. package/cli/agents/solver-epic-developer.md +0 -39
  153. package/cli/agents/solver-epic-devops.json +0 -15
  154. package/cli/agents/solver-epic-devops.md +0 -39
  155. package/cli/agents/solver-epic-frontend.json +0 -15
  156. package/cli/agents/solver-epic-frontend.md +0 -39
  157. package/cli/agents/solver-epic-mobile.json +0 -15
  158. package/cli/agents/solver-epic-mobile.md +0 -39
  159. package/cli/agents/solver-epic-qa.json +0 -15
  160. package/cli/agents/solver-epic-qa.md +0 -39
  161. package/cli/agents/solver-epic-security.json +0 -15
  162. package/cli/agents/solver-epic-security.md +0 -39
  163. package/cli/agents/solver-epic-solution-architect.json +0 -15
  164. package/cli/agents/solver-epic-solution-architect.md +0 -39
  165. package/cli/agents/solver-epic-test-architect.json +0 -15
  166. package/cli/agents/solver-epic-test-architect.md +0 -39
  167. package/cli/agents/solver-epic-ui.json +0 -15
  168. package/cli/agents/solver-epic-ui.md +0 -39
  169. package/cli/agents/solver-epic-ux.json +0 -15
  170. package/cli/agents/solver-epic-ux.md +0 -39
  171. package/cli/agents/solver-story-api.json +0 -15
  172. package/cli/agents/solver-story-api.md +0 -39
  173. package/cli/agents/solver-story-backend.json +0 -15
  174. package/cli/agents/solver-story-backend.md +0 -39
  175. package/cli/agents/solver-story-cloud.json +0 -15
  176. package/cli/agents/solver-story-cloud.md +0 -39
  177. package/cli/agents/solver-story-data.json +0 -15
  178. package/cli/agents/solver-story-data.md +0 -39
  179. package/cli/agents/solver-story-database.json +0 -15
  180. package/cli/agents/solver-story-database.md +0 -39
  181. package/cli/agents/solver-story-developer.json +0 -15
  182. package/cli/agents/solver-story-developer.md +0 -39
  183. package/cli/agents/solver-story-devops.json +0 -15
  184. package/cli/agents/solver-story-devops.md +0 -39
  185. package/cli/agents/solver-story-frontend.json +0 -15
  186. package/cli/agents/solver-story-frontend.md +0 -39
  187. package/cli/agents/solver-story-mobile.json +0 -15
  188. package/cli/agents/solver-story-mobile.md +0 -39
  189. package/cli/agents/solver-story-qa.json +0 -15
  190. package/cli/agents/solver-story-qa.md +0 -39
  191. package/cli/agents/solver-story-security.json +0 -15
  192. package/cli/agents/solver-story-security.md +0 -39
  193. package/cli/agents/solver-story-solution-architect.json +0 -15
  194. package/cli/agents/solver-story-solution-architect.md +0 -39
  195. package/cli/agents/solver-story-test-architect.json +0 -15
  196. package/cli/agents/solver-story-test-architect.md +0 -39
  197. package/cli/agents/solver-story-ui.json +0 -15
  198. package/cli/agents/solver-story-ui.md +0 -39
  199. package/cli/agents/solver-story-ux.json +0 -15
  200. package/cli/agents/solver-story-ux.md +0 -39
  201. package/cli/agents/validator-epic-api.json +0 -93
  202. package/cli/agents/validator-epic-api.md +0 -137
  203. package/cli/agents/validator-epic-backend.json +0 -93
  204. package/cli/agents/validator-epic-backend.md +0 -130
  205. package/cli/agents/validator-epic-cloud.json +0 -93
  206. package/cli/agents/validator-epic-cloud.md +0 -137
  207. package/cli/agents/validator-epic-data.json +0 -93
  208. package/cli/agents/validator-epic-data.md +0 -130
  209. package/cli/agents/validator-epic-database.json +0 -93
  210. package/cli/agents/validator-epic-database.md +0 -137
  211. package/cli/agents/validator-epic-developer.json +0 -74
  212. package/cli/agents/validator-epic-developer.md +0 -153
  213. package/cli/agents/validator-epic-devops.json +0 -74
  214. package/cli/agents/validator-epic-devops.md +0 -153
  215. package/cli/agents/validator-epic-frontend.json +0 -74
  216. package/cli/agents/validator-epic-frontend.md +0 -153
  217. package/cli/agents/validator-epic-mobile.json +0 -93
  218. package/cli/agents/validator-epic-mobile.md +0 -130
  219. package/cli/agents/validator-epic-qa.json +0 -93
  220. package/cli/agents/validator-epic-qa.md +0 -130
  221. package/cli/agents/validator-epic-security.json +0 -74
  222. package/cli/agents/validator-epic-security.md +0 -154
  223. package/cli/agents/validator-epic-solution-architect.json +0 -74
  224. package/cli/agents/validator-epic-solution-architect.md +0 -156
  225. package/cli/agents/validator-epic-test-architect.json +0 -93
  226. package/cli/agents/validator-epic-test-architect.md +0 -130
  227. package/cli/agents/validator-epic-ui.json +0 -93
  228. package/cli/agents/validator-epic-ui.md +0 -130
  229. package/cli/agents/validator-epic-ux.json +0 -93
  230. package/cli/agents/validator-epic-ux.md +0 -130
  231. package/cli/agents/validator-story-api.json +0 -104
  232. package/cli/agents/validator-story-api.md +0 -152
  233. package/cli/agents/validator-story-backend.json +0 -104
  234. package/cli/agents/validator-story-backend.md +0 -152
  235. package/cli/agents/validator-story-cloud.json +0 -104
  236. package/cli/agents/validator-story-cloud.md +0 -152
  237. package/cli/agents/validator-story-data.json +0 -104
  238. package/cli/agents/validator-story-data.md +0 -152
  239. package/cli/agents/validator-story-database.json +0 -104
  240. package/cli/agents/validator-story-database.md +0 -152
  241. package/cli/agents/validator-story-developer.json +0 -104
  242. package/cli/agents/validator-story-developer.md +0 -152
  243. package/cli/agents/validator-story-devops.json +0 -104
  244. package/cli/agents/validator-story-devops.md +0 -152
  245. package/cli/agents/validator-story-frontend.json +0 -104
  246. package/cli/agents/validator-story-frontend.md +0 -152
  247. package/cli/agents/validator-story-mobile.json +0 -104
  248. package/cli/agents/validator-story-mobile.md +0 -152
  249. package/cli/agents/validator-story-qa.json +0 -104
  250. package/cli/agents/validator-story-qa.md +0 -152
  251. package/cli/agents/validator-story-security.json +0 -104
  252. package/cli/agents/validator-story-security.md +0 -152
  253. package/cli/agents/validator-story-solution-architect.json +0 -104
  254. package/cli/agents/validator-story-solution-architect.md +0 -152
  255. package/cli/agents/validator-story-test-architect.json +0 -104
  256. package/cli/agents/validator-story-test-architect.md +0 -152
  257. package/cli/agents/validator-story-ui.json +0 -104
  258. package/cli/agents/validator-story-ui.md +0 -152
  259. package/cli/agents/validator-story-ux.json +0 -104
  260. package/cli/agents/validator-story-ux.md +0 -152
  261. package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
  262. package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
  import { ArrowDownToLine } from 'lucide-react';
3
3
  import { useCeremonyStore } from '../../../store/ceremonyStore';
4
4
  import { resetCeremony } from '../../../lib/api';
@@ -32,6 +32,17 @@ function buildStageGroups(progressLog) {
32
32
  export function RunningStep({ transitioning, onPause, onResume, onCancel, onBackground }) {
33
33
  const { progressLog, ceremonyStatus, ceremonyError, isPaused, setWizardStep, setCeremonyStatus, setCeremonyError } = useCeremonyStore();
34
34
  const logBottomRef = useRef(null);
35
+ const [showForceStop, setShowForceStop] = useState(false);
36
+
37
+ // Show "Force Stop" button after 5s of cancelling
38
+ useEffect(() => {
39
+ if (transitioning === 'cancelling') {
40
+ setShowForceStop(false);
41
+ const timer = setTimeout(() => setShowForceStop(true), 5000);
42
+ return () => clearTimeout(timer);
43
+ }
44
+ setShowForceStop(false);
45
+ }, [transitioning]);
35
46
 
36
47
  const handleForceReset = async () => {
37
48
  try { await resetCeremony(); } catch (_) {}
@@ -197,7 +208,17 @@ export function RunningStep({ transitioning, onPause, onResume, onCancel, onBack
197
208
  {transitioning === 'pausing' ? (
198
209
  <span className="flex items-center gap-1.5 text-xs text-slate-400"><span className="inline-block w-3 h-3 border border-slate-400 border-t-slate-200 rounded-full animate-spin" />Pausing…</span>
199
210
  ) : transitioning === 'cancelling' ? (
200
- <span className="flex items-center gap-1.5 text-xs text-red-400"><span className="inline-block w-3 h-3 border border-red-400 border-t-red-200 rounded-full animate-spin" />Cancelling…</span>
211
+ <div className="flex items-center gap-3">
212
+ <span className="flex items-center gap-1.5 text-xs text-red-400"><span className="inline-block w-3 h-3 border border-red-400 border-t-red-200 rounded-full animate-spin" />Cancelling…</span>
213
+ {showForceStop && (
214
+ <button
215
+ onClick={handleForceReset}
216
+ className="px-3 py-1.5 text-xs rounded-lg bg-red-600 text-white hover:bg-red-700 transition-colors"
217
+ >
218
+ Force Stop
219
+ </button>
220
+ )}
221
+ </div>
201
222
  ) : !isPaused ? (
202
223
  <button
203
224
  onClick={onPause}
@@ -8,6 +8,8 @@ import {
8
8
  } from '../ui/dialog';
9
9
  import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
10
10
  import { Badge } from '../ui/badge';
11
+ import { SeedButton } from './SeedButton';
12
+ import { RunButton } from './RunButton';
11
13
  import {
12
14
  FileText,
13
15
  BookOpen,
@@ -22,6 +24,8 @@ import {
22
24
  Save,
23
25
  ChevronUp,
24
26
  Wand2,
27
+ ListChecks,
28
+ Lock,
25
29
  } from 'lucide-react';
26
30
  import {
27
31
  getWorkItem,
@@ -33,6 +37,7 @@ import {
33
37
  import { getStatusMetadata } from '../../lib/status-grouping';
34
38
  import { cn } from '../../lib/utils';
35
39
  import { RefineWorkItemPopup } from './RefineWorkItemPopup';
40
+ import { useKanbanStore } from '../../store/kanbanStore';
36
41
 
37
42
  /**
38
43
  * Clickable item box — same visual style as the children list.
@@ -151,6 +156,9 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
151
156
  // Parent chain for navigation
152
157
  const [parentChain, setParentChain] = useState([]);
153
158
 
159
+ // Read-only during active ceremony
160
+ const ceremonyActive = useKanbanStore((s) => s.ceremonyActive);
161
+
154
162
  // Refine popup state
155
163
  const [refineOpen, setRefineOpen] = useState(false);
156
164
 
@@ -302,6 +310,11 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
302
310
  Children ({fullDetails.children.length})
303
311
  </TabsTrigger>
304
312
  )}
313
+ {fullDetails?.functions && fullDetails.functions.length > 0 && (
314
+ <TabsTrigger value="code">
315
+ Code ({fullDetails.functions.length})
316
+ </TabsTrigger>
317
+ )}
305
318
  </TabsList>
306
319
  </Tabs>
307
320
 
@@ -334,14 +347,24 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
334
347
  </div>
335
348
  );
336
349
  })()}
337
- {fullDetails && (
338
- <button
339
- onClick={() => setRefineOpen(true)}
340
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-violet-700 bg-violet-50 border border-violet-200 rounded-lg hover:bg-violet-100 transition-colors"
341
- >
342
- <Wand2 className="w-3.5 h-3.5" />
343
- Refine with AI
344
- </button>
350
+ {fullDetails && !ceremonyActive && (
351
+ <div className="flex items-center gap-2">
352
+ {/* Seed button — stories without children */}
353
+ {workItem.type === 'story' && (!fullDetails.children || fullDetails.children.length === 0) && (
354
+ <SeedButton storyId={workItem.id} onStarted={loadFullDetails} />
355
+ )}
356
+ {/* Run button — tasks that are planned, ready, or failed */}
357
+ {workItem.type === 'task' && (workItem.status === 'planned' || workItem.status === 'ready' || workItem.status === 'failed') && (
358
+ <RunButton taskId={workItem.id} onStarted={loadFullDetails} />
359
+ )}
360
+ <button
361
+ onClick={() => setRefineOpen(true)}
362
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-violet-700 bg-violet-50 border border-violet-200 rounded-lg hover:bg-violet-100 transition-colors"
363
+ >
364
+ <Wand2 className="w-3.5 h-3.5" />
365
+ Refine with AI
366
+ </button>
367
+ </div>
345
368
  )}
346
369
  </div>
347
370
  </div>
@@ -371,7 +394,11 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
371
394
  )}
372
395
  {/* Edit toolbar */}
373
396
  <div className="flex justify-end mb-2">
374
- {editingDoc ? (
397
+ {ceremonyActive ? (
398
+ <span className="flex items-center gap-1 px-2 py-1 text-xs text-slate-400">
399
+ <Lock className="w-3 h-3" /> Read-only during ceremony
400
+ </span>
401
+ ) : editingDoc ? (
375
402
  <div className="flex gap-2">
376
403
  <button
377
404
  onClick={() => setEditingDoc(false)}
@@ -449,12 +476,45 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
449
476
  );
450
477
  })()}
451
478
 
479
+ {/* Acceptance Criteria (stories use .acceptance, epics use .features) */}
480
+ {(() => {
481
+ const items = workItem.type === 'epic'
482
+ ? fullDetails?.features
483
+ : fullDetails?.acceptance;
484
+ if (!items || items.length === 0) return null;
485
+ return (
486
+ <div>
487
+ <div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
488
+ <ListChecks className="w-4 h-4" />
489
+ <span>Acceptance Criteria</span>
490
+ <span className="ml-auto flex items-center gap-1 text-xs font-normal text-slate-400">
491
+ <Lock className="w-3 h-3" />
492
+ updated by tests
493
+ </span>
494
+ </div>
495
+ <ul className="space-y-1.5">
496
+ {items.map((ac, idx) => (
497
+ <li key={idx} className="flex items-start gap-2.5 text-sm text-slate-700">
498
+ <input
499
+ type="checkbox"
500
+ readOnly
501
+ disabled
502
+ className="mt-0.5 h-4 w-4 flex-shrink-0 rounded border-slate-300 text-indigo-600 cursor-not-allowed opacity-60"
503
+ />
504
+ <span className="leading-snug">{ac}</span>
505
+ </li>
506
+ ))}
507
+ </ul>
508
+ </div>
509
+ );
510
+ })()}
511
+
452
512
  {/* Dependencies */}
453
513
  {fullDetails?.dependencies && fullDetails.dependencies.length > 0 && (
454
514
  <div>
455
515
  <div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
456
516
  <Link2 className="w-4 h-4" />
457
- <span>Dependencies ({fullDetails.dependencies.length})</span>
517
+ <span>Depends On ({fullDetails.dependencies.length})</span>
458
518
  </div>
459
519
  <div className="space-y-2">
460
520
  {fullDetails.dependencies.map((depId) => {
@@ -516,6 +576,33 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
516
576
  </div>
517
577
  </TabsContent>
518
578
  )}
579
+
580
+ {/* Code Tab — Function Registry */}
581
+ {fullDetails?.functions && fullDetails.functions.length > 0 && (
582
+ <TabsContent value="code">
583
+ <div className="space-y-2">
584
+ {fullDetails.functions.map((fn, idx) => (
585
+ <div key={fn.name || idx} className="flex items-start gap-3 p-2 rounded-lg border border-slate-200 hover:bg-slate-50">
586
+ <div className="flex-1 min-w-0">
587
+ <div className="flex items-center gap-2">
588
+ <code className="text-sm font-mono text-blue-700 truncate">{fn.name}</code>
589
+ {fn.pure && (
590
+ <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-green-100 text-green-700 border border-green-200">pure</span>
591
+ )}
592
+ {fn.type === 'exported' && (
593
+ <span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-blue-100 text-blue-700 border border-blue-200">export</span>
594
+ )}
595
+ </div>
596
+ <div className="text-xs text-slate-500 mt-0.5 truncate">{fn.file}</div>
597
+ {fn.satisfies && <div className="text-xs text-slate-400 mt-0.5">{fn.satisfies}</div>}
598
+ {fn.task && <div className="text-xs text-slate-400 mt-0.5">Task: {fn.task}</div>}
599
+ </div>
600
+ {fn.lines && <span className="text-xs text-slate-400 whitespace-nowrap">{fn.lines}L</span>}
601
+ </div>
602
+ ))}
603
+ </div>
604
+ </TabsContent>
605
+ )}
519
606
  </Tabs>
520
607
  )}
521
608
  </div>
@@ -1,4 +1,4 @@
1
- import { LayoutGrid, Package, Box } from 'lucide-react';
1
+ import { LayoutGrid, Package, Box, ListOrdered } from 'lucide-react';
2
2
  import { useFilterStore } from '../../store/filterStore';
3
3
  import { cn } from '../../lib/utils';
4
4
 
@@ -28,6 +28,12 @@ export function GroupingSelector() {
28
28
  icon: Box,
29
29
  description: 'Separate boards by type',
30
30
  },
31
+ {
32
+ value: 'phase',
33
+ label: 'Phase',
34
+ icon: ListOrdered,
35
+ description: 'Implementation order by dependencies',
36
+ },
31
37
  ];
32
38
 
33
39
  return (
@@ -59,21 +59,30 @@ export function KanbanCard({ workItem, onClick }) {
59
59
  {statusMeta?.icon} {statusMeta?.label}
60
60
  </div>
61
61
 
62
- {/* Type Badge */}
63
- <div
64
- className={cn(
65
- 'px-2 py-1 rounded border text-xs font-medium',
66
- typeMeta.color === 'indigo' &&
67
- 'border-indigo-300 bg-indigo-50 text-indigo-700',
68
- typeMeta.color === 'blue' &&
69
- 'border-blue-300 bg-blue-50 text-blue-700',
70
- typeMeta.color === 'emerald' &&
71
- 'border-emerald-300 bg-emerald-50 text-emerald-700',
72
- typeMeta.color === 'gray' &&
73
- 'border-gray-300 bg-gray-50 text-gray-700'
62
+ <div className="flex items-center gap-1.5">
63
+ {/* Phase Badge */}
64
+ {workItem.metadata?.codingPhase && (
65
+ <div className="px-1.5 py-0.5 rounded text-[10px] font-bold bg-amber-100 text-amber-800 border border-amber-300" title={`Implementation Phase ${workItem.metadata.codingPhase}`}>
66
+ P{workItem.metadata.codingPhase}
67
+ </div>
74
68
  )}
75
- >
76
- {typeMeta.label}
69
+
70
+ {/* Type Badge */}
71
+ <div
72
+ className={cn(
73
+ 'px-2 py-1 rounded border text-xs font-medium',
74
+ typeMeta.color === 'indigo' &&
75
+ 'border-indigo-300 bg-indigo-50 text-indigo-700',
76
+ typeMeta.color === 'blue' &&
77
+ 'border-blue-300 bg-blue-50 text-blue-700',
78
+ typeMeta.color === 'emerald' &&
79
+ 'border-emerald-300 bg-emerald-50 text-emerald-700',
80
+ typeMeta.color === 'gray' &&
81
+ 'border-gray-300 bg-gray-50 text-gray-700'
82
+ )}
83
+ >
84
+ {typeMeta.label}
85
+ </div>
77
86
  </div>
78
87
  </div>
79
88
  </div>
@@ -249,21 +249,16 @@ export function RefineWorkItemPopup({
249
249
  Promise.all([getModels(), getSettings()])
250
250
  .then(([data, settings]) => {
251
251
  setModels(data);
252
- const firstReady = data.find(
253
- (m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet
254
- );
255
- const genId = firstReady
256
- ? firstReady.modelId
257
- : data.length > 0
258
- ? data[0].modelId
259
- : '';
252
+ const ready = data.filter((m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet);
253
+ const isPro = (id) => /pro|opus|sonnet/i.test(id);
254
+ const isLite = (id) => /flash|lite|haiku|mini/i.test(id);
255
+ const generator = ready.find((m) => isPro(m.modelId)) || ready[0];
256
+ const genId = generator ? generator.modelId : (data.length > 0 ? data[0].modelId : '');
260
257
  setSelectedModelId(genId);
261
- const otherReady = data.find(
262
- (m) =>
263
- settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet &&
264
- m.modelId !== genId
265
- );
266
- setSelectedValidatorModelId(otherReady ? otherReady.modelId : genId);
258
+ const validator = ready.find((m) => isLite(m.modelId) && m.modelId !== genId)
259
+ || ready.find((m) => m.modelId !== genId)
260
+ || generator;
261
+ setSelectedValidatorModelId(validator ? validator.modelId : genId);
267
262
  })
268
263
  .catch(() => setConfigError('Failed to load models.'));
269
264
  }, []);
@@ -0,0 +1,162 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Play, Loader2, AlertTriangle, X, ChevronDown, ChevronUp } from 'lucide-react';
3
+ import { getDependencyStatus, startRunTask } from '../../lib/api';
4
+ import { cn } from '../../lib/utils';
5
+
6
+ /**
7
+ * Run Button — runs a task in a git worktree (implement + test + commit).
8
+ * Shows dependency check, and streaming progress log.
9
+ */
10
+ export function RunButton({ taskId, onStarted }) {
11
+ const [state, setState] = useState('idle'); // idle | checking | blocked | running | complete | error
12
+ const [blockers, setBlockers] = useState([]);
13
+ const [error, setError] = useState(null);
14
+ const [progress, setProgress] = useState([]);
15
+ const [showLog, setShowLog] = useState(false);
16
+ const [processId, setProcessId] = useState(null);
17
+ const logRef = useRef(null);
18
+
19
+ useEffect(() => {
20
+ if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
21
+ }, [progress]);
22
+
23
+ // Listen for WebSocket run-task events dispatched by App.jsx
24
+ useEffect(() => {
25
+ if (!processId) return;
26
+
27
+ const handler = (event) => {
28
+ const msg = event.detail;
29
+ if (!msg || msg.processId !== processId) return;
30
+
31
+ if (msg.type === 'run-task:progress') {
32
+ setProgress(prev => [...prev, msg.message]);
33
+ } else if (msg.type === 'run-task:complete') {
34
+ setState('complete');
35
+ setProgress(prev => [...prev, 'Task complete — code committed.']);
36
+ onStarted?.();
37
+ } else if (msg.type === 'run-task:error') {
38
+ setState('error');
39
+ setError(msg.error || 'Run failed');
40
+ setProgress(prev => [...prev, `Error: ${msg.error}`]);
41
+ }
42
+ };
43
+
44
+ window.addEventListener('avc-ws-message', handler);
45
+ return () => window.removeEventListener('avc-ws-message', handler);
46
+ }, [processId, onStarted]);
47
+
48
+ const handleClick = async () => {
49
+ if (state === 'checking' || state === 'running') return;
50
+
51
+ setState('checking');
52
+ setError(null);
53
+ setBlockers([]);
54
+ setProgress([]);
55
+
56
+ try {
57
+ const depStatus = await getDependencyStatus(taskId);
58
+ if (!depStatus.ready) {
59
+ setState('blocked');
60
+ setBlockers(depStatus.blockers || []);
61
+ return;
62
+ }
63
+
64
+ setState('running');
65
+ setShowLog(true);
66
+ setProgress(['Starting task implementation...']);
67
+ const result = await startRunTask(taskId);
68
+ setProcessId(result.processId);
69
+ setProgress(prev => [...prev, `Process started: ${result.processId}`]);
70
+ } catch (err) {
71
+ setError(err.message);
72
+ setState('error');
73
+ }
74
+ };
75
+
76
+ const dismiss = () => {
77
+ setState('idle');
78
+ setBlockers([]);
79
+ setError(null);
80
+ setProgress([]);
81
+ setShowLog(false);
82
+ setProcessId(null);
83
+ };
84
+
85
+ if (state === 'blocked') {
86
+ return (
87
+ <div className="space-y-2">
88
+ <div className="flex items-center gap-2 text-amber-700 text-sm bg-amber-50 border border-amber-200 rounded-lg px-3 py-2">
89
+ <AlertTriangle className="w-4 h-4 flex-shrink-0" />
90
+ <div>
91
+ <div className="font-medium">Dependencies not met</div>
92
+ <ul className="mt-1 text-xs space-y-0.5">
93
+ {blockers.map((b) => (<li key={b.id}>{b.id}: {b.name} ({b.status})</li>))}
94
+ </ul>
95
+ </div>
96
+ </div>
97
+ <button onClick={dismiss} className="text-xs text-slate-500 hover:text-slate-700">Dismiss</button>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ if ((state === 'running' || state === 'complete' || state === 'error') && showLog) {
103
+ return (
104
+ <div className="space-y-2">
105
+ <div className="flex items-center justify-between">
106
+ <div className="flex items-center gap-2 text-sm font-medium">
107
+ {state === 'running' && <Loader2 className="w-4 h-4 animate-spin text-blue-600" />}
108
+ {state === 'complete' && <Play className="w-4 h-4 text-green-600" />}
109
+ {state === 'error' && <AlertTriangle className="w-4 h-4 text-red-600" />}
110
+ <span className={cn(
111
+ state === 'running' && 'text-blue-700',
112
+ state === 'complete' && 'text-green-700',
113
+ state === 'error' && 'text-red-700',
114
+ )}>
115
+ {state === 'running' ? 'Implementing...' : state === 'complete' ? 'Complete' : 'Failed'}
116
+ </span>
117
+ </div>
118
+ <div className="flex items-center gap-1">
119
+ <button onClick={() => setShowLog(!showLog)} className="p-1 text-slate-400 hover:text-slate-600">
120
+ {showLog ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
121
+ </button>
122
+ {state !== 'running' && (
123
+ <button onClick={dismiss} className="p-1 text-slate-400 hover:text-slate-600">
124
+ <X className="w-3.5 h-3.5" />
125
+ </button>
126
+ )}
127
+ </div>
128
+ </div>
129
+ {showLog && (
130
+ <div ref={logRef} className="max-h-32 overflow-y-auto bg-slate-900 text-slate-300 text-xs font-mono rounded-md p-2 space-y-0.5">
131
+ {progress.map((msg, i) => (
132
+ <div key={i} className={cn(
133
+ msg.startsWith('Error') && 'text-red-400',
134
+ msg.includes('complete') && 'text-green-400',
135
+ )}>{msg}</div>
136
+ ))}
137
+ {state === 'running' && <div className="text-slate-500 animate-pulse">...</div>}
138
+ </div>
139
+ )}
140
+ </div>
141
+ );
142
+ }
143
+
144
+ return (
145
+ <div className="space-y-1">
146
+ <button
147
+ onClick={handleClick}
148
+ disabled={state === 'checking'}
149
+ className={cn(
150
+ 'flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors',
151
+ state === 'checking'
152
+ ? 'bg-blue-100 text-blue-700 cursor-wait'
153
+ : 'bg-blue-600 text-white hover:bg-blue-700'
154
+ )}
155
+ >
156
+ {state === 'checking' ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
157
+ {state === 'checking' ? 'Checking...' : 'Run'}
158
+ </button>
159
+ {error && <p className="text-xs text-red-600">{error}</p>}
160
+ </div>
161
+ );
162
+ }
@@ -0,0 +1,176 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Sprout, Loader2, AlertTriangle, X, ChevronDown, ChevronUp, Settings } from 'lucide-react';
3
+ import { getDependencyStatus, startSeedCeremony, getSettings } from '../../lib/api';
4
+ import { cn } from '../../lib/utils';
5
+
6
+ /**
7
+ * Seed Button — decomposes a story into tasks/subtasks.
8
+ * Shows dependency check, model selector, and streaming progress log.
9
+ */
10
+ export function SeedButton({ storyId, onStarted }) {
11
+ const [state, setState] = useState('idle'); // idle | checking | blocked | configuring | seeding | complete | error
12
+ const [blockers, setBlockers] = useState([]);
13
+ const [error, setError] = useState(null);
14
+ const [progress, setProgress] = useState([]);
15
+ const [showLog, setShowLog] = useState(false);
16
+ const [processId, setProcessId] = useState(null);
17
+ const logRef = useRef(null);
18
+
19
+ // Auto-scroll progress log
20
+ useEffect(() => {
21
+ if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
22
+ }, [progress]);
23
+
24
+ // Listen for WebSocket seed events dispatched by App.jsx
25
+ useEffect(() => {
26
+ if (!processId) return;
27
+
28
+ const handler = (event) => {
29
+ const msg = event.detail;
30
+ if (!msg || msg.processId !== processId) return;
31
+
32
+ if (msg.type === 'seed:progress') {
33
+ setProgress(prev => [...prev, msg.message]);
34
+ } else if (msg.type === 'seed:complete') {
35
+ setState('complete');
36
+ setProgress(prev => [...prev, 'Seed complete — tasks created.']);
37
+ onStarted?.();
38
+ } else if (msg.type === 'seed:error') {
39
+ setState('error');
40
+ setError(msg.error || 'Seed failed');
41
+ setProgress(prev => [...prev, `Error: ${msg.error}`]);
42
+ }
43
+ };
44
+
45
+ window.addEventListener('avc-ws-message', handler);
46
+ return () => window.removeEventListener('avc-ws-message', handler);
47
+ }, [processId, onStarted]);
48
+
49
+ const handleClick = async () => {
50
+ if (state === 'checking' || state === 'seeding') return;
51
+
52
+ setState('checking');
53
+ setError(null);
54
+ setBlockers([]);
55
+ setProgress([]);
56
+
57
+ try {
58
+ const depStatus = await getDependencyStatus(storyId);
59
+ if (!depStatus.ready) {
60
+ setState('blocked');
61
+ setBlockers(depStatus.blockers || []);
62
+ return;
63
+ }
64
+
65
+ // Go straight to seeding (model config comes from avc.json server-side)
66
+ setState('seeding');
67
+ setShowLog(true);
68
+ setProgress(['Starting seed ceremony...']);
69
+ const result = await startSeedCeremony(storyId);
70
+ setProcessId(result.processId);
71
+ setProgress(prev => [...prev, `Process started: ${result.processId}`]);
72
+ } catch (err) {
73
+ setError(err.message);
74
+ setState('error');
75
+ }
76
+ };
77
+
78
+ const dismiss = () => {
79
+ setState('idle');
80
+ setBlockers([]);
81
+ setError(null);
82
+ setProgress([]);
83
+ setShowLog(false);
84
+ setProcessId(null);
85
+ };
86
+
87
+ // Blocked state — show blockers
88
+ if (state === 'blocked') {
89
+ return (
90
+ <div className="space-y-2">
91
+ <div className="flex items-center gap-2 text-amber-700 text-sm bg-amber-50 border border-amber-200 rounded-lg px-3 py-2">
92
+ <AlertTriangle className="w-4 h-4 flex-shrink-0" />
93
+ <div>
94
+ <div className="font-medium">Dependencies not met</div>
95
+ <ul className="mt-1 text-xs space-y-0.5">
96
+ {blockers.map((b) => (
97
+ <li key={b.id}>{b.id}: {b.name} ({b.status})</li>
98
+ ))}
99
+ </ul>
100
+ </div>
101
+ </div>
102
+ <button onClick={dismiss} className="text-xs text-slate-500 hover:text-slate-700">Dismiss</button>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ // Seeding/complete/error state — show progress log
108
+ if ((state === 'seeding' || state === 'complete' || state === 'error') && showLog) {
109
+ return (
110
+ <div className="space-y-2">
111
+ <div className="flex items-center justify-between">
112
+ <div className="flex items-center gap-2 text-sm font-medium">
113
+ {state === 'seeding' && <Loader2 className="w-4 h-4 animate-spin text-green-600" />}
114
+ {state === 'complete' && <Sprout className="w-4 h-4 text-green-600" />}
115
+ {state === 'error' && <AlertTriangle className="w-4 h-4 text-red-600" />}
116
+ <span className={cn(
117
+ state === 'seeding' && 'text-green-700',
118
+ state === 'complete' && 'text-green-700',
119
+ state === 'error' && 'text-red-700',
120
+ )}>
121
+ {state === 'seeding' ? 'Seeding...' : state === 'complete' ? 'Seed Complete' : 'Seed Failed'}
122
+ </span>
123
+ </div>
124
+ <div className="flex items-center gap-1">
125
+ <button onClick={() => setShowLog(!showLog)} className="p-1 text-slate-400 hover:text-slate-600">
126
+ {showLog ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
127
+ </button>
128
+ {state !== 'seeding' && (
129
+ <button onClick={dismiss} className="p-1 text-slate-400 hover:text-slate-600">
130
+ <X className="w-3.5 h-3.5" />
131
+ </button>
132
+ )}
133
+ </div>
134
+ </div>
135
+ {showLog && (
136
+ <div
137
+ ref={logRef}
138
+ className="max-h-32 overflow-y-auto bg-slate-900 text-slate-300 text-xs font-mono rounded-md p-2 space-y-0.5"
139
+ >
140
+ {progress.map((msg, i) => (
141
+ <div key={i} className={cn(
142
+ msg.startsWith('Error') && 'text-red-400',
143
+ msg.includes('complete') && 'text-green-400',
144
+ )}>{msg}</div>
145
+ ))}
146
+ {state === 'seeding' && <div className="text-slate-500 animate-pulse">...</div>}
147
+ </div>
148
+ )}
149
+ </div>
150
+ );
151
+ }
152
+
153
+ // Default idle state — seed button
154
+ return (
155
+ <div className="space-y-1">
156
+ <button
157
+ onClick={handleClick}
158
+ disabled={state === 'checking'}
159
+ className={cn(
160
+ 'flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors',
161
+ state === 'checking'
162
+ ? 'bg-green-100 text-green-700 cursor-wait'
163
+ : 'bg-green-600 text-white hover:bg-green-700'
164
+ )}
165
+ >
166
+ {state === 'checking' ? (
167
+ <Loader2 className="w-4 h-4 animate-spin" />
168
+ ) : (
169
+ <Sprout className="w-4 h-4" />
170
+ )}
171
+ {state === 'checking' ? 'Checking...' : 'Seed'}
172
+ </button>
173
+ {error && <p className="text-xs text-red-600">{error}</p>}
174
+ </div>
175
+ );
176
+ }