@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
package/cli/init.js CHANGED
@@ -1,10 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import dotenv from 'dotenv';
3
4
  import fs from 'fs';
4
5
  import path from 'path';
5
6
  import { fileURLToPath } from 'url';
6
7
  import { execSync } from 'child_process';
7
8
  import { TemplateProcessor } from './template-processor.js';
9
+ import { ModelConfigurator } from './init-model-config.js';
10
+ import { MESSAGES, getCeremonyHeader } from './message-constants.js';
11
+ import { sendError, sendWarning, sendSuccess, sendInfo, sendOutput, sendIndented, sendSectionHeader } from './messaging-api.js';
12
+ import { boldCyan, yellow, green, cyan } from './ansi-colors.js';
13
+
14
+ /**
15
+ * Write a structured entry to the active command log file only.
16
+ * Uses [DEBUG] prefix so ConsoleOutputManager routes to file, never terminal.
17
+ */
18
+ function fileLog(level, message, data = null) {
19
+ const ts = new Date().toISOString();
20
+ if (data !== null) {
21
+ console.log(`[DEBUG] [${level}] [${ts}] ${message}`, JSON.stringify(data, null, 2));
22
+ } else {
23
+ console.log(`[DEBUG] [${level}] [${ts}] ${message}`);
24
+ }
25
+ }
8
26
 
9
27
  const __filename = fileURLToPath(import.meta.url);
10
28
  const __dirname = path.dirname(__filename);
@@ -19,11 +37,25 @@ const __dirname = path.dirname(__filename);
19
37
  */
20
38
 
21
39
  class ProjectInitiator {
22
- constructor() {
23
- this.projectRoot = process.cwd();
40
+ constructor(projectRoot = null) {
41
+ this.projectRoot = projectRoot || process.cwd();
24
42
  this.avcDir = path.join(this.projectRoot, '.avc');
43
+ this.srcDir = path.join(this.projectRoot, 'src');
44
+ this.worktreesDir = path.join(this.avcDir, 'worktrees');
25
45
  this.avcConfigPath = path.join(this.avcDir, 'avc.json');
26
- this.progressPath = path.join(this.avcDir, 'init-progress.json');
46
+ // Progress files are ceremony-specific
47
+ this.initProgressPath = path.join(this.avcDir, 'init-progress.json');
48
+ this.sponsorCallProgressPath = path.join(this.avcDir, 'sponsor-call-progress.json');
49
+
50
+ // Template processor for token usage tracking
51
+ this._lastTemplateProcessor = null;
52
+
53
+ // Load environment variables from project .env file
54
+ // Use override: true to reload even if already set (user may have edited .env)
55
+ dotenv.config({
56
+ path: path.join(this.projectRoot, '.env'),
57
+ override: true
58
+ });
27
59
  }
28
60
 
29
61
  /**
@@ -33,6 +65,47 @@ class ProjectInitiator {
33
65
  return path.basename(this.projectRoot);
34
66
  }
35
67
 
68
+ /**
69
+ * Get the current AVC package version
70
+ */
71
+ getAvcVersion() {
72
+ try {
73
+ const packagePath = path.join(__dirname, '../package.json');
74
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
75
+ return packageJson.version;
76
+ } catch (error) {
77
+ return 'unknown';
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Deep merge objects - adds new keys, preserves existing values
83
+ * @param {Object} target - The target object (user's config)
84
+ * @param {Object} source - The source object (default config)
85
+ * @returns {Object} Merged object
86
+ */
87
+ deepMerge(target, source) {
88
+ const result = { ...target };
89
+
90
+ for (const key in source) {
91
+ if (source.hasOwnProperty(key)) {
92
+ if (key in result) {
93
+ // Key exists in target
94
+ if (typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key] !== null) {
95
+ // Recursively merge objects
96
+ result[key] = this.deepMerge(result[key], source[key]);
97
+ }
98
+ // else: Keep existing value (don't overwrite)
99
+ } else {
100
+ // New key - add it
101
+ result[key] = source[key];
102
+ }
103
+ }
104
+ }
105
+
106
+ return result;
107
+ }
108
+
36
109
  /**
37
110
  * Check if .avc folder exists
38
111
  */
@@ -53,41 +126,454 @@ class ProjectInitiator {
53
126
  createAvcFolder() {
54
127
  if (!this.hasAvcFolder()) {
55
128
  fs.mkdirSync(this.avcDir, { recursive: true });
56
- console.log('✓ Created .avc/ folder');
57
129
  return true;
58
130
  }
59
- console.log('✓ .avc/ folder already exists');
60
131
  return false;
61
132
  }
62
133
 
63
134
  /**
64
- * Create avc.json with default settings
135
+ * Check if src folder exists
136
+ */
137
+ hasSrcFolder() {
138
+ return fs.existsSync(this.srcDir);
139
+ }
140
+
141
+ /**
142
+ * Create src folder for AVC-managed code
143
+ */
144
+ createSrcFolder() {
145
+ if (!this.hasSrcFolder()) {
146
+ fs.mkdirSync(this.srcDir, { recursive: true });
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+
152
+ /**
153
+ * Check if worktrees folder exists
154
+ */
155
+ hasWorktreesFolder() {
156
+ return fs.existsSync(this.worktreesDir);
157
+ }
158
+
159
+ /**
160
+ * Create worktrees folder for git worktree management
161
+ */
162
+ createWorktreesFolder() {
163
+ if (!this.hasWorktreesFolder()) {
164
+ fs.mkdirSync(this.worktreesDir, { recursive: true });
165
+ return true;
166
+ }
167
+ return false;
168
+ }
169
+
170
+ /**
171
+ * Create or update avc.json with default settings
172
+ * Merges new attributes from version updates while preserving existing values
65
173
  */
66
174
  createAvcConfig() {
67
- if (!this.hasAvcConfig()) {
68
- const defaultConfig = {
69
- version: '1.0.0',
70
- projectName: this.getProjectName(),
71
- framework: 'avc',
72
- created: new Date().toISOString(),
73
- settings: {
74
- contextScopes: ['epic', 'story', 'task', 'subtask'],
75
- workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
76
- agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
77
- model: 'claude-sonnet-4-5-20250929'
175
+ const defaultConfig = {
176
+ version: '1.0.0',
177
+ avcVersion: this.getAvcVersion(),
178
+ projectName: this.getProjectName(),
179
+ framework: 'avc',
180
+ created: new Date().toISOString(),
181
+ settings: {
182
+ workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
183
+ agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
184
+ documentation: {
185
+ port: 4173
186
+ },
187
+ ceremonies: [
188
+ {
189
+ name: 'sponsor-call',
190
+ provider: 'claude',
191
+ defaultModel: 'claude-sonnet-4-6',
192
+ stages: {
193
+ suggestions: {
194
+ provider: 'claude',
195
+ model: 'claude-sonnet-4-6'
196
+ },
197
+ documentation: {
198
+ provider: 'claude',
199
+ model: 'claude-sonnet-4-6'
200
+ },
201
+ 'architecture-recommendation': {
202
+ provider: 'claude',
203
+ model: 'claude-sonnet-4-6'
204
+ },
205
+ 'question-prefilling': {
206
+ provider: 'claude',
207
+ model: 'claude-sonnet-4-6'
208
+ }
209
+ },
210
+ agents: [
211
+ {
212
+ name: 'project-documentation-creator',
213
+ instruction: 'project-documentation-creator.md',
214
+ stage: 'documentation-generation'
215
+ },
216
+ {
217
+ name: 'validator-documentation',
218
+ instruction: 'validator-documentation.md',
219
+ stage: 'documentation-validation',
220
+ group: 'validators'
221
+ },
222
+ ],
223
+ validation: {
224
+ enabled: true,
225
+ maxIterations: 100,
226
+ acceptanceThreshold: 95,
227
+ skipOnCriticalIssues: false,
228
+ provider: 'claude',
229
+ model: 'claude-haiku-4-5-20251001',
230
+ documentation: {
231
+ provider: 'claude',
232
+ model: 'claude-haiku-4-5-20251001'
233
+ },
234
+ refinement: {
235
+ provider: 'claude',
236
+ model: 'claude-sonnet-4-6'
237
+ }
238
+ },
239
+ crossValidation: {
240
+ enabled: true,
241
+ maxIterations: 3
242
+ }
243
+ },
244
+ {
245
+ name: 'sprint-planning',
246
+ provider: 'claude',
247
+ defaultModel: 'claude-sonnet-4-6',
248
+ stages: {
249
+ decomposition: {
250
+ provider: 'claude',
251
+ model: 'claude-opus-4-6'
252
+ },
253
+ validation: {
254
+ provider: 'claude',
255
+ model: 'claude-sonnet-4-6',
256
+ useContextualSelection: true
257
+ },
258
+ 'doc-distribution': {
259
+ provider: 'claude',
260
+ model: 'claude-sonnet-4-6'
261
+ },
262
+ enrichment: {
263
+ provider: 'claude',
264
+ model: 'claude-sonnet-4-6'
265
+ },
266
+ solver: {
267
+ provider: 'claude',
268
+ model: 'claude-haiku-4-5-20251001',
269
+ maxIterations: 3,
270
+ acceptanceThreshold: 95
271
+ }
272
+ },
273
+ agents: [
274
+ {
275
+ name: 'epic-story-decomposer',
276
+ instruction: 'epic-story-decomposer.md',
277
+ stage: 'decomposition'
278
+ },
279
+ {
280
+ name: 'doc-distributor',
281
+ instruction: 'doc-distributor.md',
282
+ stage: 'doc-distribution'
283
+ },
284
+ {
285
+ name: 'project-context-extractor',
286
+ instruction: 'project-context-extractor.md',
287
+ stage: 'validation'
288
+ },
289
+ {
290
+ name: 'agent-selector',
291
+ instruction: 'agent-selector.md',
292
+ stage: 'validation'
293
+ },
294
+ {
295
+ name: 'story-doc-enricher',
296
+ instruction: 'story-doc-enricher.md',
297
+ stage: 'enrichment'
298
+ }
299
+ ]
300
+ },
301
+ {
302
+ name: 'seed',
303
+ provider: 'claude',
304
+ defaultModel: 'claude-sonnet-4-6',
305
+ stages: {
306
+ decomposition: {
307
+ provider: 'claude',
308
+ model: 'claude-opus-4-6'
309
+ },
310
+ 'doc-distribution': {
311
+ provider: 'claude',
312
+ model: 'claude-sonnet-4-6'
313
+ }
314
+ },
315
+ agents: [
316
+ {
317
+ name: 'task-subtask-decomposer',
318
+ instruction: 'task-subtask-decomposer.md',
319
+ stage: 'decomposition'
320
+ },
321
+ {
322
+ name: 'doc-distributor',
323
+ instruction: 'doc-distributor.md',
324
+ stage: 'doc-distribution'
325
+ },
326
+ {
327
+ name: 'feature-context-generator',
328
+ instruction: 'feature-context-generator.md',
329
+ stage: 'decomposition'
330
+ }
331
+ ]
332
+ }
333
+ ],
334
+ missionGenerator: {
335
+ validation: {
336
+ maxIterations: 3,
337
+ acceptanceThreshold: 95
338
+ }
339
+ },
340
+ costThresholds: {
341
+ 'sponsor-call': 2,
342
+ 'sprint-planning': 2,
343
+ 'seed': 2
344
+ },
345
+ questionnaire: {
346
+ defaults: {
347
+ MISSION_STATEMENT: null,
348
+ TARGET_USERS: null,
349
+ INITIAL_SCOPE: null,
350
+ DEPLOYMENT_TARGET: null,
351
+ TECHNICAL_CONSIDERATIONS: null,
352
+ SECURITY_AND_COMPLIANCE_REQUIREMENTS: null
353
+ }
354
+ },
355
+ models: {
356
+ // Anthropic Claude models (prices per 1M tokens in USD)
357
+ // Source: https://www.anthropic.com/pricing
358
+ 'claude-opus-4-6': {
359
+ provider: 'claude',
360
+ displayName: 'Claude Opus 4.6',
361
+ pricing: {
362
+ input: 5.00,
363
+ output: 25.00,
364
+ unit: 'million',
365
+ source: 'https://www.anthropic.com/pricing',
366
+ lastUpdated: '2026-02-24'
367
+ }
368
+ },
369
+ 'claude-sonnet-4-6': {
370
+ provider: 'claude',
371
+ displayName: 'Claude Sonnet 4.6',
372
+ pricing: {
373
+ input: 3.00,
374
+ output: 15.00,
375
+ unit: 'million',
376
+ source: 'https://www.anthropic.com/pricing',
377
+ lastUpdated: '2026-02-24'
378
+ }
379
+ },
380
+ 'claude-haiku-4-5-20251001': {
381
+ provider: 'claude',
382
+ displayName: 'Claude Haiku 4.5',
383
+ pricing: {
384
+ input: 1.00,
385
+ output: 5.00,
386
+ unit: 'million',
387
+ source: 'https://www.anthropic.com/pricing',
388
+ lastUpdated: '2026-02-24'
389
+ }
390
+ },
391
+
392
+ // Google Gemini models (prices per 1M tokens in USD)
393
+ // Source: https://ai.google.dev/pricing
394
+ 'gemini-3.1-pro-preview': {
395
+ provider: 'gemini',
396
+ displayName: 'Gemini 3.1 Pro Preview',
397
+ pricing: {
398
+ input: 2.00,
399
+ output: 12.00,
400
+ unit: 'million',
401
+ source: 'https://ai.google.dev/pricing',
402
+ lastUpdated: '2026-03-05'
403
+ }
404
+ },
405
+ 'gemini-3-flash-preview': {
406
+ provider: 'gemini',
407
+ displayName: 'Gemini 3 Flash Preview',
408
+ pricing: {
409
+ input: 0.50,
410
+ output: 3.00,
411
+ unit: 'million',
412
+ source: 'https://ai.google.dev/pricing',
413
+ lastUpdated: '2026-03-05'
414
+ }
415
+ },
416
+ 'gemini-2.5-pro': {
417
+ provider: 'gemini',
418
+ displayName: 'Gemini 2.5 Pro',
419
+ pricing: {
420
+ input: 1.25,
421
+ output: 10.00,
422
+ unit: 'million',
423
+ source: 'https://ai.google.dev/pricing',
424
+ lastUpdated: '2026-02-24'
425
+ }
426
+ },
427
+ 'gemini-2.5-flash': {
428
+ provider: 'gemini',
429
+ displayName: 'Gemini 2.5 Flash',
430
+ pricing: {
431
+ input: 0.30,
432
+ output: 2.50,
433
+ unit: 'million',
434
+ source: 'https://ai.google.dev/pricing',
435
+ lastUpdated: '2026-02-24'
436
+ }
437
+ },
438
+ 'gemini-2.5-flash-lite': {
439
+ provider: 'gemini',
440
+ displayName: 'Gemini 2.5 Flash-Lite',
441
+ pricing: {
442
+ input: 0.10,
443
+ output: 0.40,
444
+ unit: 'million',
445
+ source: 'https://ai.google.dev/pricing',
446
+ lastUpdated: '2026-02-24'
447
+ }
448
+ },
449
+
450
+ // OpenAI models (prices per 1M tokens in USD)
451
+ // Source: https://openai.com/api/pricing
452
+ 'gpt-5.2': {
453
+ provider: 'openai',
454
+ displayName: 'GPT-5.2',
455
+ pricing: {
456
+ input: 1.75,
457
+ output: 14.00,
458
+ unit: 'million',
459
+ source: 'https://openai.com/api/pricing',
460
+ lastUpdated: '2026-02-24'
461
+ }
462
+ },
463
+ 'gpt-5.1': {
464
+ provider: 'openai',
465
+ displayName: 'GPT-5.1',
466
+ pricing: {
467
+ input: 1.25,
468
+ output: 10.00,
469
+ unit: 'million',
470
+ source: 'https://openai.com/api/pricing',
471
+ lastUpdated: '2026-02-24'
472
+ }
473
+ },
474
+ 'gpt-5-mini': {
475
+ provider: 'openai',
476
+ displayName: 'GPT-5 mini',
477
+ pricing: {
478
+ input: 0.25,
479
+ output: 2.00,
480
+ unit: 'million',
481
+ source: 'https://openai.com/api/pricing',
482
+ lastUpdated: '2026-02-24'
483
+ }
484
+ },
485
+ 'o4-mini': {
486
+ provider: 'openai',
487
+ displayName: 'o4-mini',
488
+ pricing: {
489
+ input: 1.10,
490
+ output: 4.40,
491
+ unit: 'million',
492
+ source: 'https://openai.com/api/pricing',
493
+ lastUpdated: '2026-02-24'
494
+ }
495
+ },
496
+ 'o3': {
497
+ provider: 'openai',
498
+ displayName: 'o3',
499
+ pricing: {
500
+ input: 2.00,
501
+ output: 8.00,
502
+ unit: 'million',
503
+ source: 'https://openai.com/api/pricing',
504
+ lastUpdated: '2026-02-24'
505
+ }
506
+ },
507
+ 'o3-mini': {
508
+ provider: 'openai',
509
+ displayName: 'o3-mini',
510
+ pricing: {
511
+ input: 0.50,
512
+ output: 2.00,
513
+ unit: 'million',
514
+ source: 'https://openai.com/api/pricing',
515
+ lastUpdated: '2026-02-24'
516
+ }
517
+ },
518
+ 'gpt-5.2-codex': {
519
+ provider: 'openai',
520
+ displayName: 'GPT-5.2-Codex',
521
+ pricing: {
522
+ input: 1.75,
523
+ output: 14.00,
524
+ unit: 'million',
525
+ source: 'https://openai.com/api/pricing',
526
+ lastUpdated: '2026-02-24'
527
+ }
528
+ }
78
529
  }
79
- };
530
+ }
531
+ };
80
532
 
533
+ if (!this.hasAvcConfig()) {
534
+ // Create new config
81
535
  fs.writeFileSync(
82
536
  this.avcConfigPath,
83
537
  JSON.stringify(defaultConfig, null, 2),
84
538
  'utf8'
85
539
  );
86
- console.log('✓ Created .avc/avc.json configuration file');
87
540
  return true;
88
541
  }
89
- console.log('✓ .avc/avc.json already exists');
90
- return false;
542
+
543
+ // Config exists - check for merge
544
+ try {
545
+ const existingConfig = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
546
+
547
+ // Merge: add new keys, keep existing values
548
+ const mergedConfig = this.deepMerge(existingConfig, defaultConfig);
549
+
550
+ // Upgrade null cost thresholds to default (2 USD)
551
+ if (mergedConfig.settings?.costThresholds) {
552
+ for (const ceremony of ['sponsor-call', 'sprint-planning', 'seed']) {
553
+ if (mergedConfig.settings.costThresholds[ceremony] === null) {
554
+ mergedConfig.settings.costThresholds[ceremony] = 2;
555
+ }
556
+ }
557
+ }
558
+
559
+ // Update avcVersion to track CLI version
560
+ mergedConfig.avcVersion = this.getAvcVersion();
561
+ mergedConfig.updated = new Date().toISOString();
562
+
563
+ // Check if anything changed
564
+ const existingJson = JSON.stringify(existingConfig, null, 2);
565
+ const mergedJson = JSON.stringify(mergedConfig, null, 2);
566
+
567
+ if (existingJson !== mergedJson) {
568
+ fs.writeFileSync(this.avcConfigPath, mergedJson, 'utf8');
569
+ return true;
570
+ }
571
+
572
+ return false;
573
+ } catch (error) {
574
+ console.error(`Warning: Could not merge avc.json: ${error.message}`);
575
+ return false;
576
+ }
91
577
  }
92
578
 
93
579
  /**
@@ -104,22 +590,70 @@ class ProjectInitiator {
104
590
 
105
591
  /**
106
592
  * Create .env file for API keys
593
+ * If .env exists, check and add any missing API key variables
107
594
  */
108
595
  createEnvFile() {
109
596
  const envPath = path.join(this.projectRoot, '.env');
110
597
 
111
- if (!fs.existsSync(envPath)) {
112
- const envContent = `# Anthropic API Key for AI-powered Sponsor Call ceremony
113
- # Get your key at: https://console.anthropic.com/settings/keys
114
- ANTHROPIC_API_KEY=
598
+ // Define required API key variables with metadata
599
+ const requiredApiKeys = [
600
+ {
601
+ key: 'ANTHROPIC_API_KEY',
602
+ comment: 'Anthropic API Key for AI-powered Sponsor Call ceremony',
603
+ url: 'https://console.anthropic.com/settings/keys'
604
+ },
605
+ {
606
+ key: 'GEMINI_API_KEY',
607
+ comment: 'Google Gemini API Key (alternative LLM provider)',
608
+ url: 'https://aistudio.google.com/app/apikey'
609
+ },
610
+ {
611
+ key: 'OPENAI_API_KEY',
612
+ comment: 'OpenAI API Key (alternative LLM provider)',
613
+ url: 'https://platform.openai.com/api-keys'
614
+ }
615
+ ];
115
616
 
116
- # Add other API keys below as needed
117
- `;
617
+ if (!fs.existsSync(envPath)) {
618
+ // Create new .env file with all API keys
619
+ let envContent = '';
620
+ requiredApiKeys.forEach(({ key, comment, url }, index) => {
621
+ if (index > 0) envContent += '\n';
622
+ envContent += `# ${comment}\n`;
623
+ envContent += `# Get your key at: ${url}\n`;
624
+ envContent += `${key}=\n`;
625
+ });
118
626
  fs.writeFileSync(envPath, envContent, 'utf8');
119
- console.log('✓ Created .env file for API keys');
120
627
  return true;
121
628
  }
122
- console.log('✓ .env file already exists');
629
+
630
+ // .env exists - check for missing API keys
631
+ const existingContent = fs.readFileSync(envPath, 'utf8');
632
+ const missingKeys = [];
633
+
634
+ // Check which API keys are missing
635
+ requiredApiKeys.forEach(({ key }) => {
636
+ const keyPattern = new RegExp(`^${key}=`, 'm');
637
+ if (!keyPattern.test(existingContent)) {
638
+ missingKeys.push(key);
639
+ }
640
+ });
641
+
642
+ if (missingKeys.length > 0) {
643
+ // Add missing API keys to .env file
644
+ let appendContent = '\n';
645
+ requiredApiKeys.forEach(({ key, comment, url }) => {
646
+ if (missingKeys.includes(key)) {
647
+ appendContent += `\n# ${comment}\n`;
648
+ appendContent += `# Get your key at: ${url}\n`;
649
+ appendContent += `${key}=\n`;
650
+ }
651
+ });
652
+
653
+ fs.appendFileSync(envPath, appendContent, 'utf8');
654
+ return true;
655
+ }
656
+
123
657
  return false;
124
658
  }
125
659
 
@@ -138,34 +672,88 @@ ANTHROPIC_API_KEY=
138
672
  gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
139
673
  }
140
674
 
141
- // Check if .env is already in .gitignore
142
- if (gitignoreContent.includes('.env')) {
143
- console.log('.env already in .gitignore');
144
- return;
675
+ // Items to add to gitignore
676
+ const itemsToIgnore = [
677
+ { pattern: '.env', comment: 'Environment variables' },
678
+ { pattern: '.avc/documentation/.vitepress/dist', comment: 'VitePress build output' },
679
+ { pattern: '.avc/documentation/.vitepress/cache', comment: 'VitePress cache' },
680
+ { pattern: '.avc/logs', comment: 'Command execution logs' },
681
+ { pattern: '.avc/token-history.json', comment: 'Token usage tracking' },
682
+ { pattern: '.avc/ceremonies-history.json', comment: 'Ceremony execution history' }
683
+ ];
684
+
685
+ let newContent = gitignoreContent;
686
+ let addedItems = [];
687
+
688
+ for (const item of itemsToIgnore) {
689
+ if (!newContent.includes(item.pattern)) {
690
+ if (!newContent.endsWith('\n') && newContent.length > 0) {
691
+ newContent += '\n';
692
+ }
693
+ if (!newContent.includes(`# ${item.comment}`)) {
694
+ newContent += `\n# ${item.comment}\n`;
695
+ }
696
+ newContent += `${item.pattern}\n`;
697
+ addedItems.push(item.pattern);
698
+ }
699
+ }
700
+
701
+ if (addedItems.length > 0) {
702
+ fs.writeFileSync(gitignorePath, newContent, 'utf8');
145
703
  }
704
+ }
705
+
706
+ /**
707
+ * Create VitePress documentation structure (folders and config files)
708
+ * Note: VitePress is bundled with AVC, no need to modify user's package.json
709
+ */
710
+ createVitePressStructure() {
711
+ const docsDir = path.join(this.avcDir, 'documentation');
712
+ const vitepressDir = path.join(docsDir, '.vitepress');
713
+ const publicDir = path.join(docsDir, 'public');
146
714
 
147
- // Add .env to .gitignore
148
- const newContent = gitignoreContent
149
- ? `${gitignoreContent}\n# Environment variables\n.env\n`
150
- : '# Environment variables\n.env\n';
715
+ // Create directory structure
716
+ if (!fs.existsSync(vitepressDir)) {
717
+ fs.mkdirSync(vitepressDir, { recursive: true });
718
+ }
719
+
720
+ if (!fs.existsSync(publicDir)) {
721
+ fs.mkdirSync(publicDir, { recursive: true });
722
+ }
151
723
 
152
- fs.writeFileSync(gitignorePath, newContent, 'utf8');
153
- console.log('✓ Added .env to .gitignore');
724
+ // Create VitePress config
725
+ const configPath = path.join(vitepressDir, 'config.mts');
726
+ if (!fs.existsSync(configPath)) {
727
+ const templatePath = path.join(__dirname, 'templates/vitepress-config.mts.template');
728
+ let configContent = fs.readFileSync(templatePath, 'utf8');
729
+ configContent = configContent.replace('{{PROJECT_NAME}}', this.getProjectName());
730
+ fs.writeFileSync(configPath, configContent, 'utf8');
731
+ }
732
+
733
+ // Create initial index.md
734
+ const indexPath = path.join(docsDir, 'index.md');
735
+ if (!fs.existsSync(indexPath)) {
736
+ const indexContent = `# ${this.getProjectName()}
737
+
738
+ Documentation for this project will be generated automatically once the project is defined via the Sponsor Call ceremony. Use the **Start Project** button in the [Kanban board](http://localhost:4174) to get started.
739
+ `;
740
+ fs.writeFileSync(indexPath, indexContent, 'utf8');
741
+ }
154
742
  }
155
743
 
156
744
  /**
157
- * Check if there's an incomplete init in progress
745
+ * Check if there's an incomplete ceremony in progress
158
746
  */
159
- hasIncompleteInit() {
160
- return fs.existsSync(this.progressPath);
747
+ hasIncompleteProgress(progressPath) {
748
+ return fs.existsSync(progressPath);
161
749
  }
162
750
 
163
751
  /**
164
752
  * Read progress from file
165
753
  */
166
- readProgress() {
754
+ readProgress(progressPath) {
167
755
  try {
168
- const content = fs.readFileSync(this.progressPath, 'utf8');
756
+ const content = fs.readFileSync(progressPath, 'utf8');
169
757
  return JSON.parse(content);
170
758
  } catch (error) {
171
759
  return null;
@@ -175,29 +763,261 @@ ANTHROPIC_API_KEY=
175
763
  /**
176
764
  * Write progress to file
177
765
  */
178
- writeProgress(progress) {
766
+ writeProgress(progress, progressPath) {
179
767
  if (!fs.existsSync(this.avcDir)) {
180
768
  fs.mkdirSync(this.avcDir, { recursive: true });
181
769
  }
182
- fs.writeFileSync(this.progressPath, JSON.stringify(progress, null, 2), 'utf8');
770
+ fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf8');
183
771
  }
184
772
 
185
773
  /**
186
- * Clear progress file (init completed successfully)
774
+ * Clear progress file (ceremony completed successfully)
187
775
  */
188
- clearProgress() {
189
- if (fs.existsSync(this.progressPath)) {
190
- fs.unlinkSync(this.progressPath);
776
+ clearProgress(progressPath) {
777
+ if (fs.existsSync(progressPath)) {
778
+ fs.unlinkSync(progressPath);
779
+ }
780
+ }
781
+
782
+
783
+ /**
784
+ * Parse and simplify API error messages for better UX
785
+ * @param {string|object} error - Error from API call
786
+ * @returns {string} - Human-readable error message
787
+ */
788
+ parseApiError(error) {
789
+ let errorStr = typeof error === 'string' ? error : JSON.stringify(error);
790
+
791
+ // Try to parse as JSON to extract meaningful information
792
+ let errorObj = null;
793
+ try {
794
+ errorObj = typeof error === 'object' ? error : JSON.parse(error);
795
+ } catch (e) {
796
+ // Not JSON, use as-is
191
797
  }
798
+
799
+ // Check for common error patterns
800
+ if (errorStr.includes('quota') || errorStr.includes('RESOURCE_EXHAUSTED')) {
801
+ return 'API quota exceeded. You have reached your free tier limit.\n\n Please check your usage at the provider dashboard or upgrade your plan.';
802
+ }
803
+
804
+ if (errorStr.includes('rate limit') || errorStr.includes('429')) {
805
+ const retryMatch = errorStr.match(/retry.*?(\d+)\.?\d*s/i);
806
+ const retryTime = retryMatch ? ` Try again in ${Math.ceil(parseFloat(retryMatch[1]))} seconds.` : '';
807
+ return `Rate limit exceeded.${retryTime}\n\n Please wait before making more requests.`;
808
+ }
809
+
810
+ if (errorStr.includes('401') || errorStr.includes('authentication') || errorStr.includes('unauthorized')) {
811
+ return 'Invalid API key or authentication failed.\n\n Please verify your API key is correct.';
812
+ }
813
+
814
+ if (errorStr.includes('403') || errorStr.includes('forbidden')) {
815
+ return 'Access forbidden. Your API key may not have permission for this operation.\n\n Check your API key permissions or contact support.';
816
+ }
817
+
818
+ if (errorStr.includes('404') || errorStr.includes('not found')) {
819
+ return 'API endpoint not found. The model or API version may not be available.\n\n Check that you\'re using a valid model name.';
820
+ }
821
+
822
+ if (errorStr.includes('timeout') || errorStr.includes('ETIMEDOUT')) {
823
+ return 'Request timed out.\n\n Check your internet connection and try again.';
824
+ }
825
+
826
+ if (errorStr.includes('ENOTFOUND') || errorStr.includes('DNS')) {
827
+ return 'Network error: Could not reach API server.\n\n Check your internet connection.';
828
+ }
829
+
830
+ // Extract just the error message if it's an object with a message field
831
+ if (errorObj) {
832
+ if (errorObj.error?.message) {
833
+ // Take first line or first 150 characters of the message
834
+ const msg = errorObj.error.message.split('\n')[0];
835
+ return msg.length > 150 ? msg.substring(0, 150) + '...' : msg;
836
+ }
837
+ if (errorObj.message) {
838
+ const msg = errorObj.message.split('\n')[0];
839
+ return msg.length > 150 ? msg.substring(0, 150) + '...' : msg;
840
+ }
841
+ }
842
+
843
+ // Fallback: truncate the error if it's too long
844
+ if (errorStr.length > 200) {
845
+ return errorStr.substring(0, 200) + '...\n\n (Full error logged to console)';
846
+ }
847
+
848
+ return errorStr;
192
849
  }
193
850
 
851
+ /**
852
+ * Validate that the configured provider's API key is present and working
853
+ */
854
+ async validateProviderApiKey() {
855
+ // Import LLMProvider dynamically to avoid circular dependencies
856
+ const { LLMProvider } = await import('./llm-provider.js');
857
+
858
+ // Check if config file exists
859
+ if (!fs.existsSync(this.avcConfigPath)) {
860
+ return {
861
+ valid: false,
862
+ message: 'Configuration file not found at .avc/avc.json.\n Please run /init first to set up your project.'
863
+ };
864
+ }
865
+
866
+ // Read ceremony config
867
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
868
+ const ceremony = config.settings?.ceremonies?.[0];
869
+
870
+ if (!ceremony) {
871
+ return {
872
+ valid: false,
873
+ message: 'No ceremonies configured in .avc/avc.json.\n Please check your configuration.'
874
+ };
875
+ }
876
+
877
+ const mainProvider = ceremony.provider || 'claude';
878
+ const mainModel = ceremony.defaultModel || 'claude-sonnet-4-6';
879
+
880
+ // Check validation provider if validation is enabled
881
+ const validationEnabled = ceremony.validation?.enabled !== false;
882
+ const validationProvider = ceremony.validation?.provider || null;
883
+ const validationModel = ceremony.validation?.model || null;
884
+
885
+ const envVarMap = {
886
+ 'claude': 'ANTHROPIC_API_KEY',
887
+ 'gemini': 'GEMINI_API_KEY',
888
+ 'openai': 'OPENAI_API_KEY'
889
+ };
890
+
891
+ const urlMap = {
892
+ 'claude': 'https://console.anthropic.com/settings/keys',
893
+ 'gemini': 'https://aistudio.google.com/app/apikey',
894
+ 'openai': 'https://platform.openai.com/api-keys'
895
+ };
896
+
897
+ // Validate main provider
898
+ const mainEnvVar = envVarMap[mainProvider];
899
+ if (!mainEnvVar) {
900
+ return {
901
+ valid: false,
902
+ message: `Unknown provider "${mainProvider}".\n Supported providers: claude, gemini, openai`
903
+ };
904
+ }
905
+
906
+ if (!process.env[mainEnvVar]) {
907
+ return {
908
+ valid: false,
909
+ message: `${mainEnvVar} not found in .env file.\n\n Steps to fix:\n 1. Open .env file in the current directory\n 2. Add your API key: ${mainEnvVar}=your-key-here\n 3. Save the file and run /sponsor-call again\n\n Get your API key:\n • ${urlMap[mainProvider]}`
910
+ };
911
+ }
912
+
913
+ // Test the API key with a minimal call
914
+ let result;
915
+ try {
916
+ result = await LLMProvider.validate(mainProvider, mainModel);
917
+ } catch (error) {
918
+ const parsedError = this.parseApiError(error.message || error);
919
+ return {
920
+ valid: false,
921
+ message: `${mainEnvVar} validation failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[mainProvider]}`
922
+ };
923
+ }
924
+
925
+ if (!result.valid) {
926
+ const parsedError = this.parseApiError(result.error || 'Unknown error');
927
+ return {
928
+ valid: false,
929
+ message: `${mainEnvVar} is set but API call failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[mainProvider]}`
930
+ };
931
+ }
932
+
933
+ // Validate validation provider if enabled and different from main
934
+ if (validationEnabled && validationProvider && validationProvider !== mainProvider) {
935
+ const validationEnvVar = envVarMap[validationProvider];
936
+
937
+ if (!validationEnvVar) {
938
+ return {
939
+ valid: false,
940
+ message: `Unknown validation provider "${validationProvider}".\n Supported providers: claude, gemini, openai`
941
+ };
942
+ }
943
+
944
+ if (!process.env[validationEnvVar]) {
945
+ // Enhanced error message with 3 options
946
+ return {
947
+ valid: false,
948
+ validationProviderMissing: true,
949
+ ceremonyConfig: {
950
+ mainProvider,
951
+ mainModel,
952
+ validationProvider,
953
+ validationModel
954
+ },
955
+ message: `Validation Provider API Key Missing\n\nYour ceremony is configured to use:\n • Generation: ${mainProvider} (${mainModel}) ✓\n • Validation: ${validationProvider} (${validationModel}) ✗ (${validationEnvVar} not found)\n\nYou have 3 options:\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nOption 1: Add the missing API key\n\n 1. Get API key: ${urlMap[validationProvider]}\n 2. Add to .env file: ${validationEnvVar}=your-key-here\n 3. Run /sponsor-call again\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nOption 2: Disable validation (faster, lower quality)\n\n 1. Edit .avc/avc.json\n 2. Find "sponsor-call" ceremony config\n 3. Set:\n "validation": {\n "enabled": false\n }\n 4. Run /sponsor-call again\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nOption 3: Use same provider for validation (simpler setup)\n\n 1. Edit .avc/avc.json\n 2. Find "sponsor-call" ceremony config\n 3. Change:\n "validation": {\n "enabled": true,\n "provider": "${mainProvider}",\n "model": "${mainModel}"\n }\n 4. Run /sponsor-call again\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`
956
+ };
957
+ }
958
+
959
+ try {
960
+ result = await LLMProvider.validate(validationProvider, validationModel);
961
+ } catch (error) {
962
+ const parsedError = this.parseApiError(error.message || error);
963
+ return {
964
+ valid: false,
965
+ message: `${validationEnvVar} validation failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[validationProvider]}`
966
+ };
967
+ }
968
+
969
+ if (!result.valid) {
970
+ const parsedError = this.parseApiError(result.error || 'Unknown error');
971
+ return {
972
+ valid: false,
973
+ message: `${validationEnvVar} is set but API call failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[validationProvider]}`
974
+ };
975
+ }
976
+
977
+ }
978
+
979
+ return { valid: true };
980
+ }
194
981
 
195
982
  /**
196
983
  * Generate project document via Sponsor Call ceremony
197
984
  */
198
- async generateProjectDocument(progress = null) {
199
- const processor = new TemplateProcessor(this.progressPath);
200
- await processor.processTemplate(progress);
985
+ async generateProjectDocument(progress = null, progressPath = null, nonInteractive = false, progressCallback = null, options = {}) {
986
+ const processor = new TemplateProcessor('sponsor-call', progressPath || this.sponsorCallProgressPath, nonInteractive, options);
987
+
988
+ // Set before await so processor is reachable even if processTemplate throws
989
+ this._lastTemplateProcessor = processor;
990
+
991
+ if (progressCallback) {
992
+ processor.setProgressCallback(progressCallback);
993
+ }
994
+
995
+ return await processor.processTemplate(progress);
996
+ }
997
+
998
+ /**
999
+ * Get token usage from last template processor execution
1000
+ * @returns {Object|null} Token usage object or null
1001
+ */
1002
+ getLastTokenUsage() {
1003
+ if (this._lastTemplateProcessor) {
1004
+ return this._lastTemplateProcessor.getLastTokenUsage();
1005
+ }
1006
+ return null;
1007
+ }
1008
+
1009
+ /**
1010
+ * Read ceremony configuration from avc.json
1011
+ * @param {string} ceremonyName - Name of the ceremony
1012
+ * @returns {Object|null} Ceremony config or null
1013
+ */
1014
+ readCeremonyConfig(ceremonyName) {
1015
+ try {
1016
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
1017
+ return config.settings?.ceremonies?.find(c => c.name === ceremonyName);
1018
+ } catch (error) {
1019
+ return null;
1020
+ }
201
1021
  }
202
1022
 
203
1023
  /**
@@ -208,46 +1028,539 @@ ANTHROPIC_API_KEY=
208
1028
  }
209
1029
 
210
1030
  /**
211
- * Initialize the AVC project
1031
+ * Initialize the AVC project structure (no API keys required)
1032
+ * Creates .avc folder, avc.json config, .env file, and gitignore entry
212
1033
  */
213
1034
  async init() {
214
- console.log('\n🚀 AVC Project Initiator - Sponsor Call Ceremony\n');
215
- console.log(`Project directory: ${this.projectRoot}\n`);
1035
+ const startTime = Date.now();
1036
+ fileLog('INFO', 'init() started', { projectRoot: this.projectRoot });
216
1037
 
217
- let progress = null;
1038
+ if (this.isAvcProject()) {
1039
+ // Project already initialized
1040
+ fileLog('INFO', 'Project already initialized — skipping structure creation');
1041
+ sendOutput('Project already initialized.');
1042
+ sendOutput('');
1043
+ return;
1044
+ }
218
1045
 
219
- // Check for incomplete initialization
220
- if (this.hasIncompleteInit()) {
221
- progress = this.readProgress();
1046
+ fileLog('INFO', 'New project creating structure');
1047
+ fileLog('DEBUG', 'Creating components: .avc/, src/, worktrees/, avc.json, .env, .gitignore, VitePress');
222
1048
 
223
- if (progress && progress.stage !== 'completed') {
224
- console.log('⚠️ Found incomplete initialization from previous session');
225
- console.log(` Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`);
226
- console.log(` Stage: ${progress.stage}`);
227
- console.log(` Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`);
228
- console.log('\n▶️ Continuing from where you left off...\n');
1049
+ // Suppress all console output during initialization
1050
+ const originalLog = console.log;
1051
+ console.log = () => { };
1052
+
1053
+ let initError = null;
1054
+ try {
1055
+ // Create project structure silently
1056
+ this.createAvcFolder();
1057
+ this.createSrcFolder();
1058
+ this.createWorktreesFolder();
1059
+ this.createAvcConfig();
1060
+ this.createEnvFile();
1061
+ this.addToGitignore();
1062
+ this.createVitePressStructure();
1063
+ } catch (err) {
1064
+ initError = err;
1065
+ } finally {
1066
+ console.log = originalLog;
1067
+ }
1068
+
1069
+ if (initError) {
1070
+ fileLog('ERROR', 'Structure creation failed', { error: initError.message, stack: initError.stack });
1071
+ throw initError;
1072
+ }
1073
+
1074
+ const duration = Date.now() - startTime;
1075
+ fileLog('INFO', 'Structure creation complete', {
1076
+ duration: `${duration}ms`,
1077
+ avcFolder: this.hasAvcFolder(),
1078
+ srcFolder: this.hasSrcFolder(),
1079
+ worktreesFolder: this.hasWorktreesFolder(),
1080
+ avcConfig: this.hasAvcConfig(),
1081
+ });
1082
+
1083
+ sendOutput('Project initialized — set your API keys in .env (Anthropic, Gemini, OpenAI) then open the Kanban board to get started.');
1084
+
1085
+ return;
1086
+ }
1087
+
1088
+ /**
1089
+ * Configure models command
1090
+ * Shows current model configuration and offers interactive editing
1091
+ */
1092
+ async models() {
1093
+ sendOutput('Model Configuration\n');
1094
+ sendOutput('Ceremonies are structured workflows (sponsor-call, sprint-planning, seed) that guide your project through key decisions. Each ceremony runs multiple stages in sequence, and you can assign a different LLM model to each stage.\n');
1095
+
1096
+ // Check if project is initialized
1097
+ if (!this.isAvcProject()) {
1098
+ sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
1099
+ sendOutput('');
1100
+ sendOutput(MESSAGES.PROJECT_NOT_INITIALIZED.help);
1101
+ sendOutput('');
1102
+ return;
1103
+ }
1104
+
1105
+ // Use the shared configuration method
1106
+ return this.configureModelsInteractively();
1107
+ }
1108
+
1109
+ /**
1110
+ * Interactive model configuration flow
1111
+ * Shared by both /init and /models commands
1112
+ */
1113
+ configureModelsInteractively() {
1114
+ fileLog('INFO', 'configureModels() called', { projectRoot: this.projectRoot });
1115
+
1116
+ const configurator = new ModelConfigurator(this.projectRoot);
1117
+
1118
+ // Detect available providers (used for model indicators)
1119
+ configurator.availableProviders = configurator.detectAvailableProviders();
1120
+ configurator.readConfig();
1121
+
1122
+ fileLog('DEBUG', 'Model configurator loaded', {
1123
+ availableProviders: configurator.availableProviders,
1124
+ configPath: this.avcConfigPath,
1125
+ });
1126
+
1127
+ // Show current configuration
1128
+ const ceremonies = configurator.getCeremonies();
1129
+ fileLog('INFO', 'Ceremony model configs', {
1130
+ count: ceremonies.length,
1131
+ names: ceremonies.map(c => c.name),
1132
+ });
1133
+
1134
+ ceremonies.forEach(c => {
1135
+ const ceremonyUrl = `https://agilevibecoding.org/ceremonies/${c.name}.html`;
1136
+
1137
+ const hasMainKey = configurator.availableProviders.includes(c.mainProvider);
1138
+ const stageDetails = {};
1139
+ Object.keys(c.stages).forEach(stageName => {
1140
+ const stage = c.stages[stageName];
1141
+ stageDetails[stageName] = {
1142
+ model: stage.model,
1143
+ provider: stage.provider,
1144
+ hasApiKey: configurator.availableProviders.includes(stage.provider),
1145
+ };
1146
+ });
1147
+ fileLog('DEBUG', `Ceremony: ${c.name}`, {
1148
+ mainModel: c.mainModel,
1149
+ mainProvider: c.mainProvider,
1150
+ hasMainKey,
1151
+ validationModel: c.validationModel || null,
1152
+ validationProvider: c.validationProvider || null,
1153
+ hasValidationKey: c.validationProvider ? configurator.availableProviders.includes(c.validationProvider) : null,
1154
+ stages: stageDetails,
1155
+ });
1156
+
1157
+ sendOutput(boldCyan(c.name));
1158
+ sendOutput(`${yellow('default')}: ${green(c.mainModel)} (${c.mainProvider})`);
1159
+ if (c.validationProvider) {
1160
+ const hasValidationKey = configurator.availableProviders.includes(c.validationProvider);
1161
+ const keyWarning = hasValidationKey ? '' : ' [no API key]';
1162
+ sendOutput(`${yellow('validation')}: ${green(c.validationModel)} (${c.validationProvider})${keyWarning}`);
229
1163
  }
230
- } else if (this.isAvcProject()) {
231
- // No incomplete progress but project exists - already initialized
232
- console.log('✓ AVC project already initialized');
233
- console.log('\nProject is ready to use.');
1164
+ Object.keys(c.stages).forEach(stageName => {
1165
+ const stage = c.stages[stageName];
1166
+ const hasStageKey = configurator.availableProviders.includes(stage.provider);
1167
+ const keyWarning = hasStageKey ? '' : ' [no API key]';
1168
+ sendOutput(`${yellow(stageName)}: ${green(stage.model)} (${stage.provider})${keyWarning}`);
1169
+ });
1170
+ sendOutput('');
1171
+ });
1172
+
1173
+ fileLog('INFO', 'configureModels() complete');
1174
+ // Return configurator for REPL to use
1175
+ return {
1176
+ shouldConfigure: true,
1177
+ configurator,
1178
+ ceremonies: ceremonies.map(c => c.name) // List of ceremony names for selection
1179
+ };
1180
+ }
1181
+
1182
+ /**
1183
+ * Run Sponsor Call ceremony with pre-filled answers from REPL questionnaire
1184
+ * Used when all answers are collected via REPL UI
1185
+ */
1186
+ async sponsorCallWithAnswers(answers, progressCallback = null, options = {}) {
1187
+ const startTime = Date.now();
1188
+ fileLog('INFO', 'sponsorCallWithAnswers() called', {
1189
+ answerKeys: Object.keys(answers || {}),
1190
+ answeredCount: Object.values(answers || {}).filter(v => v !== null && v !== '').length,
1191
+ hasProgressCallback: !!progressCallback,
1192
+ projectRoot: this.projectRoot,
1193
+ });
1194
+
1195
+ // Remove initial ceremony banner - will be shown in summary
1196
+
1197
+ // Check if project is initialized
1198
+ if (!this.isAvcProject()) {
1199
+ fileLog('ERROR', 'Project not initialized — aborting sponsor call');
1200
+ sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
234
1201
  return;
1202
+ }
1203
+
1204
+ const progressPath = this.sponsorCallProgressPath;
1205
+
1206
+ // Initialize ceremony history
1207
+ const { CeremonyHistory } = await import('./ceremony-history.js');
1208
+ const history = new CeremonyHistory(this.avcDir);
1209
+ history.init();
1210
+
1211
+ // Get or create execution record
1212
+ let executionId;
1213
+ const lastExecution = history.getLastExecution('sponsor-call');
1214
+
1215
+ if (lastExecution && lastExecution.status === 'in-progress' && lastExecution.stage === 'llm-generation') {
1216
+ // Resume existing execution (from REPL flow)
1217
+ executionId = lastExecution.id;
235
1218
  } else {
236
- // Fresh start
237
- console.log('Initializing AVC project...\n');
1219
+ // This shouldn't normally happen if called from REPL, but handle it
1220
+ executionId = history.startExecution('sponsor-call', 'llm-generation');
1221
+ }
1222
+
1223
+ // Count answers provided (for logging)
1224
+ const answeredCount = Object.values(answers).filter(v => v !== null && v !== '').length;
1225
+ fileLog('DEBUG', 'Ceremony history state', {
1226
+ executionId,
1227
+ lastExecutionStatus: lastExecution?.status,
1228
+ lastExecutionStage: lastExecution?.stage,
1229
+ answeredCount,
1230
+ totalQuestions: Object.keys(answers).length,
1231
+ });
1232
+
1233
+ // Validate API key before starting ceremony
1234
+ fileLog('INFO', 'Validating API key before ceremony start');
1235
+ const validationResult = await this.validateProviderApiKey();
1236
+ fileLog(validationResult.valid ? 'INFO' : 'ERROR', 'API key validation result', {
1237
+ valid: validationResult.valid,
1238
+ message: validationResult.message || 'OK',
1239
+ });
1240
+ if (!validationResult.valid) {
1241
+ // Mark execution as aborted
1242
+ history.completeExecution('sponsor-call', executionId, 'abrupt-termination', {
1243
+ answers,
1244
+ stage: 'llm-generation',
1245
+ error: 'API key validation failed'
1246
+ });
1247
+
1248
+ // Return error for REPL to display
1249
+ return {
1250
+ error: true,
1251
+ message: `API Key Validation Failed: ${validationResult.message}`
1252
+ };
1253
+ }
1254
+
1255
+ // Create progress with pre-filled answers
1256
+ const progress = {
1257
+ stage: 'questionnaire',
1258
+ totalQuestions: 5,
1259
+ answeredQuestions: 5,
1260
+ collectedValues: answers,
1261
+ lastUpdate: new Date().toISOString()
1262
+ };
1263
+ this.writeProgress(progress, progressPath);
1264
+
1265
+ try {
1266
+ // Generate project document with pre-filled answers
1267
+ const result = await this.generateProjectDocument(progress, progressPath, true, progressCallback, options);
1268
+
1269
+ fileLog('INFO', 'generateProjectDocument() complete', { resultKeys: result ? Object.keys(result) : [] });
1270
+
1271
+ // Notify progress during cleanup
1272
+ if (progressCallback) await progressCallback(null, 'Calculating token usage costs...');
1273
+
1274
+ // Get token usage from template processor
1275
+ const tokenUsage = this.getLastTokenUsage();
1276
+
1277
+ // Get model ID from ceremony config
1278
+ const ceremony = this.readCeremonyConfig('sponsor-call');
1279
+ const modelId = ceremony?.defaultModel || 'claude-sonnet-4-6';
1280
+
1281
+ // Calculate cost using token tracker
1282
+ const { TokenTracker } = await import('./token-tracker.js');
1283
+ const tracker = new TokenTracker(this.avcDir);
1284
+ const cost = tracker.calculateCost(
1285
+ tokenUsage?.inputTokens || 0,
1286
+ tokenUsage?.outputTokens || 0,
1287
+ modelId
1288
+ );
1289
+
1290
+ // Mark execution as completed with metadata
1291
+ if (progressCallback) await progressCallback(null, 'Saving ceremony history...');
1292
+ history.completeExecution('sponsor-call', executionId, 'success', {
1293
+ answers,
1294
+ filesGenerated: [
1295
+ path.join(this.avcDir, 'project/doc.md')
1296
+ ],
1297
+ tokenUsage: {
1298
+ input: tokenUsage?.inputTokens || 0,
1299
+ output: tokenUsage?.outputTokens || 0,
1300
+ total: tokenUsage?.totalTokens || 0
1301
+ },
1302
+ model: modelId,
1303
+ cost: cost,
1304
+ stage: 'completed'
1305
+ });
1306
+
1307
+ // Mark progress as completed and clean up
1308
+ if (progressCallback) await progressCallback(null, 'Finalizing ceremony...');
1309
+ progress.stage = 'completed';
1310
+ progress.lastUpdate = new Date().toISOString();
1311
+ this.writeProgress(progress, progressPath);
1312
+ this.clearProgress(progressPath);
1313
+
1314
+ // Emit a final main progress message so the UI log clearly shows completion
1315
+ if (progressCallback) await progressCallback('✓ Documentation generated successfully!');
1316
+
1317
+ fileLog('INFO', 'sponsorCallWithAnswers() complete', {
1318
+ duration: `${Date.now() - startTime}ms`,
1319
+ outputPath: result?.outputPath,
1320
+ tokenInput: result?.tokenUsage?.input,
1321
+ tokenOutput: result?.tokenUsage?.output,
1322
+ estimatedCost: result?.cost?.total,
1323
+ });
1324
+
1325
+ // Persist answers and generate Q&A documentation page (non-fatal)
1326
+ this.saveProjectBriefAnswers(answers);
1327
+
1328
+ // Return result for display in REPL
1329
+ return result;
1330
+
1331
+ } catch (error) {
1332
+ const isCancelled = error.message === 'CEREMONY_CANCELLED';
1333
+
1334
+ // Save any tokens spent before cancellation/error
1335
+ try {
1336
+ this._lastTemplateProcessor?.saveCurrentTokenTracking();
1337
+ } catch (trackErr) {
1338
+ fileLog('WARN', 'Could not save partial token tracking', { error: trackErr.message });
1339
+ }
1340
+
1341
+ if (!isCancelled) {
1342
+ fileLog('ERROR', 'sponsorCallWithAnswers() failed', {
1343
+ error: error.message,
1344
+ stack: error.stack,
1345
+ duration: `${Date.now() - startTime}ms`,
1346
+ });
1347
+ }
1348
+
1349
+ // Mark execution as aborted on error/cancel
1350
+ history.completeExecution('sponsor-call', executionId, 'abrupt-termination', {
1351
+ answers,
1352
+ stage: isCancelled ? 'cancelled' : 'llm-generation',
1353
+ error: error.message
1354
+ });
1355
+
1356
+ throw error;
1357
+ }
1358
+ }
1359
+
1360
+ /**
1361
+ * Display token usage statistics and costs
1362
+ */
1363
+ async showTokenStats() {
1364
+ fileLog('INFO', 'showTokenStats() called', { avcDir: this.avcDir });
1365
+
1366
+ const { TokenTracker } = await import('./token-tracker.js');
1367
+ const tracker = new TokenTracker(this.avcDir);
1368
+ tracker.init();
1369
+ tracker.load();
1370
+
1371
+ const data = tracker.data;
1372
+ fileLog('DEBUG', 'Token history loaded', {
1373
+ version: data.version,
1374
+ lastUpdated: data.lastUpdated,
1375
+ ceremonyTypes: Object.keys(data).filter(k => !['version', 'lastUpdated', 'totals'].includes(k)),
1376
+ allTimeTotal: data.totals?.allTime?.total,
1377
+ allTimeExecutions: data.totals?.allTime?.executions,
1378
+ });
1379
+
1380
+ const allTime = data.totals.allTime;
1381
+ const costStr = (allTime.cost && allTime.cost.total > 0) ? ` / $${allTime.cost.total.toFixed(4)}` : '';
1382
+ sendOutput(`All-time: ${allTime.total.toLocaleString()} tokens / ${allTime.executions} executions${costStr}`);
1383
+
1384
+ const ceremonyTypes = Object.keys(data).filter(k => !['version', 'lastUpdated', 'totals'].includes(k));
1385
+ for (const ceremonyType of ceremonyTypes) {
1386
+ const ceremony = data[ceremonyType];
1387
+ if (ceremony.allTime && ceremony.allTime.executions > 0) {
1388
+ const cCostStr = (ceremony.allTime.cost && ceremony.allTime.cost.total > 0) ? ` / $${ceremony.allTime.cost.total.toFixed(4)}` : '';
1389
+ sendIndented(`${ceremonyType}: ${ceremony.allTime.total.toLocaleString()} tokens / ${ceremony.allTime.executions} runs${cCostStr}`, 1);
1390
+ }
1391
+ }
1392
+
1393
+ fileLog('INFO', 'showTokenStats() complete', {
1394
+ allTimeInput: data.totals?.allTime?.input,
1395
+ allTimeOutput: data.totals?.allTime?.output,
1396
+ allTimeTotal: data.totals?.allTime?.total,
1397
+ estimatedCost: data.totals?.allTime?.cost?.total,
1398
+ });
1399
+ }
1400
+
1401
+ /**
1402
+ * Run Sprint Planning ceremony to create/expand Epics and Stories
1403
+ */
1404
+ async sprintPlanning() {
1405
+ const startTime = Date.now();
1406
+ fileLog('INFO', 'sprintPlanning() called', { projectRoot: this.projectRoot });
1407
+
1408
+ if (!this.isAvcProject()) {
1409
+ fileLog('ERROR', 'Project not initialized — aborting sprint planning');
1410
+ sendError('Project not initialized. Run /init first.');
1411
+ return;
1412
+ }
1413
+
1414
+ fileLog('INFO', 'Loading SprintPlanningProcessor');
1415
+ const { SprintPlanningProcessor } = await import('./sprint-planning-processor.js');
1416
+ const processor = new SprintPlanningProcessor();
1417
+ fileLog('DEBUG', 'SprintPlanningProcessor created', {
1418
+ projectPath: processor.projectPath,
1419
+ provider: processor._providerName,
1420
+ model: processor._modelName,
1421
+ });
1422
+
1423
+ await processor.execute();
1424
+
1425
+ // Non-fatal docs sync after sprint planning
1426
+ try {
1427
+ const { DocsSyncProcessor } = await import('./docs-sync.js');
1428
+ const syncer = new DocsSyncProcessor(process.cwd());
1429
+ if (fs.existsSync(syncer.docsDir)) {
1430
+ await syncer.sync();
1431
+ sendInfo('Documentation synced.');
1432
+ }
1433
+ } catch (_) { /* non-fatal */ }
1434
+
1435
+ fileLog('INFO', 'sprintPlanning() complete', { duration: `${Date.now() - startTime}ms` });
1436
+ }
1437
+
1438
+ /**
1439
+ * Run Sprint Planning ceremony with a progress callback (used by kanban board)
1440
+ * @param {Function|null} progressCallback - Called with (msg, substep, meta) on each stage
1441
+ * @returns {Promise<object>} Result with epicsCreated, storiesCreated, tokenUsage, model
1442
+ */
1443
+ async sprintPlanningWithCallback(progressCallback = null, options = {}) {
1444
+ fileLog('INFO', 'sprintPlanningWithCallback() called', { projectRoot: this.projectRoot });
1445
+ if (!this.isAvcProject()) {
1446
+ throw new Error('Project not initialized. Run /init first.');
1447
+ }
1448
+ const { SprintPlanningProcessor } = await import('./sprint-planning-processor.js');
1449
+ const processor = new SprintPlanningProcessor(options);
1450
+ return await processor.execute(progressCallback);
1451
+ }
1452
+
1453
+ /**
1454
+ * Run Seed ceremony to decompose a Story into Tasks and Subtasks
1455
+ * @param {string} storyId - Story ID (e.g., context-0001-0001)
1456
+ */
1457
+ async seed(storyId) {
1458
+ const startTime = Date.now();
1459
+ fileLog('INFO', 'seed() called', { storyId, projectRoot: this.projectRoot });
1460
+
1461
+ if (!this.isAvcProject()) {
1462
+ fileLog('ERROR', 'Project not initialized — aborting seed');
1463
+ sendError('Project not initialized. Run /init first.');
1464
+ return;
238
1465
  }
239
1466
 
240
- // Create .avc folder
241
- this.createAvcFolder();
1467
+ if (!storyId) {
1468
+ fileLog('ERROR', 'No story ID provided — aborting seed');
1469
+ sendError('Story ID required. Usage: /seed <story-id>');
1470
+ return;
1471
+ }
1472
+
1473
+ fileLog('INFO', 'Loading SeedProcessor', { storyId });
1474
+ const { SeedProcessor } = await import('./seed-processor.js');
1475
+ const processor = new SeedProcessor(storyId);
1476
+ fileLog('DEBUG', 'SeedProcessor created', {
1477
+ storyId,
1478
+ storyPath: processor.storyPath,
1479
+ provider: processor._providerName,
1480
+ model: processor._modelName,
1481
+ });
1482
+
1483
+ await processor.execute();
1484
+ fileLog('INFO', 'seed() complete', { storyId, duration: `${Date.now() - startTime}ms` });
1485
+ }
242
1486
 
243
- // Create avc.json
244
- this.createAvcConfig();
1487
+ /**
1488
+ * Run Sponsor Call ceremony to define project with AI assistance
1489
+ * Requires API keys to be configured in .env file
1490
+ */
1491
+ async sponsorCall() {
1492
+ const header = getCeremonyHeader('sponsor-call');
1493
+ sendOutput('');
1494
+ sendOutput(header.title);
1495
+ sendOutput('');
1496
+ sendOutput(`Project directory: ${this.projectRoot}`);
1497
+ sendOutput('');
245
1498
 
246
- // Create .env file for API keys
247
- this.createEnvFile();
1499
+ // Check if running in REPL mode
1500
+ const isReplMode = process.env.AVC_REPL_MODE === 'true';
1501
+ if (isReplMode) {
1502
+ // REPL mode is handled by repl-ink.js questionnaire display
1503
+ // This code path shouldn't be reached from REPL
1504
+ sendWarning('Unexpected: Ceremony called directly from REPL');
1505
+ return;
1506
+ }
1507
+
1508
+ // Check if project is initialized
1509
+ if (!this.isAvcProject()) {
1510
+ sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
1511
+ sendOutput('');
1512
+ sendOutput(MESSAGES.PROJECT_NOT_INITIALIZED.help);
1513
+ sendOutput('');
1514
+ return; // Don't exit in REPL mode
1515
+ }
1516
+
1517
+ // Check if sponsor call has already completed successfully
1518
+ const { CeremonyHistory } = await import('./ceremony-history.js');
1519
+ const history = new CeremonyHistory(this.avcDir);
1520
+
1521
+ if (history.hasSuccessfulCompletion('sponsor-call')) {
1522
+ sendError('Sponsor Call has already completed successfully');
1523
+ sendOutput('');
1524
+ sendOutput('Project documentation already exists at .avc/project/doc.md');
1525
+ sendOutput('');
1526
+ sendOutput('To regenerate documentation, first run /remove to clear the project,');
1527
+ sendOutput('then run /init followed by /sponsor-call again.');
1528
+ sendOutput('');
1529
+ return; // Don't allow re-running
1530
+ }
1531
+
1532
+ let progress = null;
1533
+ const progressPath = this.sponsorCallProgressPath;
1534
+
1535
+ // Check for incomplete ceremony
1536
+ if (this.hasIncompleteProgress(progressPath)) {
1537
+ progress = this.readProgress(progressPath);
1538
+
1539
+ if (progress && progress.stage !== 'completed') {
1540
+ sendWarning('Found incomplete ceremony from previous session');
1541
+ sendIndented(`Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`, 1);
1542
+ sendIndented(`Stage: ${progress.stage}`, 1);
1543
+ sendIndented(`Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`, 1);
1544
+ sendOutput('');
1545
+ sendInfo('Continuing from where you left off...');
1546
+ sendOutput('');
1547
+ }
1548
+ } else {
1549
+ // Fresh start
1550
+ sendOutput('Starting Sponsor Call ceremony...');
1551
+ sendOutput('');
1552
+ }
248
1553
 
249
- // Add .env to .gitignore if git repository
250
- this.addToGitignore();
1554
+ // Validate API key before starting ceremony
1555
+ const validationResult = await this.validateProviderApiKey();
1556
+ if (!validationResult.valid) {
1557
+ sendOutput('');
1558
+ sendError('API Key Validation Failed');
1559
+ sendOutput('');
1560
+ sendIndented(validationResult.message, 1);
1561
+ sendOutput('');
1562
+ return; // Don't exit in REPL mode
1563
+ }
251
1564
 
252
1565
  // Save initial progress
253
1566
  if (!progress) {
@@ -258,44 +1571,387 @@ ANTHROPIC_API_KEY=
258
1571
  collectedValues: {},
259
1572
  lastUpdate: new Date().toISOString()
260
1573
  };
261
- this.writeProgress(progress);
1574
+ this.writeProgress(progress, progressPath);
262
1575
  }
263
1576
 
264
1577
  // Generate project document via Sponsor Call ceremony
265
- await this.generateProjectDocument(progress);
1578
+ await this.generateProjectDocument(progress, progressPath, isReplMode);
266
1579
 
267
1580
  // Mark as completed and clean up
268
1581
  progress.stage = 'completed';
269
1582
  progress.lastUpdate = new Date().toISOString();
270
- this.writeProgress(progress);
271
- this.clearProgress();
1583
+ this.writeProgress(progress, progressPath);
1584
+ this.clearProgress(progressPath);
272
1585
 
273
- console.log('\n✅ AVC project initialized successfully!');
274
- console.log('\nNext steps:');
275
- console.log(' 1. Add your ANTHROPIC_API_KEY to .env file');
276
- console.log(' 2. Review .avc/project/doc.md for your project definition');
277
- console.log(' 3. Review .avc/avc.json configuration');
278
- console.log(' 4. Create your project context and work items');
279
- console.log(' 5. Use AI agents to implement features');
1586
+ sendOutput('');
1587
+ sendSuccess('Project defined successfully!');
1588
+ sendOutput('');
1589
+ sendOutput('Next steps:');
1590
+ sendIndented('1. Review .avc/project/doc.md for your project definition', 1);
1591
+ sendIndented('2. Review .avc/avc.json configuration', 1);
1592
+ sendIndented('3. Create your project documentation and work items', 1);
1593
+ sendIndented('4. Use AI agents to implement features', 1);
280
1594
  }
281
1595
 
282
1596
  /**
283
1597
  * Display current project status
284
1598
  */
285
1599
  status() {
286
- console.log('\n📊 AVC Project Status\n');
287
- console.log(`Project directory: ${this.projectRoot}`);
288
- console.log(`Project name: ${this.getProjectName()}\n`);
1600
+ fileLog('INFO', 'status() called', { projectRoot: this.projectRoot });
1601
+
1602
+ const hasAvc = this.hasAvcFolder();
1603
+ const hasSrc = this.hasSrcFolder();
1604
+ const hasWorktrees = this.hasWorktreesFolder();
1605
+ const hasConfig = this.hasAvcConfig();
1606
+ const isInitialized = this.isAvcProject();
1607
+ const projectName = this.getProjectName();
289
1608
 
290
- console.log('Components:');
291
- console.log(` .avc/ folder: ${this.hasAvcFolder() ? '✓' : '✗'}`);
292
- console.log(` avc.json: ${this.hasAvcConfig() ? '' : '✗'}`);
1609
+ fileLog('DEBUG', 'Component check results', {
1610
+ '.avc/': hasAvc,
1611
+ 'src/': hasSrc,
1612
+ 'worktrees/': hasWorktrees,
1613
+ 'avc.json': hasConfig,
1614
+ isAvcProject: isInitialized,
1615
+ projectName,
1616
+ });
293
1617
 
294
- console.log(`\nStatus: ${this.isAvcProject() ? '✅ Initialized' : '⚠️ Not initialized'}`);
1618
+ if (isInitialized) {
1619
+ sendOutput(`${projectName}: Initialized`);
1620
+ fileLog('INFO', 'status() complete — project is initialized');
1621
+ } else {
1622
+ fileLog('WARNING', 'Project not initialized — components missing', { hasAvc, hasConfig });
1623
+ sendOutput(`${projectName}: Not initialized. Run /init to start.`);
1624
+ }
1625
+ }
1626
+
1627
+ /**
1628
+ * Remove AVC project structure (destructive operation)
1629
+ * Requires confirmation by typing "delete all"
1630
+ */
1631
+ async remove() {
1632
+ sendSectionHeader('Remove AVC Project Structure');
1633
+ sendOutput('');
1634
+ sendOutput(`Project directory: ${this.projectRoot}`);
1635
+ sendOutput('');
295
1636
 
1637
+ // Check if project is initialized
296
1638
  if (!this.isAvcProject()) {
297
- console.log('\nRun "avc init" to initialize the project.');
1639
+ sendWarning('No AVC project found in this directory.');
1640
+ sendOutput('Nothing to remove.');
1641
+ sendOutput('');
1642
+ return;
1643
+ }
1644
+
1645
+ // Show what will be deleted
1646
+ sendWarning('WARNING: This is a DESTRUCTIVE operation!');
1647
+ sendOutput('');
1648
+ sendOutput('The following will be PERMANENTLY DELETED:');
1649
+ sendOutput('');
1650
+
1651
+ // List contents of .avc folder
1652
+ const avcContents = this.getAvcContents();
1653
+ if (avcContents.length > 0) {
1654
+ sendOutput('.avc/ folder contents:');
1655
+ sendOutput('');
1656
+ avcContents.forEach(item => {
1657
+ sendIndented(`• ${item}`, 1);
1658
+ });
1659
+ sendOutput('');
1660
+ }
1661
+
1662
+ sendError('All project definitions, epics, stories, tasks, and documentation will be lost.');
1663
+ sendError('All VitePress documentation will be deleted.');
1664
+ sendError('This action CANNOT be undone.');
1665
+ sendOutput('');
1666
+
1667
+ // Check for .env file
1668
+ const envPath = path.join(this.projectRoot, '.env');
1669
+ const hasEnvFile = fs.existsSync(envPath);
1670
+ if (hasEnvFile) {
1671
+ sendInfo('Note: The .env file will NOT be deleted.');
1672
+ sendOutput('You may want to manually remove API keys if no longer needed.');
1673
+ sendOutput('');
1674
+ }
1675
+
1676
+ // Check for src folder
1677
+ const hasSrcFolder = this.hasSrcFolder();
1678
+ if (hasSrcFolder) {
1679
+ sendSuccess('IMPORTANT: The src/ folder will NOT be deleted.');
1680
+ sendOutput('All your AVC-managed code will be preserved.');
1681
+ sendOutput('');
1682
+ }
1683
+
1684
+ // Check for worktrees folder
1685
+ const hasWorktreesFolder = this.hasWorktreesFolder();
1686
+ if (hasWorktreesFolder) {
1687
+ sendWarning('The .avc/worktrees/ folder and all git worktrees inside it WILL be deleted.');
1688
+ sendOutput('');
1689
+ }
1690
+
1691
+ // Check if running in REPL mode
1692
+ const isReplMode = process.env.AVC_REPL_MODE === 'true';
1693
+
1694
+ if (isReplMode) {
1695
+ // In REPL mode, interactive confirmation is handled by repl-ink.js
1696
+ // This code path shouldn't be reached from REPL
1697
+ sendWarning('Unexpected: Remove called directly from REPL');
1698
+ sendOutput('Interactive confirmation should be handled by REPL interface.');
1699
+ return;
1700
+ }
1701
+
1702
+ sendOutput('─'.repeat(60));
1703
+ sendOutput('To confirm deletion, type exactly: delete all');
1704
+ sendOutput('To cancel, type anything else or press Ctrl+C');
1705
+ sendOutput('─'.repeat(60));
1706
+ sendOutput('');
1707
+
1708
+ // Create readline interface for confirmation
1709
+ const readline = await import('readline');
1710
+ const rl = readline.createInterface({
1711
+ input: process.stdin,
1712
+ output: process.stdout
1713
+ });
1714
+
1715
+ return new Promise((resolve) => {
1716
+ rl.question('Confirmation: ', (answer) => {
1717
+ rl.close();
1718
+ sendOutput('');
1719
+
1720
+ if (answer.trim() === 'delete all') {
1721
+ // Proceed with deletion
1722
+ sendOutput('Deleting AVC project structure...');
1723
+ sendOutput('');
1724
+
1725
+ try {
1726
+ // Get list of what's being deleted before deletion
1727
+ const deletedItems = this.getAvcContents();
1728
+
1729
+ // Delete .avc folder
1730
+ fs.rmSync(this.avcDir, { recursive: true, force: true });
1731
+
1732
+ sendSuccess('Successfully deleted:');
1733
+ sendOutput('');
1734
+ sendOutput('.avc/ folder and all contents:');
1735
+ sendOutput('');
1736
+ deletedItems.forEach(item => {
1737
+ sendIndented(`• ${item}`, 3);
1738
+ });
1739
+ sendOutput('');
1740
+
1741
+ // Reminder about preserved files
1742
+ if (hasEnvFile || hasSrcFolder) {
1743
+ sendInfo('Preserved files:');
1744
+ sendOutput('');
1745
+
1746
+ if (hasEnvFile) {
1747
+ sendOutput('The .env file was NOT deleted and still contains:');
1748
+ sendOutput('');
1749
+ sendIndented('• ANTHROPIC_API_KEY', 1);
1750
+ sendIndented('• GEMINI_API_KEY', 1);
1751
+ sendIndented('• OPENAI_API_KEY', 1);
1752
+ sendIndented('• (and any other API keys you added)', 1);
1753
+ sendOutput('If these API keys are not used elsewhere in your project,');
1754
+ sendOutput('you may want to manually delete the .env file or remove');
1755
+ sendOutput('the unused keys.');
1756
+ sendOutput('');
1757
+ }
1758
+
1759
+ if (hasSrcFolder) {
1760
+ sendSuccess('The src/ folder was NOT deleted.');
1761
+ sendOutput('All your AVC-managed code has been preserved.');
1762
+ sendOutput('');
1763
+ }
1764
+
1765
+ }
1766
+
1767
+ sendSuccess('AVC project structure has been completely removed.');
1768
+ sendOutput('You can re-initialize anytime by running /init');
1769
+ sendOutput('');
1770
+
1771
+ resolve();
1772
+ } catch (error) {
1773
+ sendError(`Error during deletion: ${error.message}`);
1774
+ sendOutput('');
1775
+ sendOutput('The .avc folder may be partially deleted.');
1776
+ sendOutput('You may need to manually remove it.');
1777
+ sendOutput('');
1778
+ resolve();
1779
+ }
1780
+ } else {
1781
+ // Cancellation
1782
+ sendError('Operation cancelled.');
1783
+ sendOutput('No files were deleted.');
1784
+ sendOutput('');
1785
+ resolve();
1786
+ }
1787
+ });
1788
+ });
1789
+ }
1790
+
1791
+ /**
1792
+ * Get list of contents in .avc folder for display
1793
+ */
1794
+ getAvcContents() {
1795
+ const contents = [];
1796
+
1797
+ try {
1798
+ if (!fs.existsSync(this.avcDir)) {
1799
+ return contents;
1800
+ }
1801
+
1802
+ // Read .avc directory
1803
+ const items = fs.readdirSync(this.avcDir);
1804
+
1805
+ items.forEach(item => {
1806
+ const itemPath = path.join(this.avcDir, item);
1807
+ const stat = fs.statSync(itemPath);
1808
+
1809
+ if (stat.isDirectory()) {
1810
+ // Count items in subdirectories
1811
+ const subItems = this.countItemsRecursive(itemPath);
1812
+ contents.push(`${item}/ (${subItems} items)`);
1813
+ } else {
1814
+ contents.push(item);
1815
+ }
1816
+ });
1817
+ } catch (error) {
1818
+ // Ignore errors, return what we have
1819
+ }
1820
+
1821
+ return contents;
1822
+ }
1823
+
1824
+ /**
1825
+ * Save sponsor-call Q&A answers to avc.json and generate VitePress Q&A page.
1826
+ * Wrapped in try/catch — failures are non-fatal.
1827
+ */
1828
+ saveProjectBriefAnswers(answers) {
1829
+ try {
1830
+ // Normalize: CLI path has top-level keys; Kanban path may nest under .requirements
1831
+ const qa = answers.MISSION_STATEMENT ? answers : (answers.requirements || answers);
1832
+
1833
+ // Read current avc.json and persist answers
1834
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
1835
+ if (!config.settings) config.settings = {};
1836
+ if (!config.settings.projectBrief) config.settings.projectBrief = {};
1837
+ config.settings.projectBrief.answers = {
1838
+ MISSION_STATEMENT: qa.MISSION_STATEMENT || null,
1839
+ TARGET_USERS: qa.TARGET_USERS || null,
1840
+ INITIAL_SCOPE: qa.INITIAL_SCOPE || null,
1841
+ DEPLOYMENT_TARGET: qa.DEPLOYMENT_TARGET || null,
1842
+ TECHNICAL_CONSIDERATIONS: qa.TECHNICAL_CONSIDERATIONS || null,
1843
+ TECHNICAL_EXCLUSIONS: qa.TECHNICAL_EXCLUSIONS || null,
1844
+ SECURITY_AND_COMPLIANCE_REQUIREMENTS: qa.SECURITY_AND_COMPLIANCE_REQUIREMENTS || null,
1845
+ };
1846
+ config.settings.projectBrief.savedAt = new Date().toISOString();
1847
+ fs.writeFileSync(this.avcConfigPath, JSON.stringify(config, null, 2), 'utf8');
1848
+
1849
+ fileLog('INFO', 'saveProjectBriefAnswers() — answers persisted to avc.json');
1850
+
1851
+ this.generateQADocumentationPage(qa);
1852
+ } catch (error) {
1853
+ fileLog('WARN', 'saveProjectBriefAnswers() failed (non-fatal)', { error: error.message });
1854
+ }
1855
+ }
1856
+
1857
+ /**
1858
+ * Generate .avc/documentation/questions-and-answers.md with the sponsor-call answers.
1859
+ * No-ops silently if the documentation directory doesn't exist yet.
1860
+ */
1861
+ generateQADocumentationPage(qa) {
1862
+ const docDir = path.join(this.avcDir, 'documentation');
1863
+ if (!fs.existsSync(docDir)) {
1864
+ fileLog('INFO', 'generateQADocumentationPage() — documentation dir not found, skipping');
1865
+ return;
1866
+ }
1867
+
1868
+ const lines = ['# Questions & Answers', ''];
1869
+ lines.push('Sponsor-call questionnaire answers captured during project brief generation.', '');
1870
+
1871
+ lines.push('## Mission Statement', '');
1872
+ lines.push(qa.MISSION_STATEMENT || '_Not provided._', '');
1873
+
1874
+ lines.push('## Initial Scope', '');
1875
+ lines.push(qa.INITIAL_SCOPE || '_Not provided._', '');
1876
+
1877
+ lines.push('## Target Users', '');
1878
+ lines.push(qa.TARGET_USERS || '_Not provided._', '');
1879
+
1880
+ lines.push('## Deployment Target', '');
1881
+ lines.push(qa.DEPLOYMENT_TARGET || '_Not provided._', '');
1882
+
1883
+ lines.push('## Technical Considerations', '');
1884
+ lines.push(qa.TECHNICAL_CONSIDERATIONS || '_Not provided._', '');
1885
+
1886
+ if (qa.TECHNICAL_EXCLUSIONS) {
1887
+ lines.push('## Technical Exclusions', '');
1888
+ lines.push(qa.TECHNICAL_EXCLUSIONS, '');
1889
+ }
1890
+
1891
+ lines.push('## Security & Compliance Requirements', '');
1892
+ lines.push(qa.SECURITY_AND_COMPLIANCE_REQUIREMENTS || '_Not provided._', '');
1893
+
1894
+ const qaPath = path.join(docDir, 'questions-and-answers.md');
1895
+ fs.writeFileSync(qaPath, lines.join('\n'), 'utf8');
1896
+
1897
+ fileLog('INFO', 'generateQADocumentationPage() — written', { qaPath });
1898
+
1899
+ this.addQAToVitePressConfig();
1900
+ }
1901
+
1902
+ /**
1903
+ * Insert "Questions & Answers" into the VitePress sidebar, immediately after "Project Brief".
1904
+ * Idempotent — skips if already present.
1905
+ */
1906
+ addQAToVitePressConfig() {
1907
+ const configPath = path.join(this.avcDir, 'documentation', '.vitepress', 'config.mts');
1908
+ if (!fs.existsSync(configPath)) {
1909
+ fileLog('INFO', 'addQAToVitePressConfig() — config.mts not found, skipping');
1910
+ return;
1911
+ }
1912
+
1913
+ let content = fs.readFileSync(configPath, 'utf8');
1914
+
1915
+ if (content.includes('questions-and-answers')) {
1916
+ fileLog('INFO', 'addQAToVitePressConfig() — already present, skipping');
1917
+ return;
298
1918
  }
1919
+
1920
+ // Insert after the Project Brief link line
1921
+ const projectBriefLine = "{ text: 'Project Brief', link: '/' }";
1922
+ const qaLine = "{ text: 'Questions & Answers', link: '/questions-and-answers' }";
1923
+ content = content.replace(
1924
+ projectBriefLine,
1925
+ `${projectBriefLine},\n ${qaLine}`
1926
+ );
1927
+
1928
+ fs.writeFileSync(configPath, content, 'utf8');
1929
+ fileLog('INFO', 'addQAToVitePressConfig() — sidebar updated', { configPath });
1930
+ }
1931
+
1932
+ /**
1933
+ * Recursively count items in a directory
1934
+ */
1935
+ countItemsRecursive(dirPath) {
1936
+ let count = 0;
1937
+
1938
+ try {
1939
+ const items = fs.readdirSync(dirPath);
1940
+ count += items.length;
1941
+
1942
+ items.forEach(item => {
1943
+ const itemPath = path.join(dirPath, item);
1944
+ const stat = fs.statSync(itemPath);
1945
+
1946
+ if (stat.isDirectory()) {
1947
+ count += this.countItemsRecursive(itemPath);
1948
+ }
1949
+ });
1950
+ } catch (error) {
1951
+ // Ignore errors
1952
+ }
1953
+
1954
+ return count;
299
1955
  }
300
1956
  }
301
1957
 
@@ -311,11 +1967,20 @@ if (import.meta.url === `file://${process.argv[1]}`) {
311
1967
  case 'init':
312
1968
  initiator.init();
313
1969
  break;
1970
+ case 'sponsor-call':
1971
+ initiator.sponsorCall();
1972
+ break;
314
1973
  case 'status':
315
1974
  initiator.status();
316
1975
  break;
1976
+ case 'models':
1977
+ initiator.models();
1978
+ break;
1979
+ case 'remove':
1980
+ initiator.remove();
1981
+ break;
317
1982
  default:
318
- console.log('Unknown command. Available commands: init, status');
1983
+ sendError('Unknown command. Available commands: init, sponsor-call, status, models, remove');
319
1984
  process.exit(1);
320
1985
  }
321
1986
  }