@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,704 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import dotenv from 'dotenv';
4
+ import { getModelPricing, calculateCost, formatCost, getEstimatedTokens } from './model-pricing.js';
5
+
6
+ /**
7
+ * Handles interactive model configuration during project initialization.
8
+ * Allows users to select which LLM models to use for ceremonies and stages.
9
+ */
10
+ export class ModelConfigurator {
11
+ constructor(projectRoot) {
12
+ this.projectRoot = projectRoot;
13
+ this.avcConfigPath = path.join(projectRoot, '.avc', 'avc.json');
14
+ this.envPath = path.join(projectRoot, '.env');
15
+ this.availableProviders = [];
16
+ this.config = null;
17
+ }
18
+
19
+ /**
20
+ * Detect which API keys are available in .env file.
21
+ * Returns array of provider names (informational - used for UI indicators).
22
+ * @returns {string[]} Array of provider names: ['claude', 'gemini', 'openai']
23
+ */
24
+ detectAvailableProviders() {
25
+ // Load .env file if it exists
26
+ if (fs.existsSync(this.envPath)) {
27
+ dotenv.config({ path: this.envPath });
28
+ }
29
+
30
+ const providers = [];
31
+
32
+ // Check for each provider's API key or alternative auth token
33
+ if (process.env.ANTHROPIC_API_KEY) providers.push('claude');
34
+
35
+ if (process.env.GEMINI_API_KEY) providers.push('gemini');
36
+
37
+ const oauthFile = path.join(this.projectRoot, '.avc', 'openai-oauth.json');
38
+ const openaiOk = !!(process.env.OPENAI_API_KEY ||
39
+ (process.env.OPENAI_AUTH_MODE === 'oauth' && fs.existsSync(oauthFile)));
40
+ if (openaiOk) providers.push('openai');
41
+
42
+ if (process.env.XIAOMI_API_KEY) providers.push('xiaomi');
43
+
44
+ return providers;
45
+ }
46
+
47
+ /**
48
+ * Read current ceremony configuration from avc.json.
49
+ * @returns {Object} Parsed configuration object
50
+ */
51
+ readConfig() {
52
+ if (!fs.existsSync(this.avcConfigPath)) {
53
+ throw new Error('avc.json not found. Run /init first.');
54
+ }
55
+
56
+ const raw = fs.readFileSync(this.avcConfigPath, 'utf8');
57
+ this.config = JSON.parse(raw);
58
+ return this.config;
59
+ }
60
+
61
+ /**
62
+ * Get all ceremonies with their stage configurations.
63
+ * @returns {Array} Array of ceremony objects with provider/model info
64
+ */
65
+ getCeremonies() {
66
+ if (!this.config) {
67
+ this.readConfig();
68
+ }
69
+
70
+ return this.config.settings.ceremonies.map(ceremony => {
71
+ // Build stages object with all available stages for this ceremony
72
+ const stages = {};
73
+
74
+ // Define available stages per ceremony (excluding 'main' and 'validation' which are handled separately)
75
+ const ceremonyStages = {
76
+ 'sponsor-call': ['suggestions', 'architecture-recommendation', 'question-prefilling', 'documentation'],
77
+ 'sprint-planning': ['decomposition', 'doc-distribution', 'validation'],
78
+ 'seed': ['decomposition', 'validation', 'context-generation'],
79
+ 'run': ['code-generation', 'code-validation', 'test-execution'],
80
+ 'context-retrospective': ['documentation-update', 'context-refinement']
81
+ };
82
+
83
+ const availableStages = ceremonyStages[ceremony.name] || [];
84
+
85
+ // Build stages object with effective provider/model for each stage
86
+ availableStages.forEach(stageName => {
87
+ const stageConfig = ceremony.stages?.[stageName];
88
+
89
+ if (stageConfig) {
90
+ // Stage is explicitly configured
91
+ stages[stageName] = {
92
+ provider: stageConfig.provider,
93
+ model: stageConfig.model
94
+ };
95
+ } else {
96
+ // Stage not configured, use ceremony default
97
+ stages[stageName] = {
98
+ provider: ceremony.provider,
99
+ model: ceremony.defaultModel
100
+ };
101
+ }
102
+ });
103
+
104
+ return {
105
+ name: ceremony.name,
106
+ mainProvider: ceremony.provider,
107
+ mainModel: ceremony.defaultModel,
108
+ validationProvider: ceremony.validation?.provider,
109
+ validationModel: ceremony.validation?.model,
110
+ stages
111
+ };
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Get descriptive name for a stage based on its type and ceremony context.
117
+ * @param {string} stageName - The stage identifier (e.g., 'suggestions', 'documentation')
118
+ * @param {string} ceremonyName - The ceremony name for context
119
+ * @returns {string} Descriptive stage name
120
+ */
121
+ _getStageDisplayName(stageName, ceremonyName) {
122
+ // Context-aware stage descriptions that explain what each stage does
123
+ const ceremonyStageDescriptions = {
124
+ 'sponsor-call': {
125
+ 'suggestions': 'Questionnaire Suggestions - AI analyzes project name and suggests answers',
126
+ 'architecture-recommendation': 'Architecture Recommendation - AI suggests 3-5 deployment architectures based on project scope',
127
+ 'question-prefilling': 'Question Pre-filling - AI generates intelligent answers based on selected architecture',
128
+ 'documentation': 'Documentation Generation - AI creates initial project documentation'
129
+ },
130
+ 'sprint-planning': {
131
+ 'decomposition': 'Epic & Story Decomposition - AI breaks down project scope into epics and stories',
132
+ 'validation': 'Multi-Agent Validation - Domain experts validate each epic and story',
133
+ 'doc-distribution': 'Documentation Distribution - AI moves and elaborates content from parent doc.md to each epic and story'
134
+ },
135
+ 'seed': {
136
+ 'decomposition': 'Task Decomposition - AI breaks down stories into tasks and subtasks',
137
+ 'validation': 'Task Validation - AI validates task hierarchy and completeness',
138
+ 'context-generation': 'Task Documentation - AI creates doc.md for each task'
139
+ },
140
+ 'run': {
141
+ 'code-generation': 'Code Generation - AI implements task code with traceability rules',
142
+ 'code-validation': 'Code Validation - AI verifies code follows all quality and traceability checks',
143
+ 'test-execution': 'Test Execution - Runs generated tests to verify implementation'
144
+ },
145
+ 'context-retrospective': {
146
+ 'documentation-update': 'Documentation Enhancement - AI refines and improves project documentation',
147
+ 'context-refinement': 'Documentation Enhancement - AI enriches doc.md files with learned insights'
148
+ }
149
+ };
150
+
151
+ // Try ceremony-specific description first, then fallback to generic
152
+ const ceremonyDescriptions = ceremonyStageDescriptions[ceremonyName];
153
+ if (ceremonyDescriptions && ceremonyDescriptions[stageName]) {
154
+ return ceremonyDescriptions[stageName];
155
+ }
156
+
157
+ // Generic fallback descriptions
158
+ const genericDescriptions = {
159
+ suggestions: 'AI-Assisted Questionnaire',
160
+ documentation: 'Project Documentation Creation',
161
+ decomposition: 'Work Item Decomposition',
162
+ 'context-generation': 'Task Documentation Generation',
163
+ 'doc-distribution': 'Documentation Distribution',
164
+ 'documentation-update': 'Documentation Refinement',
165
+ 'context-refinement': 'Context Enhancement',
166
+ enhancement: 'Content Enhancement'
167
+ };
168
+
169
+ return genericDescriptions[stageName] || `${stageName.charAt(0).toUpperCase()}${stageName.slice(1)}`;
170
+ }
171
+
172
+ /**
173
+ * Get all stages for a specific ceremony.
174
+ * @param {string} ceremonyName - Name of the ceremony
175
+ * @returns {Array} Array of stage objects with id, name, provider, model, hasValidationTypes
176
+ */
177
+ getStagesForCeremony(ceremonyName) {
178
+ if (!this.config) {
179
+ this.readConfig();
180
+ }
181
+
182
+ const ceremony = this.config.settings.ceremonies.find(c => c.name === ceremonyName);
183
+ if (!ceremony) {
184
+ return [];
185
+ }
186
+
187
+ // Determine main stage description based on ceremony type
188
+ let mainStageName = 'Primary Execution';
189
+ if (ceremonyName === 'sponsor-call') {
190
+ mainStageName = 'Project Definition & Planning';
191
+ } else if (ceremonyName === 'sprint-planning') {
192
+ mainStageName = 'Epic & Story Expansion';
193
+ } else if (ceremonyName === 'context-retrospective') {
194
+ mainStageName = 'Context & Documentation Review';
195
+ } else if (ceremonyName === 'seed') {
196
+ mainStageName = 'Task & Subtask Planning';
197
+ }
198
+
199
+ const stages = [
200
+ {
201
+ id: 'main',
202
+ name: mainStageName,
203
+ provider: ceremony.provider,
204
+ model: ceremony.defaultModel,
205
+ hasValidationTypes: false
206
+ }
207
+ ];
208
+
209
+ // Add validation + refinement stages if configured
210
+ if (ceremony.validation) {
211
+ stages.push({
212
+ id: 'validation',
213
+ name: 'Quality Validator - Scores and identifies issues in generated output',
214
+ provider: ceremony.validation.provider || ceremony.provider,
215
+ model: ceremony.validation.model || ceremony.defaultModel,
216
+ hasValidationTypes: false
217
+ });
218
+ stages.push({
219
+ id: 'refinement',
220
+ name: 'Quality Refiner - Improves output based on validator feedback',
221
+ provider: ceremony.validation.refinement?.provider || ceremony.validation.provider || ceremony.provider,
222
+ model: ceremony.validation.refinement?.model || ceremony.validation.model || ceremony.defaultModel,
223
+ hasValidationTypes: false
224
+ });
225
+ }
226
+
227
+ // Define available stages per ceremony (same as getCeremonies)
228
+ const ceremonyStages = {
229
+ 'sponsor-call': ['suggestions', 'documentation'],
230
+ 'sprint-planning': ['decomposition', 'doc-distribution', 'validation'],
231
+ 'seed': ['decomposition', 'validation', 'context-generation'],
232
+ 'context-retrospective': ['documentation-update', 'context-refinement']
233
+ };
234
+
235
+ const availableStages = ceremonyStages[ceremonyName] || [];
236
+
237
+ // Add all available stages with effective provider/model
238
+ availableStages.forEach(stageName => {
239
+ const stageConfig = ceremony.stages?.[stageName];
240
+
241
+ // Check if this is the validation stage for sprint-planning
242
+ const hasValidationTypes = (ceremonyName === 'sprint-planning' && stageName === 'validation');
243
+
244
+ stages.push({
245
+ id: `stage-${stageName}`,
246
+ name: this._getStageDisplayName(stageName, ceremonyName),
247
+ provider: stageConfig?.provider || ceremony.provider,
248
+ model: stageConfig?.model || ceremony.defaultModel,
249
+ hasValidationTypes,
250
+ stageName // Store original stage name for validation type lookup
251
+ });
252
+ });
253
+
254
+ return stages;
255
+ }
256
+
257
+ /**
258
+ * Get validation types for sprint-planning validation stage
259
+ * @returns {Array} Array of validation type objects
260
+ */
261
+ getValidationTypes() {
262
+ return [
263
+ {
264
+ id: 'universal',
265
+ name: 'Universal Validators',
266
+ description: 'Always-applied (solution-architect, security, developer, qa, test-architect)'
267
+ },
268
+ {
269
+ id: 'domain',
270
+ name: 'Domain Validators',
271
+ description: 'Domain-specific (devops, database, frontend, api, backend, cloud, mobile, ui, ux, data)'
272
+ },
273
+ {
274
+ id: 'feature',
275
+ name: 'Feature Validators',
276
+ description: 'Inferred from acceptance criteria keywords'
277
+ },
278
+ {
279
+ id: 'default',
280
+ name: 'Default (All Validators)',
281
+ description: 'Fallback configuration for all validation types'
282
+ }
283
+ ];
284
+ }
285
+
286
+ /**
287
+ * Get current model configuration for a validation type
288
+ * @param {string} ceremonyName - Ceremony name (should be 'sprint-planning')
289
+ * @param {string} validationType - Validation type ID ('universal', 'domain', 'feature', 'default')
290
+ * @returns {Object|null} { provider, model } or null if not configured
291
+ */
292
+ getValidationTypeConfig(ceremonyName, validationType) {
293
+ if (!this.config) {
294
+ this.readConfig();
295
+ }
296
+
297
+ const ceremony = this.config.settings.ceremonies.find(c => c.name === ceremonyName);
298
+ if (!ceremony || !ceremony.stages || !ceremony.stages.validation) {
299
+ return null;
300
+ }
301
+
302
+ const validationStage = ceremony.stages.validation;
303
+ const validationTypeConfig = validationStage.validationTypes?.[validationType];
304
+
305
+ if (validationTypeConfig) {
306
+ return {
307
+ provider: validationTypeConfig.provider,
308
+ model: validationTypeConfig.model
309
+ };
310
+ }
311
+
312
+ // Return default validation stage config if validation type not configured
313
+ return {
314
+ provider: validationStage.provider || ceremony.provider,
315
+ model: validationStage.model || ceremony.defaultModel
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Get available models, optionally filtered by provider.
321
+ * Shows ALL models from settings, regardless of API key availability.
322
+ * @param {string|null} providerFilter - Optional provider to filter by
323
+ * @returns {Array} Array of model objects with hasApiKey indicator
324
+ */
325
+ getAvailableModels(providerFilter = null) {
326
+ if (!this.config) {
327
+ this.readConfig();
328
+ }
329
+
330
+ const allModels = this.config.settings.models;
331
+ const filtered = [];
332
+
333
+ for (const [modelId, modelInfo] of Object.entries(allModels)) {
334
+ // Filter by provider if specified
335
+ if (providerFilter && modelInfo.provider !== providerFilter) {
336
+ continue;
337
+ }
338
+
339
+ // Add indicator if API key is available (informational only)
340
+ const hasApiKey = this.availableProviders.includes(modelInfo.provider);
341
+
342
+ filtered.push({
343
+ id: modelId,
344
+ displayName: modelInfo.displayName,
345
+ provider: modelInfo.provider,
346
+ pricing: modelInfo.pricing,
347
+ hasApiKey // User can still select models without keys
348
+ });
349
+ }
350
+
351
+ // Sort by provider, then by input price (high to low for quality indication)
352
+ return filtered.sort((a, b) => {
353
+ if (a.provider !== b.provider) {
354
+ return a.provider.localeCompare(b.provider);
355
+ }
356
+ return b.pricing.input - a.pricing.input;
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Update stage configuration with new model.
362
+ * @param {string} ceremonyName - Name of the ceremony
363
+ * @param {string} stageId - ID of the stage (main, validation, stage-*)
364
+ * @param {string} newModel - New model ID to use
365
+ * @param {string|null} validationType - Optional validation type for sprint-planning validation stage
366
+ */
367
+ updateStage(ceremonyName, stageId, newModel, validationType = null) {
368
+ if (!this.config) {
369
+ this.readConfig();
370
+ }
371
+
372
+ const ceremony = this.config.settings.ceremonies.find(c => c.name === ceremonyName);
373
+ if (!ceremony) {
374
+ throw new Error(`Ceremony ${ceremonyName} not found`);
375
+ }
376
+
377
+ const modelInfo = this.config.settings.models[newModel];
378
+ if (!modelInfo) {
379
+ throw new Error(`Model ${newModel} not found`);
380
+ }
381
+
382
+ // Update the appropriate configuration section
383
+ if (stageId === 'main') {
384
+ ceremony.provider = modelInfo.provider;
385
+ ceremony.defaultModel = newModel;
386
+ } else if (stageId === 'validation') {
387
+ if (!ceremony.validation) {
388
+ ceremony.validation = { enabled: true };
389
+ }
390
+ ceremony.validation.provider = modelInfo.provider;
391
+ ceremony.validation.model = newModel;
392
+ } else if (stageId === 'refinement') {
393
+ if (!ceremony.validation) {
394
+ ceremony.validation = { enabled: true };
395
+ }
396
+ if (!ceremony.validation.refinement) {
397
+ ceremony.validation.refinement = {};
398
+ }
399
+ ceremony.validation.refinement.provider = modelInfo.provider;
400
+ ceremony.validation.refinement.model = newModel;
401
+ } else if (stageId.startsWith('stage-')) {
402
+ const stageName = stageId.replace('stage-', '');
403
+ if (!ceremony.stages) {
404
+ ceremony.stages = {};
405
+ }
406
+ if (!ceremony.stages[stageName]) {
407
+ ceremony.stages[stageName] = {};
408
+ }
409
+
410
+ // Handle validation type sub-configuration for sprint-planning validation stage
411
+ if (validationType && stageName === 'validation' && ceremonyName === 'sprint-planning') {
412
+ if (!ceremony.stages[stageName].validationTypes) {
413
+ ceremony.stages[stageName].validationTypes = {};
414
+ }
415
+ ceremony.stages[stageName].validationTypes[validationType] = {
416
+ provider: modelInfo.provider,
417
+ model: newModel
418
+ };
419
+ } else {
420
+ ceremony.stages[stageName].provider = modelInfo.provider;
421
+ ceremony.stages[stageName].model = newModel;
422
+ }
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Save updated configuration to avc.json.
428
+ */
429
+ saveConfig() {
430
+ if (!this.config) {
431
+ throw new Error('No configuration loaded');
432
+ }
433
+
434
+ fs.writeFileSync(
435
+ this.avcConfigPath,
436
+ JSON.stringify(this.config, null, 2),
437
+ 'utf8'
438
+ );
439
+ }
440
+
441
+ /**
442
+ * Check if configuration has providers without API keys.
443
+ * Returns array of issues (informational warnings).
444
+ * @returns {Array} Array of issue objects with ceremony, stage, provider
445
+ */
446
+ validateConfig() {
447
+ if (!this.config) {
448
+ this.readConfig();
449
+ }
450
+
451
+ const issues = [];
452
+ const ceremonies = this.config.settings.ceremonies;
453
+
454
+ ceremonies.forEach(ceremony => {
455
+ // Check main provider
456
+ if (!this.availableProviders.includes(ceremony.provider)) {
457
+ issues.push({
458
+ ceremony: ceremony.name,
459
+ stage: 'main',
460
+ provider: ceremony.provider
461
+ });
462
+ }
463
+
464
+ // Check validation provider
465
+ if (ceremony.validation && !this.availableProviders.includes(ceremony.validation.provider)) {
466
+ issues.push({
467
+ ceremony: ceremony.name,
468
+ stage: 'validation',
469
+ provider: ceremony.validation.provider
470
+ });
471
+ }
472
+
473
+ // Check stage-specific providers
474
+ if (ceremony.stages) {
475
+ Object.keys(ceremony.stages).forEach(stageName => {
476
+ const stage = ceremony.stages[stageName];
477
+ if (stage.provider && !this.availableProviders.includes(stage.provider)) {
478
+ issues.push({
479
+ ceremony: ceremony.name,
480
+ stage: stageName,
481
+ provider: stage.provider
482
+ });
483
+ }
484
+ });
485
+ }
486
+ });
487
+
488
+ return issues;
489
+ }
490
+
491
+ /**
492
+ * Get complete configuration overview with cost estimates for a ceremony
493
+ * @param {string} ceremonyName - Name of ceremony (e.g., 'sprint-planning')
494
+ * @returns {Object} Configuration overview with costs
495
+ */
496
+ getConfigurationOverview(ceremonyName) {
497
+ if (!this.config) {
498
+ this.readConfig();
499
+ }
500
+
501
+ const ceremony = this.config.settings.ceremonies.find(c => c.name === ceremonyName);
502
+ if (!ceremony) {
503
+ throw new Error(`Ceremony '${ceremonyName}' not found in configuration`);
504
+ }
505
+
506
+ // Get ceremony-specific main label
507
+ const mainLabels = {
508
+ 'sponsor-call': 'Default Fallback Model - Used when specific stages are not configured',
509
+ 'sprint-planning': 'Default Fallback Model - Used when specific stages are not configured',
510
+ 'seed': 'Default Fallback Model - Used when specific stages are not configured',
511
+ 'context-retrospective': 'Default Fallback Model - Used when specific stages are not configured'
512
+ };
513
+
514
+ const overview = {
515
+ ceremony: ceremonyName,
516
+ main: {
517
+ label: mainLabels[ceremonyName] || 'Default Fallback Model',
518
+ provider: ceremony.provider || 'claude',
519
+ model: ceremony.defaultModel || 'claude-sonnet-4-5-20250929',
520
+ calls: 0,
521
+ cost: 0,
522
+ formattedCost: 'Free',
523
+ isDefault: true,
524
+ level: 0
525
+ },
526
+ stages: [],
527
+ validationTypes: null,
528
+ totalCost: 0,
529
+ totalCalls: 0
530
+ };
531
+
532
+ // Add stages
533
+ const stages = this.getStagesForCeremony(ceremonyName);
534
+ stages.forEach(stage => {
535
+ // Skip 'main' stage as it's already in overview.main
536
+ if (stage.id === 'main') {
537
+ return;
538
+ }
539
+
540
+ if (stage.id.startsWith('stage-') && stage.stageName === 'validation' && ceremonyName === 'sprint-planning') {
541
+ // Handle validation stage specially for sprint-planning
542
+ overview.stages.push(this._getValidationStageOverview(ceremony, stage));
543
+ } else {
544
+ const stageProvider = stage.provider || ceremony.provider;
545
+ const stageModel = stage.model || ceremony.defaultModel;
546
+
547
+ // Use stageName (without 'stage-' prefix) for lookups, fallback to id
548
+ const lookupName = stage.stageName || stage.id.replace('stage-', '');
549
+ const tokens = getEstimatedTokens(ceremonyName, lookupName);
550
+ const calls = this._getCallCount(ceremonyName, lookupName);
551
+ const cost = calculateCost(stageModel, tokens);
552
+
553
+ overview.stages.push({
554
+ id: stage.id,
555
+ label: stage.name,
556
+ provider: stageProvider,
557
+ model: stageModel,
558
+ calls,
559
+ cost,
560
+ formattedCost: formatCost(cost),
561
+ usingDefault: !stage.provider,
562
+ level: 1
563
+ });
564
+
565
+ overview.totalCost += cost;
566
+ overview.totalCalls += calls;
567
+ }
568
+ });
569
+
570
+ return overview;
571
+ }
572
+
573
+ /**
574
+ * Get validation stage overview with validation types (sprint-planning only)
575
+ */
576
+ _getValidationStageOverview(ceremony, validationStage) {
577
+ const validationConfig = ceremony.stages?.validation || {};
578
+ const validationProvider = validationConfig.provider || ceremony.provider;
579
+ const validationModel = validationConfig.model || ceremony.defaultModel;
580
+
581
+ const overview = {
582
+ id: 'validation',
583
+ label: 'Multi-Agent Validation - Domain experts validate each epic and story',
584
+ provider: validationProvider,
585
+ model: validationModel,
586
+ calls: 145,
587
+ cost: 0, // Calculated from types
588
+ formattedCost: '', // Set after calculating types
589
+ usingDefault: !validationConfig.provider,
590
+ level: 1,
591
+ validationTypes: []
592
+ };
593
+
594
+ const types = [
595
+ {
596
+ id: 'universal',
597
+ name: 'Universal Validators - Always applied (architecture, security, quality)',
598
+ calls: 30,
599
+ validators: ['solution-architect', 'developer', 'security', 'qa', 'test-architect']
600
+ },
601
+ {
602
+ id: 'domain',
603
+ name: 'Domain Validators - Applied based on project tech stack',
604
+ calls: 90,
605
+ validators: ['devops', 'database', 'api', 'frontend', 'backend', 'cloud', 'mobile', 'ui', 'ux', 'data']
606
+ },
607
+ {
608
+ id: 'feature',
609
+ name: 'Feature Validators - Applied based on acceptance criteria keywords',
610
+ calls: 25,
611
+ validators: ['testing', 'security', 'file-upload', 'notifications', 'reporting']
612
+ }
613
+ ];
614
+
615
+ let totalValidationCost = 0;
616
+
617
+ types.forEach(type => {
618
+ const typeConfig = validationConfig.validationTypes?.[type.id];
619
+ const typeProvider = typeConfig?.provider || validationProvider;
620
+ const typeModel = typeConfig?.model || validationModel;
621
+ const tokens = getEstimatedTokens('sprint-planning', `validation-${type.id}`);
622
+ const cost = calculateCost(typeModel, tokens);
623
+
624
+ overview.validationTypes.push({
625
+ id: type.id,
626
+ label: `${type.name} Validators`,
627
+ provider: typeProvider,
628
+ model: typeModel,
629
+ calls: type.calls,
630
+ cost,
631
+ formattedCost: formatCost(cost),
632
+ validators: type.validators,
633
+ usingDefault: !typeConfig,
634
+ level: 2
635
+ });
636
+
637
+ totalValidationCost += cost;
638
+ });
639
+
640
+ overview.cost = totalValidationCost;
641
+ overview.formattedCost = formatCost(totalValidationCost);
642
+
643
+ return overview;
644
+ }
645
+
646
+ /**
647
+ * Get call count for a stage
648
+ */
649
+ _getCallCount(ceremonyName, stageId) {
650
+ const callCounts = {
651
+ 'sprint-planning': {
652
+ 'decomposition': 1,
653
+ 'doc-distribution': 25,
654
+ 'validation': 145
655
+ },
656
+ 'sponsor-call': {
657
+ 'suggestions': 1,
658
+ 'documentation': 1,
659
+ 'validation': 2,
660
+ 'refinement': 2
661
+ },
662
+ 'seed': {
663
+ 'decomposition': 1,
664
+ 'validation': 20,
665
+ 'context-generation': 10
666
+ },
667
+ 'context-retrospective': {
668
+ 'documentation-update': 10,
669
+ 'context-refinement': 15
670
+ }
671
+ };
672
+
673
+ return callCounts[ceremonyName]?.[stageId] || 0;
674
+ }
675
+
676
+ /**
677
+ * Reset a configuration path to use main default
678
+ * @param {string} ceremonyName - Ceremony name
679
+ * @param {string} path - Configuration path (e.g., 'stages.decomposition')
680
+ */
681
+ resetToDefault(ceremonyName, configPath) {
682
+ if (!this.config) {
683
+ this.readConfig();
684
+ }
685
+
686
+ const ceremony = this.config.settings.ceremonies.find(c => c.name === ceremonyName);
687
+ if (!ceremony) {
688
+ throw new Error(`Ceremony '${ceremonyName}' not found`);
689
+ }
690
+
691
+ // Delete the configuration at the path to fall back to default
692
+ const pathParts = configPath.split('.');
693
+ let current = ceremony;
694
+
695
+ for (let i = 0; i < pathParts.length - 1; i++) {
696
+ current = current[pathParts[i]];
697
+ if (!current) return; // Path doesn't exist, nothing to reset
698
+ }
699
+
700
+ delete current[pathParts[pathParts.length - 1]];
701
+
702
+ this.saveConfig(this.config);
703
+ }
704
+ }