@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
@@ -3,11 +3,38 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import readline from 'readline';
5
5
  import { LLMProvider } from './llm-provider.js';
6
+ import { LLMVerifier } from './llm-verifier.js';
7
+ import { TokenTracker } from './token-tracker.js';
8
+ import { VerificationTracker } from './verification-tracker.js';
6
9
  import { fileURLToPath } from 'url';
10
+ import { sendError, sendWarning, sendSuccess, sendInfo, sendOutput, sendIndented, sendSectionHeader, sendProgress } from './messaging-api.js';
11
+ import { loadAgent } from './agent-loader.js';
7
12
 
8
13
  const __filename = fileURLToPath(import.meta.url);
9
14
  const __dirname = path.dirname(__filename);
10
15
 
16
+ /**
17
+ * Module-level debug log file path.
18
+ * Set via TemplateProcessor.setDebugLogFile() from repl-ink.js before creating
19
+ * any TemplateProcessor instance. When null, debug() is silent (no terminal output).
20
+ * This ensures diagnostic output NEVER corrupts the Ink UI.
21
+ */
22
+ let _debugLogFile = null;
23
+
24
+ /**
25
+ * Debug logging helper - writes directly to the log file, never to the terminal.
26
+ * @param {string} message - Log message
27
+ * @param {Object} data - Optional data to log
28
+ */
29
+ function debug(message, data = null) {
30
+ if (!_debugLogFile) return;
31
+ const timestamp = new Date().toISOString();
32
+ const line = data !== null
33
+ ? `[${timestamp}] [DEBUG] ${message}\n${JSON.stringify(data, null, 2)}\n`
34
+ : `[${timestamp}] [DEBUG] ${message}\n`;
35
+ try { fs.appendFileSync(_debugLogFile, line, 'utf8'); } catch (_) {}
36
+ }
37
+
11
38
  /**
12
39
  * TemplateProcessor - Handles interactive template processing with AI suggestions
13
40
  *
@@ -20,55 +47,291 @@ const __dirname = path.dirname(__filename);
20
47
  * 6. Write to .avc/project/doc.md
21
48
  */
22
49
  class TemplateProcessor {
23
- constructor(progressPath = null, nonInteractive = false) {
50
+ constructor(ceremonyName = 'sponsor-call', progressPath = null, nonInteractive = false, options = {}) {
24
51
  // Load environment variables from project .env
25
52
  dotenv.config({ path: path.join(process.cwd(), '.env') });
26
53
 
54
+ debug('TemplateProcessor constructor called', { ceremonyName, nonInteractive });
55
+
56
+ this.ceremonyName = ceremonyName;
57
+
58
+ // Initialize verification tracker
59
+ this.verificationTracker = new VerificationTracker(ceremonyName);
27
60
  this.templatePath = path.join(__dirname, 'templates/project.md');
28
61
  this.outputDir = path.join(process.cwd(), '.avc/project');
29
62
  this.outputPath = path.join(this.outputDir, 'doc.md');
30
63
  this.avcConfigPath = path.join(process.cwd(), '.avc/avc.json');
64
+ this.agentsPath = path.join(__dirname, 'agents');
65
+ this.avcPath = path.join(process.cwd(), '.avc');
31
66
  this.progressPath = progressPath;
32
67
  this.nonInteractive = nonInteractive;
33
68
 
34
- // Read model configuration from avc.json
35
- const { provider, model } = this.readModelConfig();
69
+ // Track verification feedback for agent learning
70
+ this.verificationFeedback = {};
71
+
72
+ // Collect validation issues for final ceremony summary
73
+ this.validationIssues = [];
74
+
75
+ // Read ceremony-specific configuration
76
+ const { provider, model, validationConfig, stagesConfig } = this.readCeremonyConfig(ceremonyName);
36
77
  this._providerName = provider;
37
78
  this._modelName = model;
38
79
  this.llmProvider = null;
80
+ this.stagesConfig = stagesConfig;
81
+
82
+ // Read validation provider config
83
+ this._validationProvider = null;
84
+ this._validationModel = null;
85
+ this.validationLLMProvider = null;
86
+ this.validationConfig = validationConfig;
87
+
88
+ if (validationConfig?.enabled) {
89
+ this._validationProvider = validationConfig.provider;
90
+ this._validationModel = validationConfig.model;
91
+
92
+ // Validate required fields
93
+ if (!this._validationProvider || !this._validationModel) {
94
+ throw new Error(
95
+ `Validation is enabled for '${ceremonyName}' but validation.provider or validation.model is not configured in avc.json`
96
+ );
97
+ }
98
+ }
99
+
100
+ // Read refinement provider config (separate from validator — used in improveDocument)
101
+ const refinementConfig = validationConfig?.refinement;
102
+ this._refinementProvider = refinementConfig?.provider || this._validationProvider;
103
+ this._refinementModel = refinementConfig?.model || this._validationModel;
104
+ this._refinementProviderInstance = null;
105
+
106
+ // Initialize token tracker
107
+ this.tokenTracker = new TokenTracker(this.avcPath);
108
+ this.tokenTracker.init();
109
+
110
+ // Track last token usage for ceremony history
111
+ this._lastTokenUsage = null;
112
+
113
+ // Cost threshold protection
114
+ this._costThreshold = options?.costThreshold ?? null;
115
+ this._costLimitReachedCallback = options?.costLimitReachedCallback ?? null;
116
+ this._runningCost = 0;
117
+
118
+ // Progress reporting
119
+ this.progressCallback = null;
120
+ this.activityLog = [];
121
+ }
122
+
123
+ /**
124
+ * Strip markdown code fences from content
125
+ * Handles: ```json ... ``` or ``` ... ```
126
+ */
127
+ stripMarkdownCodeFences(content) {
128
+ if (typeof content !== 'string') return content;
129
+
130
+ // Remove opening fence (```json, ```JSON, or just ```)
131
+ let stripped = content.replace(/^```(?:json|JSON)?\s*\n?/, '');
132
+
133
+ // Remove closing fence (```)
134
+ stripped = stripped.replace(/\n?\s*```\s*$/, '');
135
+
136
+ return stripped.trim();
137
+ }
138
+
139
+ /**
140
+ * Set progress callback for updating UI during execution
141
+ */
142
+ setProgressCallback(callback) {
143
+ this.progressCallback = callback;
144
+ }
145
+
146
+ /**
147
+ * Report progress to callback and log activity
148
+ */
149
+ async reportProgress(message, activity = null) {
150
+ if (this.progressCallback) {
151
+ await this.progressCallback(message);
152
+ }
153
+ if (activity) {
154
+ this.activityLog.push(activity);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Report substep to callback (for detailed progress tracking)
160
+ * @param {string} substep - The substep message
161
+ * @param {Object} metadata - Additional metadata (tokensUsed, filesCreated)
162
+ */
163
+ async reportSubstep(substep, metadata = {}) {
164
+ if (this.progressCallback) {
165
+ await this.progressCallback(null, substep, metadata);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Report a level-3 detail line to callback
171
+ * @param {string} detail - The detail message
172
+ */
173
+ async reportDetail(detail) {
174
+ if (this.progressCallback) {
175
+ await this.progressCallback(null, null, { detail });
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Report progress with small delay to ensure UI updates
181
+ * Adds async delay to force React state updates and re-renders between stages
182
+ * @param {string} message - Main progress message
183
+ * @param {string} activity - Activity to log
184
+ * @param {number} delayMs - Delay in milliseconds (default 50ms)
185
+ */
186
+ async reportProgressWithDelay(message, activity = null, delayMs = 50) {
187
+ await this.reportProgress(message, activity);
188
+ // Only delay in non-interactive mode (REPL UI) to allow UI re-renders
189
+ if (!this.nonInteractive && this.progressCallback) {
190
+ await new Promise(resolve => setTimeout(resolve, delayMs));
191
+ }
39
192
  }
40
193
 
41
194
  /**
42
- * Read model configuration from avc.json
195
+ * Read ceremony-specific configuration from avc.json
43
196
  */
44
- readModelConfig() {
197
+ readCeremonyConfig(ceremonyName) {
45
198
  try {
46
199
  const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
47
- const ceremony = config.settings?.ceremonies?.[0];
48
- if (ceremony) {
49
- return { provider: ceremony.provider || 'claude', model: ceremony.defaultModel || 'claude-sonnet-4-5-20250929' };
200
+ const ceremony = config.settings?.ceremonies?.find(c => c.name === ceremonyName);
201
+
202
+ if (!ceremony) {
203
+ sendWarning(`Ceremony '${ceremonyName}' not found in config, using defaults`);
204
+ return {
205
+ provider: 'claude',
206
+ model: 'claude-sonnet-4-5-20250929',
207
+ validationConfig: null,
208
+ stagesConfig: null
209
+ };
50
210
  }
51
- // Legacy fallback: settings.model without ceremonies array
52
- return { provider: 'claude', model: config.settings?.model || 'claude-sonnet-4-5-20250929' };
211
+
212
+ return {
213
+ provider: ceremony.provider || 'claude',
214
+ model: ceremony.defaultModel || 'claude-sonnet-4-5-20250929',
215
+ validationConfig: ceremony.validation || null,
216
+ stagesConfig: ceremony.stages || null
217
+ };
53
218
  } catch (error) {
54
- console.warn('⚠️ Could not read model config, using default');
55
- return { provider: 'claude', model: 'claude-sonnet-4-5-20250929' };
219
+ sendWarning(`Could not read ceremony config: ${error.message}`);
220
+ return {
221
+ provider: 'claude',
222
+ model: 'claude-sonnet-4-5-20250929',
223
+ validationConfig: null,
224
+ stagesConfig: null
225
+ };
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Get provider and model for a specific stage
231
+ * Falls back to ceremony-level config if stage-specific config not found
232
+ * @param {string} stageName - Stage name ('suggestions', 'documentation')
233
+ * @returns {Object} { provider, model }
234
+ */
235
+ getProviderForStage(stageName) {
236
+ // Check if stage-specific config exists
237
+ if (this.stagesConfig && this.stagesConfig[stageName]) {
238
+ const stageConfig = this.stagesConfig[stageName];
239
+ return {
240
+ provider: stageConfig.provider || this._providerName,
241
+ model: stageConfig.model || this._modelName
242
+ };
243
+ }
244
+
245
+ // Fall back to ceremony-level config
246
+ return {
247
+ provider: this._providerName,
248
+ model: this._modelName
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Get provider and model for validation of a specific content type
254
+ * Falls back to ceremony-level validation config if type-specific config not found
255
+ * @param {string} validationType - Validation type ('documentation')
256
+ * @returns {Object} { provider, model }
257
+ */
258
+ getValidationProviderForType(validationType) {
259
+ // Check if type-specific validation config exists
260
+ if (this.validationConfig && this.validationConfig[validationType]) {
261
+ const typeConfig = this.validationConfig[validationType];
262
+ return {
263
+ provider: typeConfig.provider || this._validationProvider,
264
+ model: typeConfig.model || this._validationModel
265
+ };
56
266
  }
267
+
268
+ // Fall back to ceremony-level validation config
269
+ return {
270
+ provider: this._validationProvider,
271
+ model: this._validationModel
272
+ };
57
273
  }
58
274
 
59
275
  /**
60
- * Read guidelines from avc.json ceremony configuration
276
+ * Read defaults from avc.json questionnaire configuration
61
277
  */
62
- readGuidelines() {
278
+ readDefaults() {
63
279
  try {
64
280
  const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
65
- const ceremony = config.settings?.ceremonies?.[0];
66
- return ceremony?.guidelines || {};
281
+ return config.settings?.questionnaire?.defaults || {};
67
282
  } catch (error) {
68
283
  return {};
69
284
  }
70
285
  }
71
286
 
287
+ /**
288
+ * Get human-readable feedback for a rule violation
289
+ * @param {string} ruleId - Rule ID
290
+ * @returns {string} - Feedback message
291
+ */
292
+ getRuleFeedback(ruleId) {
293
+ const feedbackMap = {
294
+ 'valid-json': 'You wrapped JSON in markdown code fences (```json). Output raw JSON only, no markdown formatting.',
295
+ 'required-fields': 'You missed required JSON fields. Include all mandatory fields as specified in the output format.',
296
+ 'array-fields': 'Field that should be an array was output as a different type. Check array field requirements.',
297
+ 'required-validator-fields': 'Validator output missing required fields. Include validationStatus, overallScore, and issues.',
298
+ 'score-range': 'Score must be between 0-100. Check your overallScore value.',
299
+ 'valid-severity': 'Invalid severity value. Use only: critical, major, or minor.',
300
+ 'valid-status': 'Invalid status value. Use only the allowed status values from the spec.'
301
+ };
302
+ return feedbackMap[ruleId] || `Rule "${ruleId}" was violated. Follow the output format exactly.`;
303
+ }
304
+
305
+ /**
306
+ * Enhance prompt with verification feedback from previous runs
307
+ * @param {string} prompt - Original prompt
308
+ * @param {string} agentName - Agent name to get feedback for
309
+ * @returns {string} - Enhanced prompt with feedback
310
+ */
311
+ enhancePromptWithFeedback(prompt, agentName) {
312
+ const feedback = this.verificationFeedback[agentName];
313
+
314
+ if (!feedback || feedback.length === 0) {
315
+ return prompt;
316
+ }
317
+
318
+ const feedbackText = `
319
+ WARNING: **LEARN FROM PREVIOUS MISTAKES**
320
+
321
+ In your last run, verification found these issues that you MUST avoid:
322
+
323
+ ${feedback.map((rule, idx) => `${idx + 1}. **${rule.ruleName}** (${rule.severity}):
324
+ ${this.getRuleFeedback(rule.ruleId)}`).join('\n\n')}
325
+
326
+ Please carefully follow the output format requirements to avoid these issues.
327
+
328
+ ---
329
+
330
+ `;
331
+
332
+ return feedbackText + prompt;
333
+ }
334
+
72
335
  /**
73
336
  * Load agent instructions from markdown file
74
337
  * @param {string} agentFileName - Filename in src/cli/agents/
@@ -78,12 +341,12 @@ class TemplateProcessor {
78
341
  try {
79
342
  const agentPath = path.join(__dirname, 'agents', agentFileName);
80
343
  if (!fs.existsSync(agentPath)) {
81
- console.warn(`⚠️ Agent instruction file not found: ${agentFileName}`);
344
+ sendWarning(`Agent instruction file not found: ${agentFileName}`);
82
345
  return null;
83
346
  }
84
347
  return fs.readFileSync(agentPath, 'utf8');
85
348
  } catch (error) {
86
- console.warn(`⚠️ Could not load agent instructions: ${error.message}`);
349
+ sendWarning(`Could not load agent instructions: ${error.message}`);
87
350
  return null;
88
351
  }
89
352
  }
@@ -109,7 +372,7 @@ class TemplateProcessor {
109
372
 
110
373
  return this.loadAgentInstructions(agent.instruction);
111
374
  } catch (error) {
112
- console.warn(`⚠️ Could not get agent for stage ${stage}: ${error.message}`);
375
+ sendWarning(`Could not get agent for stage ${stage}: ${error.message}`);
113
376
  return null;
114
377
  }
115
378
  }
@@ -164,7 +427,9 @@ class TemplateProcessor {
164
427
 
165
428
  'INITIAL_SCOPE': 'Describe the initial scope of your application: key features, main workflows, and core functionality.\n What will users be able to do? What are the essential capabilities?\n Example: "Users can create tasks, assign them to team members, track progress, set deadlines, and receive notifications."',
166
429
 
167
- 'TECHNICAL_CONSIDERATIONS': 'Technical requirements, constraints, or preferences for your application.\n Examples: "Mobile-first responsive design", "Must work offline", "Real-time data sync", "PostgreSQL database"',
430
+ 'DEPLOYMENT_TARGET': 'Where will this application be deployed and hosted?\n Consider: Cloud providers (AWS, Google Cloud, Azure, DigitalOcean, Vercel, Netlify), platform types (serverless, containerized, VM-based, static hosting), local deployment (desktop app, mobile app, browser extension), CMS platforms (WordPress plugin, Shopify app), hybrid approaches (local with cloud sync, PWA), infrastructure constraints (on-premises, air-gapped, specific regions)\n Example: "AWS cloud using Lambda and S3, with CloudFront CDN for global distribution"',
431
+
432
+ 'TECHNICAL_CONSIDERATIONS': 'Technical stack and requirements for your application (backend AND frontend).\n Backend examples: "Node.js with Express API", "PostgreSQL database", "Real-time data sync with WebSockets"\n Frontend examples: "React SPA with TypeScript", "VitePress for documentation", "Next.js with SSR for e-commerce", "Material-UI design system"\n UI/UX examples: "Mobile-first responsive design", "WCAG 2.1 AA accessibility", "Must work offline", "Multi-language support"',
168
433
 
169
434
  'SECURITY_AND_COMPLIANCE_REQUIREMENTS': 'Security, privacy, or regulatory requirements your application must meet.\n Examples: "GDPR compliance for EU users", "PCI DSS for payment data", "Two-factor authentication", "Data encryption at rest"'
170
435
  };
@@ -196,11 +461,11 @@ class TemplateProcessor {
196
461
  async promptSingular(name, guidance) {
197
462
  const rl = this.createInterface();
198
463
 
199
- console.log(`\n📝 ${name}`);
464
+ sendSectionHeader(name);
200
465
  if (guidance) {
201
- console.log(` ${guidance}`);
466
+ console.log(`${guidance}`);
202
467
  }
203
- console.log(' Enter response (press Enter twice when done, or Enter immediately to skip):\n');
468
+ console.log('Enter response (press Enter twice when done, or Enter immediately to skip):\n');
204
469
 
205
470
  const lines = [];
206
471
  let emptyLineCount = 0;
@@ -245,11 +510,11 @@ class TemplateProcessor {
245
510
  async promptPlural(name, guidance) {
246
511
  const rl = this.createInterface();
247
512
 
248
- console.log(`\n📝 ${name}`);
513
+ sendSectionHeader(name);
249
514
  if (guidance) {
250
- console.log(` ${guidance}`);
515
+ console.log(`${guidance}`);
251
516
  }
252
- console.log(' Enter items one per line (empty line to finish, or Enter immediately to skip):\n');
517
+ console.log('Enter items one per line (empty line to finish, or Enter immediately to skip):\n');
253
518
 
254
519
  const items = [];
255
520
  let itemNumber = 1;
@@ -283,16 +548,222 @@ class TemplateProcessor {
283
548
  });
284
549
  }
285
550
 
551
+ /**
552
+ * Register a per-call token callback on a provider instance.
553
+ * Saves tokens to disk after every LLM API call for crash-safe accounting.
554
+ * @param {LLMProvider} provider - Provider to register on
555
+ * @param {string} ceremonyType - Ceremony type key (e.g., 'sponsor-call')
556
+ */
557
+ _registerTokenCallback(provider, ceremonyType) {
558
+ if (!provider) return;
559
+ provider.onCall((delta) => {
560
+ this.tokenTracker.addIncremental(ceremonyType, delta);
561
+ if (delta.model) {
562
+ const cost = this.tokenTracker.calculateCost(delta.input, delta.output, delta.model);
563
+ this._runningCost += cost?.total ?? 0;
564
+ }
565
+ });
566
+ }
567
+
286
568
  /**
287
569
  * Initialize LLM provider
288
570
  */
289
571
  async initializeLLMProvider() {
290
572
  try {
573
+ // Initialize main provider
291
574
  this.llmProvider = await LLMProvider.create(this._providerName, this._modelName);
575
+ this._registerTokenCallback(this.llmProvider, this.ceremonyName);
576
+ debug(`Using ${this._providerName} (${this._modelName}) for generation`);
577
+
578
+ // Initialize validation provider if validation is enabled
579
+ if (this._validationProvider) {
580
+ debug(`Using ${this._validationProvider} (${this._validationModel}) for validation`);
581
+ this.validationLLMProvider = await LLMProvider.create(
582
+ this._validationProvider,
583
+ this._validationModel
584
+ );
585
+ this._registerTokenCallback(this.validationLLMProvider, `${this.ceremonyName}-validation`);
586
+ }
587
+
292
588
  return this.llmProvider;
293
589
  } catch (error) {
294
- console.log(`⚠️ Could not initialize ${this._providerName} provider - AI suggestions will be skipped`);
295
- console.log(` ${error.message}`);
590
+ sendWarning(`Could not initialize LLM provider`);
591
+ console.log(`${error.message}`);
592
+ throw error;
593
+ }
594
+ }
595
+
596
+ /**
597
+ * Get or create LLM provider for a specific stage
598
+ * @param {string} stageName - Stage name ('suggestions', 'documentation')
599
+ * @returns {Promise<LLMProvider>} LLM provider instance
600
+ */
601
+ async getProviderForStageInstance(stageName) {
602
+ let { provider, model } = this.getProviderForStage(stageName);
603
+
604
+ // Resolve to an available provider if current one has no credentials
605
+ const resolved = await LLMProvider.resolveAvailableProvider(provider, model);
606
+ if (resolved.fellBack) {
607
+ debug(`Provider fallback for ${stageName}: ${provider}→${resolved.provider} (${resolved.model})`);
608
+ console.warn(`[WARN] ${provider} has no API key — falling back to ${resolved.provider} for stage "${stageName}"`);
609
+ provider = resolved.provider;
610
+ model = resolved.model;
611
+ }
612
+
613
+ // Check if we already have a provider for this stage
614
+ const cacheKey = `${stageName}:${provider}:${model}`;
615
+ if (!this._stageProviders) {
616
+ this._stageProviders = {};
617
+ }
618
+
619
+ if (this._stageProviders[cacheKey]) {
620
+ return this._stageProviders[cacheKey];
621
+ }
622
+
623
+ // Create new provider
624
+ const providerInstance = await LLMProvider.create(provider, model);
625
+ this._registerTokenCallback(providerInstance, this.ceremonyName);
626
+ this._stageProviders[cacheKey] = providerInstance;
627
+
628
+ debug(`Using ${provider} (${model}) for ${stageName}`);
629
+
630
+ return providerInstance;
631
+ }
632
+
633
+ /**
634
+ * Get or create validation provider for a specific content type
635
+ * @param {string} validationType - Validation type ('documentation')
636
+ * @returns {Promise<LLMProvider>} LLM provider instance
637
+ */
638
+ async getValidationProviderForTypeInstance(validationType) {
639
+ let { provider, model } = this.getValidationProviderForType(validationType);
640
+
641
+ // Resolve to an available provider if current one has no credentials
642
+ const resolved = await LLMProvider.resolveAvailableProvider(provider, model);
643
+ if (resolved.fellBack) {
644
+ debug(`Validation provider fallback for ${validationType}: ${provider}→${resolved.provider} (${resolved.model})`);
645
+ console.warn(`[WARN] ${provider} has no API key — falling back to ${resolved.provider} for validation "${validationType}"`);
646
+ provider = resolved.provider;
647
+ model = resolved.model;
648
+ }
649
+
650
+ // Check if we already have a provider for this validation type
651
+ const cacheKey = `validation:${validationType}:${provider}:${model}`;
652
+ if (!this._validationProviders) {
653
+ this._validationProviders = {};
654
+ }
655
+
656
+ if (this._validationProviders[cacheKey]) {
657
+ return this._validationProviders[cacheKey];
658
+ }
659
+
660
+ // Create new provider
661
+ const providerInstance = await LLMProvider.create(provider, model);
662
+ this._registerTokenCallback(providerInstance, `${this.ceremonyName}-validation`);
663
+ this._validationProviders[cacheKey] = providerInstance;
664
+
665
+ debug(`Using ${provider} (${model}) for ${validationType} validation`);
666
+
667
+ return providerInstance;
668
+ }
669
+
670
+ /**
671
+ * Get or create LLM provider for document refinement (improvement after validation).
672
+ * Uses validation.refinement config if set, otherwise falls back to validation provider.
673
+ * @returns {Promise<LLMProvider>} LLM provider instance
674
+ */
675
+ async getRefinementProviderInstance() {
676
+ if (this._refinementProviderInstance) {
677
+ return this._refinementProviderInstance;
678
+ }
679
+
680
+ const provider = this._refinementProvider;
681
+ const model = this._refinementModel;
682
+
683
+ if (!provider || !model) {
684
+ return this.getValidationProviderForTypeInstance('documentation');
685
+ }
686
+
687
+ const providerInstance = await LLMProvider.create(provider, model);
688
+ this._registerTokenCallback(providerInstance, `${this.ceremonyName}-refinement`);
689
+ this._refinementProviderInstance = providerInstance;
690
+
691
+ debug(`Using ${provider} (${model}) for document refinement`);
692
+ return providerInstance;
693
+ }
694
+
695
+ /**
696
+ * Run an async fn while emitting periodic heartbeat substep messages.
697
+ * Clears the interval once fn resolves or rejects.
698
+ * @param {Function} fn - Async function to run
699
+ * @param {Function} getMessageFn - Called with elapsed seconds, returns substep string
700
+ * @param {number} intervalMs - How often to emit (default 10s)
701
+ */
702
+ async withHeartbeat(fn, getMessageFn, intervalMs = 10000) {
703
+ const startTime = Date.now();
704
+ const timer = setInterval(() => {
705
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
706
+ this.reportDetail(getMessageFn(elapsed)).catch(() => {});
707
+ }, intervalMs);
708
+ try {
709
+ return await fn();
710
+ } finally {
711
+ clearInterval(timer);
712
+ }
713
+ }
714
+
715
+ /**
716
+ * Retry wrapper for LLM calls with exponential backoff
717
+ * @param {Function} fn - Async function to retry
718
+ * @param {string} operation - Description of operation for logging
719
+ * @param {number} maxRetries - Maximum retry attempts (default: 3)
720
+ * @returns {Promise} Result from function call
721
+ */
722
+ async retryWithBackoff(fn, operation, maxRetries = 3) {
723
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
724
+ try {
725
+ return await fn();
726
+ } catch (error) {
727
+ const isLastAttempt = attempt === maxRetries;
728
+ const isRetriable = error.message?.includes('rate limit') ||
729
+ error.message?.includes('timeout') ||
730
+ error.message?.includes('503') ||
731
+ error.message?.includes('network');
732
+
733
+ if (isLastAttempt || !isRetriable) {
734
+ throw error;
735
+ }
736
+
737
+ const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
738
+ sendWarning(`Retry ${attempt}/${maxRetries} in ${delay/1000}s: ${operation}`);
739
+ console.log(`Error: ${error.message}`);
740
+ await new Promise(resolve => setTimeout(resolve, delay));
741
+ }
742
+ }
743
+ }
744
+
745
+ /**
746
+ * Get domain-specific agent for a variable
747
+ * Returns agent instructions for generating suggestions
748
+ */
749
+ getAgentForVariable(variableName) {
750
+ const agentMap = {
751
+ 'TARGET_USERS': 'suggestion-ux-researcher.md',
752
+ 'INITIAL_SCOPE': 'suggestion-product-manager.md',
753
+ 'DEPLOYMENT_TARGET': 'suggestion-deployment-architect.md',
754
+ 'TECHNICAL_CONSIDERATIONS': 'suggestion-technical-architect.md',
755
+ 'SECURITY_AND_COMPLIANCE_REQUIREMENTS': 'suggestion-security-specialist.md'
756
+ };
757
+
758
+ const agentFile = agentMap[variableName];
759
+ if (!agentFile) {
760
+ return null;
761
+ }
762
+
763
+ try {
764
+ return loadAgent(agentFile, path.dirname(this.avcPath));
765
+ } catch {
766
+ sendWarning(`Agent file not found: ${agentFile}`);
296
767
  return null;
297
768
  }
298
769
  }
@@ -318,11 +789,8 @@ class TemplateProcessor {
318
789
  contextSection += '\n';
319
790
  }
320
791
 
321
- if (isPlural) {
322
- return `${contextSection}Suggest 3-5 appropriate values for "${displayName}".\n\nReturn only the suggestions, one per line, no numbering or bullets.`;
323
- } else {
324
- return `${contextSection}Suggest an appropriate value for "${displayName}".\n\nReturn only the suggestion text, concise (1-3 sentences).`;
325
- }
792
+ // Create simple user prompt with context
793
+ return `${contextSection}Please provide your response for "${displayName}".`;
326
794
  }
327
795
 
328
796
  /**
@@ -339,19 +807,35 @@ class TemplateProcessor {
339
807
  }
340
808
 
341
809
  /**
342
- * Generate AI suggestions for a variable
810
+ * Generate AI suggestions for a variable using domain-specific agent
343
811
  */
344
812
  async generateSuggestions(variableName, isPlural, context) {
345
- if (!this.llmProvider && !(await this.initializeLLMProvider())) {
346
- return null;
347
- }
348
-
349
813
  try {
350
- const prompt = this.buildPrompt(variableName, isPlural, context);
351
- const text = await this.llmProvider.generate(prompt, isPlural ? 512 : 256);
352
- return this.parseLLMResponse(text, isPlural);
814
+ // Get stage-specific provider for suggestions
815
+ const provider = await this.getProviderForStageInstance('suggestions');
816
+
817
+ // Get domain-specific agent for this variable
818
+ const agentInstructions = this.getAgentForVariable(variableName);
819
+
820
+ if (agentInstructions) {
821
+ // Use domain-specific agent with context
822
+ const prompt = this.buildPrompt(variableName, isPlural, context);
823
+ debug(`Using specialized agent: ${variableName.toLowerCase().replace(/_/g, '-')}`);
824
+
825
+ const text = await this.retryWithBackoff(
826
+ () => provider.generate(prompt, isPlural ? 512 : 1024, agentInstructions),
827
+ `${variableName} suggestion`
828
+ );
829
+
830
+ return this.parseLLMResponse(text, isPlural);
831
+ } else {
832
+ // Fallback to generic prompt if no agent available
833
+ const prompt = this.buildPrompt(variableName, isPlural, context);
834
+ const text = await provider.generate(prompt, isPlural ? 512 : 256);
835
+ return this.parseLLMResponse(text, isPlural);
836
+ }
353
837
  } catch (error) {
354
- console.warn(`⚠️ Could not generate suggestions: ${error.message}`);
838
+ sendWarning(`Could not generate suggestions: ${error.message}`);
355
839
  return null;
356
840
  }
357
841
  }
@@ -363,13 +847,10 @@ class TemplateProcessor {
363
847
  async promptUser(variable, context) {
364
848
  let value;
365
849
 
366
- // In non-interactive mode, skip readline prompts and use guidelines/AI
850
+ // In non-interactive mode, skip readline prompts and use defaults/AI
367
851
  if (this.nonInteractive) {
368
- console.log(`\n📝 ${variable.displayName}`);
369
- if (variable.guidance) {
370
- console.log(` ${variable.guidance}`);
371
- }
372
- console.log(' Generating AI response...');
852
+ // No section header output — silent AI generation to avoid polluting terminal output
853
+ sendProgress(`Generating AI suggestion for ${variable.displayName}...`);
373
854
  value = null; // Force AI generation
374
855
  } else {
375
856
  // Interactive mode - use readline prompts
@@ -380,37 +861,37 @@ class TemplateProcessor {
380
861
  }
381
862
  }
382
863
 
383
- // If user skipped (or non-interactive mode), try to use guideline or generate AI suggestions
864
+ // If user skipped (or non-interactive mode), try to use default or generate AI suggestions
384
865
  if (value === null) {
385
- // Check if there's a guideline for this variable
386
- const guidelines = this.readGuidelines();
387
- const guidelineKey = variable.name.toLowerCase().replace(/_/g, '');
866
+ // Check if there's a default for this variable
867
+ const defaults = this.readDefaults();
868
+ const defaultValue = defaults[variable.name];
388
869
 
389
- if (guidelines[guidelineKey]) {
390
- console.log(' 📋 Using default guideline...');
870
+ if (defaultValue) {
871
+ sendInfo('Using default from settings...');
391
872
  value = variable.isPlural
392
- ? [guidelines[guidelineKey]] // Wrap in array for plural variables
393
- : guidelines[guidelineKey];
873
+ ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue])
874
+ : defaultValue;
394
875
 
395
- console.log(' Guideline applied:');
876
+ sendSuccess('Default applied:');
396
877
  if (Array.isArray(value)) {
397
- value.forEach((item, idx) => console.log(` ${idx + 1}. ${item}`));
878
+ value.forEach((item, idx) => console.log(`${idx + 1}. ${item}`));
398
879
  } else {
399
- console.log(` ${value}`);
880
+ console.log(`${value}`);
400
881
  }
401
- return { variable: variable.name, value, source: 'guideline', skipped: true };
882
+ return { variable: variable.name, value, source: 'default', skipped: false };
402
883
  }
403
884
 
404
- // No guideline available, try AI suggestions
405
- console.log('Generating AI suggestion...');
885
+ // No default available, try AI suggestions
886
+ sendInfo('Generating AI suggestion...');
406
887
  value = await this.generateSuggestions(variable.name, variable.isPlural, context);
407
888
 
408
889
  if (value) {
409
- console.log('AI suggestion:');
890
+ sendSuccess('AI suggestion:');
410
891
  if (Array.isArray(value)) {
411
- value.forEach((item, idx) => console.log(` ${idx + 1}. ${item}`));
892
+ value.forEach((item, idx) => console.log(`${idx + 1}. ${item}`));
412
893
  } else {
413
- console.log(` ${value}`);
894
+ console.log(`${value}`);
414
895
  }
415
896
  return { variable: variable.name, value, source: 'ai', skipped: true };
416
897
  } else {
@@ -450,33 +931,121 @@ class TemplateProcessor {
450
931
  /**
451
932
  * Generate final document with LLM enhancement
452
933
  */
453
- async generateFinalDocument(templateWithValues) {
454
- if (!this.llmProvider && !(await this.initializeLLMProvider())) {
455
- // No provider available - save template as-is
456
- console.log('\n⚠️ AI enhancement skipped (no LLM provider available)');
457
- return templateWithValues;
458
- }
459
-
460
- console.log('\n🤖 Enhancing document with AI...');
461
-
934
+ async generateFinalDocument(templateWithValues, questionnaire = null) {
935
+ const t0 = Date.now();
936
+ debug('generateFinalDocument called', {
937
+ templateLength: templateWithValues?.length,
938
+ hasQuestionnaire: !!questionnaire,
939
+ ceremony: this.ceremonyName
940
+ });
462
941
  try {
942
+ // Get stage-specific provider for documentation
943
+ const provider = await this.getProviderForStageInstance('documentation');
944
+
463
945
  // Try to load agent instructions for enhancement stage
946
+ this.reportSubstep('Reading agent: project-documentation-creator.md');
464
947
  const agentInstructions = this.getAgentForStage('enhancement');
465
948
 
466
949
  if (agentInstructions) {
467
- console.log(' Using custom agent instructions for enhancement');
468
950
  // Use agent instructions as system context
469
- const userPrompt = `Here is the project information with all variables filled in:
951
+ let userPrompt = `Here is the project information with all variables filled in:
952
+
953
+ ${templateWithValues}`;
954
+
955
+ // Inject explicit user choices so the LLM cannot override them with defaults
956
+ if (questionnaire) {
957
+ const deploymentTarget = questionnaire.DEPLOYMENT_TARGET || '';
958
+ const isLocal = /local|localhost|dev\s*machine|on.?prem/i.test(deploymentTarget);
959
+
960
+ userPrompt += `\n\n**User's Stated Choices — MUST be respected exactly as provided:**
961
+ - MISSION_STATEMENT: ${questionnaire.MISSION_STATEMENT || 'N/A'}
962
+ - TARGET_USERS: ${questionnaire.TARGET_USERS || 'N/A'}
963
+ - INITIAL_SCOPE: ${questionnaire.INITIAL_SCOPE || 'N/A'}
964
+ - TECHNICAL_CONSIDERATIONS: ${questionnaire.TECHNICAL_CONSIDERATIONS || 'N/A'}
965
+ - DEPLOYMENT_TARGET: ${deploymentTarget || 'N/A'}
966
+ - TECHNICAL_EXCLUSIONS: ${questionnaire.TECHNICAL_EXCLUSIONS || 'None'}
967
+ - SECURITY_AND_COMPLIANCE_REQUIREMENTS: ${questionnaire.SECURITY_AND_COMPLIANCE_REQUIREMENTS || 'N/A'}`;
968
+
969
+ if (isLocal) {
970
+ userPrompt += `\n\n**DEPLOYMENT CONSTRAINT — CRITICAL:**
971
+ The user has chosen LOCAL deployment ("${deploymentTarget}").
972
+ - The Deployment Environment section MUST describe local-first setup only (e.g. Docker Compose, localhost, local DB, npm run dev).
973
+ - Do NOT add cloud providers (AWS, GCP, Azure), managed services, Kubernetes, Terraform, CI/CD pipelines, or any cloud infrastructure.
974
+ - Do NOT suggest migration to cloud unless the user's INITIAL_SCOPE or TECHNICAL_CONSIDERATIONS explicitly requests it.
975
+ - Infrastructure means: local processes, local database, local file system.`;
976
+ }
470
977
 
471
- ${templateWithValues}
978
+ if (questionnaire.TECHNICAL_EXCLUSIONS) {
979
+ userPrompt += `\n\n**TECHNOLOGY EXCLUSION CONSTRAINT — CRITICAL:**
980
+ The user has explicitly excluded the following from this project:
981
+ ${questionnaire.TECHNICAL_EXCLUSIONS}
982
+ - These technologies MUST NOT appear as recommendations anywhere in the document.
983
+ - Add an "Explicitly Excluded Technologies" subsection in section 6 (Technical Architecture).
984
+ - Do NOT soften with "consider using" or "if needed" language.`;
985
+ }
986
+ }
472
987
 
473
- Please review and enhance this document according to your role.`;
988
+ userPrompt += `\n\nPlease review and enhance this document according to your role.`;
989
+
990
+ // Enhance prompt with verification feedback if available
991
+ userPrompt = this.enhancePromptWithFeedback(userPrompt, 'project-documentation-creator');
992
+
993
+ this.reportSubstep('Generating Project Brief (this may take 20-30 seconds)...');
994
+ await this.reportDetail(`Sending to ${provider.providerName || 'LLM'} (${provider.model || ''})…`);
995
+ const enhanced = await this.withHeartbeat(
996
+ () => this.retryWithBackoff(
997
+ () => provider.generate(userPrompt, 4096, agentInstructions),
998
+ 'document enhancement'
999
+ ),
1000
+ (elapsed) => {
1001
+ if (elapsed < 15) return 'Structuring project documentation…';
1002
+ if (elapsed < 30) return 'Writing mission and scope sections…';
1003
+ if (elapsed < 45) return 'Generating technical architecture…';
1004
+ if (elapsed < 60) return 'Finalizing project brief…';
1005
+ return `Generating Project Brief… (${elapsed}s)`;
1006
+ },
1007
+ 15000
1008
+ );
1009
+
1010
+ // Report token usage after generation
1011
+ if (provider && typeof provider.getTokenUsage === 'function') {
1012
+ const usage = provider.getTokenUsage();
1013
+ this.reportSubstep('Processing generated content...', {
1014
+ tokensUsed: {
1015
+ input: usage.inputTokens,
1016
+ output: usage.outputTokens
1017
+ }
1018
+ });
1019
+ await this.reportDetail(`${usage.inputTokens.toLocaleString()} in · ${usage.outputTokens.toLocaleString()} out tokens`);
1020
+ }
474
1021
 
475
- const enhanced = await this.llmProvider.generate(userPrompt, 4096, agentInstructions);
476
- console.log(' Document enhanced successfully');
477
- return enhanced;
1022
+ // Post-process verification
1023
+ this.reportSubstep('Verifying documentation quality...');
1024
+ const verifier = new LLMVerifier(provider, 'project-documentation-creator', this.verificationTracker);
1025
+ const verificationResult = await verifier.verify(
1026
+ enhanced,
1027
+ (mainMsg, substep) => this.reportSubstep(substep)
1028
+ );
1029
+
1030
+ if (verificationResult.rulesApplied.length > 0) {
1031
+ this.reportSubstep(`Applied ${verificationResult.rulesApplied.length} fixes`);
1032
+
1033
+ // Store feedback for agent learning
1034
+ this.verificationFeedback['project-documentation-creator'] = verificationResult.rulesApplied.map(rule => ({
1035
+ ruleId: rule.id,
1036
+ ruleName: rule.name,
1037
+ severity: rule.severity
1038
+ }));
1039
+
1040
+ // Collect for final ceremony summary
1041
+ for (const rule of verificationResult.rulesApplied) {
1042
+ this.validationIssues.push({ stage: 'Project Documentation', ruleId: rule.id, name: rule.name, severity: rule.severity, description: rule.description });
1043
+ }
1044
+ }
1045
+
1046
+ debug('generateFinalDocument complete (agent path)', { duration: `${Date.now() - t0}ms`, resultLength: verificationResult.content?.length });
1047
+ return verificationResult.content;
478
1048
  } else {
479
- console.log(' Using default enhancement instructions');
480
1049
  // Fallback to legacy hardcoded prompt for backward compatibility
481
1050
  const legacyPrompt = `You are creating a project definition document for an Agile Vibe Coding (AVC) project.
482
1051
 
@@ -492,13 +1061,30 @@ Please review and enhance this document to ensure:
492
1061
 
493
1062
  Return the enhanced markdown document.`;
494
1063
 
495
- const enhanced = await this.llmProvider.generate(legacyPrompt, 4096);
496
- console.log(' Document enhanced successfully');
1064
+ this.reportSubstep('Generating Project Brief (this may take 20-30 seconds)...');
1065
+ await this.reportDetail(`Sending to ${provider.providerName || 'LLM'} (${provider.model || ''})…`);
1066
+ const enhanced = await this.withHeartbeat(
1067
+ () => this.retryWithBackoff(
1068
+ () => provider.generate(legacyPrompt, 4096),
1069
+ 'document enhancement (legacy)'
1070
+ ),
1071
+ (elapsed) => {
1072
+ if (elapsed < 15) return 'Structuring project documentation…';
1073
+ if (elapsed < 30) return 'Writing mission and scope sections…';
1074
+ if (elapsed < 45) return 'Generating technical architecture…';
1075
+ return `Generating Project Brief… (${elapsed}s)`;
1076
+ },
1077
+ 15000
1078
+ );
1079
+ if (provider && typeof provider.getTokenUsage === 'function') {
1080
+ const usage = provider.getTokenUsage();
1081
+ await this.reportDetail(`${usage.inputTokens.toLocaleString()} in · ${usage.outputTokens.toLocaleString()} out tokens`);
1082
+ }
1083
+ debug('generateFinalDocument complete (legacy path)', { duration: `${Date.now() - t0}ms`, resultLength: enhanced?.length });
497
1084
  return enhanced;
498
1085
  }
499
1086
  } catch (error) {
500
- console.warn(`\n⚠️ Could not enhance document: ${error.message}`);
501
- console.log(' Using template without AI enhancement');
1087
+ debug('generateFinalDocument error - returning template as-is', { error: error.message, duration: `${Date.now() - t0}ms` });
502
1088
  return templateWithValues;
503
1089
  }
504
1090
  }
@@ -522,16 +1108,16 @@ Return the enhanced markdown document.`;
522
1108
 
523
1109
  // Check if documentation folder exists
524
1110
  if (!fs.existsSync(docsDir)) {
525
- console.log(' ℹ️ VitePress documentation folder not found, skipping sync');
1111
+ sendInfo('VitePress documentation folder not found, skipping sync');
526
1112
  return false;
527
1113
  }
528
1114
 
529
1115
  // Write to .avc/documentation/index.md
530
1116
  fs.writeFileSync(indexPath, content, 'utf8');
531
- console.log(` ✓ Synced to .avc/documentation/index.md`);
1117
+ debug('Synced to .avc/documentation/index.md');
532
1118
  return true;
533
1119
  } catch (error) {
534
- console.warn(` ⚠️ Could not sync to VitePress: ${error.message}`);
1120
+ sendWarning(`Could not sync to VitePress: ${error.message}`);
535
1121
  return false;
536
1122
  }
537
1123
  }
@@ -565,10 +1151,10 @@ Return the enhanced markdown document.`;
565
1151
  stdio: 'inherit'
566
1152
  });
567
1153
 
568
- console.log('VitePress build completed');
1154
+ sendSuccess('VitePress build completed');
569
1155
  return true;
570
1156
  } catch (error) {
571
- console.warn(`⚠️ VitePress build failed: ${error.message}`);
1157
+ sendWarning(`VitePress build failed: ${error.message}`);
572
1158
  return false;
573
1159
  }
574
1160
  }
@@ -577,18 +1163,23 @@ Return the enhanced markdown document.`;
577
1163
  * Write document to file
578
1164
  */
579
1165
  async writeDocument(content) {
1166
+ const fileSize = Math.ceil(Buffer.byteLength(content, 'utf8') / 1024);
1167
+ debug('writeDocument called', { outputPath: this.outputPath, sizeKB: fileSize });
1168
+
580
1169
  // Create .avc/project/ directory
581
1170
  if (!fs.existsSync(this.outputDir)) {
582
1171
  fs.mkdirSync(this.outputDir, { recursive: true });
583
1172
  }
584
1173
 
585
1174
  // Write doc.md
1175
+ this.reportSubstep(`Writing doc.md (${fileSize} KB)`);
586
1176
  fs.writeFileSync(this.outputPath, content, 'utf8');
587
1177
 
588
- console.log(`\n✅ Project document generated!`);
589
- console.log(` Location: ${this.outputPath}`);
1178
+ // Report files created
1179
+ const filesCreated = ['.avc/project/project/doc.md'];
1180
+ this.reportSubstep('Project Brief created successfully', { filesCreated });
590
1181
 
591
- // Sync to VitePress if configured
1182
+ // Sync to VitePress if configured (silent for sponsor-call)
592
1183
  const synced = this.syncToVitePress(content);
593
1184
 
594
1185
  // Optionally build VitePress (commented out by default to avoid slow builds during dev)
@@ -601,37 +1192,73 @@ Return the enhanced markdown document.`;
601
1192
  * Main workflow - process template and generate document
602
1193
  */
603
1194
  async processTemplate(initialProgress = null) {
604
- console.log('\n📋 Project Setup Questionnaire\n');
1195
+ debug('Starting processTemplate', { hasInitialProgress: !!initialProgress, ceremony: this.ceremonyName });
1196
+ debug('Project Setup Questionnaire');
1197
+
1198
+ // Cost threshold protection — wrap callback to check running cost before each progress call
1199
+ if (this._costThreshold != null && this.progressCallback) {
1200
+ const _origCallback = this.progressCallback;
1201
+ this.progressCallback = async (...args) => {
1202
+ if (this._costThreshold != null && this._runningCost >= this._costThreshold) {
1203
+ if (this._costLimitReachedCallback) {
1204
+ this._costThreshold = null; // disable re-triggering
1205
+ await this._costLimitReachedCallback(this._runningCost);
1206
+ // returns → ceremony continues with limit disabled
1207
+ } else {
1208
+ throw new Error(`COST_LIMIT_EXCEEDED:${this._runningCost.toFixed(6)}`);
1209
+ }
1210
+ }
1211
+ return _origCallback(...args);
1212
+ };
1213
+ }
605
1214
 
606
1215
  // 1. Read template
1216
+ debug('Reading template file', { templatePath: this.templatePath });
607
1217
  const templateContent = fs.readFileSync(this.templatePath, 'utf8');
1218
+ debug('Template loaded', { size: templateContent.length });
608
1219
 
609
1220
  // 2. Extract variables
1221
+ debug('Extracting variables from template');
610
1222
  const variables = this.extractVariables(templateContent);
1223
+ debug('Variables extracted', { count: variables.length, names: variables.map(v => v.name) });
611
1224
 
612
1225
  // 3. Initialize or restore progress
613
1226
  let collectedValues = {};
614
1227
  let answeredCount = 0;
615
1228
 
616
1229
  if (initialProgress && initialProgress.collectedValues) {
1230
+ debug('Restoring from initial progress', { answeredCount: Object.keys(initialProgress.collectedValues).length });
617
1231
  collectedValues = { ...initialProgress.collectedValues };
618
1232
  answeredCount = Object.keys(collectedValues).length;
619
1233
 
620
1234
  // Check if ALL answers are pre-filled (from REPL questionnaire)
621
1235
  if (answeredCount === variables.length) {
622
- console.log(`✅ Using ${answeredCount} pre-filled answers from questionnaire.\n`);
1236
+ debug(`Using ${answeredCount} pre-filled answers from questionnaire`);
623
1237
 
624
- // Use pre-filled answers, but still allow AI enhancement for skipped (null) answers
1238
+ // Use pre-filled answers, but check defaults or AI for skipped (null) answers
625
1239
  for (const variable of variables) {
626
1240
  if (collectedValues[variable.name] === null) {
627
- console.log(`\n📝 ${variable.displayName}`);
628
- console.log(' Generating AI suggestion...');
629
- const aiValue = await this.generateSuggestions(variable.name, variable.isPlural, collectedValues);
630
- collectedValues[variable.name] = aiValue || '';
1241
+ // No section header — silent fill to avoid polluting terminal output during ceremony
1242
+
1243
+ // First, check if there's a default for this question
1244
+ const defaults = this.readDefaults();
1245
+ const defaultValue = defaults[variable.name];
1246
+
1247
+ if (defaultValue) {
1248
+ sendSuccess('Using default from settings');
1249
+ collectedValues[variable.name] = variable.isPlural
1250
+ ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue])
1251
+ : defaultValue;
1252
+ } else {
1253
+ // No default found, generate AI suggestion
1254
+ sendProgress('Generating AI suggestion...');
1255
+ const aiValue = await this.generateSuggestions(variable.name, variable.isPlural, collectedValues);
1256
+ collectedValues[variable.name] = aiValue || '';
1257
+ }
631
1258
  }
632
1259
  }
633
1260
  } else {
634
- console.log(`Resuming with ${answeredCount}/${variables.length} questions already answered.\n`);
1261
+ sendOutput(`Resuming with ${answeredCount}/${variables.length} questions already answered.\n`);
635
1262
 
636
1263
  // Continue with normal interactive flow for remaining questions
637
1264
  for (const variable of variables) {
@@ -677,15 +1304,1432 @@ Return the enhanced markdown document.`;
677
1304
  }
678
1305
  }
679
1306
 
1307
+ // Compute total stages based on what will actually run for this ceremony
1308
+ const _scValidation = this.ceremonyName === 'sponsor-call' && this.isValidationEnabled();
1309
+ const _T = 5 + (_scValidation ? 1 : 0);
1310
+ let _s = 0;
1311
+
1312
+ // Report questionnaire completion (with delay for UI update)
1313
+ await this.reportProgressWithDelay(`Stage ${++_s}/${_T}: Processing questionnaire answers...`, 'Processed questionnaire responses');
1314
+
680
1315
  // 5. Replace variables in template
681
- console.log('\n🔄 Preparing project document...');
1316
+ await this.reportProgressWithDelay(`Stage ${++_s}/${_T}: Preparing project template...`, 'Template preparation complete');
1317
+ this.reportSubstep('Reading template: project.md');
682
1318
  const templateWithValues = this.replaceVariables(templateContent, collectedValues);
1319
+ this.reportSubstep('Replaced 6 template variables');
1320
+
1321
+ // Preparation complete
1322
+ await this.reportProgressWithDelay(`Stage ${++_s}/${_T}: Preparing for documentation generation...`, 'Ready to generate documentation');
683
1323
 
684
1324
  // 6. Enhance document with LLM
685
- const finalDocument = await this.generateFinalDocument(templateWithValues);
1325
+ await this.reportProgressWithDelay(`Stage ${++_s}/${_T}: Creating project documentation...`, 'Created project documentation');
1326
+ let finalDocument = await this.generateFinalDocument(templateWithValues, collectedValues);
1327
+
1328
+ // 7. Validate and improve documentation (if validation enabled)
1329
+ if (this.ceremonyName === 'sponsor-call' && this.isValidationEnabled()) {
1330
+ ++_s;
1331
+ finalDocument = await this.iterativeValidation(finalDocument, 'documentation', collectedValues, _s, _T);
1332
+ }
686
1333
 
687
- // 7. Write to file
1334
+ // 8. Write documentation to file
688
1335
  await this.writeDocument(finalDocument);
1336
+ this.reportSubstep('Wrapping up...');
1337
+
1338
+ // 11. Track token usage (only for sponsor-call)
1339
+ let tokenUsage = null;
1340
+ let cost = null;
1341
+ if (this.ceremonyName === 'sponsor-call') {
1342
+ ({ tokenUsage, cost } = this.saveCurrentTokenTracking());
1343
+ }
1344
+
1345
+ // 12. Return comprehensive result
1346
+ // Save verification tracking summary
1347
+ if (this.verificationTracker) {
1348
+ try {
1349
+ this.verificationTracker.logCeremonySummary();
1350
+ const { jsonPath, summaryPath } = this.verificationTracker.saveToFile();
1351
+
1352
+ if (jsonPath && summaryPath) {
1353
+ debug('Verification tracking saved', { json: jsonPath, summary: summaryPath });
1354
+ }
1355
+
1356
+ // Clean up old verification logs (keep last 10)
1357
+ VerificationTracker.cleanupOldLogs(this.ceremonyName);
1358
+ } catch (error) {
1359
+ console.error('Error saving verification tracking:', error.message);
1360
+ }
1361
+ }
1362
+
1363
+ return {
1364
+ outputPath: this.outputPath,
1365
+ activities: this.activityLog,
1366
+ tokenUsage: tokenUsage,
1367
+ cost: cost,
1368
+ model: this._modelName,
1369
+ validationIssues: this.validationIssues
1370
+ };
1371
+ }
1372
+
1373
+ /**
1374
+ * Aggregate token usage from all providers, write to token-history.json, and cache on this instance.
1375
+ * Safe to call at any point (success or partial/cancelled run).
1376
+ * @returns {{ tokenUsage: Object|null, cost: Object|null }}
1377
+ */
1378
+ saveCurrentTokenTracking() {
1379
+ let totalInput = 0;
1380
+ let totalOutput = 0;
1381
+ let totalCalls = 0;
1382
+ const stageBreakdown = {};
1383
+
1384
+ if (this._stageProviders) {
1385
+ for (const [cacheKey, provider] of Object.entries(this._stageProviders)) {
1386
+ if (typeof provider.getTokenUsage === 'function') {
1387
+ const usage = provider.getTokenUsage();
1388
+ totalInput += usage.inputTokens || 0;
1389
+ totalOutput += usage.outputTokens || 0;
1390
+ totalCalls += usage.totalCalls || 0;
1391
+ const stageName = cacheKey.split(':')[0];
1392
+ stageBreakdown[stageName] = { input: usage.inputTokens, output: usage.outputTokens, calls: usage.totalCalls };
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ if (this._validationProviders) {
1398
+ for (const [cacheKey, provider] of Object.entries(this._validationProviders)) {
1399
+ if (typeof provider.getTokenUsage === 'function') {
1400
+ const usage = provider.getTokenUsage();
1401
+ totalInput += usage.inputTokens || 0;
1402
+ totalOutput += usage.outputTokens || 0;
1403
+ totalCalls += usage.totalCalls || 0;
1404
+ const validationType = cacheKey.split(':')[1];
1405
+ stageBreakdown[`validation:${validationType}`] = { input: usage.inputTokens, output: usage.outputTokens, calls: usage.totalCalls };
1406
+ }
1407
+ }
1408
+ }
1409
+
1410
+ if (totalInput === 0 && totalOutput === 0 && this.llmProvider && typeof this.llmProvider.getTokenUsage === 'function') {
1411
+ const usage = this.llmProvider.getTokenUsage();
1412
+ totalInput = usage.inputTokens || 0;
1413
+ totalOutput = usage.outputTokens || 0;
1414
+ totalCalls = usage.totalCalls || 0;
1415
+ }
1416
+
1417
+ if (totalInput === 0 && totalOutput === 0) return { tokenUsage: null, cost: null };
1418
+
1419
+ const cost = this.tokenTracker.calculateCost(totalInput, totalOutput, this._modelName);
1420
+
1421
+ this._lastTokenUsage = {
1422
+ inputTokens: totalInput,
1423
+ outputTokens: totalOutput,
1424
+ totalTokens: totalInput + totalOutput,
1425
+ totalCalls: totalCalls
1426
+ };
1427
+
1428
+ const tokenUsage = {
1429
+ input: totalInput,
1430
+ output: totalOutput,
1431
+ total: totalInput + totalOutput,
1432
+ calls: totalCalls,
1433
+ stageBreakdown: stageBreakdown
1434
+ };
1435
+
1436
+ return { tokenUsage, cost };
1437
+ }
1438
+
1439
+ /**
1440
+ * Get token usage from last LLM execution
1441
+ * @returns {Object|null} Token usage object or null
1442
+ */
1443
+ getLastTokenUsage() {
1444
+ return this._lastTokenUsage;
1445
+ }
1446
+
1447
+ /**
1448
+ * Generate hierarchical work items (Project → Epic → Story)
1449
+ * (Now used by Project Expansion ceremony)
1450
+ * @param {Object} collectedValues - Values from questionnaire
1451
+ */
1452
+ async generateHierarchy(collectedValues) {
1453
+ const t0 = Date.now();
1454
+ debug('generateHierarchy called', { ceremony: this.ceremonyName, valuesCount: Object.keys(collectedValues).length });
1455
+ sendSectionHeader('Generating project hierarchy...');
1456
+
1457
+ // Read agent instructions
1458
+ const epicStoryDecomposerAgent = loadAgent('epic-story-decomposer.md', path.dirname(this.avcPath));
1459
+
1460
+ // 1. Decompose into Epics and Stories
1461
+ sendInfo('Stage 5/6: Decomposing features into Epics and Stories...');
1462
+ const decompositionPrompt = this.buildDecompositionPrompt(collectedValues);
1463
+ const hierarchy = await this.retryWithBackoff(
1464
+ () => this.llmProvider.generateJSON(decompositionPrompt, epicStoryDecomposerAgent),
1465
+ 'hierarchy decomposition'
1466
+ );
1467
+
1468
+ // Validate response structure
1469
+ if (!hierarchy.epics || !Array.isArray(hierarchy.epics)) {
1470
+ throw new Error('Invalid hierarchy response: missing epics array');
1471
+ }
1472
+
1473
+ sendSuccess(`Generated ${hierarchy.epics.length} Epics with ${hierarchy.validation?.storyCount || 0} Stories`);
1474
+
1475
+ // 2. Write all files to disk
1476
+ sendSectionHeader('Stage 6/6: Writing files to disk...');
1477
+ await this.writeHierarchyToFiles(hierarchy, collectedValues);
1478
+
1479
+ // Display token usage statistics
1480
+ if (this.llmProvider) {
1481
+ const mainUsage = this.llmProvider.getTokenUsage();
1482
+
1483
+ sendSectionHeader('Token Usage:');
1484
+
1485
+ // Main provider usage
1486
+ console.log(` Main Provider (${this._providerName}):`);
1487
+ console.log(` Input: ${mainUsage.inputTokens.toLocaleString()} tokens`);
1488
+ console.log(` Output: ${mainUsage.outputTokens.toLocaleString()} tokens`);
1489
+ console.log(` Calls: ${mainUsage.totalCalls}`);
1490
+
1491
+ // Validation provider usage (if used)
1492
+ if (this.validationLLMProvider) {
1493
+ const validationUsage = this.validationLLMProvider.getTokenUsage();
1494
+ console.log(`\n Validation Provider (${this._validationProvider}):`);
1495
+ console.log(` Input: ${validationUsage.inputTokens.toLocaleString()} tokens`);
1496
+ console.log(` Output: ${validationUsage.outputTokens.toLocaleString()} tokens`);
1497
+ console.log(` Calls: ${validationUsage.totalCalls}`);
1498
+
1499
+ // Total
1500
+ const totalInput = mainUsage.inputTokens + validationUsage.inputTokens;
1501
+ const totalOutput = mainUsage.outputTokens + validationUsage.outputTokens;
1502
+ const totalCalls = mainUsage.totalCalls + validationUsage.totalCalls;
1503
+
1504
+ console.log(`\n Total:`);
1505
+ console.log(` Input: ${totalInput.toLocaleString()} tokens`);
1506
+ console.log(` Output: ${totalOutput.toLocaleString()} tokens`);
1507
+ console.log(` Calls: ${totalCalls}`);
1508
+ }
1509
+
1510
+ // Finalize run — tokens already saved per-call via addIncremental
1511
+ this.tokenTracker.finalizeRun(this.ceremonyName);
1512
+
1513
+ if (this.validationLLMProvider) {
1514
+ this.tokenTracker.finalizeRun(`${this.ceremonyName}-validation`);
1515
+ }
1516
+
1517
+ sendSuccess('Token history updated');
1518
+ const mainUsageFinal = this.llmProvider.getTokenUsage();
1519
+ debug('generateHierarchy complete', {
1520
+ duration: `${Date.now() - t0}ms`,
1521
+ epicCount: hierarchy?.epics?.length,
1522
+ mainProvider: { provider: this._providerName, inputTokens: mainUsageFinal.inputTokens, outputTokens: mainUsageFinal.outputTokens, calls: mainUsageFinal.totalCalls }
1523
+ });
1524
+ }
1525
+ }
1526
+
1527
+ /**
1528
+ * Build prompt for Epic/Story decomposition
1529
+ * @param {Object} collectedValues - Questionnaire responses
1530
+ * @returns {string} Prompt for decomposition agent
1531
+ */
1532
+ buildDecompositionPrompt(collectedValues) {
1533
+ const { INITIAL_SCOPE, TARGET_USERS, MISSION_STATEMENT, TECHNICAL_CONSIDERATIONS, SECURITY_AND_COMPLIANCE_REQUIREMENTS } = collectedValues;
1534
+
1535
+ return `Given the following project definition:
1536
+
1537
+ **Initial Scope (Features to Implement):**
1538
+ ${INITIAL_SCOPE}
1539
+
1540
+ **Target Users:**
1541
+ ${TARGET_USERS}
1542
+
1543
+ **Mission Statement:**
1544
+ ${MISSION_STATEMENT}
1545
+
1546
+ **Technical Considerations:**
1547
+ ${TECHNICAL_CONSIDERATIONS}
1548
+
1549
+ **Security and Compliance Requirements:**
1550
+ ${SECURITY_AND_COMPLIANCE_REQUIREMENTS}
1551
+
1552
+ Decompose this project into Epics (domain-based groupings) and Stories (user-facing capabilities per Epic) — create as many as needed to fully cover the scope.
1553
+
1554
+ Return your response as JSON following the exact structure specified in your instructions.`;
1555
+ }
1556
+
1557
+ /**
1558
+ * Write hierarchy files to .avc/project/ directory
1559
+ * @param {Object} hierarchy - Decomposition result (epics array)
1560
+ * @param {Object} collectedValues - Questionnaire responses
1561
+ */
1562
+ async writeHierarchyToFiles(hierarchy, collectedValues) {
1563
+ const projectPath = path.join(this.avcPath, 'project');
1564
+
1565
+ // Write Epic and Story files
1566
+ for (const epic of hierarchy.epics) {
1567
+ const epicDir = path.join(projectPath, epic.id);
1568
+ if (!fs.existsSync(epicDir)) {
1569
+ fs.mkdirSync(epicDir, { recursive: true });
1570
+ }
1571
+
1572
+ // Write Epic doc.md (initially empty, updated by retrospective ceremony)
1573
+ fs.writeFileSync(
1574
+ path.join(epicDir, 'doc.md'),
1575
+ `# ${epic.name}\n\n*Documentation will be added during implementation and retrospective ceremonies.*\n`,
1576
+ 'utf8'
1577
+ );
1578
+ sendIndented(`${epic.id}/doc.md`);
1579
+
1580
+ // Write Epic work.json
1581
+ const epicWorkJson = {
1582
+ id: epic.id,
1583
+ name: epic.name,
1584
+ type: 'epic',
1585
+ domain: epic.domain,
1586
+ description: epic.description,
1587
+ features: epic.features,
1588
+ status: 'planned',
1589
+ dependencies: epic.dependencies || [],
1590
+ children: (epic.stories || []).map(s => s.id),
1591
+ metadata: {
1592
+ created: new Date().toISOString(),
1593
+ ceremony: 'sponsor-call'
1594
+ }
1595
+ };
1596
+ fs.writeFileSync(
1597
+ path.join(epicDir, 'work.json'),
1598
+ JSON.stringify(epicWorkJson, null, 2),
1599
+ 'utf8'
1600
+ );
1601
+ sendIndented(`${epic.id}/work.json`);
1602
+
1603
+ // Write Story files
1604
+ for (const story of epic.stories || []) {
1605
+ const storyDir = path.join(epicDir, story.id);
1606
+ if (!fs.existsSync(storyDir)) {
1607
+ fs.mkdirSync(storyDir, { recursive: true });
1608
+ }
1609
+
1610
+ // Write Story doc.md (initially empty, updated by retrospective ceremony)
1611
+ fs.writeFileSync(
1612
+ path.join(storyDir, 'doc.md'),
1613
+ `# ${story.name}\n\n*Documentation will be added during implementation and retrospective ceremonies.*\n`,
1614
+ 'utf8'
1615
+ );
1616
+ sendIndented(`${story.id}/doc.md`);
1617
+
1618
+ // Write Story work.json
1619
+ const storyWorkJson = {
1620
+ id: story.id,
1621
+ name: story.name,
1622
+ type: 'story',
1623
+ userType: story.userType,
1624
+ description: story.description,
1625
+ acceptance: story.acceptance,
1626
+ status: 'planned',
1627
+ dependencies: story.dependencies || [],
1628
+ children: [],
1629
+ metadata: {
1630
+ created: new Date().toISOString(),
1631
+ ceremony: 'sponsor-call'
1632
+ }
1633
+ };
1634
+ fs.writeFileSync(
1635
+ path.join(storyDir, 'work.json'),
1636
+ JSON.stringify(storyWorkJson, null, 2),
1637
+ 'utf8'
1638
+ );
1639
+ sendIndented(`${story.id}/work.json`);
1640
+ }
1641
+
1642
+ console.log(''); // Empty line between epics
1643
+ }
1644
+
1645
+ sendSuccess(`Hierarchy written to ${projectPath}/`);
1646
+ sendSectionHeader(`Summary:`);
1647
+ console.log(` • ${hierarchy.epics.length} Epics (doc.md + work.json each)`);
1648
+ console.log(` • ${hierarchy.validation?.storyCount || 0} Stories (doc.md + work.json each)`);
1649
+
1650
+ const epicCount = hierarchy.epics.length;
1651
+ const storyCount = hierarchy.validation?.storyCount || 0;
1652
+ const totalFiles = (2 * epicCount) + (2 * storyCount); // Epic: 2 each, Story: 2 each
1653
+ console.log(` • ${totalFiles} files created\n`);
1654
+ }
1655
+
1656
+ /**
1657
+ * Get validation settings from ceremony configuration
1658
+ */
1659
+ getValidationSettings() {
1660
+ try {
1661
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
1662
+ const ceremony = config.settings?.ceremonies?.find(c => c.name === this.ceremonyName);
1663
+
1664
+ return ceremony?.validation || {
1665
+ enabled: true,
1666
+ maxIterations: 3,
1667
+ acceptanceThreshold: 95,
1668
+ skipOnCriticalIssues: false
1669
+ };
1670
+ } catch (error) {
1671
+ // Default settings if config can't be read
1672
+ return {
1673
+ enabled: true,
1674
+ maxIterations: 3,
1675
+ acceptanceThreshold: 95,
1676
+ skipOnCriticalIssues: false
1677
+ };
1678
+ }
1679
+ }
1680
+
1681
+ /**
1682
+ * Check if validation is enabled for this ceremony
1683
+ */
1684
+ isValidationEnabled() {
1685
+ const settings = this.getValidationSettings();
1686
+ return settings.enabled !== false;
1687
+ }
1688
+
1689
+ /**
1690
+ * Validate documentation (doc.md) using validator-documentation agent
1691
+ */
1692
+ async validateDocument(docContent, questionnaire) {
1693
+ // Get validation type-specific provider for documentation
1694
+ let provider;
1695
+ try {
1696
+ provider = await this.getValidationProviderForTypeInstance('documentation');
1697
+ } catch (error) {
1698
+ sendWarning('Skipping validation (validation provider not available)');
1699
+ return {
1700
+ validationStatus: 'acceptable',
1701
+ overallScore: 75,
1702
+ issues: [],
1703
+ strengths: ['Validation skipped - validation provider not available'],
1704
+ improvementPriorities: [],
1705
+ readyForPublication: true
1706
+ };
1707
+ }
1708
+
1709
+ if (!provider || typeof provider.generateJSON !== 'function') {
1710
+ sendWarning('Skipping validation (validation provider not available)');
1711
+ return {
1712
+ validationStatus: 'acceptable',
1713
+ overallScore: 75,
1714
+ issues: [],
1715
+ strengths: ['Validation skipped - validation provider not available'],
1716
+ improvementPriorities: [],
1717
+ readyForPublication: true
1718
+ };
1719
+ }
1720
+
1721
+ // Read validator agent instructions
1722
+ const validatorAgent = this.loadAgentInstructions('validator-documentation.md');
1723
+
1724
+ // Build validation prompt
1725
+ let prompt = `Validate the following project documentation:\n\n`;
1726
+ prompt += `**doc.md Content:**\n\`\`\`markdown\n${docContent}\n\`\`\`\n\n`;
1727
+
1728
+ // Enhance prompt with verification feedback if available
1729
+ prompt = this.enhancePromptWithFeedback(prompt, 'validator-documentation');
1730
+
1731
+ const deploymentTarget = questionnaire.DEPLOYMENT_TARGET || '';
1732
+ const isLocalDeployment = /local|localhost|dev\s*machine|on.?prem/i.test(deploymentTarget);
1733
+
1734
+ prompt += `**Original Questionnaire Data:**\n`;
1735
+ prompt += `- MISSION_STATEMENT: ${questionnaire.MISSION_STATEMENT || 'N/A'}\n`;
1736
+ prompt += `- TARGET_USERS: ${questionnaire.TARGET_USERS || 'N/A'}\n`;
1737
+ prompt += `- INITIAL_SCOPE: ${questionnaire.INITIAL_SCOPE || 'N/A'}\n`;
1738
+ prompt += `- TECHNICAL_CONSIDERATIONS: ${questionnaire.TECHNICAL_CONSIDERATIONS || 'N/A'}\n`;
1739
+ prompt += `- DEPLOYMENT_TARGET: ${deploymentTarget || 'N/A'}\n`;
1740
+ prompt += `- TECHNICAL_EXCLUSIONS: ${questionnaire.TECHNICAL_EXCLUSIONS || 'None'}\n`;
1741
+ prompt += `- SECURITY_AND_COMPLIANCE_REQUIREMENTS: ${questionnaire.SECURITY_AND_COMPLIANCE_REQUIREMENTS || 'N/A'}\n\n`;
1742
+
1743
+ if (isLocalDeployment) {
1744
+ prompt += `**DEPLOYMENT ALIGNMENT CHECK — CRITICAL:**\n`;
1745
+ prompt += `The user chose LOCAL deployment ("${deploymentTarget}").\n`;
1746
+ prompt += `Flag as a CRITICAL contentIssue (category: "consistency") any mention of:\n`;
1747
+ prompt += `- Cloud providers: AWS, GCP, Google Cloud, Azure, DigitalOcean, Cloudflare, etc.\n`;
1748
+ prompt += `- Container orchestration: Kubernetes, ECS, EKS, GKE, AKS, Fargate, etc.\n`;
1749
+ prompt += `- Managed cloud services: RDS, S3, CloudFront, Lambda, Firebase, Supabase, etc.\n`;
1750
+ prompt += `- CI/CD pipelines: GitHub Actions, GitLab CI, CircleCI, Jenkins, etc. (unless user explicitly mentioned them in TECHNICAL_CONSIDERATIONS)\n`;
1751
+ prompt += `- Infrastructure as Code: Terraform, CloudFormation, Pulumi, etc.\n`;
1752
+ prompt += `These contradict the user's stated local deployment target and must be removed or replaced with local equivalents.\n\n`;
1753
+ }
1754
+
1755
+ if (questionnaire.TECHNICAL_EXCLUSIONS) {
1756
+ prompt += `**EXCLUSION ALIGNMENT CHECK — CRITICAL:**\n`;
1757
+ prompt += `The user explicitly excluded: ${questionnaire.TECHNICAL_EXCLUSIONS}\n`;
1758
+ prompt += `Flag as a CRITICAL contentIssue (category: "consistency") any mention of an excluded technology being recommended, suggested, or used in the document.\n\n`;
1759
+ }
1760
+
1761
+ prompt += `Return your validation as JSON following the exact structure specified in your instructions.`;
1762
+
1763
+ // Call validation provider for validation
1764
+ const validation = await this.retryWithBackoff(
1765
+ () => provider.generateJSON(prompt, validatorAgent),
1766
+ 'documentation validation'
1767
+ );
1768
+
1769
+ // Post-process verification of validator output
1770
+ const verifier = new LLMVerifier(provider, 'validator-documentation', this.verificationTracker);
1771
+ console.log('[DEBUG] validateDocument - Input to verifier (preview):', JSON.stringify(validation, null, 2).substring(0, 200));
1772
+ const verificationResult = await verifier.verify(JSON.stringify(validation, null, 2));
1773
+ console.log('[DEBUG] validateDocument - Verification result:', {
1774
+ rulesApplied: verificationResult.rulesApplied.map(r => r.id),
1775
+ contentLength: verificationResult.content.length,
1776
+ contentPreview: verificationResult.content.substring(0, 300)
1777
+ });
1778
+
1779
+ if (verificationResult.rulesApplied.length > 0) {
1780
+ // Store feedback for agent learning
1781
+ this.verificationFeedback['validator-documentation'] = verificationResult.rulesApplied.map(rule => ({
1782
+ ruleId: rule.id,
1783
+ ruleName: rule.name,
1784
+ severity: rule.severity
1785
+ }));
1786
+
1787
+ // Collect for final ceremony summary
1788
+ for (const rule of verificationResult.rulesApplied) {
1789
+ this.validationIssues.push({ stage: 'Documentation Validation', ruleId: rule.id, name: rule.name, severity: rule.severity, description: rule.description });
1790
+ }
1791
+
1792
+ // Parse corrected JSON back to object
1793
+ console.log('[DEBUG] validateDocument - Attempting to parse verification result as JSON');
1794
+
1795
+ // Strip markdown code fences if present
1796
+ const cleanedContent = this.stripMarkdownCodeFences(verificationResult.content);
1797
+ console.log('[DEBUG] validateDocument - After stripping code fences:', {
1798
+ originalLength: verificationResult.content.length,
1799
+ cleanedLength: cleanedContent.length,
1800
+ cleanedPreview: cleanedContent.substring(0, 200)
1801
+ });
1802
+
1803
+ try {
1804
+ const parsed = JSON.parse(cleanedContent);
1805
+ console.log('[DEBUG] validateDocument - Successfully parsed JSON');
1806
+ return parsed;
1807
+ } catch (error) {
1808
+ console.error('[ERROR] validateDocument - JSON parse failed:', error.message);
1809
+ console.error('[ERROR] validateDocument - Raw content that failed to parse:', cleanedContent);
1810
+ throw error;
1811
+ }
1812
+ }
1813
+
1814
+ return validation;
1815
+ }
1816
+
1817
+ /**
1818
+ * Improve documentation based on validation feedback
1819
+ */
1820
+ async improveDocument(docContent, validationResult, questionnaire) {
1821
+ // Use refinement provider (separate model from validator for improve/refine step)
1822
+ let provider;
1823
+ try {
1824
+ provider = await this.getRefinementProviderInstance();
1825
+ } catch (error) {
1826
+ sendWarning('Skipping improvement (refinement provider not available)');
1827
+ return docContent;
1828
+ }
1829
+
1830
+ if (!provider || typeof provider.generateText !== 'function') {
1831
+ sendWarning('Skipping improvement (validation provider not available)');
1832
+ return docContent;
1833
+ }
1834
+
1835
+ // Read documentation creator agent
1836
+ const creatorAgent = this.loadAgentInstructions('project-documentation-creator.md');
1837
+
1838
+ // Build improvement prompt with validation feedback
1839
+ let prompt = `Improve the following project documentation based on validation feedback:\n\n`;
1840
+
1841
+ prompt += `**Current doc.md:**\n\`\`\`markdown\n${docContent}\n\`\`\`\n\n`;
1842
+
1843
+ prompt += `**Validation Feedback:**\n`;
1844
+ prompt += `- Overall Score: ${validationResult.overallScore}/100\n`;
1845
+ prompt += `- Status: ${validationResult.validationStatus}\n\n`;
1846
+
1847
+ if (validationResult.structuralIssues && validationResult.structuralIssues.length > 0) {
1848
+ prompt += `**Structural Issues to Fix:**\n`;
1849
+ validationResult.structuralIssues.forEach((issue, i) => {
1850
+ prompt += `${i + 1}. [${issue.severity.toUpperCase()}] ${issue.section}: ${issue.issue}\n`;
1851
+ prompt += ` Suggestion: ${issue.suggestion}\n`;
1852
+ });
1853
+ prompt += `\n`;
1854
+ }
1855
+
1856
+ if (validationResult.contentIssues && validationResult.contentIssues.length > 0) {
1857
+ prompt += `**Content Issues to Fix:**\n`;
1858
+ validationResult.contentIssues.forEach((issue, i) => {
1859
+ prompt += `${i + 1}. [${issue.severity.toUpperCase()}] ${issue.section}: ${issue.description}\n`;
1860
+ prompt += ` Suggestion: ${issue.suggestion}\n`;
1861
+ });
1862
+ prompt += `\n`;
1863
+ }
1864
+
1865
+ if (validationResult.applicationFlowGaps && validationResult.applicationFlowGaps.length > 0) {
1866
+ prompt += `**Application Flow Gaps to Address:**\n`;
1867
+ validationResult.applicationFlowGaps.forEach((gap, i) => {
1868
+ prompt += `${i + 1}. Missing: ${gap.missingFlow}\n`;
1869
+ prompt += ` Impact: ${gap.impact}\n`;
1870
+ prompt += ` Suggestion: ${gap.suggestion}\n`;
1871
+ });
1872
+ prompt += `\n`;
1873
+ }
1874
+
1875
+ prompt += `**Improvement Priorities:**\n`;
1876
+ validationResult.improvementPriorities.forEach((priority, i) => {
1877
+ prompt += `${i + 1}. ${priority}\n`;
1878
+ });
1879
+ prompt += `\n`;
1880
+
1881
+ prompt += `**Original Questionnaire Data:**\n`;
1882
+ prompt += `- MISSION_STATEMENT: ${questionnaire.MISSION_STATEMENT || 'N/A'}\n`;
1883
+ prompt += `- TARGET_USERS: ${questionnaire.TARGET_USERS || 'N/A'}\n`;
1884
+ prompt += `- INITIAL_SCOPE: ${questionnaire.INITIAL_SCOPE || 'N/A'}\n`;
1885
+ prompt += `- TECHNICAL_CONSIDERATIONS: ${questionnaire.TECHNICAL_CONSIDERATIONS || 'N/A'}\n`;
1886
+ prompt += `- SECURITY_AND_COMPLIANCE_REQUIREMENTS: ${questionnaire.SECURITY_AND_COMPLIANCE_REQUIREMENTS || 'N/A'}\n\n`;
1887
+
1888
+ prompt += `Generate an improved version of the project documentation that addresses all the issues identified above. `;
1889
+ prompt += `Maintain the existing strengths while fixing the identified problems. `;
1890
+ prompt += `Return ONLY the improved markdown content, not wrapped in JSON.`;
1891
+
1892
+ // Call validation provider to regenerate documentation
1893
+ const improvedDoc = await this.retryWithBackoff(
1894
+ () => provider.generateText(prompt, creatorAgent),
1895
+ 'documentation improvement'
1896
+ );
1897
+
1898
+ return improvedDoc;
1899
+ }
1900
+
1901
+ /**
1902
+ /**
1903
+ * Iterative validation and improvement loop
1904
+ */
1905
+ async iterativeValidation(content, type, questionnaire, stageNum = null, stageTotal = null) {
1906
+ const settings = this.getValidationSettings();
1907
+ const maxIterations = settings.maxIterations || 100;
1908
+ const threshold = settings.acceptanceThreshold || 95;
1909
+
1910
+ let currentContent = content;
1911
+ let iteration = 0;
1912
+
1913
+ while (iteration < maxIterations) {
1914
+ // Emit fractional stage progress so the progress bar moves within this stage
1915
+ if (stageNum !== null && stageTotal !== null) {
1916
+ const current = parseFloat((stageNum + iteration * 0.2).toFixed(1));
1917
+ await this.reportProgressWithDelay(
1918
+ `Stage ${current}/${stageTotal}: Validating documentation (pass ${iteration + 1})...`,
1919
+ null, 50
1920
+ );
1921
+ }
1922
+ // Report validation iteration progress
1923
+ this.reportSubstep(`Validation ${iteration + 1}/${maxIterations}: Validating Project Brief structure...`);
1924
+ await this.reportDetail(`Calling ${this.validationLLMProvider?.model || 'LLM'} to validate…`);
1925
+ const issueCountBefore = this.validationIssues.length;
1926
+ const validation = await this.withHeartbeat(
1927
+ () => this.validateDocument(currentContent, questionnaire),
1928
+ (elapsed) => {
1929
+ if (elapsed < 15) return `Checking structure and completeness…`;
1930
+ if (elapsed < 30) return `Reviewing content quality…`;
1931
+ if (elapsed < 45) return `Analyzing gaps and issues…`;
1932
+ return `Validating documentation… (${elapsed}s)`;
1933
+ },
1934
+ 15000
1935
+ );
1936
+ // Tag newly-added issues with the current iteration number
1937
+ for (let i = issueCountBefore; i < this.validationIssues.length; i++) {
1938
+ this.validationIssues[i].iteration = iteration + 1;
1939
+ }
1940
+
1941
+ this.reportSubstep('Analyzing validation results...');
1942
+
1943
+ // Report token usage
1944
+ if (this.validationLLMProvider && typeof this.validationLLMProvider.getTokenUsage === 'function') {
1945
+ const usage = this.validationLLMProvider.getTokenUsage();
1946
+ this.reportSubstep('Validation complete', {
1947
+ tokensUsed: {
1948
+ input: usage.inputTokens,
1949
+ output: usage.outputTokens
1950
+ }
1951
+ });
1952
+ await this.reportDetail(`${usage.inputTokens.toLocaleString()} in · ${usage.outputTokens.toLocaleString()} out tokens`);
1953
+ }
1954
+
1955
+ // Log validation results to debug (not console)
1956
+ const issues = validation.issues || validation.contentIssues || [];
1957
+ const structuralIssues = validation.structuralIssues || [];
1958
+ const flowGaps = validation.applicationFlowGaps || [];
1959
+ const allIssues = [...issues, ...structuralIssues];
1960
+
1961
+ debug(`Score: ${validation.overallScore}/100`, {
1962
+ status: validation.validationStatus,
1963
+ issues: allIssues.length,
1964
+ flowGaps: flowGaps.length
1965
+ });
1966
+
1967
+ await this.reportDetail(`Score: ${validation.overallScore ?? '?'}/100 — ${allIssues.length} issue(s) found`);
1968
+
1969
+ // Check if ready — also accept immediately if no issues found at all
1970
+ const noIssues = allIssues.length === 0 && flowGaps.length === 0;
1971
+ if (noIssues || (validation.readyForPublication && validation.overallScore >= threshold)) {
1972
+ const reason = noIssues ? 'no issues found' : `score ≥ ${threshold}`;
1973
+ await this.reportDetail(`✓ Accepted (${reason})`);
1974
+ debug(`${type} passed validation`);
1975
+ break;
1976
+ }
1977
+
1978
+ // Check if max iterations reached
1979
+ if (iteration + 1 >= maxIterations) {
1980
+ await this.reportDetail(`Max iterations reached — accepting current version`);
1981
+ debug('Max iterations reached. Accepting current version.');
1982
+ break;
1983
+ }
1984
+
1985
+ // Improve
1986
+ debug(`Improving ${type} based on feedback`);
1987
+ if (stageNum !== null && stageTotal !== null) {
1988
+ const current = parseFloat((stageNum + iteration * 0.2 + 0.1).toFixed(1));
1989
+ await this.reportProgressWithDelay(
1990
+ `Stage ${current}/${stageTotal}: Improving documentation (pass ${iteration + 1})...`,
1991
+ null, 50
1992
+ );
1993
+ }
1994
+ this.reportSubstep(`Improving Project Brief based on validation...`);
1995
+ await this.reportDetail(`Calling ${this._refinementModel || this.validationLLMProvider?.model || 'LLM'} to improve…`);
1996
+ currentContent = await this.withHeartbeat(
1997
+ () => this.improveDocument(currentContent, validation, questionnaire),
1998
+ (elapsed) => {
1999
+ if (elapsed < 15) return `Applying structural improvements…`;
2000
+ if (elapsed < 30) return `Enhancing content quality…`;
2001
+ if (elapsed < 45) return `Resolving identified issues…`;
2002
+ return `Improving Project Brief… (${elapsed}s)`;
2003
+ },
2004
+ 15000
2005
+ );
2006
+
2007
+ // Report token usage after improvement
2008
+ if (this.validationLLMProvider && typeof this.validationLLMProvider.getTokenUsage === 'function') {
2009
+ const usage = this.validationLLMProvider.getTokenUsage();
2010
+ this.reportSubstep('Applying improvements...', {
2011
+ tokensUsed: {
2012
+ input: usage.inputTokens,
2013
+ output: usage.outputTokens
2014
+ }
2015
+ });
2016
+ await this.reportDetail(`${usage.inputTokens.toLocaleString()} in · ${usage.outputTokens.toLocaleString()} out tokens`);
2017
+ } else {
2018
+ this.reportSubstep('Applying improvements...');
2019
+ }
2020
+
2021
+ iteration++;
2022
+ }
2023
+
2024
+ return currentContent;
2025
+ }
2026
+
2027
+ /**
2028
+ * Get architecture recommendations based on mission statement and initial scope
2029
+ * @param {string} missionStatement - The project's mission statement
2030
+ * @param {string} initialScope - The initial scope/features
2031
+ * @param {Object|null} databaseRecommendation - Optional database recommendation context
2032
+ * @returns {Promise<Array>} Array of architecture recommendations
2033
+ */
2034
+ async getArchitectureRecommendations(missionStatement, initialScope, databaseContext = null, deploymentStrategy = null) {
2035
+ debug('getArchitectureRecommendations called', {
2036
+ missionStatement,
2037
+ initialScope,
2038
+ hasDatabaseContext: !!databaseContext,
2039
+ userChoice: databaseContext?.userChoice,
2040
+ deploymentStrategy
2041
+ });
2042
+
2043
+ try {
2044
+ // Get stage-specific provider for architecture recommendation
2045
+ const provider = await this.getProviderForStageInstance('architecture-recommendation');
2046
+
2047
+ if (!provider || typeof provider.generateJSON !== 'function') {
2048
+ throw new Error('Architecture recommendation provider not available');
2049
+ }
2050
+
2051
+ // Read agent instructions
2052
+ debug('Loading architecture-recommender.md agent');
2053
+ const architectureRecommenderAgent = loadAgent('architecture-recommender.md', path.dirname(this.avcPath));
2054
+
2055
+ // Build prompt
2056
+ let prompt = `Given the following project definition:
2057
+
2058
+ **Mission Statement:**
2059
+ ${missionStatement}
2060
+
2061
+ **Initial Scope (Features to Implement):**
2062
+ ${initialScope}`;
2063
+
2064
+ // Add database context if available (new comparison format)
2065
+ if (databaseContext?.comparison) {
2066
+ prompt += `
2067
+
2068
+ **Database Context:**`;
2069
+
2070
+ if (databaseContext.userChoice) {
2071
+ const chosenOption = databaseContext.userChoice === 'sql' ? databaseContext.comparison.sqlOption : databaseContext.comparison.nosqlOption;
2072
+ prompt += `
2073
+ - User's Choice: ${databaseContext.userChoice.toUpperCase()} (${chosenOption.database})
2074
+ - Specific Version: ${chosenOption.specificVersion || chosenOption.database}`;
2075
+
2076
+ if (chosenOption.estimatedCosts) {
2077
+ prompt += `
2078
+ - Estimated Monthly Cost: ${chosenOption.estimatedCosts.monthly}`;
2079
+ }
2080
+ } else {
2081
+ // No user choice yet, provide both options
2082
+ prompt += `
2083
+ - SQL Option: ${databaseContext.comparison.sqlOption.database} (~${databaseContext.comparison.sqlOption.estimatedCosts?.monthly || 'TBD'}/mo)
2084
+ - NoSQL Option: ${databaseContext.comparison.nosqlOption.database} (~${databaseContext.comparison.nosqlOption.estimatedCosts?.monthly || 'TBD'}/mo)
2085
+ - Note: User has not chosen yet, provide architectures compatible with both`;
2086
+ }
2087
+
2088
+ if (databaseContext.keyMetrics) {
2089
+ prompt += `
2090
+ - Read/Write Ratio: ${databaseContext.keyMetrics.estimatedReadWriteRatio || 'Not specified'}
2091
+ - Expected Throughput: ${databaseContext.keyMetrics.expectedThroughput || 'Not specified'}
2092
+ - Data Complexity: ${databaseContext.keyMetrics.dataComplexity || 'Not specified'}`;
2093
+ }
2094
+ }
2095
+
2096
+ // Add deployment strategy context if provided
2097
+ if (deploymentStrategy === 'local-mvp') {
2098
+ prompt += `
2099
+
2100
+ **Deployment Strategy:** Local MVP First
2101
+
2102
+ CRITICAL FILTERING REQUIREMENT:
2103
+ - Return ONLY local development architectures (no cloud-specific services)
2104
+ - Every architecture MUST include a migrationPath object with cloud migration details
2105
+ - NO Lambda, ECS, AKS, GKE, or other cloud-managed services
2106
+ - Focus on: Docker Compose, localhost setups, local databases
2107
+
2108
+ Required architectures to consider:
2109
+ 1. Docker Compose full-stack (PostgreSQL/MongoDB, backend, frontend)
2110
+ 2. Lightweight localhost setup (SQLite/JSON, Express/Flask, React dev server)
2111
+ 3. Framework-specific local development (Django, Rails, Next.js dev)
2112
+
2113
+ Each architecture must include:
2114
+ - Clear "zero cloud costs" messaging
2115
+ - Production parity explanation (Docker vs simple localhost)
2116
+ - Specific migration path to 2-3 cloud architectures
2117
+ - Estimated migration effort (days) and complexity level
2118
+
2119
+ Example architecture structure:
2120
+ {
2121
+ "name": "Local Docker Compose Full-Stack",
2122
+ "description": "...runs entirely on your machine with zero cloud costs...Ready to migrate to AWS ECS, Azure Container Apps, or GCP Cloud Run when ready for production...",
2123
+ "requiresCloudProvider": false,
2124
+ "bestFor": "MVP development with production-like local environment",
2125
+ "migrationPath": {
2126
+ "readyForCloud": true,
2127
+ "suggestedCloudArchitectures": ["AWS ECS", "Azure Container Apps", "GCP Cloud Run"],
2128
+ "estimatedMigrationEffort": "2-3 days",
2129
+ "migrationComplexity": "Medium",
2130
+ "keyMigrationSteps": ["Set up managed database", "Create container registry", "Deploy to cloud service"]
2131
+ }
2132
+ }`;
2133
+ } else if (deploymentStrategy === 'cloud') {
2134
+ prompt += `
2135
+
2136
+ **Deployment Strategy:** Cloud Deployment
2137
+
2138
+ CRITICAL FILTERING REQUIREMENT:
2139
+ - Return ONLY cloud-native architectures (AWS/Azure/GCP managed services or PaaS)
2140
+ - NO local development options (Docker Compose, localhost setups)
2141
+ - Focus on: Serverless, containers, managed services, PaaS platforms
2142
+
2143
+ Required considerations:
2144
+ - Serverless options (Lambda, Cloud Functions, Cloud Run)
2145
+ - Container orchestration (ECS, AKS, GKE)
2146
+ - PaaS platforms (Vercel, Railway, Render for simpler projects)
2147
+ - Managed databases (RDS, DynamoDB, Atlas, Cosmos DB)
2148
+
2149
+ Each architecture must include:
2150
+ - Specific cloud services and managed offerings
2151
+ - Estimated monthly costs (low/medium/high traffic scenarios)
2152
+ - Auto-scaling and managed infrastructure benefits
2153
+ - Production-ready from day one messaging
2154
+
2155
+ Example architecture structure:
2156
+ {
2157
+ "name": "Serverless Backend + SPA on AWS",
2158
+ "description": "AWS Lambda for backend API, API Gateway for routing, DynamoDB for database, S3 + CloudFront for frontend. Scales automatically, pay only for usage...",
2159
+ "requiresCloudProvider": true,
2160
+ "bestFor": "Scalable APIs with variable traffic, cost optimization",
2161
+ "estimatedMonthlyCost": {
2162
+ "low": "$10-30 (< 10K requests/day)",
2163
+ "medium": "$50-150 (10K-100K requests/day)",
2164
+ "high": "$200-500 (100K+ requests/day)"
2165
+ }
2166
+ }`;
2167
+ }
2168
+
2169
+ prompt += `
2170
+
2171
+ Analyze this project and recommend 3-5 deployment architectures that best fit these requirements.${databaseContext ? ' Consider the database context when recommending deployment patterns and cloud services.' : ''}${deploymentStrategy ? ` IMPORTANT: Follow the deployment strategy filtering requirements above - return ONLY ${deploymentStrategy === 'local-mvp' ? 'local' : 'cloud'} architectures.` : ''}
2172
+
2173
+ Return your response as JSON following the exact structure specified in your instructions.`;
2174
+
2175
+ debug('Calling LLM for architecture recommendations');
2176
+ const result = await this.retryWithBackoff(
2177
+ () => provider.generateJSON(prompt, architectureRecommenderAgent),
2178
+ 'architecture recommendations'
2179
+ );
2180
+
2181
+ debug('Architecture recommendations received', { count: result.architectures?.length });
2182
+
2183
+ // Validate response structure
2184
+ if (!result.architectures || !Array.isArray(result.architectures)) {
2185
+ throw new Error('Invalid architecture recommendation response: missing architectures array');
2186
+ }
2187
+
2188
+ if (result.architectures.length < 1) {
2189
+ throw new Error('No architecture recommendations received');
2190
+ }
2191
+
2192
+ // Validate each architecture has required fields
2193
+ result.architectures.forEach((arch, index) => {
2194
+ if (!arch.name || !arch.description || typeof arch.requiresCloudProvider !== 'boolean' || !arch.bestFor) {
2195
+ throw new Error(`Architecture at index ${index} is missing required fields`);
2196
+ }
2197
+ });
2198
+
2199
+ return result.architectures;
2200
+ } catch (error) {
2201
+ console.error('[ERROR] getArchitectureRecommendations failed:', error.message);
2202
+ debug('getArchitectureRecommendations error', { error: error.message, stack: error.stack });
2203
+ throw error;
2204
+ }
2205
+ }
2206
+
2207
+ /**
2208
+ * Get database recommendation based on mission statement and initial scope (quick analysis)
2209
+ * @param {string} missionStatement - The project's mission statement
2210
+ * @param {string} initialScope - The initial scope/features
2211
+ * @returns {Promise<Object>} Database recommendation object
2212
+ */
2213
+ async getDatabaseRecommendation(missionStatement, initialScope, deploymentStrategy = null) {
2214
+ debug('getDatabaseRecommendation called', { missionStatement, initialScope, deploymentStrategy });
2215
+
2216
+ try {
2217
+ // Get stage-specific provider for database recommendation
2218
+ const provider = await this.getProviderForStageInstance('database-recommendation');
2219
+
2220
+ if (!provider || typeof provider.generateJSON !== 'function') {
2221
+ throw new Error('Database recommendation provider not available');
2222
+ }
2223
+
2224
+ // Read agent instructions
2225
+ debug('Loading database-recommender.md agent');
2226
+ const databaseRecommenderAgent = loadAgent('database-recommender.md', path.dirname(this.avcPath));
2227
+
2228
+ // Build prompt with deployment strategy context
2229
+ let prompt = `Given the following project definition:
2230
+
2231
+ **Mission Statement:**
2232
+ ${missionStatement}
2233
+
2234
+ **Initial Scope (Features to Implement):**
2235
+ ${initialScope}
2236
+ `;
2237
+
2238
+ // Add deployment strategy context if provided
2239
+ if (deploymentStrategy === 'local-mvp') {
2240
+ prompt += `
2241
+ **Deployment Strategy:** Local MVP First
2242
+ The user has chosen to start with a local development environment and migrate to cloud later.
2243
+
2244
+ IMPORTANT: Prioritize local-friendly databases:
2245
+ - For SQL: Recommend SQLite (zero setup, file-based) or PostgreSQL in Docker (production parity)
2246
+ - For NoSQL: Recommend local MongoDB in Docker or JSON file storage
2247
+ - Include clear migration paths to cloud databases (SQLite → RDS/Cloud SQL, local MongoDB → Atlas)
2248
+ - Emphasize zero cost during MVP phase
2249
+ - Show cost comparison: "$0/month local" vs cloud costs
2250
+
2251
+ `;
2252
+ } else if (deploymentStrategy === 'cloud') {
2253
+ prompt += `
2254
+ **Deployment Strategy:** Cloud Deployment
2255
+ The user has chosen to deploy to production cloud infrastructure from day one.
2256
+
2257
+ IMPORTANT: Prioritize managed cloud databases:
2258
+ - For SQL: Recommend AWS RDS, Azure Database, Google Cloud SQL
2259
+ - For NoSQL: Recommend DynamoDB, MongoDB Atlas, Azure Cosmos DB
2260
+ - Emphasize managed features (backups, scaling, monitoring, high availability)
2261
+ - Include realistic monthly cost estimates
2262
+ - Focus on production-ready, scalable options
2263
+
2264
+ `;
2265
+ }
2266
+
2267
+ prompt += `
2268
+ Analyze this project and determine if it needs a database, and if so, recommend the most appropriate database solution.
2269
+
2270
+ Return your response as JSON following the exact structure specified in your instructions.`;
2271
+
2272
+ debug('Calling LLM for database recommendation');
2273
+ const result = await this.retryWithBackoff(
2274
+ () => provider.generateJSON(prompt, databaseRecommenderAgent),
2275
+ 'database recommendation'
2276
+ );
2277
+
2278
+ debug('Database recommendation received', {
2279
+ hasDatabaseNeeds: result.hasDatabaseNeeds,
2280
+ confidence: result.confidence,
2281
+ hasComparison: !!result.comparison
2282
+ });
2283
+
2284
+ // Validate response structure
2285
+ if (typeof result.hasDatabaseNeeds !== 'boolean' || !result.confidence) {
2286
+ throw new Error('Invalid database recommendation response: missing required fields');
2287
+ }
2288
+
2289
+ // If database is needed, validate comparison structure
2290
+ if (result.hasDatabaseNeeds && result.comparison) {
2291
+ if (!result.comparison.sqlOption || !result.comparison.nosqlOption) {
2292
+ throw new Error('Invalid database recommendation: missing sqlOption or nosqlOption in comparison');
2293
+ }
2294
+ }
2295
+
2296
+ return result;
2297
+ } catch (error) {
2298
+ console.error('[ERROR] getDatabaseRecommendation failed:', error.message);
2299
+ debug('getDatabaseRecommendation error', { error: error.message, stack: error.stack });
2300
+ throw error;
2301
+ }
2302
+ }
2303
+
2304
+ /**
2305
+ * Get detailed database recommendation with user inputs
2306
+ * @param {string} missionStatement - The project's mission statement
2307
+ * @param {string} initialScope - The initial scope/features
2308
+ * @param {Object} userAnswers - User's detailed answers
2309
+ * @param {string} userAnswers.readWriteRatio - e.g., "70/30"
2310
+ * @param {string} userAnswers.dailyRequests - e.g., "10000"
2311
+ * @param {string} userAnswers.costSensitivity - "Low" | "Medium" | "High"
2312
+ * @param {string} userAnswers.dataRelationships - "Simple" | "Moderate" | "Complex"
2313
+ * @returns {Promise<Object>} Detailed database recommendation
2314
+ */
2315
+ async getDatabaseDetailedRecommendation(missionStatement, initialScope, userAnswers) {
2316
+ debug('getDatabaseDetailedRecommendation called', {
2317
+ missionStatement,
2318
+ initialScope,
2319
+ userAnswers
2320
+ });
2321
+
2322
+ try {
2323
+ // Get stage-specific provider for detailed database recommendation
2324
+ const provider = await this.getProviderForStageInstance('database-deep-dive');
2325
+
2326
+ if (!provider || typeof provider.generateJSON !== 'function') {
2327
+ throw new Error('Database deep-dive provider not available');
2328
+ }
2329
+
2330
+ // Read agent instructions
2331
+ debug('Loading database-deep-dive.md agent');
2332
+ const databaseDeepDiveAgent = loadAgent('database-deep-dive.md', path.dirname(this.avcPath));
2333
+
2334
+ // Build prompt
2335
+ const prompt = `Given the following project definition and user requirements:
2336
+
2337
+ **Mission Statement:**
2338
+ ${missionStatement}
2339
+
2340
+ **Initial Scope (Features to Implement):**
2341
+ ${initialScope}
2342
+
2343
+ **User Requirements:**
2344
+ - Read/Write Ratio: ${userAnswers.readWriteRatio}
2345
+ - Expected Daily Requests: ${userAnswers.dailyRequests}
2346
+ - Cost Sensitivity: ${userAnswers.costSensitivity}
2347
+ - Data Relationships: ${userAnswers.dataRelationships}
2348
+
2349
+ Provide a detailed database architecture recommendation including specific configurations, sizing, and cost estimates.
2350
+
2351
+ Return your response as JSON following the exact structure specified in your instructions.`;
2352
+
2353
+ debug('Calling LLM for detailed database recommendation');
2354
+ const result = await this.retryWithBackoff(
2355
+ () => provider.generateJSON(prompt, databaseDeepDiveAgent),
2356
+ 'detailed database recommendation'
2357
+ );
2358
+
2359
+ debug('Detailed database recommendation received', {
2360
+ hasComparison: !!result.comparison,
2361
+ recommendation: result.recommendation
2362
+ });
2363
+
2364
+ // Validate response structure (new comparison format)
2365
+ if (!result.comparison || !result.comparison.sqlOption || !result.comparison.nosqlOption) {
2366
+ throw new Error('Invalid detailed database recommendation response: missing comparison with sqlOption and nosqlOption');
2367
+ }
2368
+
2369
+ // Validate sqlOption has required fields
2370
+ if (!result.comparison.sqlOption.database || !result.comparison.sqlOption.architecture || !result.comparison.sqlOption.estimatedCosts) {
2371
+ throw new Error('Invalid sqlOption in detailed database recommendation: missing required fields');
2372
+ }
2373
+
2374
+ // Validate nosqlOption has required fields
2375
+ if (!result.comparison.nosqlOption.database || !result.comparison.nosqlOption.architecture || !result.comparison.nosqlOption.estimatedCosts) {
2376
+ throw new Error('Invalid nosqlOption in detailed database recommendation: missing required fields');
2377
+ }
2378
+
2379
+ return result;
2380
+ } catch (error) {
2381
+ console.error('[ERROR] getDatabaseDetailedRecommendation failed:', error.message);
2382
+ debug('getDatabaseDetailedRecommendation error', { error: error.message, stack: error.stack });
2383
+ throw error;
2384
+ }
2385
+ }
2386
+
2387
+ /**
2388
+ * Pre-fill questionnaire answers based on architecture selection
2389
+ * @param {string} missionStatement - The project's mission statement
2390
+ * @param {string} initialScope - The initial scope/features
2391
+ * @param {Object} architecture - Selected architecture object
2392
+ * @param {string|null} cloudProvider - Selected cloud provider (AWS/Azure/GCP) or null
2393
+ * @param {Object|null} databaseRecommendation - Optional database recommendation context
2394
+ * @returns {Promise<Object>} Object with pre-filled answers
2395
+ */
2396
+ async prefillQuestions(missionStatement, initialScope, architecture, cloudProvider = null, databaseRecommendation = null, deploymentStrategy = null) {
2397
+ debug('prefillQuestions called', {
2398
+ missionStatement,
2399
+ initialScope,
2400
+ architectureName: architecture.name,
2401
+ cloudProvider,
2402
+ deploymentStrategy
2403
+ });
2404
+
2405
+ try {
2406
+ // Get stage-specific provider for question prefilling
2407
+ const provider = await this.getProviderForStageInstance('question-prefilling');
2408
+
2409
+ if (!provider || typeof provider.generateJSON !== 'function') {
2410
+ throw new Error('Question prefilling provider not available');
2411
+ }
2412
+
2413
+ // Read agent instructions
2414
+ debug('Loading question-prefiller.md agent');
2415
+ const questionPrefillerAgent = loadAgent('question-prefiller.md', path.dirname(this.avcPath));
2416
+
2417
+ // Build prompt
2418
+ let prompt = `Given the following project context:
2419
+
2420
+ **Mission Statement:**
2421
+ ${missionStatement}
2422
+
2423
+ **Initial Scope (Features to Implement):**
2424
+ ${initialScope}
2425
+
2426
+ **Selected Architecture:**
2427
+ - Name: ${architecture.name}
2428
+ - Description: ${architecture.description}
2429
+ - Best For: ${architecture.bestFor}`;
2430
+
2431
+ if (cloudProvider) {
2432
+ prompt += `\n- Cloud Provider: ${cloudProvider}`;
2433
+ }
2434
+
2435
+ // Add deployment strategy context if provided
2436
+ if (deploymentStrategy) {
2437
+ const strategyName = deploymentStrategy === 'local-mvp' ? 'Local MVP First' : 'Cloud Deployment';
2438
+ prompt += `
2439
+
2440
+ **Deployment Strategy:** ${strategyName}`;
2441
+
2442
+ if (deploymentStrategy === 'local-mvp') {
2443
+ prompt += `
2444
+
2445
+ CRITICAL: Deployment strategy affects ONLY deployment and technical choices, NOT target users.
2446
+
2447
+ **TARGET_USERS:**
2448
+ - Infer from mission statement and scope ONLY
2449
+ - Ignore deployment strategy completely
2450
+ - Example: If mission is "task management for remote teams", target users are remote team members, NOT developers
2451
+ - The deployment choice (local vs cloud) does NOT change who the end users are
2452
+
2453
+ **DEPLOYMENT_TARGET requirements:**
2454
+ - Emphasize local development environment (Docker Compose, localhost)
2455
+ - Mention zero cloud costs during MVP development phase
2456
+ - Include migration readiness: "Ready to migrate to [cloud options] when scaling to production"
2457
+
2458
+ **TECHNICAL_CONSIDERATIONS requirements:**
2459
+ - Include local stack details (SQLite or local PostgreSQL/MongoDB in Docker, containerization)
2460
+ - Focus on: Zero cost, rapid iteration, easy debugging, production parity with Docker
2461
+ - DO NOT mention cloud services (RDS, Lambda, ECS, etc.) in technical details`;
2462
+ } else if (deploymentStrategy === 'cloud') {
2463
+ prompt += `
2464
+
2465
+ CRITICAL: Deployment strategy affects ONLY deployment and technical choices, NOT target users.
2466
+
2467
+ **TARGET_USERS:**
2468
+ - Infer from mission statement and scope ONLY
2469
+ - Ignore deployment strategy completely
2470
+ - The deployment choice (local vs cloud) does NOT change who the end users are
2471
+
2472
+ **DEPLOYMENT_TARGET requirements:**
2473
+ - Detail cloud infrastructure (managed services, auto-scaling, regions)
2474
+ - Emphasize production-ready from day one
2475
+ - Include cost considerations and scaling strategy
2476
+
2477
+ **TECHNICAL_CONSIDERATIONS requirements:**
2478
+ - Include cloud-specific details (managed database, CDN, load balancers)
2479
+ - Emphasize: monitoring, backups, high availability, auto-scaling
2480
+ - Focus on: Managed services, production features`;
2481
+ }
2482
+ }
2483
+
2484
+ // Add database context if available (supports both old and new format)
2485
+ if (databaseRecommendation) {
2486
+ prompt += `
2487
+
2488
+ **Database Recommendation:**`;
2489
+
2490
+ // New format with comparison
2491
+ if (databaseRecommendation.comparison) {
2492
+ const { sqlOption, nosqlOption } = databaseRecommendation.comparison;
2493
+ const userChoice = databaseRecommendation.userChoice;
2494
+
2495
+ if (userChoice === 'sql') {
2496
+ prompt += `
2497
+ - Chosen Database: ${sqlOption.database} (SQL)`;
2498
+ if (sqlOption.specificVersion) {
2499
+ prompt += `
2500
+ - Specific Version: ${sqlOption.specificVersion}`;
2501
+ }
2502
+ if (sqlOption.architecture) {
2503
+ prompt += `
2504
+ - Architecture: ${sqlOption.architecture.primaryInstance || ''}`;
2505
+ if (sqlOption.architecture.readReplicas) {
2506
+ prompt += ` with ${sqlOption.architecture.readReplicas} read replica(s)`;
2507
+ }
2508
+ }
2509
+ if (sqlOption.estimatedCosts) {
2510
+ prompt += `
2511
+ - Estimated Cost: ${sqlOption.estimatedCosts.monthly}`;
2512
+ }
2513
+ } else if (userChoice === 'nosql') {
2514
+ prompt += `
2515
+ - Chosen Database: ${nosqlOption.database} (NoSQL)`;
2516
+ if (nosqlOption.specificVersion) {
2517
+ prompt += `
2518
+ - Specific Version: ${nosqlOption.specificVersion}`;
2519
+ }
2520
+ if (nosqlOption.architecture) {
2521
+ prompt += `
2522
+ - Architecture: ${nosqlOption.architecture.capacity || nosqlOption.architecture.primaryInstance || ''}`;
2523
+ if (nosqlOption.architecture.indexes) {
2524
+ prompt += `, ${nosqlOption.architecture.indexes} indexes`;
2525
+ }
2526
+ }
2527
+ if (nosqlOption.estimatedCosts) {
2528
+ prompt += `
2529
+ - Estimated Cost: ${nosqlOption.estimatedCosts.monthly}`;
2530
+ }
2531
+ } else {
2532
+ // No user choice yet, show both options
2533
+ prompt += `
2534
+ - SQL Option: ${sqlOption.database} (~${sqlOption.estimatedCosts?.monthly || 'TBD'})
2535
+ - NoSQL Option: ${nosqlOption.database} (~${nosqlOption.estimatedCosts?.monthly || 'TBD'})`;
2536
+ }
2537
+
2538
+ if (databaseRecommendation.keyMetrics) {
2539
+ prompt += `
2540
+ - Read/Write Ratio: ${databaseRecommendation.keyMetrics.readWriteRatio || databaseRecommendation.keyMetrics.estimatedReadWriteRatio || 'Not specified'}`;
2541
+ }
2542
+ }
2543
+ // Legacy format (backward compatibility)
2544
+ else {
2545
+ prompt += `
2546
+ - Primary Database: ${databaseRecommendation.primaryDatabase || databaseRecommendation.recommendation?.primaryDatabase || 'Not specified'}
2547
+ - Type: ${databaseRecommendation.type || databaseRecommendation.recommendation?.type || 'Not specified'}`;
2548
+
2549
+ if (databaseRecommendation.specificVersion) {
2550
+ prompt += `
2551
+ - Specific Version: ${databaseRecommendation.specificVersion}`;
2552
+ }
2553
+
2554
+ if (databaseRecommendation.architecture) {
2555
+ prompt += `
2556
+ - Architecture: ${databaseRecommendation.architecture.primaryInstance || ''}`;
2557
+ if (databaseRecommendation.architecture.readReplicas) {
2558
+ prompt += ` with ${databaseRecommendation.architecture.readReplicas} read replica(s)`;
2559
+ }
2560
+ }
2561
+
2562
+ if (databaseRecommendation.estimatedCosts) {
2563
+ prompt += `
2564
+ - Estimated Cost: ${databaseRecommendation.estimatedCosts.monthly || ''}`;
2565
+ }
2566
+ }
2567
+ }
2568
+
2569
+ prompt += `
2570
+
2571
+ Generate intelligent, context-aware answers for these questions:
2572
+ 1. TARGET_USERS: Who will use this application and what are their roles/characteristics?
2573
+ 2. DEPLOYMENT_TARGET: Where and how will this application be deployed?
2574
+ 3. TECHNICAL_CONSIDERATIONS: Technology stack, architectural patterns, scalability, and performance
2575
+ 4. SECURITY_AND_COMPLIANCE_REQUIREMENTS: Security measures, privacy, authentication, and compliance
2576
+
2577
+ Return your response as JSON following the exact structure specified in your instructions.`;
2578
+
2579
+ debug('Calling LLM for question prefilling');
2580
+ const result = await this.retryWithBackoff(
2581
+ () => provider.generateJSON(prompt, questionPrefillerAgent),
2582
+ 'question prefilling'
2583
+ );
2584
+
2585
+ // Normalize common field name variants that local LLMs sometimes produce.
2586
+ // Maps known abbreviations/alternatives → canonical field names.
2587
+ const FIELD_ALIASES = {
2588
+ DEPLOY_TARGET: 'DEPLOYMENT_TARGET',
2589
+ DEPLOY: 'DEPLOYMENT_TARGET',
2590
+ DEPLOYMENT: 'DEPLOYMENT_TARGET',
2591
+ TARGET_DEPLOYMENT: 'DEPLOYMENT_TARGET',
2592
+ SECURITY_REQUIREMENTS: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2593
+ SECURITY_COMPLIANCE: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2594
+ COMPLIANCE_REQUIREMENTS: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2595
+ SECURITY: 'SECURITY_AND_COMPLIANCE_REQUIREMENTS',
2596
+ TECHNICAL: 'TECHNICAL_CONSIDERATIONS',
2597
+ TECH_CONSIDERATIONS: 'TECHNICAL_CONSIDERATIONS',
2598
+ TECH_STACK: 'TECHNICAL_CONSIDERATIONS',
2599
+ USERS: 'TARGET_USERS',
2600
+ TARGET_USER: 'TARGET_USERS',
2601
+ };
2602
+
2603
+ for (const [alias, canonical] of Object.entries(FIELD_ALIASES)) {
2604
+ if (result[alias] !== undefined) {
2605
+ if (!result[canonical] && result[alias]) {
2606
+ // Canonical missing or empty — use alias value
2607
+ debug(`Normalizing field name: ${alias} → ${canonical}`);
2608
+ result[canonical] = result[alias];
2609
+ }
2610
+ delete result[alias];
2611
+ }
2612
+ }
2613
+
2614
+ debug('Question prefilling received', {
2615
+ hasTargetUsers: !!result.TARGET_USERS,
2616
+ hasDeploymentTarget: !!result.DEPLOYMENT_TARGET,
2617
+ hasTechnicalConsiderations: !!result.TECHNICAL_CONSIDERATIONS,
2618
+ hasSecurityRequirements: !!result.SECURITY_AND_COMPLIANCE_REQUIREMENTS
2619
+ });
2620
+
2621
+ // Validate response structure
2622
+ const requiredFields = [
2623
+ 'TARGET_USERS',
2624
+ 'DEPLOYMENT_TARGET',
2625
+ 'TECHNICAL_CONSIDERATIONS',
2626
+ 'SECURITY_AND_COMPLIANCE_REQUIREMENTS'
2627
+ ];
2628
+
2629
+ const missingFields = requiredFields.filter(field => !result[field]);
2630
+ if (missingFields.length > 0) {
2631
+ sendWarning(`Warning: Pre-filling missing fields: ${missingFields.join(', ')}`);
2632
+ // Fill missing fields with empty strings
2633
+ missingFields.forEach(field => {
2634
+ result[field] = '';
2635
+ });
2636
+ }
2637
+
2638
+ return result;
2639
+ } catch (error) {
2640
+ console.error('[ERROR] prefillQuestions failed:', error.message);
2641
+ debug('prefillQuestions error', { error: error.message, stack: error.stack });
2642
+ throw error;
2643
+ }
2644
+ }
2645
+
2646
+ /**
2647
+ * Generate migration guide for local-to-cloud deployment
2648
+ * @param {Object} architecture - Selected local architecture
2649
+ * @param {string} databaseType - Database type ('sql' or 'nosql')
2650
+ * @param {Object} questionnaire - Full questionnaire answers
2651
+ * @returns {Promise<string>} Migration guide markdown
2652
+ */
2653
+ async generateMigrationGuide(architecture, databaseType, questionnaire) {
2654
+ debug('generateMigrationGuide called', {
2655
+ architectureName: architecture.name,
2656
+ databaseType
2657
+ });
2658
+
2659
+ try {
2660
+ // Get stage-specific provider for migration guide generation
2661
+ this.reportSubstep('Preparing migration guide generator...');
2662
+ const provider = await this.getProviderForStageInstance('migration-guide-generation');
2663
+
2664
+ if (!provider || typeof provider.generateText !== 'function') {
2665
+ throw new Error('Migration guide generation provider not available');
2666
+ }
2667
+
2668
+ // Read agent instructions
2669
+ debug('Loading migration-guide-generator.md agent');
2670
+ const migrationGuideAgent = loadAgent('migration-guide-generator.md', path.dirname(this.avcPath));
2671
+
2672
+ // Build comprehensive prompt
2673
+ this.reportSubstep('Generating cloud migration guide (this may take 30-60 seconds)...');
2674
+ const prompt = `Generate a comprehensive cloud migration guide for the following local development setup:
2675
+
2676
+ **Local Architecture:**
2677
+ - Name: ${architecture.name}
2678
+ - Description: ${architecture.description}
2679
+ - Best For: ${architecture.bestFor}
2680
+
2681
+ **Database:**
2682
+ - Type: ${databaseType ? (databaseType.toUpperCase()) : 'Not specified'}
2683
+
2684
+ **Project Context:**
2685
+ - Mission: ${questionnaire.MISSION_STATEMENT}
2686
+ - Scope: ${questionnaire.INITIAL_SCOPE}
2687
+ - Technical Stack: ${questionnaire.TECHNICAL_CONSIDERATIONS || 'To be determined'}
2688
+
2689
+ **Target Users:** ${questionnaire.TARGET_USERS || 'General users'}
2690
+
2691
+ Generate a complete DEPLOYMENT_MIGRATION.md document following the structure specified in your instructions.
2692
+
2693
+ Include:
2694
+ 1. Current local stack summary
2695
+ 2. When to migrate decision criteria
2696
+ 3. 3-4 cloud migration options with costs and complexity
2697
+ 4. Database-specific migration guide
2698
+ 5. Environment variable changes
2699
+ 6. CI/CD pipeline recommendation
2700
+ 7. Monitoring and observability setup
2701
+ 8. Cost comparison table
2702
+ 9. Comprehensive migration checklist
2703
+ 10. Common issues and solutions
2704
+ 11. Support resources
2705
+
2706
+ Make it actionable with specific CLI commands, code examples, and cost estimates.`;
2707
+
2708
+ debug('Calling LLM for migration guide generation');
2709
+ const result = await this.retryWithBackoff(
2710
+ () => provider.generateText(prompt, migrationGuideAgent),
2711
+ 'migration guide generation'
2712
+ );
2713
+
2714
+ debug('Migration guide generated', { length: result.length });
2715
+ this.reportSubstep('Writing DEPLOYMENT_MIGRATION.md...');
2716
+
2717
+ return result;
2718
+ } catch (error) {
2719
+ console.error('[ERROR] generateMigrationGuide failed:', error.message);
2720
+ debug('generateMigrationGuide error', { error: error.message, stack: error.stack });
2721
+ throw error;
2722
+ }
2723
+ }
2724
+
2725
+ /**
2726
+ * Configure the log file for debug() writes.
2727
+ * Called from repl-ink.js when the CommandLogger starts/stops.
2728
+ * When filePath is null, debug() is silent (no terminal or file output).
2729
+ * @param {string|null} filePath - Absolute path to the active log file
2730
+ */
2731
+ static setDebugLogFile(filePath) {
2732
+ _debugLogFile = filePath;
689
2733
  }
690
2734
  }
691
2735