@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,7 +1,7 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
- import { X, Info, ArrowDownToLine } from 'lucide-react';
2
+ import { X, Settings, ArrowDownToLine } from 'lucide-react';
3
3
  import { useSprintPlanningStore } from '../../store/sprintPlanningStore';
4
- import { runSprintPlanning, getSettings, getModels, saveCeremonies, pauseCeremony, resumeCeremony, cancelCeremony, resetCeremony } from '../../lib/api';
4
+ import { runSprintPlanning, getSprintPlanningResumable, getSettings, getModels, saveCeremonies, pauseCeremony, resumeCeremony, cancelCeremony, resetCeremony } from '../../lib/api';
5
5
  import { CeremonyWorkflowModal } from './CeremonyWorkflowModal';
6
6
 
7
7
  // ── Step progress header ─────────────────────────────────────────────────────
@@ -55,7 +55,7 @@ function Stat({ label, value }) {
55
55
 
56
56
  // ── Step 1: Ready ─────────────────────────────────────────────────────────────
57
57
 
58
- function ReadyStep({ onStart }) {
58
+ function ReadyStep({ onStart, onResume, resumeInfo }) {
59
59
  return (
60
60
  <div className="space-y-6">
61
61
  <div>
@@ -65,12 +65,39 @@ function ReadyStep({ onStart }) {
65
65
  </p>
66
66
  </div>
67
67
 
68
- <div className="flex items-center justify-end pt-2">
68
+ {resumeInfo && (
69
+ <div className="bg-amber-50 border border-amber-200 rounded-lg p-4 space-y-2">
70
+ <p className="text-sm font-medium text-amber-800">Previous run can be resumed</p>
71
+ <p className="text-xs text-amber-600">
72
+ Stopped at: <span className="font-medium">{resumeInfo.checkpointLabel}</span>
73
+ {resumeInfo.epics > 0 && <> &middot; {resumeInfo.epics} epics on disk</>}
74
+ </p>
75
+ {resumeInfo.timestamp && (
76
+ <p className="text-xs text-amber-500">
77
+ {new Date(resumeInfo.timestamp).toLocaleString()}
78
+ </p>
79
+ )}
80
+ </div>
81
+ )}
82
+
83
+ <div className="flex items-center justify-end gap-3 pt-2">
84
+ {resumeInfo && (
85
+ <button
86
+ onClick={onResume}
87
+ className="px-5 py-2 text-sm font-medium bg-amber-600 text-white rounded-lg hover:bg-amber-500 transition-colors"
88
+ >
89
+ Resume
90
+ </button>
91
+ )}
69
92
  <button
70
93
  onClick={onStart}
71
- className="px-5 py-2 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors"
94
+ className={`px-5 py-2 text-sm font-medium rounded-lg transition-colors ${
95
+ resumeInfo
96
+ ? 'border border-slate-200 text-slate-700 hover:bg-slate-50'
97
+ : 'bg-slate-900 text-white hover:bg-slate-700'
98
+ }`}
72
99
  >
73
- Start
100
+ {resumeInfo ? 'Start Fresh' : 'Start'}
74
101
  </button>
75
102
  </div>
76
103
  </div>
@@ -454,9 +481,74 @@ function CompleteStep({ onClose }) {
454
481
  );
455
482
  }
456
483
 
484
+ // ── Quota-limit pause overlay ─────────────────────────────────────────────────
485
+
486
+ function QuotaLimitOverlay({ quotaLimitPending, onContinueAfterQuota, onCancel, onConfigureModels }) {
487
+ const [resuming, setResuming] = useState(false);
488
+
489
+ // Resume reads the current settings so any model change made via Configure Models is picked up.
490
+ async function handleResume() {
491
+ setResuming(true);
492
+ try {
493
+ const s = await getSettings();
494
+ const ceremony = s.ceremonies?.find(c => c.name === 'sprint-planning');
495
+ const newProvider = ceremony?.stages?.validation?.provider || null;
496
+ const newModel = ceremony?.stages?.validation?.model || null;
497
+ await onContinueAfterQuota(newProvider, newModel);
498
+ } finally {
499
+ setResuming(false);
500
+ }
501
+ }
502
+
503
+ return (
504
+ <div className="absolute inset-0 z-20 flex items-center justify-center bg-white/90 rounded-2xl">
505
+ <div className="bg-white border border-red-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
506
+ <div className="text-3xl">⚠️</div>
507
+ <p className="text-base font-semibold text-slate-900">API Quota Exceeded</p>
508
+ <p className="text-sm text-slate-600">
509
+ <span className="font-mono font-medium">{quotaLimitPending.provider}</span>
510
+ {' / '}
511
+ <span className="font-mono text-xs">{quotaLimitPending.model}</span>
512
+ {' returned a quota error.'}
513
+ </p>
514
+ {quotaLimitPending.validatorName && (
515
+ <p className="text-xs text-slate-400">
516
+ Validator: {quotaLimitPending.validatorName.replace('validator-story-', '').replace('validator-epic-', '')}
517
+ </p>
518
+ )}
519
+ <p className="text-sm text-slate-500">The ceremony is paused. What would you like to do?</p>
520
+ <div className="flex flex-col gap-2 pt-1">
521
+ <button
522
+ onClick={handleResume}
523
+ disabled={resuming}
524
+ className="px-4 py-2 text-sm rounded-lg bg-slate-900 text-white hover:bg-slate-700 disabled:opacity-50"
525
+ >
526
+ {resuming ? 'Resuming…' : 'Resume'}
527
+ </button>
528
+ <button
529
+ onClick={onConfigureModels}
530
+ className="px-4 py-2 text-sm rounded-lg border border-slate-200 text-slate-700 hover:bg-slate-50"
531
+ >
532
+ Configure Models
533
+ </button>
534
+ <button
535
+ onClick={onCancel}
536
+ className="px-4 py-2 text-sm rounded-lg border border-red-200 text-red-600 hover:bg-red-50"
537
+ >
538
+ Cancel Ceremony
539
+ </button>
540
+ </div>
541
+ <p className="text-xs text-slate-400">
542
+ Use <strong>Configure Models</strong> to switch provider, then <strong>Resume</strong>.
543
+ </p>
544
+ </div>
545
+ </div>
546
+ );
547
+ }
548
+
457
549
  // ── Main modal ────────────────────────────────────────────────────────────────
458
550
 
459
- export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastCostLimit, onCancelFromCostLimit }) {
551
+ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastCostLimit, onCancelFromCostLimit, quotaLimitPending, onContinueAfterQuota, onCancelFromQuota }) {
460
552
  const {
461
553
  isOpen,
462
554
  step,
@@ -473,8 +565,19 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
473
565
  const [workflowCeremony, setWorkflowCeremony] = useState(null);
474
566
  const [workflowModels, setWorkflowModels] = useState([]);
475
567
  const [workflowAllCeremonies, setWorkflowAllCeremonies] = useState([]);
568
+ const [workflowApiKeys, setWorkflowApiKeys] = useState({});
476
569
  const [showCancelConfirm, setShowCancelConfirm] = useState(false);
477
570
  const [transitioning, setTransitioning] = useState(null); // null | 'pausing' | 'cancelling'
571
+ const [resumeInfo, setResumeInfo] = useState(null);
572
+
573
+ // Fetch resumable state when modal opens at step 1
574
+ useEffect(() => {
575
+ if (isOpen && step === 1 && status !== 'running') {
576
+ getSprintPlanningResumable()
577
+ .then(data => setResumeInfo(data?.resumable ? data : null))
578
+ .catch(() => setResumeInfo(null));
579
+ }
580
+ }, [isOpen, step, status]);
478
581
 
479
582
  if (!isOpen) return null;
480
583
 
@@ -486,11 +589,12 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
486
589
  onClose?.();
487
590
  };
488
591
 
489
- const handleStart = async () => {
592
+ const handleStart = async (resumeFromCheckpoint = null) => {
490
593
  setStatus('running');
491
594
  setStep(2);
595
+ setResumeInfo(null);
492
596
  try {
493
- const result = await runSprintPlanning();
597
+ const result = await runSprintPlanning(resumeFromCheckpoint);
494
598
  if (result?.processId) setProcessId(result.processId);
495
599
  // Completion is handled via WebSocket in App.jsx
496
600
  } catch (err) {
@@ -506,6 +610,7 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
506
610
  setWorkflowCeremony(sc);
507
611
  setWorkflowModels(m);
508
612
  setWorkflowAllCeremonies(s.ceremonies || []);
613
+ setWorkflowApiKeys(s.apiKeys || {});
509
614
  setWorkflowOpen(true);
510
615
  } catch {}
511
616
  };
@@ -527,10 +632,10 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
527
632
  try { await resumeCeremony(); } catch (_) {}
528
633
  };
529
634
 
530
- const handleConfirmCancel = async () => {
635
+ const handleConfirmCancel = async (keepItems = false) => {
531
636
  setShowCancelConfirm(false);
532
637
  setTransitioning('cancelling');
533
- try { await cancelCeremony(); } catch (_) {}
638
+ try { await cancelCeremony(keepItems); } catch (_) {}
534
639
  };
535
640
 
536
641
  // Clear transitioning state when WS events arrive (isPaused / status change)
@@ -544,7 +649,13 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
544
649
 
545
650
  const renderStep = () => {
546
651
  switch (step) {
547
- case 1: return <ReadyStep onStart={handleStart} />;
652
+ case 1: return (
653
+ <ReadyStep
654
+ onStart={() => handleStart(null)}
655
+ onResume={() => handleStart(resumeInfo?.checkpoint)}
656
+ resumeInfo={resumeInfo}
657
+ />
658
+ );
548
659
  case 2: return (
549
660
  <RunningStep
550
661
  transitioning={transitioning}
@@ -611,15 +722,25 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
611
722
  </div>
612
723
  )}
613
724
 
725
+ {/* Quota-limit pause overlay */}
726
+ {quotaLimitPending && (
727
+ <QuotaLimitOverlay
728
+ quotaLimitPending={quotaLimitPending}
729
+ onContinueAfterQuota={onContinueAfterQuota}
730
+ onCancel={onCancelFromQuota}
731
+ onConfigureModels={openWorkflow}
732
+ />
733
+ )}
734
+
614
735
  {/* Cancel confirmation overlay */}
615
736
  {showCancelConfirm && (
616
737
  <div className="absolute inset-0 z-10 flex items-center justify-center bg-white/90 rounded-2xl">
617
738
  <div className="bg-white border border-slate-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
618
739
  <p className="text-base font-semibold text-slate-900">Cancel sprint planning?</p>
619
740
  <p className="text-sm text-slate-500">
620
- Any epics and stories created in this run will be permanently deleted.
741
+ What should happen with epics and stories already created in this run?
621
742
  </p>
622
- <div className="flex gap-3 justify-center pt-1">
743
+ <div className="flex flex-col gap-2 pt-1">
623
744
  <button
624
745
  onClick={() => setShowCancelConfirm(false)}
625
746
  className="px-4 py-2 text-sm rounded-lg border border-slate-200 text-slate-700 hover:bg-slate-50"
@@ -627,10 +748,16 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
627
748
  Keep Running
628
749
  </button>
629
750
  <button
630
- onClick={handleConfirmCancel}
751
+ onClick={() => handleConfirmCancel(true)}
752
+ className="px-4 py-2 text-sm rounded-lg border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100"
753
+ >
754
+ Cancel &amp; Keep Items
755
+ </button>
756
+ <button
757
+ onClick={() => handleConfirmCancel(false)}
631
758
  className="px-4 py-2 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700"
632
759
  >
633
- Cancel Run
760
+ Cancel &amp; Delete Items
634
761
  </button>
635
762
  </div>
636
763
  </div>
@@ -650,11 +777,11 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
650
777
  <button
651
778
  type="button"
652
779
  onClick={openWorkflow}
653
- className="flex items-center gap-1 text-xs text-slate-400 hover:text-blue-500 transition-colors whitespace-nowrap"
654
- title="View ceremony workflow"
780
+ className="flex items-center gap-1.5 text-xs font-medium text-slate-500 hover:text-blue-600 bg-slate-50 hover:bg-blue-50 border border-slate-200 hover:border-blue-200 rounded-md px-2.5 py-1.5 transition-colors whitespace-nowrap"
781
+ title="Configure ceremony models"
655
782
  >
656
- <Info className="w-3.5 h-3.5" />
657
- How it works
783
+ <Settings className="w-3.5 h-3.5" />
784
+ Select Model(s)
658
785
  </button>
659
786
  )}
660
787
  {!isBlocked && (
@@ -693,10 +820,17 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
693
820
  {workflowOpen && workflowCeremony && (
694
821
  <CeremonyWorkflowModal
695
822
  ceremony={workflowCeremony}
823
+ allCeremonies={workflowAllCeremonies}
824
+ apiKeys={workflowApiKeys}
696
825
  models={workflowModels}
697
- readOnly={status === 'running'}
698
- onSave={status !== 'running' ? handleWorkflowSave : undefined}
826
+ readOnly={status === 'running' && !quotaLimitPending}
827
+ onSave={(status !== 'running' || quotaLimitPending) ? handleWorkflowSave : undefined}
699
828
  onClose={() => setWorkflowOpen(false)}
829
+ onCeremoniesUpdated={(updated) => {
830
+ setWorkflowAllCeremonies(updated);
831
+ const sc = updated.find((c) => c.name === 'sprint-planning');
832
+ if (sc) setWorkflowCeremony(sc);
833
+ }}
700
834
  />
701
835
  )}
702
836
  </div>
@@ -1,5 +1,4 @@
1
1
  import { useState } from 'react';
2
- import { Wand2 } from 'lucide-react';
3
2
  import { useCeremonyStore } from '../../../store/ceremonyStore';
4
3
  import { AskArchPopup } from '../AskArchPopup';
5
4
 
@@ -73,7 +72,17 @@ export function ArchitectureStep({ onNext, onBack, analyzing, onOpenSettings })
73
72
  return (
74
73
  <div className="space-y-6">
75
74
  <div>
76
- <h2 className="text-xl font-semibold text-slate-900">Architecture Selection</h2>
75
+ <div className="flex items-center justify-between">
76
+ <h2 className="text-xl font-semibold text-slate-900">Architecture Selection</h2>
77
+ <button
78
+ type="button"
79
+ onClick={() => setShowAskArch(true)}
80
+ disabled={analyzing}
81
+ className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1 transition-colors disabled:opacity-40"
82
+ >
83
+ ✨ Ask a Model
84
+ </button>
85
+ </div>
77
86
  <p className="text-sm text-slate-500 mt-1">
78
87
  Choose the deployment architecture that fits your project best.
79
88
  </p>
@@ -100,15 +109,6 @@ export function ArchitectureStep({ onNext, onBack, analyzing, onOpenSettings })
100
109
  ← Back
101
110
  </button>
102
111
  <div className="flex items-center gap-3">
103
- <button
104
- type="button"
105
- onClick={() => setShowAskArch(true)}
106
- disabled={analyzing}
107
- className="flex items-center gap-1.5 px-3 py-2 text-sm text-violet-700 bg-violet-50 border border-violet-200 rounded-lg hover:bg-violet-100 disabled:opacity-40 transition-colors"
108
- >
109
- <Wand2 className="w-3.5 h-3.5" />
110
- Ask a Model
111
- </button>
112
112
  <button
113
113
  type="button"
114
114
  onClick={onNext}
@@ -1,13 +1,5 @@
1
1
  import { useCeremonyStore } from '../../../store/ceremonyStore';
2
2
 
3
- const EXAMPLE_ISSUES = [
4
- { stage: 'Project Documentation', ruleId: 'fix-header-formatting', name: 'Fix Header Spacing', severity: 'major' },
5
- { stage: 'Project Documentation', ruleId: 'add-section-spacing', name: 'Add Section Spacing', severity: 'minor' },
6
- { stage: 'Project Context', ruleId: 'token-count-too-short', name: 'Expand If Too Short', severity: 'major' },
7
- { stage: 'Project Context', ruleId: 'no-redundant-info', name: 'Remove Truly Redundant Information', severity: 'minor' },
8
- { stage: 'Context Validation', ruleId: 'fix-unclosed-code-blocks', name: 'Fix Unclosed Code Blocks', severity: 'major' },
9
- ];
10
-
11
3
  function IssueTag({ severity }) {
12
4
  const cls =
13
5
  severity === 'critical' ? 'bg-red-100 text-red-700' :
@@ -46,20 +38,12 @@ export function CompleteStep({ onClose }) {
46
38
  const tokenInput = r.tokenUsage?.input || r.tokenUsage?.inputTokens || 0;
47
39
  const tokenOutput = r.tokenUsage?.output || r.tokenUsage?.outputTokens || 0;
48
40
  const tokenTotal = r.tokenUsage?.total || r.tokenUsage?.totalTokens || tokenInput + tokenOutput;
49
- const costTotal =
50
- r.cost?.total != null
51
- ? `$${r.cost.total.toFixed(4)}`
52
- : r.cost
53
- ? `$${Object.values(r.cost).reduce((a, v) => a + (typeof v === 'number' ? v : 0), 0).toFixed(4)}`
54
- : '—';
55
-
56
41
  const files = r.outputPath && r.contextPath
57
42
  ? [r.outputPath, r.contextPath]
58
43
  : r.filesGenerated || [];
59
44
 
60
- // undefined show example preview; [] → no issues (hide); [...] real data
61
- const isExample = r.validationIssues === undefined;
62
- const rawIssues = r.validationIssues ?? EXAMPLE_ISSUES;
45
+ // Only show real validation issues; never show the example preview
46
+ const rawIssues = Array.isArray(r.validationIssues) ? r.validationIssues : [];
63
47
 
64
48
  // Group duplicate rule applications by ruleId
65
49
  const issueMap = {};
@@ -96,10 +80,9 @@ export function CompleteStep({ onClose }) {
96
80
  </div>
97
81
  )}
98
82
 
99
- <div className="grid grid-cols-3 gap-3">
83
+ <div className="grid grid-cols-2 gap-3">
100
84
  <Stat label="Input tokens" value={tokenInput.toLocaleString()} />
101
85
  <Stat label="Output tokens" value={tokenOutput.toLocaleString()} />
102
- <Stat label="Estimated cost" value={costTotal} />
103
86
  </div>
104
87
 
105
88
  {r.model && (
@@ -112,7 +95,6 @@ export function CompleteStep({ onClose }) {
112
95
  <div>
113
96
  <p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
114
97
  Quality fixes applied
115
- {isExample && <span className="ml-2 normal-case font-normal text-slate-300">(example preview)</span>}
116
98
  </p>
117
99
  <div className="space-y-1.5">
118
100
  {issues.map((issue, i) => (
@@ -1,4 +1,7 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Sparkles } from 'lucide-react';
1
3
  import { useCeremonyStore } from '../../../store/ceremonyStore';
4
+ import { getModels, getSettings, refineField } from '../../../lib/api';
2
5
 
3
6
  const FIELDS = [
4
7
  {
@@ -40,14 +43,176 @@ const FIELDS = [
40
43
  },
41
44
  ];
42
45
 
46
+ // Maps model provider → apiKeys key returned by getSettings()
47
+ function normalizeProvider(p = '') {
48
+ if (p === 'claude') return 'anthropic';
49
+ return p;
50
+ }
51
+
52
+ /**
53
+ * Inline "Ask a Model" panel for a single field.
54
+ */
55
+ function AskModelPanel({ fieldKey, fieldLabel, currentValue, context, onApply, onClose }) {
56
+ const [models, setModels] = useState([]);
57
+ const [selectedModelId, setSelectedModelId] = useState('');
58
+ const [instruction, setInstruction] = useState('');
59
+ const [loading, setLoading] = useState(false);
60
+ const [result, setResult] = useState(null);
61
+ const [error, setError] = useState('');
62
+
63
+ useEffect(() => {
64
+ Promise.all([getModels(), getSettings()])
65
+ .then(([data, settings]) => {
66
+ setModels(data);
67
+ const apiKeys = settings.apiKeys ?? {};
68
+ const ready = data.filter((m) => apiKeys[normalizeProvider(m.provider)]?.isSet);
69
+ const isPro = (id) => /pro|opus|sonnet/i.test(id);
70
+ const best = ready.find((m) => isPro(m.modelId)) || ready[0];
71
+ setSelectedModelId(best ? best.modelId : (data[0]?.modelId || ''));
72
+ })
73
+ .catch(() => setError('Failed to load models.'));
74
+ }, []);
75
+
76
+ const selectedModel = models.find((m) => m.modelId === selectedModelId);
77
+ const providers = [...new Set(models.map((m) => m.provider))];
78
+
79
+ async function handleRefine() {
80
+ if (!selectedModel || !instruction.trim()) return;
81
+ setLoading(true);
82
+ setError('');
83
+ setResult(null);
84
+ try {
85
+ const data = await refineField(
86
+ fieldKey, fieldLabel, currentValue,
87
+ instruction.trim(), context,
88
+ selectedModelId, selectedModel.provider,
89
+ );
90
+ setResult(data.value);
91
+ } catch (err) {
92
+ setError(err.message || 'Refinement failed.');
93
+ } finally {
94
+ setLoading(false);
95
+ }
96
+ }
97
+
98
+ return (
99
+ <div className="mt-2 rounded-lg border border-blue-200 bg-blue-50/50 p-3 space-y-2">
100
+ <div className="flex items-center justify-between">
101
+ <span className="text-xs font-semibold text-blue-700">Ask a Model — {fieldLabel}</span>
102
+ <button type="button" onClick={onClose} className="text-xs text-slate-400 hover:text-slate-600">
103
+ ×
104
+ </button>
105
+ </div>
106
+
107
+ {/* Model selector + instruction */}
108
+ {!result && (
109
+ <>
110
+ <div className="flex gap-2">
111
+ <select
112
+ value={selectedModelId}
113
+ onChange={(e) => setSelectedModelId(e.target.value)}
114
+ disabled={loading || models.length === 0}
115
+ className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-xs text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-60 bg-white"
116
+ >
117
+ {models.length === 0 && <option value="">Loading…</option>}
118
+ {providers.map((p) => (
119
+ <optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
120
+ {models.filter((m) => m.provider === p).map((m) => (
121
+ <option key={m.modelId} value={m.modelId}>
122
+ {m.displayName}{!m.hasApiKey ? ' (no key)' : ''}
123
+ </option>
124
+ ))}
125
+ </optgroup>
126
+ ))}
127
+ </select>
128
+ </div>
129
+
130
+ <textarea
131
+ value={instruction}
132
+ onChange={(e) => setInstruction(e.target.value)}
133
+ rows={2}
134
+ placeholder="What would you like to improve? E.g. Be more specific about enterprise users…"
135
+ disabled={loading}
136
+ 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"
137
+ />
138
+
139
+ {error && <p className="text-xs text-red-600">{error}</p>}
140
+
141
+ <div className="flex justify-end gap-2">
142
+ <button type="button" onClick={onClose}
143
+ className="px-3 py-1 text-xs text-slate-500 hover:text-slate-700">
144
+ Cancel
145
+ </button>
146
+ <button
147
+ type="button"
148
+ onClick={handleRefine}
149
+ disabled={loading || !instruction.trim() || !selectedModelId}
150
+ className="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-40 flex items-center gap-1.5"
151
+ >
152
+ {loading ? (
153
+ <>
154
+ <span className="w-3 h-3 border-2 border-white/30 border-t-white rounded-full animate-spin" />
155
+ Refining…
156
+ </>
157
+ ) : 'Refine'}
158
+ </button>
159
+ </div>
160
+ </>
161
+ )}
162
+
163
+ {/* Result */}
164
+ {result && (
165
+ <>
166
+ <div className="text-xs text-slate-700 bg-white rounded-md border border-slate-200 px-2.5 py-2 whitespace-pre-wrap max-h-40 overflow-y-auto">
167
+ {result}
168
+ </div>
169
+ <div className="flex justify-end gap-2">
170
+ <button type="button" onClick={() => { setResult(null); setInstruction(''); }}
171
+ className="px-3 py-1 text-xs text-slate-500 hover:text-slate-700">
172
+ Try Again
173
+ </button>
174
+ <button type="button" onClick={() => { onApply(result); onClose(); }}
175
+ className="px-3 py-1 text-xs font-medium text-white bg-slate-900 rounded-md hover:bg-slate-700">
176
+ Use This
177
+ </button>
178
+ </div>
179
+ </>
180
+ )}
181
+ </div>
182
+ );
183
+ }
184
+
43
185
  export function ReviewAnswersStep({ onNext, onBack }) {
44
186
  const { requirements, updateRequirement, mission, setMission, initialScope, setInitialScope } = useCeremonyStore();
45
187
 
188
+ // Track which field has the "Ask Model" panel open (only one at a time)
189
+ const [askModelField, setAskModelField] = useState(null);
190
+
191
+ const context = { mission, scope: initialScope };
192
+
46
193
  const canContinue =
47
194
  mission.trim().length > 0 &&
48
195
  initialScope.trim().length > 0 &&
49
196
  FIELDS.filter((f) => f.required).every((f) => requirements[f.key]?.trim());
50
197
 
198
+ function AskModelButton({ fieldKey }) {
199
+ return (
200
+ <button
201
+ type="button"
202
+ onClick={() => setAskModelField(askModelField === fieldKey ? null : fieldKey)}
203
+ className={`inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-md transition-colors ${
204
+ askModelField === fieldKey
205
+ ? 'bg-blue-100 text-blue-700'
206
+ : 'text-slate-400 hover:text-blue-600 hover:bg-blue-50'
207
+ }`}
208
+ title="Ask a model to improve this field"
209
+ >
210
+ <Sparkles className="w-3 h-3" />
211
+ Ask Model
212
+ </button>
213
+ );
214
+ }
215
+
51
216
  return (
52
217
  <div className="space-y-6">
53
218
  <div>
@@ -60,9 +225,12 @@ export function ReviewAnswersStep({ onNext, onBack }) {
60
225
  <div className="space-y-4">
61
226
  {/* Mission & Scope */}
62
227
  <div>
63
- <label className="block text-sm font-medium text-slate-700 mb-0.5">
64
- Mission Statement <span className="text-red-500">*</span>
65
- </label>
228
+ <div className="flex items-center justify-between mb-0.5">
229
+ <label className="block text-sm font-medium text-slate-700">
230
+ Mission Statement <span className="text-red-500">*</span>
231
+ </label>
232
+ <AskModelButton fieldKey="MISSION_STATEMENT" />
233
+ </div>
66
234
  <textarea
67
235
  value={mission}
68
236
  onChange={(e) => setMission(e.target.value)}
@@ -70,11 +238,24 @@ export function ReviewAnswersStep({ onNext, onBack }) {
70
238
  className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
71
239
  placeholder="Required..."
72
240
  />
241
+ {askModelField === 'MISSION_STATEMENT' && (
242
+ <AskModelPanel
243
+ fieldKey="MISSION_STATEMENT"
244
+ fieldLabel="Mission Statement"
245
+ currentValue={mission}
246
+ context={context}
247
+ onApply={(val) => setMission(val)}
248
+ onClose={() => setAskModelField(null)}
249
+ />
250
+ )}
73
251
  </div>
74
252
  <div>
75
- <label className="block text-sm font-medium text-slate-700 mb-0.5">
76
- Initial Scope <span className="text-red-500">*</span>
77
- </label>
253
+ <div className="flex items-center justify-between mb-0.5">
254
+ <label className="block text-sm font-medium text-slate-700">
255
+ Initial Scope <span className="text-red-500">*</span>
256
+ </label>
257
+ <AskModelButton fieldKey="INITIAL_SCOPE" />
258
+ </div>
78
259
  <textarea
79
260
  value={initialScope}
80
261
  onChange={(e) => setInitialScope(e.target.value)}
@@ -82,16 +263,29 @@ export function ReviewAnswersStep({ onNext, onBack }) {
82
263
  className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
83
264
  placeholder="Required..."
84
265
  />
266
+ {askModelField === 'INITIAL_SCOPE' && (
267
+ <AskModelPanel
268
+ fieldKey="INITIAL_SCOPE"
269
+ fieldLabel="Initial Scope"
270
+ currentValue={initialScope}
271
+ context={context}
272
+ onApply={(val) => setInitialScope(val)}
273
+ onClose={() => setAskModelField(null)}
274
+ />
275
+ )}
85
276
  </div>
86
277
 
87
278
  <hr className="border-slate-200" />
88
279
 
89
280
  {FIELDS.map((field) => (
90
281
  <div key={field.key}>
91
- <label className="block text-sm font-medium text-slate-700 mb-0.5">
92
- {field.label}
93
- {field.required && <span className="text-red-500 ml-1">*</span>}
94
- </label>
282
+ <div className="flex items-center justify-between mb-0.5">
283
+ <label className="block text-sm font-medium text-slate-700">
284
+ {field.label}
285
+ {field.required && <span className="text-red-500 ml-1">*</span>}
286
+ </label>
287
+ <AskModelButton fieldKey={field.key} />
288
+ </div>
95
289
  <p className="text-xs text-slate-400 mb-1">{field.description}</p>
96
290
  <textarea
97
291
  value={requirements[field.key] || ''}
@@ -100,6 +294,16 @@ export function ReviewAnswersStep({ onNext, onBack }) {
100
294
  className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
101
295
  placeholder={field.required ? `Required...` : 'Optional...'}
102
296
  />
297
+ {askModelField === field.key && (
298
+ <AskModelPanel
299
+ fieldKey={field.key}
300
+ fieldLabel={field.label}
301
+ currentValue={requirements[field.key] || ''}
302
+ context={context}
303
+ onApply={(val) => updateRequirement(field.key, val)}
304
+ onClose={() => setAskModelField(null)}
305
+ />
306
+ )}
103
307
  </div>
104
308
  ))}
105
309
  </div>