@agile-vibe-coding/avc 0.1.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +129 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/database-deep-dive.md +470 -0
  5. package/cli/agents/database-recommender.md +634 -0
  6. package/cli/agents/doc-distributor.md +176 -0
  7. package/cli/agents/documentation-updater.md +203 -0
  8. package/cli/agents/epic-story-decomposer.md +280 -0
  9. package/cli/agents/feature-context-generator.md +91 -0
  10. package/cli/agents/gap-checker-epic.md +52 -0
  11. package/cli/agents/impact-checker-story.md +51 -0
  12. package/cli/agents/migration-guide-generator.md +305 -0
  13. package/cli/agents/mission-scope-generator.md +79 -0
  14. package/cli/agents/mission-scope-validator.md +112 -0
  15. package/cli/agents/project-context-extractor.md +107 -0
  16. package/cli/agents/project-documentation-creator.json +226 -0
  17. package/cli/agents/project-documentation-creator.md +595 -0
  18. package/cli/agents/question-prefiller.md +269 -0
  19. package/cli/agents/refiner-epic.md +39 -0
  20. package/cli/agents/refiner-story.md +42 -0
  21. package/cli/agents/solver-epic-api.json +15 -0
  22. package/cli/agents/solver-epic-api.md +39 -0
  23. package/cli/agents/solver-epic-backend.json +15 -0
  24. package/cli/agents/solver-epic-backend.md +39 -0
  25. package/cli/agents/solver-epic-cloud.json +15 -0
  26. package/cli/agents/solver-epic-cloud.md +39 -0
  27. package/cli/agents/solver-epic-data.json +15 -0
  28. package/cli/agents/solver-epic-data.md +39 -0
  29. package/cli/agents/solver-epic-database.json +15 -0
  30. package/cli/agents/solver-epic-database.md +39 -0
  31. package/cli/agents/solver-epic-developer.json +15 -0
  32. package/cli/agents/solver-epic-developer.md +39 -0
  33. package/cli/agents/solver-epic-devops.json +15 -0
  34. package/cli/agents/solver-epic-devops.md +39 -0
  35. package/cli/agents/solver-epic-frontend.json +15 -0
  36. package/cli/agents/solver-epic-frontend.md +39 -0
  37. package/cli/agents/solver-epic-mobile.json +15 -0
  38. package/cli/agents/solver-epic-mobile.md +39 -0
  39. package/cli/agents/solver-epic-qa.json +15 -0
  40. package/cli/agents/solver-epic-qa.md +39 -0
  41. package/cli/agents/solver-epic-security.json +15 -0
  42. package/cli/agents/solver-epic-security.md +39 -0
  43. package/cli/agents/solver-epic-solution-architect.json +15 -0
  44. package/cli/agents/solver-epic-solution-architect.md +39 -0
  45. package/cli/agents/solver-epic-test-architect.json +15 -0
  46. package/cli/agents/solver-epic-test-architect.md +39 -0
  47. package/cli/agents/solver-epic-ui.json +15 -0
  48. package/cli/agents/solver-epic-ui.md +39 -0
  49. package/cli/agents/solver-epic-ux.json +15 -0
  50. package/cli/agents/solver-epic-ux.md +39 -0
  51. package/cli/agents/solver-story-api.json +15 -0
  52. package/cli/agents/solver-story-api.md +39 -0
  53. package/cli/agents/solver-story-backend.json +15 -0
  54. package/cli/agents/solver-story-backend.md +39 -0
  55. package/cli/agents/solver-story-cloud.json +15 -0
  56. package/cli/agents/solver-story-cloud.md +39 -0
  57. package/cli/agents/solver-story-data.json +15 -0
  58. package/cli/agents/solver-story-data.md +39 -0
  59. package/cli/agents/solver-story-database.json +15 -0
  60. package/cli/agents/solver-story-database.md +39 -0
  61. package/cli/agents/solver-story-developer.json +15 -0
  62. package/cli/agents/solver-story-developer.md +39 -0
  63. package/cli/agents/solver-story-devops.json +15 -0
  64. package/cli/agents/solver-story-devops.md +39 -0
  65. package/cli/agents/solver-story-frontend.json +15 -0
  66. package/cli/agents/solver-story-frontend.md +39 -0
  67. package/cli/agents/solver-story-mobile.json +15 -0
  68. package/cli/agents/solver-story-mobile.md +39 -0
  69. package/cli/agents/solver-story-qa.json +15 -0
  70. package/cli/agents/solver-story-qa.md +39 -0
  71. package/cli/agents/solver-story-security.json +15 -0
  72. package/cli/agents/solver-story-security.md +39 -0
  73. package/cli/agents/solver-story-solution-architect.json +15 -0
  74. package/cli/agents/solver-story-solution-architect.md +39 -0
  75. package/cli/agents/solver-story-test-architect.json +15 -0
  76. package/cli/agents/solver-story-test-architect.md +39 -0
  77. package/cli/agents/solver-story-ui.json +15 -0
  78. package/cli/agents/solver-story-ui.md +39 -0
  79. package/cli/agents/solver-story-ux.json +15 -0
  80. package/cli/agents/solver-story-ux.md +39 -0
  81. package/cli/agents/story-doc-enricher.md +133 -0
  82. package/cli/agents/suggestion-business-analyst.md +88 -0
  83. package/cli/agents/suggestion-deployment-architect.md +263 -0
  84. package/cli/agents/suggestion-product-manager.md +129 -0
  85. package/cli/agents/suggestion-security-specialist.md +156 -0
  86. package/cli/agents/suggestion-technical-architect.md +269 -0
  87. package/cli/agents/suggestion-ux-researcher.md +93 -0
  88. package/cli/agents/task-subtask-decomposer.md +188 -0
  89. package/cli/agents/validator-documentation.json +152 -0
  90. package/cli/agents/validator-documentation.md +453 -0
  91. package/cli/agents/validator-epic-api.json +93 -0
  92. package/cli/agents/validator-epic-api.md +137 -0
  93. package/cli/agents/validator-epic-backend.json +93 -0
  94. package/cli/agents/validator-epic-backend.md +130 -0
  95. package/cli/agents/validator-epic-cloud.json +93 -0
  96. package/cli/agents/validator-epic-cloud.md +137 -0
  97. package/cli/agents/validator-epic-data.json +93 -0
  98. package/cli/agents/validator-epic-data.md +130 -0
  99. package/cli/agents/validator-epic-database.json +93 -0
  100. package/cli/agents/validator-epic-database.md +137 -0
  101. package/cli/agents/validator-epic-developer.json +74 -0
  102. package/cli/agents/validator-epic-developer.md +153 -0
  103. package/cli/agents/validator-epic-devops.json +74 -0
  104. package/cli/agents/validator-epic-devops.md +153 -0
  105. package/cli/agents/validator-epic-frontend.json +74 -0
  106. package/cli/agents/validator-epic-frontend.md +153 -0
  107. package/cli/agents/validator-epic-mobile.json +93 -0
  108. package/cli/agents/validator-epic-mobile.md +130 -0
  109. package/cli/agents/validator-epic-qa.json +93 -0
  110. package/cli/agents/validator-epic-qa.md +130 -0
  111. package/cli/agents/validator-epic-security.json +74 -0
  112. package/cli/agents/validator-epic-security.md +154 -0
  113. package/cli/agents/validator-epic-solution-architect.json +74 -0
  114. package/cli/agents/validator-epic-solution-architect.md +156 -0
  115. package/cli/agents/validator-epic-test-architect.json +93 -0
  116. package/cli/agents/validator-epic-test-architect.md +130 -0
  117. package/cli/agents/validator-epic-ui.json +93 -0
  118. package/cli/agents/validator-epic-ui.md +130 -0
  119. package/cli/agents/validator-epic-ux.json +93 -0
  120. package/cli/agents/validator-epic-ux.md +130 -0
  121. package/cli/agents/validator-selector.md +211 -0
  122. package/cli/agents/validator-story-api.json +104 -0
  123. package/cli/agents/validator-story-api.md +152 -0
  124. package/cli/agents/validator-story-backend.json +104 -0
  125. package/cli/agents/validator-story-backend.md +152 -0
  126. package/cli/agents/validator-story-cloud.json +104 -0
  127. package/cli/agents/validator-story-cloud.md +152 -0
  128. package/cli/agents/validator-story-data.json +104 -0
  129. package/cli/agents/validator-story-data.md +152 -0
  130. package/cli/agents/validator-story-database.json +104 -0
  131. package/cli/agents/validator-story-database.md +152 -0
  132. package/cli/agents/validator-story-developer.json +104 -0
  133. package/cli/agents/validator-story-developer.md +152 -0
  134. package/cli/agents/validator-story-devops.json +104 -0
  135. package/cli/agents/validator-story-devops.md +152 -0
  136. package/cli/agents/validator-story-frontend.json +104 -0
  137. package/cli/agents/validator-story-frontend.md +152 -0
  138. package/cli/agents/validator-story-mobile.json +104 -0
  139. package/cli/agents/validator-story-mobile.md +152 -0
  140. package/cli/agents/validator-story-qa.json +104 -0
  141. package/cli/agents/validator-story-qa.md +152 -0
  142. package/cli/agents/validator-story-security.json +104 -0
  143. package/cli/agents/validator-story-security.md +152 -0
  144. package/cli/agents/validator-story-solution-architect.json +104 -0
  145. package/cli/agents/validator-story-solution-architect.md +152 -0
  146. package/cli/agents/validator-story-test-architect.json +104 -0
  147. package/cli/agents/validator-story-test-architect.md +152 -0
  148. package/cli/agents/validator-story-ui.json +104 -0
  149. package/cli/agents/validator-story-ui.md +152 -0
  150. package/cli/agents/validator-story-ux.json +104 -0
  151. package/cli/agents/validator-story-ux.md +152 -0
  152. package/cli/ansi-colors.js +21 -0
  153. package/cli/build-docs.js +29 -8
  154. package/cli/ceremony-history.js +369 -0
  155. package/cli/command-logger.js +49 -12
  156. package/cli/components/static-output.js +63 -0
  157. package/cli/console-output-manager.js +94 -0
  158. package/cli/docs-sync.js +306 -0
  159. package/cli/epic-story-validator.js +1174 -0
  160. package/cli/evaluation-prompts.js +1008 -0
  161. package/cli/execution-context.js +195 -0
  162. package/cli/generate-summary-table.js +340 -0
  163. package/cli/index.js +0 -0
  164. package/cli/init-model-config.js +697 -0
  165. package/cli/init.js +1311 -274
  166. package/cli/kanban-server-manager.js +228 -0
  167. package/cli/llm-claude.js +83 -1
  168. package/cli/llm-gemini.js +85 -0
  169. package/cli/llm-mock.js +233 -0
  170. package/cli/llm-openai.js +233 -0
  171. package/cli/llm-provider.js +240 -3
  172. package/cli/llm-token-limits.js +102 -0
  173. package/cli/llm-verifier.js +454 -0
  174. package/cli/message-constants.js +58 -0
  175. package/cli/message-manager.js +334 -0
  176. package/cli/message-types.js +96 -0
  177. package/cli/messaging-api.js +297 -0
  178. package/cli/model-pricing.js +169 -0
  179. package/cli/model-query-engine.js +468 -0
  180. package/cli/model-recommendation-analyzer.js +495 -0
  181. package/cli/model-selector.js +269 -0
  182. package/cli/output-buffer.js +107 -0
  183. package/cli/process-manager.js +73 -2
  184. package/cli/repl-ink.js +4988 -1217
  185. package/cli/repl-old.js +4 -4
  186. package/cli/seed-processor.js +792 -0
  187. package/cli/sprint-planning-processor.js +1813 -0
  188. package/cli/template-processor.js +2102 -105
  189. package/cli/templates/project.md +25 -8
  190. package/cli/templates/vitepress-config.mts.template +5 -4
  191. package/cli/token-tracker.js +520 -0
  192. package/cli/tools/generate-story-validators.js +317 -0
  193. package/cli/tools/generate-validators.js +669 -0
  194. package/cli/update-checker.js +19 -17
  195. package/cli/update-notifier.js +4 -4
  196. package/cli/validation-router.js +605 -0
  197. package/cli/verification-tracker.js +563 -0
  198. package/kanban/README.md +386 -0
  199. package/kanban/client/README.md +205 -0
  200. package/kanban/client/components.json +20 -0
  201. package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
  202. package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
  203. package/kanban/client/dist/index.html +16 -0
  204. package/kanban/client/dist/vite.svg +1 -0
  205. package/kanban/client/index.html +15 -0
  206. package/kanban/client/package-lock.json +9442 -0
  207. package/kanban/client/package.json +44 -0
  208. package/kanban/client/postcss.config.js +6 -0
  209. package/kanban/client/public/vite.svg +1 -0
  210. package/kanban/client/src/App.jsx +622 -0
  211. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  212. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
  213. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
  214. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
  215. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  216. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
  217. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
  218. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  219. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
  220. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  221. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  222. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  223. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
  224. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
  225. package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
  226. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  227. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  228. package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
  229. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  230. package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
  231. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  232. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
  233. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  234. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  235. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  236. package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
  237. package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
  238. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
  239. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
  240. package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
  241. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  242. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  243. package/kanban/client/src/components/stats/CostModal.jsx +353 -0
  244. package/kanban/client/src/components/ui/badge.jsx +27 -0
  245. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  246. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  247. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  248. package/kanban/client/src/hooks/useGrouping.js +118 -0
  249. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  250. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  251. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  252. package/kanban/client/src/lib/api.js +401 -0
  253. package/kanban/client/src/lib/status-grouping.js +144 -0
  254. package/kanban/client/src/lib/utils.js +11 -0
  255. package/kanban/client/src/main.jsx +10 -0
  256. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  257. package/kanban/client/src/store/ceremonyStore.js +172 -0
  258. package/kanban/client/src/store/filterStore.js +201 -0
  259. package/kanban/client/src/store/kanbanStore.js +115 -0
  260. package/kanban/client/src/store/processStore.js +65 -0
  261. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  262. package/kanban/client/src/styles/globals.css +59 -0
  263. package/kanban/client/tailwind.config.js +77 -0
  264. package/kanban/client/vite.config.js +28 -0
  265. package/kanban/client/vitest.config.js +28 -0
  266. package/kanban/dev-start.sh +47 -0
  267. package/kanban/package.json +12 -0
  268. package/kanban/server/index.js +516 -0
  269. package/kanban/server/routes/ceremony.js +305 -0
  270. package/kanban/server/routes/costs.js +157 -0
  271. package/kanban/server/routes/processes.js +50 -0
  272. package/kanban/server/routes/settings.js +303 -0
  273. package/kanban/server/routes/websocket.js +276 -0
  274. package/kanban/server/routes/work-items.js +347 -0
  275. package/kanban/server/services/CeremonyService.js +1190 -0
  276. package/kanban/server/services/FileSystemScanner.js +95 -0
  277. package/kanban/server/services/FileWatcher.js +144 -0
  278. package/kanban/server/services/HierarchyBuilder.js +196 -0
  279. package/kanban/server/services/ProcessRegistry.js +122 -0
  280. package/kanban/server/services/WorkItemReader.js +123 -0
  281. package/kanban/server/services/WorkItemRefineService.js +510 -0
  282. package/kanban/server/start.js +49 -0
  283. package/kanban/server/utils/kanban-logger.js +132 -0
  284. package/kanban/server/utils/markdown.js +91 -0
  285. package/kanban/server/utils/status-grouping.js +107 -0
  286. package/kanban/server/workers/sponsor-call-worker.js +84 -0
  287. package/kanban/server/workers/sprint-planning-worker.js +130 -0
  288. package/package.json +18 -5
  289. package/cli/agents/documentation.md +0 -302
package/cli/init.js CHANGED
@@ -6,6 +6,23 @@ import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { execSync } from 'child_process';
8
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
+ }
9
26
 
10
27
  const __filename = fileURLToPath(import.meta.url);
11
28
  const __dirname = path.dirname(__filename);
@@ -23,11 +40,16 @@ class ProjectInitiator {
23
40
  constructor(projectRoot = null) {
24
41
  this.projectRoot = projectRoot || process.cwd();
25
42
  this.avcDir = path.join(this.projectRoot, '.avc');
43
+ this.srcDir = path.join(this.projectRoot, 'src');
44
+ this.worktreesDir = path.join(this.avcDir, 'worktrees');
26
45
  this.avcConfigPath = path.join(this.avcDir, 'avc.json');
27
46
  // Progress files are ceremony-specific
28
47
  this.initProgressPath = path.join(this.avcDir, 'init-progress.json');
29
48
  this.sponsorCallProgressPath = path.join(this.avcDir, 'sponsor-call-progress.json');
30
49
 
50
+ // Template processor for token usage tracking
51
+ this._lastTemplateProcessor = null;
52
+
31
53
  // Load environment variables from project .env file
32
54
  // Use override: true to reload even if already set (user may have edited .env)
33
55
  dotenv.config({
@@ -104,10 +126,44 @@ class ProjectInitiator {
104
126
  createAvcFolder() {
105
127
  if (!this.hasAvcFolder()) {
106
128
  fs.mkdirSync(this.avcDir, { recursive: true });
107
- console.log('✓ Created .avc/ folder');
108
129
  return true;
109
130
  }
110
- console.log('✓ .avc/ folder already exists');
131
+ return false;
132
+ }
133
+
134
+ /**
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
+ }
111
167
  return false;
112
168
  }
113
169
 
@@ -123,7 +179,6 @@ class ProjectInitiator {
123
179
  framework: 'avc',
124
180
  created: new Date().toISOString(),
125
181
  settings: {
126
- contextScopes: ['epic', 'story', 'task', 'subtask'],
127
182
  workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
128
183
  agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
129
184
  documentation: {
@@ -132,20 +187,346 @@ class ProjectInitiator {
132
187
  ceremonies: [
133
188
  {
134
189
  name: 'sponsor-call',
135
- defaultModel: 'claude-sonnet-4-5-20250929',
136
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
+ },
137
210
  agents: [
138
211
  {
139
- name: 'documentation',
140
- instruction: 'documentation.md',
141
- stage: 'enhancement'
142
- }
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
+ },
143
222
  ],
144
- guidelines: {
145
- technicalConsiderations: 'Use AWS serverless stack with Lambda functions for compute, API Gateway for REST APIs, DynamoDB for database, S3 for storage. Use CloudFormation for infrastructure definition and AWS CodePipeline/CodeBuild for CI/CD deployment.'
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
146
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
147
353
  }
148
- ]
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
+ }
529
+ }
149
530
  }
150
531
  };
151
532
 
@@ -156,7 +537,6 @@ class ProjectInitiator {
156
537
  JSON.stringify(defaultConfig, null, 2),
157
538
  'utf8'
158
539
  );
159
- console.log('✓ Created .avc/avc.json configuration file');
160
540
  return true;
161
541
  }
162
542
 
@@ -167,6 +547,15 @@ class ProjectInitiator {
167
547
  // Merge: add new keys, keep existing values
168
548
  const mergedConfig = this.deepMerge(existingConfig, defaultConfig);
169
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
+
170
559
  // Update avcVersion to track CLI version
171
560
  mergedConfig.avcVersion = this.getAvcVersion();
172
561
  mergedConfig.updated = new Date().toISOString();
@@ -177,15 +566,12 @@ class ProjectInitiator {
177
566
 
178
567
  if (existingJson !== mergedJson) {
179
568
  fs.writeFileSync(this.avcConfigPath, mergedJson, 'utf8');
180
- console.log('✓ Updated .avc/avc.json with new configuration attributes');
181
569
  return true;
182
570
  }
183
571
 
184
- console.log('✓ .avc/avc.json is up to date');
185
572
  return false;
186
573
  } catch (error) {
187
- console.error(`⚠️ Warning: Could not merge avc.json: ${error.message}`);
188
- console.log('✓ .avc/avc.json already exists (merge skipped)');
574
+ console.error(`Warning: Could not merge avc.json: ${error.message}`);
189
575
  return false;
190
576
  }
191
577
  }
@@ -204,24 +590,70 @@ class ProjectInitiator {
204
590
 
205
591
  /**
206
592
  * Create .env file for API keys
593
+ * If .env exists, check and add any missing API key variables
207
594
  */
208
595
  createEnvFile() {
209
596
  const envPath = path.join(this.projectRoot, '.env');
210
597
 
211
- if (!fs.existsSync(envPath)) {
212
- const envContent = `# Anthropic API Key for AI-powered Sponsor Call ceremony
213
- # Get your key at: https://console.anthropic.com/settings/keys
214
- 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
+ ];
215
616
 
216
- # Google Gemini API Key (alternative LLM provider)
217
- # Get your key at: https://aistudio.google.com/app/apikey
218
- GEMINI_API_KEY=
219
- `;
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
+ });
220
626
  fs.writeFileSync(envPath, envContent, 'utf8');
221
- console.log('✓ Created .env file for API keys');
222
627
  return true;
223
628
  }
224
- 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
+
225
657
  return false;
226
658
  }
227
659
 
@@ -245,7 +677,9 @@ GEMINI_API_KEY=
245
677
  { pattern: '.env', comment: 'Environment variables' },
246
678
  { pattern: '.avc/documentation/.vitepress/dist', comment: 'VitePress build output' },
247
679
  { pattern: '.avc/documentation/.vitepress/cache', comment: 'VitePress cache' },
248
- { pattern: '.avc/logs', comment: 'Command execution logs' }
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' }
249
683
  ];
250
684
 
251
685
  let newContent = gitignoreContent;
@@ -266,16 +700,14 @@ GEMINI_API_KEY=
266
700
 
267
701
  if (addedItems.length > 0) {
268
702
  fs.writeFileSync(gitignorePath, newContent, 'utf8');
269
- console.log(`✓ Added to .gitignore: ${addedItems.join(', ')}`);
270
- } else {
271
- console.log('✓ .gitignore already up to date');
272
703
  }
273
704
  }
274
705
 
275
706
  /**
276
- * Create VitePress documentation setup
707
+ * Create VitePress documentation structure (folders and config files)
708
+ * Note: VitePress is bundled with AVC, no need to modify user's package.json
277
709
  */
278
- createVitePressSetup() {
710
+ createVitePressStructure() {
279
711
  const docsDir = path.join(this.avcDir, 'documentation');
280
712
  const vitepressDir = path.join(docsDir, '.vitepress');
281
713
  const publicDir = path.join(docsDir, 'public');
@@ -283,16 +715,10 @@ GEMINI_API_KEY=
283
715
  // Create directory structure
284
716
  if (!fs.existsSync(vitepressDir)) {
285
717
  fs.mkdirSync(vitepressDir, { recursive: true });
286
- console.log('✓ Created .avc/documentation/.vitepress/ folder');
287
- } else {
288
- console.log('✓ .avc/documentation/.vitepress/ folder already exists');
289
718
  }
290
719
 
291
720
  if (!fs.existsSync(publicDir)) {
292
721
  fs.mkdirSync(publicDir, { recursive: true });
293
- console.log('✓ Created .avc/documentation/public/ folder');
294
- } else {
295
- console.log('✓ .avc/documentation/public/ folder already exists');
296
722
  }
297
723
 
298
724
  // Create VitePress config
@@ -302,9 +728,6 @@ GEMINI_API_KEY=
302
728
  let configContent = fs.readFileSync(templatePath, 'utf8');
303
729
  configContent = configContent.replace('{{PROJECT_NAME}}', this.getProjectName());
304
730
  fs.writeFileSync(configPath, configContent, 'utf8');
305
- console.log('✓ Created .avc/documentation/.vitepress/config.mts');
306
- } else {
307
- console.log('✓ .avc/documentation/.vitepress/config.mts already exists');
308
731
  }
309
732
 
310
733
  // Create initial index.md
@@ -312,107 +735,9 @@ GEMINI_API_KEY=
312
735
  if (!fs.existsSync(indexPath)) {
313
736
  const indexContent = `# ${this.getProjectName()}
314
737
 
315
- ## Project Status
316
-
317
- This project is being developed using the [Agile Vibe Coding](https://agilevibecoding.org) framework.
318
-
319
- **Current Stage**: Initial Setup
320
-
321
- Project documentation will be generated automatically as the project is defined and developed.
322
-
323
- ## About This Documentation
324
-
325
- This site provides comprehensive documentation about **${this.getProjectName()}**, including:
326
-
327
- - Project overview and objectives
328
- - Feature specifications organized by epics and stories
329
- - Technical architecture and design decisions
330
- - Implementation progress and status
331
-
332
- Documentation is automatically updated from the AVC project structure as development progresses.
333
-
334
- ## Getting Started with AVC
335
-
336
- If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilevibecoding.org) to learn about:
337
-
338
- - [CLI Commands](https://agilevibecoding.org/commands) - Available commands and their usage
339
- - [Installation Guide](https://agilevibecoding.org/install) - Setup instructions
340
- - [Framework Overview](https://agilevibecoding.org) - Core concepts and workflow
341
-
342
- ---
343
-
344
- *Documentation powered by [Agile Vibe Coding](https://agilevibecoding.org)*
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.
345
739
  `;
346
740
  fs.writeFileSync(indexPath, indexContent, 'utf8');
347
- console.log('✓ Created .avc/documentation/index.md');
348
- } else {
349
- console.log('✓ .avc/documentation/index.md already exists');
350
- }
351
-
352
- // Update package.json with VitePress scripts
353
- this.updatePackageJsonForVitePress();
354
- }
355
-
356
- /**
357
- * Update package.json with VitePress dependencies and scripts
358
- */
359
- updatePackageJsonForVitePress() {
360
- const packagePath = path.join(this.projectRoot, 'package.json');
361
-
362
- let packageJson;
363
- if (fs.existsSync(packagePath)) {
364
- packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
365
- } else {
366
- packageJson = {
367
- name: this.getProjectName(),
368
- version: '1.0.0',
369
- private: true
370
- };
371
- }
372
-
373
- // Add scripts
374
- if (!packageJson.scripts) {
375
- packageJson.scripts = {};
376
- }
377
-
378
- const scriptsToAdd = {
379
- 'docs:dev': 'vitepress dev .avc/documentation',
380
- 'docs:build': 'vitepress build .avc/documentation',
381
- 'docs:preview': 'vitepress preview .avc/documentation'
382
- };
383
-
384
- let addedScripts = [];
385
- for (const [name, command] of Object.entries(scriptsToAdd)) {
386
- if (!packageJson.scripts[name]) {
387
- packageJson.scripts[name] = command;
388
- addedScripts.push(name);
389
- }
390
- }
391
-
392
- // Add devDependencies
393
- if (!packageJson.devDependencies) {
394
- packageJson.devDependencies = {};
395
- }
396
-
397
- let addedDeps = false;
398
- if (!packageJson.devDependencies.vitepress) {
399
- packageJson.devDependencies.vitepress = '^1.6.4';
400
- addedDeps = true;
401
- }
402
-
403
- // Write package.json
404
- fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
405
-
406
- if (addedScripts.length > 0 || addedDeps) {
407
- console.log('✓ Updated package.json with VitePress configuration');
408
- if (addedScripts.length > 0) {
409
- console.log(` Added scripts: ${addedScripts.join(', ')}`);
410
- }
411
- if (addedDeps) {
412
- console.log(' Added devDependency: vitepress');
413
- }
414
- } else {
415
- console.log('✓ package.json already has VitePress configuration');
416
741
  }
417
742
  }
418
743
 
@@ -455,6 +780,74 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
455
780
  }
456
781
 
457
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
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;
849
+ }
850
+
458
851
  /**
459
852
  * Validate that the configured provider's API key is present and working
460
853
  */
@@ -470,7 +863,7 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
470
863
  };
471
864
  }
472
865
 
473
- // Read provider config from avc.json
866
+ // Read ceremony config
474
867
  const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
475
868
  const ceremony = config.settings?.ceremonies?.[0];
476
869
 
@@ -481,62 +874,150 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
481
874
  };
482
875
  }
483
876
 
484
- const providerName = ceremony.provider || 'claude';
485
- const modelName = ceremony.defaultModel || 'claude-sonnet-4-5-20250929';
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;
486
884
 
487
- // Check which env var is required
488
885
  const envVarMap = {
489
886
  'claude': 'ANTHROPIC_API_KEY',
490
- 'gemini': 'GEMINI_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'
491
895
  };
492
896
 
493
- const requiredEnvVar = envVarMap[providerName];
494
- if (!requiredEnvVar) {
897
+ // Validate main provider
898
+ const mainEnvVar = envVarMap[mainProvider];
899
+ if (!mainEnvVar) {
495
900
  return {
496
901
  valid: false,
497
- message: `Unknown provider "${providerName}".\n Supported providers: claude, gemini`
902
+ message: `Unknown provider "${mainProvider}".\n Supported providers: claude, gemini, openai`
498
903
  };
499
904
  }
500
905
 
501
- // Check if API key is set in environment
502
- if (!process.env[requiredEnvVar]) {
906
+ if (!process.env[mainEnvVar]) {
503
907
  return {
504
908
  valid: false,
505
- message: `${requiredEnvVar} 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: ${requiredEnvVar}=your-key-here\n 3. Save the file and run /init again\n\n Get your API key:\n ${providerName === 'claude' ? '• https://console.anthropic.com/settings/keys' : '• https://aistudio.google.com/app/apikey'}`
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]}`
506
910
  };
507
911
  }
508
912
 
509
- console.log(`\n🔑 Validating ${providerName} API key...`);
510
-
511
913
  // Test the API key with a minimal call
512
914
  let result;
513
915
  try {
514
- result = await LLMProvider.validate(providerName, modelName);
916
+ result = await LLMProvider.validate(mainProvider, mainModel);
515
917
  } catch (error) {
918
+ const parsedError = this.parseApiError(error.message || error);
516
919
  return {
517
920
  valid: false,
518
- message: `${requiredEnvVar} validation failed.\n\n Error: ${error.message}\n\n This could be due to:\n • Network connectivity issues\n • API service temporarily unavailable\n • Invalid API key\n\n Please check your connection and try again.`
921
+ message: `${mainEnvVar} validation failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[mainProvider]}`
519
922
  };
520
923
  }
521
924
 
522
925
  if (!result.valid) {
523
- const errorMsg = result.error || 'Unknown error';
926
+ const parsedError = this.parseApiError(result.error || 'Unknown error');
524
927
  return {
525
928
  valid: false,
526
- message: `${requiredEnvVar} is set but API call failed.\n\n Error: ${errorMsg}\n\n Steps to fix:\n 1. Verify your API key is correct in .env file\n 2. Check that the key has not expired\n 3. Ensure you have API credits/quota available\n\n Get a new API key if needed:\n ${providerName === 'claude' ? '• https://console.anthropic.com/settings/keys' : '• https://aistudio.google.com/app/apikey'}`
929
+ message: `${mainEnvVar} is set but API call failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n ${urlMap[mainProvider]}`
527
930
  };
528
931
  }
529
932
 
530
- console.log(`✓ API key validated successfully\n`);
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
+
531
979
  return { valid: true };
532
980
  }
533
981
 
534
982
  /**
535
983
  * Generate project document via Sponsor Call ceremony
536
984
  */
537
- async generateProjectDocument(progress = null, progressPath = null, nonInteractive = false) {
538
- const processor = new TemplateProcessor(progressPath || this.sponsorCallProgressPath, nonInteractive);
539
- 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
+ }
540
1021
  }
541
1022
 
542
1023
  /**
@@ -551,73 +1032,227 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
551
1032
  * Creates .avc folder, avc.json config, .env file, and gitignore entry
552
1033
  */
553
1034
  async init() {
554
- console.log('\n🚀 AVC Project Initiator\n');
555
- console.log(`Project directory: ${this.projectRoot}\n`);
1035
+ const startTime = Date.now();
1036
+ fileLog('INFO', 'init() started', { projectRoot: this.projectRoot });
556
1037
 
557
1038
  if (this.isAvcProject()) {
558
1039
  // Project already initialized
559
- console.log(' AVC project already initialized');
560
- console.log('\nProject is ready to use.');
1040
+ fileLog('INFO', 'Project already initialized — skipping structure creation');
1041
+ sendOutput('Project already initialized.');
1042
+ sendOutput('');
561
1043
  return;
562
1044
  }
563
1045
 
1046
+ fileLog('INFO', 'New project — creating structure');
1047
+ fileLog('DEBUG', 'Creating components: .avc/, src/, worktrees/, avc.json, .env, .gitignore, VitePress');
1048
+
564
1049
  // Suppress all console output during initialization
565
1050
  const originalLog = console.log;
566
- console.log = () => {};
1051
+ console.log = () => { };
567
1052
 
1053
+ let initError = null;
568
1054
  try {
569
1055
  // Create project structure silently
570
1056
  this.createAvcFolder();
1057
+ this.createSrcFolder();
1058
+ this.createWorktreesFolder();
571
1059
  this.createAvcConfig();
572
1060
  this.createEnvFile();
573
1061
  this.addToGitignore();
574
- this.createVitePressSetup();
1062
+ this.createVitePressStructure();
1063
+ } catch (err) {
1064
+ initError = err;
575
1065
  } finally {
576
1066
  console.log = originalLog;
577
1067
  }
578
1068
 
579
- console.log('\n✅ AVC project initialized!\n');
580
- console.log('Next steps:');
581
- console.log(' 1. Add your API key(s) to .env file');
582
- console.log(' • ANTHROPIC_API_KEY for Claude');
583
- console.log(' • GEMINI_API_KEY for Gemini');
584
- console.log(' 2. Run /sponsor-call to start\n');
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}`);
1163
+ }
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
+ };
585
1180
  }
586
1181
 
587
1182
  /**
588
1183
  * Run Sponsor Call ceremony with pre-filled answers from REPL questionnaire
589
1184
  * Used when all answers are collected via REPL UI
590
1185
  */
591
- async sponsorCallWithAnswers(answers) {
592
- console.log('\n🎯 Sponsor Call Ceremony\n');
593
- console.log(`Project directory: ${this.projectRoot}\n`);
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
594
1196
 
595
1197
  // Check if project is initialized
596
1198
  if (!this.isAvcProject()) {
597
- console.log(' Project not initialized\n');
598
- console.log(' Please run /init first to create the project structure.\n');
1199
+ fileLog('ERROR', 'Project not initialized — aborting sponsor call');
1200
+ sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
599
1201
  return;
600
1202
  }
601
1203
 
602
1204
  const progressPath = this.sponsorCallProgressPath;
603
1205
 
604
- console.log('Starting Sponsor Call ceremony with provided answers...\n');
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');
605
1214
 
606
- // Count answers provided
1215
+ if (lastExecution && lastExecution.status === 'in-progress' && lastExecution.stage === 'llm-generation') {
1216
+ // Resume existing execution (from REPL flow)
1217
+ executionId = lastExecution.id;
1218
+ } else {
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)
607
1224
  const answeredCount = Object.values(answers).filter(v => v !== null && v !== '').length;
608
- console.log(`📊 Received ${answeredCount}/5 answers from questionnaire\n`);
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
+ });
609
1232
 
610
1233
  // Validate API key before starting ceremony
611
- console.log('Step 1/3: Validating API configuration...');
1234
+ fileLog('INFO', 'Validating API key before ceremony start');
612
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
+ });
613
1240
  if (!validationResult.valid) {
614
- console.log('\n❌ API Key Validation Failed\n');
615
- console.log(` ${validationResult.message}\n`);
616
- return;
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
+ };
617
1253
  }
618
1254
 
619
1255
  // Create progress with pre-filled answers
620
- console.log('Step 2/3: Processing questionnaire answers...');
621
1256
  const progress = {
622
1257
  stage: 'questionnaire',
623
1258
  totalQuestions: 5,
@@ -627,20 +1262,226 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
627
1262
  };
628
1263
  this.writeProgress(progress, progressPath);
629
1264
 
630
- // Generate project document with pre-filled answers
631
- console.log('Step 3/3: Generating project document...');
632
- await this.generateProjectDocument(progress, progressPath, true); // nonInteractive = true
1265
+ try {
1266
+ // Generate project document with pre-filled answers
1267
+ const result = await this.generateProjectDocument(progress, progressPath, true, progressCallback, options);
633
1268
 
634
- // Mark as completed and clean up
635
- progress.stage = 'completed';
636
- progress.lastUpdate = new Date().toISOString();
637
- this.writeProgress(progress, progressPath);
638
- this.clearProgress(progressPath);
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;
1465
+ }
1466
+
1467
+ if (!storyId) {
1468
+ fileLog('ERROR', 'No story ID provided — aborting seed');
1469
+ sendError('Story ID required. Usage: /seed <story-id>');
1470
+ return;
1471
+ }
639
1472
 
640
- console.log('\n✅ Project defined successfully!');
641
- console.log('\nNext steps:');
642
- console.log(' 1. Review .avc/project/doc.md for your project definition');
643
- console.log(' 2. Review .avc/avc.json configuration');
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` });
644
1485
  }
645
1486
 
646
1487
  /**
@@ -648,25 +1489,46 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
648
1489
  * Requires API keys to be configured in .env file
649
1490
  */
650
1491
  async sponsorCall() {
651
- console.log('\n🎯 Sponsor Call Ceremony\n');
652
- console.log(`Project directory: ${this.projectRoot}\n`);
1492
+ const header = getCeremonyHeader('sponsor-call');
1493
+ sendOutput('');
1494
+ sendOutput(header.title);
1495
+ sendOutput('');
1496
+ sendOutput(`Project directory: ${this.projectRoot}`);
1497
+ sendOutput('');
653
1498
 
654
1499
  // Check if running in REPL mode
655
1500
  const isReplMode = process.env.AVC_REPL_MODE === 'true';
656
1501
  if (isReplMode) {
657
1502
  // REPL mode is handled by repl-ink.js questionnaire display
658
1503
  // This code path shouldn't be reached from REPL
659
- console.log('⚠️ Unexpected: Ceremony called directly from REPL');
1504
+ sendWarning('Unexpected: Ceremony called directly from REPL');
660
1505
  return;
661
1506
  }
662
1507
 
663
1508
  // Check if project is initialized
664
1509
  if (!this.isAvcProject()) {
665
- console.log('❌ Project not initialized\n');
666
- console.log(' Please run /init first to create the project structure.\n');
1510
+ sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
1511
+ sendOutput('');
1512
+ sendOutput(MESSAGES.PROJECT_NOT_INITIALIZED.help);
1513
+ sendOutput('');
667
1514
  return; // Don't exit in REPL mode
668
1515
  }
669
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
+
670
1532
  let progress = null;
671
1533
  const progressPath = this.sponsorCallProgressPath;
672
1534
 
@@ -675,22 +1537,28 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
675
1537
  progress = this.readProgress(progressPath);
676
1538
 
677
1539
  if (progress && progress.stage !== 'completed') {
678
- console.log('⚠️ Found incomplete ceremony from previous session');
679
- console.log(` Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`);
680
- console.log(` Stage: ${progress.stage}`);
681
- console.log(` Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`);
682
- console.log('\n▶️ Continuing from where you left off...\n');
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('');
683
1547
  }
684
1548
  } else {
685
1549
  // Fresh start
686
- console.log('Starting Sponsor Call ceremony...\n');
1550
+ sendOutput('Starting Sponsor Call ceremony...');
1551
+ sendOutput('');
687
1552
  }
688
1553
 
689
1554
  // Validate API key before starting ceremony
690
1555
  const validationResult = await this.validateProviderApiKey();
691
1556
  if (!validationResult.valid) {
692
- console.log('\n❌ API Key Validation Failed\n');
693
- console.log(` ${validationResult.message}\n`);
1557
+ sendOutput('');
1558
+ sendError('API Key Validation Failed');
1559
+ sendOutput('');
1560
+ sendIndented(validationResult.message, 1);
1561
+ sendOutput('');
694
1562
  return; // Don't exit in REPL mode
695
1563
  }
696
1564
 
@@ -715,30 +1583,44 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
715
1583
  this.writeProgress(progress, progressPath);
716
1584
  this.clearProgress(progressPath);
717
1585
 
718
- console.log('\n✅ Project defined successfully!');
719
- console.log('\nNext steps:');
720
- console.log(' 1. Review .avc/project/doc.md for your project definition');
721
- console.log(' 2. Review .avc/avc.json configuration');
722
- console.log(' 3. Create your project context and work items');
723
- console.log(' 4. 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);
724
1594
  }
725
1595
 
726
1596
  /**
727
1597
  * Display current project status
728
1598
  */
729
1599
  status() {
730
- console.log('\n📊 AVC Project Status\n');
731
- console.log(`Project directory: ${this.projectRoot}`);
732
- console.log(`Project name: ${this.getProjectName()}\n`);
733
-
734
- console.log('Components:');
735
- console.log(` .avc/ folder: ${this.hasAvcFolder() ? '✓' : '✗'}`);
736
- console.log(` avc.json: ${this.hasAvcConfig() ? '✓' : '✗'}`);
737
-
738
- console.log(`\nStatus: ${this.isAvcProject() ? '✅ Initialized' : '⚠️ Not initialized'}`);
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();
1608
+
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
+ });
739
1617
 
740
- if (!this.isAvcProject()) {
741
- console.log('\nRun "avc init" to initialize the project.');
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.`);
742
1624
  }
743
1625
  }
744
1626
 
@@ -747,40 +1629,63 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
747
1629
  * Requires confirmation by typing "delete all"
748
1630
  */
749
1631
  async remove() {
750
- console.log('\n🗑️ Remove AVC Project Structure\n');
751
- console.log(`Project directory: ${this.projectRoot}\n`);
1632
+ sendSectionHeader('Remove AVC Project Structure');
1633
+ sendOutput('');
1634
+ sendOutput(`Project directory: ${this.projectRoot}`);
1635
+ sendOutput('');
752
1636
 
753
1637
  // Check if project is initialized
754
1638
  if (!this.isAvcProject()) {
755
- console.log('⚠️ No AVC project found in this directory.\n');
756
- console.log('Nothing to remove.\n');
1639
+ sendWarning('No AVC project found in this directory.');
1640
+ sendOutput('Nothing to remove.');
1641
+ sendOutput('');
757
1642
  return;
758
1643
  }
759
1644
 
760
1645
  // Show what will be deleted
761
- console.log('⚠️ WARNING: This is a DESTRUCTIVE operation!\n');
762
- console.log('The following will be PERMANENTLY DELETED:\n');
1646
+ sendWarning('WARNING: This is a DESTRUCTIVE operation!');
1647
+ sendOutput('');
1648
+ sendOutput('The following will be PERMANENTLY DELETED:');
1649
+ sendOutput('');
763
1650
 
764
1651
  // List contents of .avc folder
765
1652
  const avcContents = this.getAvcContents();
766
1653
  if (avcContents.length > 0) {
767
- console.log('📁 .avc/ folder contents:');
1654
+ sendOutput('.avc/ folder contents:');
1655
+ sendOutput('');
768
1656
  avcContents.forEach(item => {
769
- console.log(` • ${item}`);
1657
+ sendIndented(`• ${item}`, 1);
770
1658
  });
771
- console.log('');
1659
+ sendOutput('');
772
1660
  }
773
1661
 
774
- console.log('All project definitions, epics, stories, tasks, and documentation will be lost.');
775
- console.log('All VitePress documentation will be deleted.');
776
- console.log('This action CANNOT be undone.\n');
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('');
777
1666
 
778
1667
  // Check for .env file
779
1668
  const envPath = path.join(this.projectRoot, '.env');
780
1669
  const hasEnvFile = fs.existsSync(envPath);
781
1670
  if (hasEnvFile) {
782
- console.log('ℹ️ Note: The .env file will NOT be deleted.');
783
- console.log(' You may want to manually remove API keys if no longer needed.\n');
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('');
784
1689
  }
785
1690
 
786
1691
  // Check if running in REPL mode
@@ -789,16 +1694,16 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
789
1694
  if (isReplMode) {
790
1695
  // In REPL mode, interactive confirmation is handled by repl-ink.js
791
1696
  // This code path shouldn't be reached from REPL
792
- console.log('⚠️ Unexpected: Remove called directly from REPL');
793
- console.log('Interactive confirmation should be handled by REPL interface.');
1697
+ sendWarning('Unexpected: Remove called directly from REPL');
1698
+ sendOutput('Interactive confirmation should be handled by REPL interface.');
794
1699
  return;
795
1700
  }
796
1701
 
797
- console.log('─'.repeat(60));
798
- console.log('To confirm deletion, type exactly: delete all');
799
- console.log('To cancel, type anything else or press Ctrl+C');
800
- console.log('─'.repeat(60));
801
- console.log('');
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('');
802
1707
 
803
1708
  // Create readline interface for confirmation
804
1709
  const readline = await import('readline');
@@ -810,11 +1715,12 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
810
1715
  return new Promise((resolve) => {
811
1716
  rl.question('Confirmation: ', (answer) => {
812
1717
  rl.close();
813
- console.log('');
1718
+ sendOutput('');
814
1719
 
815
1720
  if (answer.trim() === 'delete all') {
816
1721
  // Proceed with deletion
817
- console.log('🗑️ Deleting AVC project structure...\n');
1722
+ sendOutput('Deleting AVC project structure...');
1723
+ sendOutput('');
818
1724
 
819
1725
  try {
820
1726
  // Get list of what's being deleted before deletion
@@ -823,39 +1729,59 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
823
1729
  // Delete .avc folder
824
1730
  fs.rmSync(this.avcDir, { recursive: true, force: true });
825
1731
 
826
- console.log('Successfully deleted:\n');
827
- console.log(' 📁 .avc/ folder and all contents:');
1732
+ sendSuccess('Successfully deleted:');
1733
+ sendOutput('');
1734
+ sendOutput('.avc/ folder and all contents:');
1735
+ sendOutput('');
828
1736
  deletedItems.forEach(item => {
829
- console.log(` • ${item}`);
1737
+ sendIndented(`• ${item}`, 3);
830
1738
  });
831
- console.log('');
832
-
833
- // Reminder about .env file
834
- if (hasEnvFile) {
835
- console.log('ℹ️ Manual cleanup reminder:\n');
836
- console.log(' The .env file was NOT deleted and still contains:');
837
- console.log(' • ANTHROPIC_API_KEY');
838
- console.log(' • GEMINI_API_KEY');
839
- console.log(' (and any other API keys you added)\n');
840
- console.log(' If these API keys are not used elsewhere in your project,');
841
- console.log(' you may want to manually delete the .env file or remove');
842
- console.log(' the unused keys.\n');
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
+
843
1765
  }
844
1766
 
845
- console.log('AVC project structure has been completely removed.\n');
846
- console.log('You can re-initialize anytime by running /init\n');
1767
+ sendSuccess('AVC project structure has been completely removed.');
1768
+ sendOutput('You can re-initialize anytime by running /init');
1769
+ sendOutput('');
847
1770
 
848
1771
  resolve();
849
1772
  } catch (error) {
850
- console.log(`❌ Error during deletion: ${error.message}\n`);
851
- console.log('The .avc folder may be partially deleted.');
852
- console.log('You may need to manually remove it.\n');
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('');
853
1778
  resolve();
854
1779
  }
855
1780
  } else {
856
1781
  // Cancellation
857
- console.log('Operation cancelled.\n');
858
- console.log('No files were deleted.\n');
1782
+ sendError('Operation cancelled.');
1783
+ sendOutput('No files were deleted.');
1784
+ sendOutput('');
859
1785
  resolve();
860
1786
  }
861
1787
  });
@@ -895,6 +1821,114 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
895
1821
  return contents;
896
1822
  }
897
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;
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
+
898
1932
  /**
899
1933
  * Recursively count items in a directory
900
1934
  */
@@ -939,11 +1973,14 @@ if (import.meta.url === `file://${process.argv[1]}`) {
939
1973
  case 'status':
940
1974
  initiator.status();
941
1975
  break;
1976
+ case 'models':
1977
+ initiator.models();
1978
+ break;
942
1979
  case 'remove':
943
1980
  initiator.remove();
944
1981
  break;
945
1982
  default:
946
- console.log('Unknown command. Available commands: init, sponsor-call, status, remove');
1983
+ sendError('Unknown command. Available commands: init, sponsor-call, status, models, remove');
947
1984
  process.exit(1);
948
1985
  }
949
1986
  }