@agile-vibe-coding/avc 0.1.1 → 0.2.3

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