@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.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +129 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/epic-story-decomposer.md +280 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +79 -0
- package/cli/agents/mission-scope-validator.md +112 -0
- package/cli/agents/project-context-extractor.md +107 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/solver-epic-api.json +15 -0
- package/cli/agents/solver-epic-api.md +39 -0
- package/cli/agents/solver-epic-backend.json +15 -0
- package/cli/agents/solver-epic-backend.md +39 -0
- package/cli/agents/solver-epic-cloud.json +15 -0
- package/cli/agents/solver-epic-cloud.md +39 -0
- package/cli/agents/solver-epic-data.json +15 -0
- package/cli/agents/solver-epic-data.md +39 -0
- package/cli/agents/solver-epic-database.json +15 -0
- package/cli/agents/solver-epic-database.md +39 -0
- package/cli/agents/solver-epic-developer.json +15 -0
- package/cli/agents/solver-epic-developer.md +39 -0
- package/cli/agents/solver-epic-devops.json +15 -0
- package/cli/agents/solver-epic-devops.md +39 -0
- package/cli/agents/solver-epic-frontend.json +15 -0
- package/cli/agents/solver-epic-frontend.md +39 -0
- package/cli/agents/solver-epic-mobile.json +15 -0
- package/cli/agents/solver-epic-mobile.md +39 -0
- package/cli/agents/solver-epic-qa.json +15 -0
- package/cli/agents/solver-epic-qa.md +39 -0
- package/cli/agents/solver-epic-security.json +15 -0
- package/cli/agents/solver-epic-security.md +39 -0
- package/cli/agents/solver-epic-solution-architect.json +15 -0
- package/cli/agents/solver-epic-solution-architect.md +39 -0
- package/cli/agents/solver-epic-test-architect.json +15 -0
- package/cli/agents/solver-epic-test-architect.md +39 -0
- package/cli/agents/solver-epic-ui.json +15 -0
- package/cli/agents/solver-epic-ui.md +39 -0
- package/cli/agents/solver-epic-ux.json +15 -0
- package/cli/agents/solver-epic-ux.md +39 -0
- package/cli/agents/solver-story-api.json +15 -0
- package/cli/agents/solver-story-api.md +39 -0
- package/cli/agents/solver-story-backend.json +15 -0
- package/cli/agents/solver-story-backend.md +39 -0
- package/cli/agents/solver-story-cloud.json +15 -0
- package/cli/agents/solver-story-cloud.md +39 -0
- package/cli/agents/solver-story-data.json +15 -0
- package/cli/agents/solver-story-data.md +39 -0
- package/cli/agents/solver-story-database.json +15 -0
- package/cli/agents/solver-story-database.md +39 -0
- package/cli/agents/solver-story-developer.json +15 -0
- package/cli/agents/solver-story-developer.md +39 -0
- package/cli/agents/solver-story-devops.json +15 -0
- package/cli/agents/solver-story-devops.md +39 -0
- package/cli/agents/solver-story-frontend.json +15 -0
- package/cli/agents/solver-story-frontend.md +39 -0
- package/cli/agents/solver-story-mobile.json +15 -0
- package/cli/agents/solver-story-mobile.md +39 -0
- package/cli/agents/solver-story-qa.json +15 -0
- package/cli/agents/solver-story-qa.md +39 -0
- package/cli/agents/solver-story-security.json +15 -0
- package/cli/agents/solver-story-security.md +39 -0
- package/cli/agents/solver-story-solution-architect.json +15 -0
- package/cli/agents/solver-story-solution-architect.md +39 -0
- package/cli/agents/solver-story-test-architect.json +15 -0
- package/cli/agents/solver-story-test-architect.md +39 -0
- package/cli/agents/solver-story-ui.json +15 -0
- package/cli/agents/solver-story-ui.md +39 -0
- package/cli/agents/solver-story-ux.json +15 -0
- package/cli/agents/solver-story-ux.md +39 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +152 -0
- package/cli/agents/validator-documentation.md +453 -0
- package/cli/agents/validator-epic-api.json +93 -0
- package/cli/agents/validator-epic-api.md +137 -0
- package/cli/agents/validator-epic-backend.json +93 -0
- package/cli/agents/validator-epic-backend.md +130 -0
- package/cli/agents/validator-epic-cloud.json +93 -0
- package/cli/agents/validator-epic-cloud.md +137 -0
- package/cli/agents/validator-epic-data.json +93 -0
- package/cli/agents/validator-epic-data.md +130 -0
- package/cli/agents/validator-epic-database.json +93 -0
- package/cli/agents/validator-epic-database.md +137 -0
- package/cli/agents/validator-epic-developer.json +74 -0
- package/cli/agents/validator-epic-developer.md +153 -0
- package/cli/agents/validator-epic-devops.json +74 -0
- package/cli/agents/validator-epic-devops.md +153 -0
- package/cli/agents/validator-epic-frontend.json +74 -0
- package/cli/agents/validator-epic-frontend.md +153 -0
- package/cli/agents/validator-epic-mobile.json +93 -0
- package/cli/agents/validator-epic-mobile.md +130 -0
- package/cli/agents/validator-epic-qa.json +93 -0
- package/cli/agents/validator-epic-qa.md +130 -0
- package/cli/agents/validator-epic-security.json +74 -0
- package/cli/agents/validator-epic-security.md +154 -0
- package/cli/agents/validator-epic-solution-architect.json +74 -0
- package/cli/agents/validator-epic-solution-architect.md +156 -0
- package/cli/agents/validator-epic-test-architect.json +93 -0
- package/cli/agents/validator-epic-test-architect.md +130 -0
- package/cli/agents/validator-epic-ui.json +93 -0
- package/cli/agents/validator-epic-ui.md +130 -0
- package/cli/agents/validator-epic-ux.json +93 -0
- package/cli/agents/validator-epic-ux.md +130 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/agents/validator-story-api.json +104 -0
- package/cli/agents/validator-story-api.md +152 -0
- package/cli/agents/validator-story-backend.json +104 -0
- package/cli/agents/validator-story-backend.md +152 -0
- package/cli/agents/validator-story-cloud.json +104 -0
- package/cli/agents/validator-story-cloud.md +152 -0
- package/cli/agents/validator-story-data.json +104 -0
- package/cli/agents/validator-story-data.md +152 -0
- package/cli/agents/validator-story-database.json +104 -0
- package/cli/agents/validator-story-database.md +152 -0
- package/cli/agents/validator-story-developer.json +104 -0
- package/cli/agents/validator-story-developer.md +152 -0
- package/cli/agents/validator-story-devops.json +104 -0
- package/cli/agents/validator-story-devops.md +152 -0
- package/cli/agents/validator-story-frontend.json +104 -0
- package/cli/agents/validator-story-frontend.md +152 -0
- package/cli/agents/validator-story-mobile.json +104 -0
- package/cli/agents/validator-story-mobile.md +152 -0
- package/cli/agents/validator-story-qa.json +104 -0
- package/cli/agents/validator-story-qa.md +152 -0
- package/cli/agents/validator-story-security.json +104 -0
- package/cli/agents/validator-story-security.md +152 -0
- package/cli/agents/validator-story-solution-architect.json +104 -0
- package/cli/agents/validator-story-solution-architect.md +152 -0
- package/cli/agents/validator-story-test-architect.json +104 -0
- package/cli/agents/validator-story-test-architect.md +152 -0
- package/cli/agents/validator-story-ui.json +104 -0
- package/cli/agents/validator-story-ui.md +152 -0
- package/cli/agents/validator-story-ux.json +104 -0
- package/cli/agents/validator-story-ux.md +152 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +1174 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1311 -274
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +83 -1
- package/cli/llm-gemini.js +85 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +240 -3
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- package/cli/message-constants.js +58 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +297 -0
- package/cli/model-pricing.js +169 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +269 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/repl-ink.js +4988 -1217
- package/cli/repl-old.js +4 -4
- package/cli/seed-processor.js +792 -0
- package/cli/sprint-planning-processor.js +1813 -0
- package/cli/template-processor.js +2102 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +520 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +605 -0
- package/cli/verification-tracker.js +563 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
- package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +622 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +353 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +118 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +401 -0
- package/kanban/client/src/lib/status-grouping.js +144 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +115 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +516 -0
- package/kanban/server/routes/ceremony.js +305 -0
- package/kanban/server/routes/costs.js +157 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +303 -0
- package/kanban/server/routes/websocket.js +276 -0
- package/kanban/server/routes/work-items.js +347 -0
- package/kanban/server/services/CeremonyService.js +1190 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/sponsor-call-worker.js +84 -0
- package/kanban/server/workers/sprint-planning-worker.js +130 -0
- package/package.json +18 -5
- 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
|
-
|
|
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: '
|
|
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
|
-
|
|
145
|
-
|
|
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(
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
485
|
-
const
|
|
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
|
-
|
|
494
|
-
|
|
897
|
+
// Validate main provider
|
|
898
|
+
const mainEnvVar = envVarMap[mainProvider];
|
|
899
|
+
if (!mainEnvVar) {
|
|
495
900
|
return {
|
|
496
901
|
valid: false,
|
|
497
|
-
message: `Unknown provider "${
|
|
902
|
+
message: `Unknown provider "${mainProvider}".\n Supported providers: claude, gemini, openai`
|
|
498
903
|
};
|
|
499
904
|
}
|
|
500
905
|
|
|
501
|
-
|
|
502
|
-
if (!process.env[requiredEnvVar]) {
|
|
906
|
+
if (!process.env[mainEnvVar]) {
|
|
503
907
|
return {
|
|
504
908
|
valid: false,
|
|
505
|
-
message: `${
|
|
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(
|
|
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: `${
|
|
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
|
|
926
|
+
const parsedError = this.parseApiError(result.error || 'Unknown error');
|
|
524
927
|
return {
|
|
525
928
|
valid: false,
|
|
526
|
-
message: `${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
|
|
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
|
-
|
|
560
|
-
|
|
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.
|
|
1062
|
+
this.createVitePressStructure();
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
initError = err;
|
|
575
1065
|
} finally {
|
|
576
1066
|
console.log = originalLog;
|
|
577
1067
|
}
|
|
578
1068
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
593
|
-
|
|
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
|
-
|
|
598
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1265
|
+
try {
|
|
1266
|
+
// Generate project document with pre-filled answers
|
|
1267
|
+
const result = await this.generateProjectDocument(progress, progressPath, true, progressCallback, options);
|
|
633
1268
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
652
|
-
|
|
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
|
-
|
|
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
|
-
|
|
666
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
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
|
-
|
|
693
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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 (
|
|
741
|
-
|
|
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
|
-
|
|
751
|
-
|
|
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
|
-
|
|
756
|
-
|
|
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
|
-
|
|
762
|
-
|
|
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
|
-
|
|
1654
|
+
sendOutput('.avc/ folder contents:');
|
|
1655
|
+
sendOutput('');
|
|
768
1656
|
avcContents.forEach(item => {
|
|
769
|
-
|
|
1657
|
+
sendIndented(`• ${item}`, 1);
|
|
770
1658
|
});
|
|
771
|
-
|
|
1659
|
+
sendOutput('');
|
|
772
1660
|
}
|
|
773
1661
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
783
|
-
|
|
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
|
-
|
|
793
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
1718
|
+
sendOutput('');
|
|
814
1719
|
|
|
815
1720
|
if (answer.trim() === 'delete all') {
|
|
816
1721
|
// Proceed with deletion
|
|
817
|
-
|
|
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
|
-
|
|
827
|
-
|
|
1732
|
+
sendSuccess('Successfully deleted:');
|
|
1733
|
+
sendOutput('');
|
|
1734
|
+
sendOutput('.avc/ folder and all contents:');
|
|
1735
|
+
sendOutput('');
|
|
828
1736
|
deletedItems.forEach(item => {
|
|
829
|
-
|
|
1737
|
+
sendIndented(`• ${item}`, 3);
|
|
830
1738
|
});
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
// Reminder about
|
|
834
|
-
if (hasEnvFile) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
846
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
|
|
858
|
-
|
|
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
|
-
|
|
1983
|
+
sendError('Unknown command. Available commands: init, sponsor-call, status, models, remove');
|
|
947
1984
|
process.exit(1);
|
|
948
1985
|
}
|
|
949
1986
|
}
|