@agile-vibe-coding/avc 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,454 @@
1
+ import express from 'express';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Ceremony Router
7
+ * Handles all /api/ceremony/* endpoints for the sponsor-call wizard.
8
+ * @param {CeremonyService} ceremonyService
9
+ */
10
+ export function createCeremonyRouter(ceremonyService, processRegistry, taskRunnerService = null) {
11
+ const router = express.Router();
12
+
13
+ // GET /api/ceremony/status — current ceremony state
14
+ router.get('/status', (req, res) => {
15
+ res.json(ceremonyService.getStatus());
16
+ });
17
+
18
+ // GET /api/ceremony/models — available LLM models with API key status
19
+ router.get('/models', async (req, res) => {
20
+ try {
21
+ const models = await ceremonyService.getAvailableModels();
22
+ res.json(models);
23
+ } catch (err) {
24
+ console.error('getAvailableModels error:', err);
25
+ res.status(500).json({ error: err.message });
26
+ }
27
+ });
28
+
29
+ // POST /api/ceremony/generate-mission
30
+ // Body: { description, modelId, provider, validatorModelId, validatorProvider }
31
+ router.post('/generate-mission', async (req, res) => {
32
+ const { description, modelId, provider, validatorModelId, validatorProvider } = req.body;
33
+ console.log('[ceremony] POST /generate-mission', {
34
+ descriptionLength: description?.length,
35
+ modelId,
36
+ provider,
37
+ validatorModelId,
38
+ validatorProvider,
39
+ });
40
+
41
+ if (!description?.trim() || !modelId || !provider || !validatorModelId || !validatorProvider) {
42
+ console.warn('[ceremony] generate-mission: missing required fields');
43
+ return res.status(400).json({
44
+ error: 'description, modelId, provider, validatorModelId and validatorProvider are required',
45
+ });
46
+ }
47
+
48
+ const start = Date.now();
49
+ try {
50
+ const result = await ceremonyService.generateMissionScope(
51
+ description, modelId, provider, validatorModelId, validatorProvider
52
+ );
53
+ console.log(`[ceremony] generate-mission completed in ${Date.now() - start}ms`, {
54
+ validationScore: result.validationScore,
55
+ iterations: result.iterations,
56
+ issueCount: result.issues?.length,
57
+ });
58
+ res.json(result);
59
+ } catch (err) {
60
+ console.error(`[ceremony] generate-mission failed in ${Date.now() - start}ms:`, err.message);
61
+ res.status(500).json({ error: err.message });
62
+ }
63
+ });
64
+
65
+ // POST /api/ceremony/refine-mission
66
+ // Body: { missionStatement, initialScope, refinementRequest, modelId, provider, validatorModelId, validatorProvider }
67
+ router.post('/refine-mission', async (req, res) => {
68
+ const { missionStatement, initialScope, refinementRequest, modelId, provider, validatorModelId, validatorProvider } = req.body;
69
+ if (!missionStatement?.trim() || !initialScope?.trim() || !refinementRequest?.trim() || !modelId || !provider || !validatorModelId || !validatorProvider) {
70
+ return res.status(400).json({ error: 'missionStatement, initialScope, refinementRequest, modelId, provider, validatorModelId and validatorProvider are required' });
71
+ }
72
+ const start = Date.now();
73
+ try {
74
+ const result = await ceremonyService.refineMissionScope(
75
+ missionStatement, initialScope, refinementRequest, modelId, provider, validatorModelId, validatorProvider
76
+ );
77
+ console.log(`[ceremony] refine-mission completed in ${Date.now() - start}ms`, { validationScore: result.validationScore, iterations: result.iterations });
78
+ res.json(result);
79
+ } catch (err) {
80
+ console.error('[ceremony] refine-mission failed:', err.message);
81
+ res.status(500).json({ error: err.message });
82
+ }
83
+ });
84
+
85
+ // POST /api/ceremony/generate-architecture
86
+ // Body: { description, modelId, provider }
87
+ router.post('/generate-architecture', async (req, res) => {
88
+ const { description, modelId, provider } = req.body;
89
+ if (!description?.trim() || !modelId || !provider) {
90
+ return res.status(400).json({ error: 'description, modelId and provider are required' });
91
+ }
92
+ try {
93
+ const arch = await ceremonyService.generateCustomArchitecture(description, modelId, provider);
94
+ res.json(arch);
95
+ } catch (err) {
96
+ console.error('[ceremony] generate-architecture failed:', err.message);
97
+ res.status(500).json({ error: err.message });
98
+ }
99
+ });
100
+
101
+ // POST /api/ceremony/refine-architecture
102
+ // Body: { currentArch, refinementRequest, modelId, provider }
103
+ router.post('/refine-architecture', async (req, res) => {
104
+ const { currentArch, refinementRequest, modelId, provider } = req.body;
105
+ if (!currentArch || !refinementRequest?.trim() || !modelId || !provider) {
106
+ return res.status(400).json({ error: 'currentArch, refinementRequest, modelId and provider are required' });
107
+ }
108
+ try {
109
+ const arch = await ceremonyService.refineCustomArchitecture(currentArch, refinementRequest, modelId, provider);
110
+ res.json(arch);
111
+ } catch (err) {
112
+ console.error('[ceremony] refine-architecture failed:', err.message);
113
+ res.status(500).json({ error: err.message });
114
+ }
115
+ });
116
+
117
+ // POST /api/ceremony/analyze/database
118
+ // Body: { mission, scope, strategy }
119
+ router.post('/analyze/database', async (req, res) => {
120
+ const { mission, scope, strategy } = req.body;
121
+ console.log('[ceremony] POST /analyze/database', {
122
+ missionLength: mission?.length,
123
+ scopeLines: scope?.split('\n').length,
124
+ strategy,
125
+ });
126
+ if (!mission || !scope) {
127
+ return res.status(400).json({ error: 'mission and scope are required' });
128
+ }
129
+ const start = Date.now();
130
+ try {
131
+ const result = await ceremonyService.analyzeDatabase(mission, scope, strategy || null);
132
+ console.log(`[ceremony] analyze/database completed in ${Date.now() - start}ms`);
133
+ res.json(result);
134
+ } catch (err) {
135
+ console.error(`[ceremony] analyze/database failed in ${Date.now() - start}ms:`, err.message);
136
+ res.status(500).json({ error: err.message });
137
+ }
138
+ });
139
+
140
+ // POST /api/ceremony/analyze/architecture
141
+ // Body: { mission, scope, dbContext, strategy }
142
+ router.post('/analyze/architecture', async (req, res) => {
143
+ const { mission, scope, dbContext, strategy } = req.body;
144
+ console.log('[ceremony] POST /analyze/architecture', {
145
+ missionLength: mission?.length,
146
+ dbContext: dbContext ? 'provided' : 'null',
147
+ strategy,
148
+ });
149
+ if (!mission || !scope) {
150
+ return res.status(400).json({ error: 'mission and scope are required' });
151
+ }
152
+ const start = Date.now();
153
+ try {
154
+ const result = await ceremonyService.analyzeArchitecture(
155
+ mission,
156
+ scope,
157
+ dbContext || null,
158
+ strategy || null
159
+ );
160
+ console.log(`[ceremony] analyze/architecture completed in ${Date.now() - start}ms`);
161
+ res.json(result);
162
+ } catch (err) {
163
+ console.error(`[ceremony] analyze/architecture failed in ${Date.now() - start}ms:`, err.message);
164
+ res.status(500).json({ error: err.message });
165
+ }
166
+ });
167
+
168
+ // POST /api/ceremony/analyze/prefill
169
+ // Body: { mission, scope, arch, dbContext, strategy }
170
+ router.post('/analyze/prefill', async (req, res) => {
171
+ const { mission, scope, arch, dbContext, strategy } = req.body;
172
+ console.log('[ceremony] POST /analyze/prefill', {
173
+ missionLength: mission?.length,
174
+ arch: arch ? 'provided' : 'null',
175
+ strategy,
176
+ });
177
+ if (!mission || !scope || !arch) {
178
+ return res.status(400).json({ error: 'mission, scope, and arch are required' });
179
+ }
180
+ const start = Date.now();
181
+ try {
182
+ const result = await ceremonyService.prefillAnswers(
183
+ mission,
184
+ scope,
185
+ arch,
186
+ dbContext || null,
187
+ strategy || null
188
+ );
189
+ console.log(`[ceremony] analyze/prefill completed in ${Date.now() - start}ms`);
190
+ res.json(result);
191
+ } catch (err) {
192
+ console.error(`[ceremony] analyze/prefill failed in ${Date.now() - start}ms:`, err.message);
193
+ res.status(500).json({ error: err.message });
194
+ }
195
+ });
196
+
197
+ // POST /api/ceremony/refine-field
198
+ // Body: { fieldKey, fieldLabel, currentValue, refinementRequest, context: { mission, scope }, modelId, provider }
199
+ router.post('/refine-field', async (req, res) => {
200
+ const { fieldKey, fieldLabel, currentValue, refinementRequest, context, modelId, provider } = req.body;
201
+ if (!fieldKey || !currentValue?.trim() || !refinementRequest?.trim() || !context?.mission || !modelId || !provider) {
202
+ return res.status(400).json({ error: 'fieldKey, currentValue, refinementRequest, context.mission, modelId and provider are required' });
203
+ }
204
+ const start = Date.now();
205
+ try {
206
+ const result = await ceremonyService.refineField(fieldKey, fieldLabel, currentValue, refinementRequest, context, modelId, provider);
207
+ console.log(`[ceremony] refine-field completed in ${Date.now() - start}ms`, { fieldKey });
208
+ res.json(result);
209
+ } catch (err) {
210
+ console.error(`[ceremony] refine-field failed in ${Date.now() - start}ms:`, err.message);
211
+ res.status(500).json({ error: err.message });
212
+ }
213
+ });
214
+
215
+ // POST /api/ceremony/run
216
+ // Body: { requirements } — all 7 template variables
217
+ router.post('/run', async (req, res) => {
218
+ const { requirements } = req.body;
219
+ console.log('[ceremony] POST /run', {
220
+ requirementKeys: Object.keys(requirements || {}),
221
+ missionLength: requirements?.MISSION_STATEMENT?.length,
222
+ });
223
+ if (!requirements || !requirements.MISSION_STATEMENT) {
224
+ return res.status(400).json({ error: 'requirements.MISSION_STATEMENT is required' });
225
+ }
226
+ try {
227
+ const processId = await ceremonyService.runSponsorCallInProcess(processRegistry, requirements);
228
+ console.log('[ceremony] run started in process', processId);
229
+ res.json({ started: true, processId });
230
+ } catch (err) {
231
+ console.error('[ceremony] run error:', err.message);
232
+ res.status(500).json({ error: err.message });
233
+ }
234
+ });
235
+
236
+ // GET /api/ceremony/sprint-planning/resumable — check if previous run can be resumed
237
+ router.get('/sprint-planning/resumable', async (req, res) => {
238
+ try {
239
+ const { CeremonyHistory } = await import('../../../cli/ceremony-history.js');
240
+ const historyPath = path.join(ceremonyService.projectRoot, '.avc');
241
+ const history = new CeremonyHistory(historyPath);
242
+ history.init();
243
+
244
+ const resumableStages = ['files-written', 'docs-generated', 'enrichment-complete'];
245
+ const ceremonies = history.data?.ceremonies?.['sprint-planning'];
246
+ const executions = ceremonies?.executions || [];
247
+ const last = executions[executions.length - 1];
248
+
249
+ if (!last) return res.json({ resumable: false });
250
+
251
+ const isAborted = last.status === 'aborted' || last.outcome === 'abrupt-termination' ||
252
+ (last.status === 'in-progress');
253
+ const hasResumableCheckpoint = resumableStages.includes(last.stage);
254
+
255
+ if (!isAborted || !hasResumableCheckpoint) {
256
+ return res.json({ resumable: false, reason: isAborted ? 'checkpoint-too-early' : 'not-aborted' });
257
+ }
258
+
259
+ // Validate files exist on disk
260
+ const projectDir = path.join(ceremonyService.projectRoot, '.avc', 'project');
261
+ const epicDirs = fs.existsSync(projectDir)
262
+ ? fs.readdirSync(projectDir).filter(d => d.startsWith('context-') && fs.existsSync(path.join(projectDir, d, 'work.json')))
263
+ : [];
264
+
265
+ if (epicDirs.length === 0) {
266
+ return res.json({ resumable: false, reason: 'no-files-on-disk' });
267
+ }
268
+
269
+ const stageLabels = {
270
+ 'files-written': 'File Writing (doc generation pending)',
271
+ 'docs-generated': 'Doc Generation (enrichment pending)',
272
+ 'enrichment-complete': 'Enrichment (summary pending)',
273
+ };
274
+
275
+ res.json({
276
+ resumable: true,
277
+ checkpoint: last.stage,
278
+ checkpointLabel: stageLabels[last.stage] || last.stage,
279
+ executionId: last.id,
280
+ timestamp: last.lastCheckpoint || last.startTime,
281
+ epics: epicDirs.length,
282
+ });
283
+ } catch (err) {
284
+ res.json({ resumable: false, reason: err.message });
285
+ }
286
+ });
287
+
288
+ // POST /api/ceremony/sprint-planning/run
289
+ router.post('/sprint-planning/run', async (req, res) => {
290
+ try {
291
+ const { resumeFrom } = req.body || {};
292
+ const processId = await ceremonyService.runSprintPlanningInProcess(processRegistry, resumeFrom || null);
293
+ console.log('[ceremony] sprint-planning/run started in process', processId, resumeFrom ? `(resume from ${resumeFrom})` : '');
294
+ res.json({ started: true, processId });
295
+ } catch (err) {
296
+ console.error('[ceremony] sprint-planning/run error:', err.message);
297
+ res.status(400).json({ error: err.message });
298
+ }
299
+ });
300
+
301
+ // POST /api/ceremony/sprint-planning/confirm-selection
302
+ // Body: { selectedEpicIds: string[], selectedStoryIds: string[] }
303
+ router.post('/sprint-planning/confirm-selection', (req, res) => {
304
+ const { selectedEpicIds, selectedStoryIds } = req.body;
305
+ if (!Array.isArray(selectedEpicIds) || !Array.isArray(selectedStoryIds)) {
306
+ return res.status(400).json({ error: 'selectedEpicIds and selectedStoryIds must be arrays' });
307
+ }
308
+ ceremonyService.confirmSprintPlanningSelection(selectedEpicIds, selectedStoryIds);
309
+ res.json({ ok: true });
310
+ });
311
+
312
+ // POST /api/ceremony/pause
313
+ router.post('/pause', (req, res) => {
314
+ ceremonyService.pause();
315
+ res.json({ ok: true });
316
+ });
317
+
318
+ // POST /api/ceremony/resume
319
+ router.post('/resume', (req, res) => {
320
+ ceremonyService.resume();
321
+ res.json({ ok: true });
322
+ });
323
+
324
+ // POST /api/ceremony/cancel
325
+ router.post('/cancel', (req, res) => {
326
+ const keepItems = req.body?.keepItems === true;
327
+ ceremonyService.cancel({ keepItems });
328
+ res.json({ ok: true });
329
+ });
330
+
331
+ // POST /api/ceremony/cost-limit-continue
332
+ router.post('/cost-limit-continue', (req, res) => {
333
+ ceremonyService.continuePastCostLimit();
334
+ res.json({ ok: true });
335
+ });
336
+
337
+ // POST /api/ceremony/quota-continue
338
+ // Body: { newProvider?: string, newModel?: string }
339
+ // Omit newProvider to retry with same model (e.g. user added credits).
340
+ router.post('/quota-continue', (req, res) => {
341
+ const { newProvider = null, newModel = null } = req.body || {};
342
+ ceremonyService.continueAfterQuota(newProvider, newModel);
343
+ res.json({ ok: true });
344
+ });
345
+
346
+ // POST /api/ceremony/reset — force-stop any running ceremony and reset state immediately
347
+ router.post('/reset', (req, res) => {
348
+ ceremonyService.forceReset();
349
+ res.json({ ok: true });
350
+ });
351
+
352
+ // ── Sponsor-call draft (resume support) ────────────────────────────────────
353
+
354
+ const draftFilePath = () =>
355
+ path.join(ceremonyService.projectRoot, '.avc', 'sponsor-call-draft.json');
356
+
357
+ // GET /api/ceremony/sponsor-call/draft
358
+ router.get('/sponsor-call/draft', (req, res) => {
359
+ try {
360
+ const content = fs.readFileSync(draftFilePath(), 'utf8');
361
+ res.json(JSON.parse(content));
362
+ } catch (_) {
363
+ res.status(404).json({ draft: null });
364
+ }
365
+ });
366
+
367
+ // PUT /api/ceremony/sponsor-call/draft
368
+ router.put('/sponsor-call/draft', (req, res) => {
369
+ try {
370
+ const data = { ...req.body, savedAt: new Date().toISOString() };
371
+ fs.writeFileSync(draftFilePath(), JSON.stringify(data, null, 2));
372
+ res.json({ ok: true });
373
+ } catch (err) {
374
+ console.error('[ceremony] draft save error:', err.message);
375
+ res.status(500).json({ error: err.message });
376
+ }
377
+ });
378
+
379
+ // DELETE /api/ceremony/sponsor-call/draft
380
+ router.delete('/sponsor-call/draft', (req, res) => {
381
+ try { fs.unlinkSync(draftFilePath()); } catch (_) {}
382
+ res.json({ ok: true });
383
+ });
384
+
385
+ // ---- Task Runner: Seed + Run ----
386
+
387
+ // POST /api/ceremony/seed/run — seed a story into tasks/subtasks
388
+ router.post('/seed/run', async (req, res) => {
389
+ if (!taskRunnerService) {
390
+ return res.status(503).json({ error: 'TaskRunnerService not available' });
391
+ }
392
+
393
+ const { storyId } = req.body;
394
+ if (!storyId || !/^context-\d{4}-\d{4}[a-z]?$/.test(storyId)) {
395
+ return res.status(400).json({ error: 'Valid storyId is required (format: context-XXXX-XXXX)' });
396
+ }
397
+
398
+ if (taskRunnerService.isRunning(storyId)) {
399
+ return res.status(409).json({ error: `Seed already running for ${storyId}` });
400
+ }
401
+
402
+ try {
403
+ const processId = taskRunnerService.runSeed(storyId);
404
+ res.json({ started: true, processId, storyId });
405
+ } catch (err) {
406
+ res.status(500).json({ error: err.message });
407
+ }
408
+ });
409
+
410
+ // POST /api/ceremony/run-task/run — run a task in a worktree
411
+ router.post('/run-task/run', async (req, res) => {
412
+ if (!taskRunnerService) {
413
+ return res.status(503).json({ error: 'TaskRunnerService not available' });
414
+ }
415
+
416
+ const { taskId } = req.body;
417
+ if (!taskId || !/^context-\d{4}-\d{4}[a-z]?-\d{4}$/.test(taskId)) {
418
+ return res.status(400).json({ error: 'Valid taskId is required (format: context-XXXX-XXXX-XXXX)' });
419
+ }
420
+
421
+ if (taskRunnerService.isRunning(taskId)) {
422
+ return res.status(409).json({ error: `Run already active for ${taskId}` });
423
+ }
424
+
425
+ try {
426
+ const processId = taskRunnerService.runTask(taskId);
427
+ res.json({ started: true, processId, taskId });
428
+ } catch (err) {
429
+ res.status(500).json({ error: err.message });
430
+ }
431
+ });
432
+
433
+ // GET /api/ceremony/task-runner/status — list all active runs
434
+ router.get('/task-runner/status', (req, res) => {
435
+ if (!taskRunnerService) {
436
+ return res.status(503).json({ error: 'TaskRunnerService not available' });
437
+ }
438
+ res.json({ runs: taskRunnerService.listRuns() });
439
+ });
440
+
441
+ // POST /api/ceremony/task-runner/cancel — cancel a run
442
+ router.post('/task-runner/cancel', (req, res) => {
443
+ if (!taskRunnerService) {
444
+ return res.status(503).json({ error: 'TaskRunnerService not available' });
445
+ }
446
+ const { processId } = req.body;
447
+ if (!processId) return res.status(400).json({ error: 'processId is required' });
448
+
449
+ const cancelled = taskRunnerService.cancel(processId);
450
+ res.json({ cancelled });
451
+ });
452
+
453
+ return router;
454
+ }
@@ -0,0 +1,163 @@
1
+ import express from 'express';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Costs Router
7
+ * Handles GET /api/costs/summary and /api/costs/history
8
+ * Reads token-history.json written by TokenTracker.
9
+ * @param {string} projectRoot - Absolute path to project root
10
+ */
11
+ export function createCostsRouter(projectRoot) {
12
+ const router = express.Router();
13
+ const historyPath = path.join(projectRoot, '.avc', 'token-history.json');
14
+
15
+ // Top-level ceremony names — each becomes a parent node in the hierarchy
16
+ const PARENT_CEREMONIES = ['sponsor-call', 'sprint-planning', 'seed'];
17
+
18
+ // Explicit stage → parent mapping for stages whose names don't carry a prefix
19
+ const STAGE_PARENT_MAP = {
20
+ 'mission-scope': 'sponsor-call',
21
+ 'mission-refine': 'sponsor-call',
22
+ 'analyze-database': 'sponsor-call',
23
+ 'analyze-architecture': 'sponsor-call',
24
+ 'prefill-answers': 'sponsor-call',
25
+ 'sprint-planning-decomposition': 'sprint-planning',
26
+ 'sprint-planning-validation': 'sprint-planning',
27
+ 'sprint-planning-solver': 'sprint-planning',
28
+ 'sprint-planning-doc-distribution':'sprint-planning',
29
+ 'sprint-planning-enrichment': 'sprint-planning',
30
+ };
31
+
32
+ function getParentCeremony(key) {
33
+ if (STAGE_PARENT_MAP[key]) return STAGE_PARENT_MAP[key];
34
+ for (const parent of PARENT_CEREMONIES) {
35
+ if (key !== parent && key.startsWith(`${parent}-`)) return parent;
36
+ }
37
+ if (PARENT_CEREMONIES.includes(key)) return key; // self
38
+ return null;
39
+ }
40
+
41
+ function readHistory() {
42
+ if (!fs.existsSync(historyPath)) return null;
43
+ try {
44
+ return JSON.parse(fs.readFileSync(historyPath, 'utf8'));
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ function getCurrentMonthKey() {
51
+ return new Date().toISOString().substring(0, 7); // YYYY-MM
52
+ }
53
+
54
+ // GET /api/costs/summary — current month totals for header chip
55
+ router.get('/summary', (req, res) => {
56
+ const history = readHistory();
57
+ if (!history) return res.json({ totalCost: 0, totalTokens: 0, apiCalls: 0 });
58
+
59
+ const monthKey = getCurrentMonthKey();
60
+ const monthly = history.totals?.monthly?.[monthKey] ?? {};
61
+
62
+ res.json({
63
+ totalCost: monthly.cost?.total ?? 0,
64
+ totalTokens: (monthly.input ?? 0) + (monthly.output ?? 0),
65
+ apiCalls: monthly.executions ?? 0,
66
+ });
67
+ });
68
+
69
+ // GET /api/costs/history?days=30 (or ?from=YYYY-MM-DD&to=YYYY-MM-DD)
70
+ router.get('/history', (req, res) => {
71
+ const history = readHistory();
72
+ if (!history) return res.json({ daily: [], ceremonies: [] });
73
+
74
+ // Determine date range
75
+ let cutoff, endDate;
76
+ if (req.query.from && req.query.to) {
77
+ cutoff = new Date(req.query.from);
78
+ endDate = new Date(req.query.to);
79
+ endDate.setDate(endDate.getDate() + 1); // inclusive end
80
+ } else {
81
+ const days = Math.max(1, Math.min(365, parseInt(req.query.days ?? '30', 10)));
82
+ cutoff = new Date();
83
+ cutoff.setDate(cutoff.getDate() - days);
84
+ endDate = new Date();
85
+ endDate.setDate(endDate.getDate() + 1);
86
+ }
87
+
88
+ // Filter daily totals
89
+ const dailyData = history.totals?.daily ?? {};
90
+ const daily = Object.entries(dailyData)
91
+ .filter(([date]) => {
92
+ const d = new Date(date);
93
+ return d >= cutoff && d < endDate;
94
+ })
95
+ .sort(([a], [b]) => a.localeCompare(b))
96
+ .map(([date, data]) => ({
97
+ date,
98
+ cost: data.cost?.total ?? 0,
99
+ saved: data.cost?.saved ?? 0,
100
+ tokens: data.total ?? 0,
101
+ cached: data.cached ?? 0,
102
+ executions: data.executions ?? 0,
103
+ }));
104
+
105
+ // Build parent node skeletons
106
+ const SKIP_KEYS = new Set(['version', 'lastUpdated', 'totals']);
107
+ const parentNodes = {};
108
+ for (const p of PARENT_CEREMONIES) {
109
+ parentNodes[p] = { name: p, calls: 0, tokens: 0, cost: 0, cached: 0, saved: 0, stages: [] };
110
+ }
111
+ const orphans = []; // keys that don't map to a known parent
112
+
113
+ for (const [key, value] of Object.entries(history)) {
114
+ if (SKIP_KEYS.has(key)) continue;
115
+ if (!value || typeof value !== 'object') continue;
116
+
117
+ let totalInput = 0, totalOutput = 0, totalCost = 0, totalExec = 0, totalCached = 0, totalSaved = 0;
118
+ const dailyForKey = value.daily ?? {};
119
+ for (const [date, data] of Object.entries(dailyForKey)) {
120
+ const d = new Date(date);
121
+ if (d >= cutoff && d < endDate) {
122
+ totalInput += data.input ?? 0;
123
+ totalOutput += data.output ?? 0;
124
+ totalCost += data.cost?.total ?? 0;
125
+ totalExec += data.executions ?? 0;
126
+ totalCached += data.cached ?? 0;
127
+ totalSaved += data.cost?.saved ?? 0;
128
+ }
129
+ }
130
+
131
+ if (totalExec === 0 && totalInput === 0 && totalOutput === 0) continue;
132
+
133
+ const entry = { name: key, calls: totalExec, tokens: totalInput + totalOutput, cost: totalCost, cached: totalCached, saved: totalSaved };
134
+ const parent = getParentCeremony(key);
135
+
136
+ if (parent && parentNodes[parent]) {
137
+ // Don't add a ceremony as a stage of itself — only add sub-stages
138
+ if (key !== parent) {
139
+ parentNodes[parent].stages.push(entry);
140
+ }
141
+ parentNodes[parent].calls += totalExec;
142
+ parentNodes[parent].tokens += totalInput + totalOutput;
143
+ parentNodes[parent].cost += totalCost;
144
+ parentNodes[parent].cached += totalCached;
145
+ parentNodes[parent].saved += totalSaved;
146
+ } else {
147
+ orphans.push({ ...entry, stages: [] });
148
+ }
149
+ }
150
+
151
+ // Sort stages within each parent by cost desc
152
+ const ceremonies = [
153
+ ...Object.values(parentNodes).filter(c => c.cost > 0 || c.tokens > 0),
154
+ ...orphans,
155
+ ]
156
+ .map(c => ({ ...c, stages: (c.stages || []).sort((a, b) => b.cost - a.cost) }))
157
+ .sort((a, b) => b.cost - a.cost);
158
+
159
+ res.json({ daily, ceremonies });
160
+ });
161
+
162
+ return router;
163
+ }