@agile-vibe-coding/avc 0.1.0 → 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 (290) hide show
  1. package/README.md +2 -0
  2. package/cli/agent-loader.js +21 -0
  3. package/cli/agents/agent-selector.md +129 -0
  4. package/cli/agents/architecture-recommender.md +418 -0
  5. package/cli/agents/database-deep-dive.md +470 -0
  6. package/cli/agents/database-recommender.md +634 -0
  7. package/cli/agents/doc-distributor.md +176 -0
  8. package/cli/agents/documentation-updater.md +203 -0
  9. package/cli/agents/epic-story-decomposer.md +280 -0
  10. package/cli/agents/feature-context-generator.md +91 -0
  11. package/cli/agents/gap-checker-epic.md +52 -0
  12. package/cli/agents/impact-checker-story.md +51 -0
  13. package/cli/agents/migration-guide-generator.md +305 -0
  14. package/cli/agents/mission-scope-generator.md +79 -0
  15. package/cli/agents/mission-scope-validator.md +112 -0
  16. package/cli/agents/project-context-extractor.md +107 -0
  17. package/cli/agents/project-documentation-creator.json +226 -0
  18. package/cli/agents/project-documentation-creator.md +595 -0
  19. package/cli/agents/question-prefiller.md +269 -0
  20. package/cli/agents/refiner-epic.md +39 -0
  21. package/cli/agents/refiner-story.md +42 -0
  22. package/cli/agents/solver-epic-api.json +15 -0
  23. package/cli/agents/solver-epic-api.md +39 -0
  24. package/cli/agents/solver-epic-backend.json +15 -0
  25. package/cli/agents/solver-epic-backend.md +39 -0
  26. package/cli/agents/solver-epic-cloud.json +15 -0
  27. package/cli/agents/solver-epic-cloud.md +39 -0
  28. package/cli/agents/solver-epic-data.json +15 -0
  29. package/cli/agents/solver-epic-data.md +39 -0
  30. package/cli/agents/solver-epic-database.json +15 -0
  31. package/cli/agents/solver-epic-database.md +39 -0
  32. package/cli/agents/solver-epic-developer.json +15 -0
  33. package/cli/agents/solver-epic-developer.md +39 -0
  34. package/cli/agents/solver-epic-devops.json +15 -0
  35. package/cli/agents/solver-epic-devops.md +39 -0
  36. package/cli/agents/solver-epic-frontend.json +15 -0
  37. package/cli/agents/solver-epic-frontend.md +39 -0
  38. package/cli/agents/solver-epic-mobile.json +15 -0
  39. package/cli/agents/solver-epic-mobile.md +39 -0
  40. package/cli/agents/solver-epic-qa.json +15 -0
  41. package/cli/agents/solver-epic-qa.md +39 -0
  42. package/cli/agents/solver-epic-security.json +15 -0
  43. package/cli/agents/solver-epic-security.md +39 -0
  44. package/cli/agents/solver-epic-solution-architect.json +15 -0
  45. package/cli/agents/solver-epic-solution-architect.md +39 -0
  46. package/cli/agents/solver-epic-test-architect.json +15 -0
  47. package/cli/agents/solver-epic-test-architect.md +39 -0
  48. package/cli/agents/solver-epic-ui.json +15 -0
  49. package/cli/agents/solver-epic-ui.md +39 -0
  50. package/cli/agents/solver-epic-ux.json +15 -0
  51. package/cli/agents/solver-epic-ux.md +39 -0
  52. package/cli/agents/solver-story-api.json +15 -0
  53. package/cli/agents/solver-story-api.md +39 -0
  54. package/cli/agents/solver-story-backend.json +15 -0
  55. package/cli/agents/solver-story-backend.md +39 -0
  56. package/cli/agents/solver-story-cloud.json +15 -0
  57. package/cli/agents/solver-story-cloud.md +39 -0
  58. package/cli/agents/solver-story-data.json +15 -0
  59. package/cli/agents/solver-story-data.md +39 -0
  60. package/cli/agents/solver-story-database.json +15 -0
  61. package/cli/agents/solver-story-database.md +39 -0
  62. package/cli/agents/solver-story-developer.json +15 -0
  63. package/cli/agents/solver-story-developer.md +39 -0
  64. package/cli/agents/solver-story-devops.json +15 -0
  65. package/cli/agents/solver-story-devops.md +39 -0
  66. package/cli/agents/solver-story-frontend.json +15 -0
  67. package/cli/agents/solver-story-frontend.md +39 -0
  68. package/cli/agents/solver-story-mobile.json +15 -0
  69. package/cli/agents/solver-story-mobile.md +39 -0
  70. package/cli/agents/solver-story-qa.json +15 -0
  71. package/cli/agents/solver-story-qa.md +39 -0
  72. package/cli/agents/solver-story-security.json +15 -0
  73. package/cli/agents/solver-story-security.md +39 -0
  74. package/cli/agents/solver-story-solution-architect.json +15 -0
  75. package/cli/agents/solver-story-solution-architect.md +39 -0
  76. package/cli/agents/solver-story-test-architect.json +15 -0
  77. package/cli/agents/solver-story-test-architect.md +39 -0
  78. package/cli/agents/solver-story-ui.json +15 -0
  79. package/cli/agents/solver-story-ui.md +39 -0
  80. package/cli/agents/solver-story-ux.json +15 -0
  81. package/cli/agents/solver-story-ux.md +39 -0
  82. package/cli/agents/story-doc-enricher.md +133 -0
  83. package/cli/agents/suggestion-business-analyst.md +88 -0
  84. package/cli/agents/suggestion-deployment-architect.md +263 -0
  85. package/cli/agents/suggestion-product-manager.md +129 -0
  86. package/cli/agents/suggestion-security-specialist.md +156 -0
  87. package/cli/agents/suggestion-technical-architect.md +269 -0
  88. package/cli/agents/suggestion-ux-researcher.md +93 -0
  89. package/cli/agents/task-subtask-decomposer.md +188 -0
  90. package/cli/agents/validator-documentation.json +152 -0
  91. package/cli/agents/validator-documentation.md +453 -0
  92. package/cli/agents/validator-epic-api.json +93 -0
  93. package/cli/agents/validator-epic-api.md +137 -0
  94. package/cli/agents/validator-epic-backend.json +93 -0
  95. package/cli/agents/validator-epic-backend.md +130 -0
  96. package/cli/agents/validator-epic-cloud.json +93 -0
  97. package/cli/agents/validator-epic-cloud.md +137 -0
  98. package/cli/agents/validator-epic-data.json +93 -0
  99. package/cli/agents/validator-epic-data.md +130 -0
  100. package/cli/agents/validator-epic-database.json +93 -0
  101. package/cli/agents/validator-epic-database.md +137 -0
  102. package/cli/agents/validator-epic-developer.json +74 -0
  103. package/cli/agents/validator-epic-developer.md +153 -0
  104. package/cli/agents/validator-epic-devops.json +74 -0
  105. package/cli/agents/validator-epic-devops.md +153 -0
  106. package/cli/agents/validator-epic-frontend.json +74 -0
  107. package/cli/agents/validator-epic-frontend.md +153 -0
  108. package/cli/agents/validator-epic-mobile.json +93 -0
  109. package/cli/agents/validator-epic-mobile.md +130 -0
  110. package/cli/agents/validator-epic-qa.json +93 -0
  111. package/cli/agents/validator-epic-qa.md +130 -0
  112. package/cli/agents/validator-epic-security.json +74 -0
  113. package/cli/agents/validator-epic-security.md +154 -0
  114. package/cli/agents/validator-epic-solution-architect.json +74 -0
  115. package/cli/agents/validator-epic-solution-architect.md +156 -0
  116. package/cli/agents/validator-epic-test-architect.json +93 -0
  117. package/cli/agents/validator-epic-test-architect.md +130 -0
  118. package/cli/agents/validator-epic-ui.json +93 -0
  119. package/cli/agents/validator-epic-ui.md +130 -0
  120. package/cli/agents/validator-epic-ux.json +93 -0
  121. package/cli/agents/validator-epic-ux.md +130 -0
  122. package/cli/agents/validator-selector.md +211 -0
  123. package/cli/agents/validator-story-api.json +104 -0
  124. package/cli/agents/validator-story-api.md +152 -0
  125. package/cli/agents/validator-story-backend.json +104 -0
  126. package/cli/agents/validator-story-backend.md +152 -0
  127. package/cli/agents/validator-story-cloud.json +104 -0
  128. package/cli/agents/validator-story-cloud.md +152 -0
  129. package/cli/agents/validator-story-data.json +104 -0
  130. package/cli/agents/validator-story-data.md +152 -0
  131. package/cli/agents/validator-story-database.json +104 -0
  132. package/cli/agents/validator-story-database.md +152 -0
  133. package/cli/agents/validator-story-developer.json +104 -0
  134. package/cli/agents/validator-story-developer.md +152 -0
  135. package/cli/agents/validator-story-devops.json +104 -0
  136. package/cli/agents/validator-story-devops.md +152 -0
  137. package/cli/agents/validator-story-frontend.json +104 -0
  138. package/cli/agents/validator-story-frontend.md +152 -0
  139. package/cli/agents/validator-story-mobile.json +104 -0
  140. package/cli/agents/validator-story-mobile.md +152 -0
  141. package/cli/agents/validator-story-qa.json +104 -0
  142. package/cli/agents/validator-story-qa.md +152 -0
  143. package/cli/agents/validator-story-security.json +104 -0
  144. package/cli/agents/validator-story-security.md +152 -0
  145. package/cli/agents/validator-story-solution-architect.json +104 -0
  146. package/cli/agents/validator-story-solution-architect.md +152 -0
  147. package/cli/agents/validator-story-test-architect.json +104 -0
  148. package/cli/agents/validator-story-test-architect.md +152 -0
  149. package/cli/agents/validator-story-ui.json +104 -0
  150. package/cli/agents/validator-story-ui.md +152 -0
  151. package/cli/agents/validator-story-ux.json +104 -0
  152. package/cli/agents/validator-story-ux.md +152 -0
  153. package/cli/ansi-colors.js +21 -0
  154. package/cli/build-docs.js +298 -0
  155. package/cli/ceremony-history.js +369 -0
  156. package/cli/command-logger.js +245 -0
  157. package/cli/components/static-output.js +63 -0
  158. package/cli/console-output-manager.js +94 -0
  159. package/cli/docs-sync.js +306 -0
  160. package/cli/epic-story-validator.js +1174 -0
  161. package/cli/evaluation-prompts.js +1008 -0
  162. package/cli/execution-context.js +195 -0
  163. package/cli/generate-summary-table.js +340 -0
  164. package/cli/index.js +3 -25
  165. package/cli/init-model-config.js +697 -0
  166. package/cli/init.js +1765 -100
  167. package/cli/kanban-server-manager.js +228 -0
  168. package/cli/llm-claude.js +109 -0
  169. package/cli/llm-gemini.js +115 -0
  170. package/cli/llm-mock.js +233 -0
  171. package/cli/llm-openai.js +233 -0
  172. package/cli/llm-provider.js +300 -0
  173. package/cli/llm-token-limits.js +102 -0
  174. package/cli/llm-verifier.js +454 -0
  175. package/cli/logger.js +32 -5
  176. package/cli/message-constants.js +58 -0
  177. package/cli/message-manager.js +334 -0
  178. package/cli/message-types.js +96 -0
  179. package/cli/messaging-api.js +297 -0
  180. package/cli/model-pricing.js +169 -0
  181. package/cli/model-query-engine.js +468 -0
  182. package/cli/model-recommendation-analyzer.js +495 -0
  183. package/cli/model-selector.js +269 -0
  184. package/cli/output-buffer.js +107 -0
  185. package/cli/process-manager.js +332 -0
  186. package/cli/repl-ink.js +5840 -504
  187. package/cli/repl-old.js +4 -4
  188. package/cli/seed-processor.js +792 -0
  189. package/cli/sprint-planning-processor.js +1813 -0
  190. package/cli/template-processor.js +2306 -108
  191. package/cli/templates/project.md +25 -8
  192. package/cli/templates/vitepress-config.mts.template +34 -0
  193. package/cli/token-tracker.js +520 -0
  194. package/cli/tools/generate-story-validators.js +317 -0
  195. package/cli/tools/generate-validators.js +669 -0
  196. package/cli/update-checker.js +19 -17
  197. package/cli/update-notifier.js +4 -4
  198. package/cli/validation-router.js +605 -0
  199. package/cli/verification-tracker.js +563 -0
  200. package/kanban/README.md +386 -0
  201. package/kanban/client/README.md +205 -0
  202. package/kanban/client/components.json +20 -0
  203. package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
  204. package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
  205. package/kanban/client/dist/index.html +16 -0
  206. package/kanban/client/dist/vite.svg +1 -0
  207. package/kanban/client/index.html +15 -0
  208. package/kanban/client/package-lock.json +9442 -0
  209. package/kanban/client/package.json +44 -0
  210. package/kanban/client/postcss.config.js +6 -0
  211. package/kanban/client/public/vite.svg +1 -0
  212. package/kanban/client/src/App.jsx +622 -0
  213. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  214. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
  215. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
  216. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
  217. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  218. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
  219. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
  220. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  221. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
  222. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  223. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  224. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  225. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
  226. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
  227. package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
  228. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  229. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  230. package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
  231. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  232. package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
  233. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  234. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
  235. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  236. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  237. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  238. package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
  239. package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
  240. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
  241. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
  242. package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
  243. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  244. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  245. package/kanban/client/src/components/stats/CostModal.jsx +353 -0
  246. package/kanban/client/src/components/ui/badge.jsx +27 -0
  247. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  248. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  249. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  250. package/kanban/client/src/hooks/useGrouping.js +118 -0
  251. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  252. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  253. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  254. package/kanban/client/src/lib/api.js +401 -0
  255. package/kanban/client/src/lib/status-grouping.js +144 -0
  256. package/kanban/client/src/lib/utils.js +11 -0
  257. package/kanban/client/src/main.jsx +10 -0
  258. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  259. package/kanban/client/src/store/ceremonyStore.js +172 -0
  260. package/kanban/client/src/store/filterStore.js +201 -0
  261. package/kanban/client/src/store/kanbanStore.js +115 -0
  262. package/kanban/client/src/store/processStore.js +65 -0
  263. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  264. package/kanban/client/src/styles/globals.css +59 -0
  265. package/kanban/client/tailwind.config.js +77 -0
  266. package/kanban/client/vite.config.js +28 -0
  267. package/kanban/client/vitest.config.js +28 -0
  268. package/kanban/dev-start.sh +47 -0
  269. package/kanban/package.json +12 -0
  270. package/kanban/server/index.js +516 -0
  271. package/kanban/server/routes/ceremony.js +305 -0
  272. package/kanban/server/routes/costs.js +157 -0
  273. package/kanban/server/routes/processes.js +50 -0
  274. package/kanban/server/routes/settings.js +303 -0
  275. package/kanban/server/routes/websocket.js +276 -0
  276. package/kanban/server/routes/work-items.js +347 -0
  277. package/kanban/server/services/CeremonyService.js +1190 -0
  278. package/kanban/server/services/FileSystemScanner.js +95 -0
  279. package/kanban/server/services/FileWatcher.js +144 -0
  280. package/kanban/server/services/HierarchyBuilder.js +196 -0
  281. package/kanban/server/services/ProcessRegistry.js +122 -0
  282. package/kanban/server/services/WorkItemReader.js +123 -0
  283. package/kanban/server/services/WorkItemRefineService.js +510 -0
  284. package/kanban/server/start.js +49 -0
  285. package/kanban/server/utils/kanban-logger.js +132 -0
  286. package/kanban/server/utils/markdown.js +91 -0
  287. package/kanban/server/utils/status-grouping.js +107 -0
  288. package/kanban/server/workers/sponsor-call-worker.js +84 -0
  289. package/kanban/server/workers/sprint-planning-worker.js +130 -0
  290. package/package.json +34 -7
@@ -2,12 +2,39 @@ import dotenv from 'dotenv';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import readline from 'readline';
5
- import Anthropic from '@anthropic-ai/sdk';
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,32 +47,333 @@ const __dirname = path.dirname(__filename);
20
47
  * 6. Write to .avc/project/doc.md
21
48
  */
22
49
  class TemplateProcessor {
23
- constructor(progressPath = null) {
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;
67
+ this.nonInteractive = nonInteractive;
68
+
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);
77
+ this._providerName = provider;
78
+ this._modelName = model;
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
+ }
192
+ }
193
+
194
+ /**
195
+ * Read ceremony-specific configuration from avc.json
196
+ */
197
+ readCeremonyConfig(ceremonyName) {
198
+ try {
199
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
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
+ };
210
+ }
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
+ };
218
+ } catch (error) {
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
+ };
266
+ }
267
+
268
+ // Fall back to ceremony-level validation config
269
+ return {
270
+ provider: this._validationProvider,
271
+ model: this._validationModel
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Read defaults from avc.json questionnaire configuration
277
+ */
278
+ readDefaults() {
279
+ try {
280
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
281
+ return config.settings?.questionnaire?.defaults || {};
282
+ } catch (error) {
283
+ return {};
284
+ }
285
+ }
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
+ }
32
317
 
33
- // Read model configuration from avc.json
34
- this.model = this.readModelConfig();
35
- this.apiKey = process.env.ANTHROPIC_API_KEY;
36
- this.claudeClient = null;
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
+
335
+ /**
336
+ * Load agent instructions from markdown file
337
+ * @param {string} agentFileName - Filename in src/cli/agents/
338
+ * @returns {string|null} - Agent instructions content or null if not found
339
+ */
340
+ loadAgentInstructions(agentFileName) {
341
+ try {
342
+ const agentPath = path.join(__dirname, 'agents', agentFileName);
343
+ if (!fs.existsSync(agentPath)) {
344
+ sendWarning(`Agent instruction file not found: ${agentFileName}`);
345
+ return null;
346
+ }
347
+ return fs.readFileSync(agentPath, 'utf8');
348
+ } catch (error) {
349
+ sendWarning(`Could not load agent instructions: ${error.message}`);
350
+ return null;
351
+ }
37
352
  }
38
353
 
39
354
  /**
40
- * Read model configuration from avc.json
355
+ * Get agent instructions for a specific ceremony stage
356
+ * @param {string} stage - Ceremony stage (e.g., 'enhancement', 'suggestion', 'validation')
357
+ * @returns {string|null} - Agent instructions or null if not configured/found
41
358
  */
42
- readModelConfig() {
359
+ getAgentForStage(stage) {
43
360
  try {
44
361
  const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
45
- return config.settings?.model || 'claude-sonnet-4-5-20250929';
362
+ const ceremony = config.settings?.ceremonies?.[0];
363
+
364
+ if (!ceremony?.agents || ceremony.agents.length === 0) {
365
+ return null;
366
+ }
367
+
368
+ const agent = ceremony.agents.find(a => a.stage === stage);
369
+ if (!agent) {
370
+ return null;
371
+ }
372
+
373
+ return this.loadAgentInstructions(agent.instruction);
46
374
  } catch (error) {
47
- console.warn('⚠️ Could not read model config, using default');
48
- return 'claude-sonnet-4-5-20250929';
375
+ sendWarning(`Could not get agent for stage ${stage}: ${error.message}`);
376
+ return null;
49
377
  }
50
378
  }
51
379
 
@@ -99,7 +427,9 @@ class TemplateProcessor {
99
427
 
100
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."',
101
429
 
102
- '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"',
103
433
 
104
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"'
105
435
  };
@@ -131,11 +461,11 @@ class TemplateProcessor {
131
461
  async promptSingular(name, guidance) {
132
462
  const rl = this.createInterface();
133
463
 
134
- console.log(`\n📝 ${name}`);
464
+ sendSectionHeader(name);
135
465
  if (guidance) {
136
- console.log(` ${guidance}`);
466
+ console.log(`${guidance}`);
137
467
  }
138
- 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');
139
469
 
140
470
  const lines = [];
141
471
  let emptyLineCount = 0;
@@ -180,11 +510,11 @@ class TemplateProcessor {
180
510
  async promptPlural(name, guidance) {
181
511
  const rl = this.createInterface();
182
512
 
183
- console.log(`\n📝 ${name}`);
513
+ sendSectionHeader(name);
184
514
  if (guidance) {
185
- console.log(` ${guidance}`);
515
+ console.log(`${guidance}`);
186
516
  }
187
- 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');
188
518
 
189
519
  const items = [];
190
520
  let itemNumber = 1;
@@ -219,18 +549,205 @@ class TemplateProcessor {
219
549
  }
220
550
 
221
551
  /**
222
- * Initialize Claude client
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
+
568
+ /**
569
+ * Initialize LLM provider
570
+ */
571
+ async initializeLLMProvider() {
572
+ try {
573
+ // Initialize main provider
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
+
588
+ return this.llmProvider;
589
+ } catch (error) {
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
223
730
  */
224
- initializeClaudeClient() {
225
- if (!process.env.ANTHROPIC_API_KEY) {
226
- console.log('⚠️ ANTHROPIC_API_KEY not found - AI suggestions will be skipped');
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) {
227
742
  return null;
228
743
  }
229
744
 
230
- this.claudeClient = new Anthropic({
231
- apiKey: process.env.ANTHROPIC_API_KEY
232
- });
233
- return this.claudeClient;
745
+ try {
746
+ return loadAgent(agentFile, path.dirname(this.avcPath));
747
+ } catch {
748
+ sendWarning(`Agent file not found: ${agentFile}`);
749
+ return null;
750
+ }
234
751
  }
235
752
 
236
753
  /**
@@ -254,45 +771,53 @@ class TemplateProcessor {
254
771
  contextSection += '\n';
255
772
  }
256
773
 
257
- if (isPlural) {
258
- return `${contextSection}Suggest 3-5 appropriate values for "${displayName}".\n\nReturn only the suggestions, one per line, no numbering or bullets.`;
259
- } else {
260
- return `${contextSection}Suggest an appropriate value for "${displayName}".\n\nReturn only the suggestion text, concise (1-3 sentences).`;
261
- }
774
+ // Create simple user prompt with context
775
+ return `${contextSection}Please provide your response for "${displayName}".`;
262
776
  }
263
777
 
264
778
  /**
265
779
  * Parse Claude's response into structured format
266
780
  */
267
- parseClaudeResponse(response, isPlural) {
781
+ parseLLMResponse(response, isPlural) {
268
782
  if (isPlural) {
269
783
  return response.split('\n')
270
784
  .map(line => line.trim())
271
- .filter(line => line.length > 0 && !line.match(/^[0-9\-*.]+\s/));
785
+ .filter(line => line.length > 0)
786
+ .map(line => line.replace(/^[0-9\-*.]+\s+/, '')); // Remove list prefixes
272
787
  }
273
788
  return response.trim();
274
789
  }
275
790
 
276
791
  /**
277
- * Generate AI suggestions for a variable
792
+ * Generate AI suggestions for a variable using domain-specific agent
278
793
  */
279
794
  async generateSuggestions(variableName, isPlural, context) {
280
- if (!this.claudeClient && !this.initializeClaudeClient()) {
281
- return null;
282
- }
283
-
284
795
  try {
285
- const prompt = this.buildPrompt(variableName, isPlural, context);
796
+ // Get stage-specific provider for suggestions
797
+ const provider = await this.getProviderForStageInstance('suggestions');
286
798
 
287
- const response = await this.claudeClient.messages.create({
288
- model: this.model,
289
- max_tokens: isPlural ? 512 : 256,
290
- messages: [{ role: "user", content: prompt }]
291
- });
799
+ // Get domain-specific agent for this variable
800
+ const agentInstructions = this.getAgentForVariable(variableName);
292
801
 
293
- return this.parseClaudeResponse(response.content[0].text, isPlural);
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
+ }
294
819
  } catch (error) {
295
- console.warn(`⚠️ Could not generate suggestions: ${error.message}`);
820
+ sendWarning(`Could not generate suggestions: ${error.message}`);
296
821
  return null;
297
822
  }
298
823
  }
@@ -304,23 +829,51 @@ class TemplateProcessor {
304
829
  async promptUser(variable, context) {
305
830
  let value;
306
831
 
307
- if (variable.isPlural) {
308
- value = await this.promptPlural(variable.displayName, variable.guidance);
832
+ // In non-interactive mode, skip readline prompts and use defaults/AI
833
+ if (this.nonInteractive) {
834
+ // No section header output — silent AI generation to avoid polluting terminal output
835
+ sendProgress(`Generating AI suggestion for ${variable.displayName}...`);
836
+ value = null; // Force AI generation
309
837
  } else {
310
- value = await this.promptSingular(variable.displayName, variable.guidance);
838
+ // Interactive mode - use readline prompts
839
+ if (variable.isPlural) {
840
+ value = await this.promptPlural(variable.displayName, variable.guidance);
841
+ } else {
842
+ value = await this.promptSingular(variable.displayName, variable.guidance);
843
+ }
311
844
  }
312
845
 
313
- // If user skipped, try to generate AI suggestions
846
+ // If user skipped (or non-interactive mode), try to use default or generate AI suggestions
314
847
  if (value === null) {
315
- console.log(' Generating AI suggestion...');
848
+ // Check if there's a default for this variable
849
+ const defaults = this.readDefaults();
850
+ const defaultValue = defaults[variable.name];
851
+
852
+ if (defaultValue) {
853
+ sendInfo('Using default from settings...');
854
+ value = variable.isPlural
855
+ ? (Array.isArray(defaultValue) ? defaultValue : [defaultValue])
856
+ : defaultValue;
857
+
858
+ sendSuccess('Default applied:');
859
+ if (Array.isArray(value)) {
860
+ value.forEach((item, idx) => console.log(`${idx + 1}. ${item}`));
861
+ } else {
862
+ console.log(`${value}`);
863
+ }
864
+ return { variable: variable.name, value, source: 'default', skipped: false };
865
+ }
866
+
867
+ // No default available, try AI suggestions
868
+ sendInfo('Generating AI suggestion...');
316
869
  value = await this.generateSuggestions(variable.name, variable.isPlural, context);
317
870
 
318
871
  if (value) {
319
- console.log('AI suggestion:');
872
+ sendSuccess('AI suggestion:');
320
873
  if (Array.isArray(value)) {
321
- value.forEach((item, idx) => console.log(` ${idx + 1}. ${item}`));
874
+ value.forEach((item, idx) => console.log(`${idx + 1}. ${item}`));
322
875
  } else {
323
- console.log(` ${value}`);
876
+ console.log(`${value}`);
324
877
  }
325
878
  return { variable: variable.name, value, source: 'ai', skipped: true };
326
879
  } else {
@@ -360,21 +913,123 @@ class TemplateProcessor {
360
913
  /**
361
914
  * Generate final document with LLM enhancement
362
915
  */
363
- async generateFinalDocument(templateWithValues) {
364
- if (!this.claudeClient && !this.initializeClaudeClient()) {
365
- // No API key - save template as-is
366
- return templateWithValues;
367
- }
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
+ });
923
+ try {
924
+ // Get stage-specific provider for documentation
925
+ const provider = await this.getProviderForStageInstance('documentation');
926
+
927
+ // Try to load agent instructions for enhancement stage
928
+ this.reportSubstep('Reading agent: project-documentation-creator.md');
929
+ const agentInstructions = this.getAgentForStage('enhancement');
930
+
931
+ if (agentInstructions) {
932
+ // Use agent instructions as system context
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
+ }
959
+
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
+ }
368
969
 
369
- console.log('\n🤖 Enhancing document with AI...');
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
+ }
370
1003
 
371
- try {
372
- const response = await this.claudeClient.messages.create({
373
- model: this.model,
374
- max_tokens: 4096,
375
- messages: [{
376
- role: "user",
377
- content: `You are creating a project definition document for an Agile Vibe Coding (AVC) project.
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;
1030
+ } else {
1031
+ // Fallback to legacy hardcoded prompt for backward compatibility
1032
+ const legacyPrompt = `You are creating a project definition document for an Agile Vibe Coding (AVC) project.
378
1033
 
379
1034
  Here is the project information with all variables filled in:
380
1035
 
@@ -386,13 +1041,32 @@ Please review and enhance this document to ensure:
386
1041
  3. Sections flow logically
387
1042
  4. Any incomplete sections are identified
388
1043
 
389
- Return the enhanced markdown document.`
390
- }]
391
- });
392
-
393
- return response.content[0].text;
1044
+ Return the enhanced markdown document.`;
1045
+
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 });
1066
+ return enhanced;
1067
+ }
394
1068
  } catch (error) {
395
- console.warn(`⚠️ Could not enhance document: ${error.message}`);
1069
+ debug('generateFinalDocument error - returning template as-is', { error: error.message, duration: `${Date.now() - t0}ms` });
396
1070
  return templateWithValues;
397
1071
  }
398
1072
  }
@@ -406,85 +1080,1609 @@ Return the enhanced markdown document.`
406
1080
  }
407
1081
  }
408
1082
 
1083
+ /**
1084
+ * Sync project documentation to VitePress documentation folder
1085
+ */
1086
+ syncToVitePress(content) {
1087
+ try {
1088
+ const docsDir = path.join(process.cwd(), '.avc/documentation');
1089
+ const indexPath = path.join(docsDir, 'index.md');
1090
+
1091
+ // Check if documentation folder exists
1092
+ if (!fs.existsSync(docsDir)) {
1093
+ sendInfo('VitePress documentation folder not found, skipping sync');
1094
+ return false;
1095
+ }
1096
+
1097
+ // Write to .avc/documentation/index.md
1098
+ fs.writeFileSync(indexPath, content, 'utf8');
1099
+ debug('Synced to .avc/documentation/index.md');
1100
+ return true;
1101
+ } catch (error) {
1102
+ sendWarning(`Could not sync to VitePress: ${error.message}`);
1103
+ return false;
1104
+ }
1105
+ }
1106
+
1107
+ /**
1108
+ * Build VitePress documentation site
1109
+ */
1110
+ async buildVitePress() {
1111
+ try {
1112
+ const docsDir = path.join(process.cwd(), '.avc/documentation');
1113
+ const packagePath = path.join(process.cwd(), 'package.json');
1114
+
1115
+ // Check if VitePress is configured
1116
+ if (!fs.existsSync(docsDir) || !fs.existsSync(packagePath)) {
1117
+ return false;
1118
+ }
1119
+
1120
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
1121
+ if (!packageJson.scripts?.['docs:build']) {
1122
+ return false;
1123
+ }
1124
+
1125
+ console.log('\n📚 Building VitePress documentation...');
1126
+
1127
+ // Import execSync for running build command
1128
+ const { execSync } = await import('child_process');
1129
+
1130
+ // Run VitePress build
1131
+ execSync('npm run docs:build', {
1132
+ cwd: process.cwd(),
1133
+ stdio: 'inherit'
1134
+ });
1135
+
1136
+ sendSuccess('VitePress build completed');
1137
+ return true;
1138
+ } catch (error) {
1139
+ sendWarning(`VitePress build failed: ${error.message}`);
1140
+ return false;
1141
+ }
1142
+ }
1143
+
409
1144
  /**
410
1145
  * Write document to file
411
1146
  */
412
1147
  async writeDocument(content) {
1148
+ const fileSize = Math.ceil(Buffer.byteLength(content, 'utf8') / 1024);
1149
+ debug('writeDocument called', { outputPath: this.outputPath, sizeKB: fileSize });
1150
+
413
1151
  // Create .avc/project/ directory
414
1152
  if (!fs.existsSync(this.outputDir)) {
415
1153
  fs.mkdirSync(this.outputDir, { recursive: true });
416
1154
  }
417
1155
 
418
1156
  // Write doc.md
1157
+ this.reportSubstep(`Writing doc.md (${fileSize} KB)`);
419
1158
  fs.writeFileSync(this.outputPath, content, 'utf8');
420
1159
 
421
- console.log(`\n✅ Project document generated!`);
422
- 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 });
1163
+
1164
+ // Sync to VitePress if configured (silent for sponsor-call)
1165
+ const synced = this.syncToVitePress(content);
1166
+
1167
+ // Optionally build VitePress (commented out by default to avoid slow builds during dev)
1168
+ // if (synced) {
1169
+ // await this.buildVitePress();
1170
+ // }
423
1171
  }
424
1172
 
425
1173
  /**
426
1174
  * Main workflow - process template and generate document
427
1175
  */
428
1176
  async processTemplate(initialProgress = null) {
429
- 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
+ }
430
1196
 
431
1197
  // 1. Read template
1198
+ debug('Reading template file', { templatePath: this.templatePath });
432
1199
  const templateContent = fs.readFileSync(this.templatePath, 'utf8');
1200
+ debug('Template loaded', { size: templateContent.length });
433
1201
 
434
1202
  // 2. Extract variables
1203
+ debug('Extracting variables from template');
435
1204
  const variables = this.extractVariables(templateContent);
1205
+ debug('Variables extracted', { count: variables.length, names: variables.map(v => v.name) });
436
1206
 
437
1207
  // 3. Initialize or restore progress
438
1208
  let collectedValues = {};
439
1209
  let answeredCount = 0;
440
1210
 
441
1211
  if (initialProgress && initialProgress.collectedValues) {
1212
+ debug('Restoring from initial progress', { answeredCount: Object.keys(initialProgress.collectedValues).length });
442
1213
  collectedValues = { ...initialProgress.collectedValues };
443
1214
  answeredCount = Object.keys(collectedValues).length;
444
- console.log(`Resuming with ${answeredCount}/${variables.length} questions already answered.\n`);
1215
+
1216
+ // Check if ALL answers are pre-filled (from REPL questionnaire)
1217
+ if (answeredCount === variables.length) {
1218
+ debug(`Using ${answeredCount} pre-filled answers from questionnaire`);
1219
+
1220
+ // Use pre-filled answers, but check defaults or AI for skipped (null) answers
1221
+ for (const variable of variables) {
1222
+ if (collectedValues[variable.name] === null) {
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
+ }
1240
+ }
1241
+ }
1242
+ } else {
1243
+ sendOutput(`Resuming with ${answeredCount}/${variables.length} questions already answered.\n`);
1244
+
1245
+ // Continue with normal interactive flow for remaining questions
1246
+ for (const variable of variables) {
1247
+ if (collectedValues[variable.name] === undefined) {
1248
+ const result = await this.promptUser(variable, collectedValues);
1249
+ collectedValues[result.variable] = result.value;
1250
+ answeredCount++;
1251
+
1252
+ // Save progress after each question
1253
+ if (this.progressPath) {
1254
+ const progress = {
1255
+ stage: 'questionnaire',
1256
+ totalQuestions: variables.length,
1257
+ answeredQuestions: answeredCount,
1258
+ collectedValues: collectedValues,
1259
+ lastUpdate: new Date().toISOString()
1260
+ };
1261
+ this.saveProgress(progress);
1262
+ }
1263
+ }
1264
+ }
1265
+ }
445
1266
  } else {
446
1267
  console.log(`Found ${variables.length} sections to complete.\n`);
447
- }
448
1268
 
449
- // 4. Collect values with context accumulation
450
- for (const variable of variables) {
451
- // Skip already answered questions when resuming
452
- if (collectedValues[variable.name] !== undefined) {
453
- console.log(`\n✓ ${variable.displayName}`);
454
- console.log(` Using previous answer: ${
455
- Array.isArray(collectedValues[variable.name])
456
- ? `${collectedValues[variable.name].length} items`
457
- : `"${collectedValues[variable.name].substring(0, 60)}${collectedValues[variable.name].length > 60 ? '...' : ''}"`
458
- }`);
459
- continue;
460
- }
461
-
462
- const result = await this.promptUser(variable, collectedValues);
463
- collectedValues[result.variable] = result.value;
464
- answeredCount++;
465
-
466
- // Save progress after each question
467
- if (this.progressPath) {
468
- const progress = {
469
- stage: 'questionnaire',
470
- totalQuestions: variables.length,
471
- answeredQuestions: answeredCount,
472
- collectedValues: collectedValues,
473
- lastUpdate: new Date().toISOString()
474
- };
475
- this.saveProgress(progress);
1269
+ // 4. Collect values with context accumulation
1270
+ for (const variable of variables) {
1271
+ const result = await this.promptUser(variable, collectedValues);
1272
+ collectedValues[result.variable] = result.value;
1273
+ answeredCount++;
1274
+
1275
+ // Save progress after each question
1276
+ if (this.progressPath) {
1277
+ const progress = {
1278
+ stage: 'questionnaire',
1279
+ totalQuestions: variables.length,
1280
+ answeredQuestions: answeredCount,
1281
+ collectedValues: collectedValues,
1282
+ lastUpdate: new Date().toISOString()
1283
+ };
1284
+ this.saveProgress(progress);
1285
+ }
476
1286
  }
477
1287
  }
478
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
+
479
1297
  // 5. Replace variables in template
480
- 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');
481
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');
482
1305
 
483
1306
  // 6. Enhance document with LLM
484
- 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
+ }
485
1315
 
486
- // 7. Write to file
1316
+ // 8. Write documentation to file
487
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;
488
2686
  }
489
2687
  }
490
2688