@agile-vibe-coding/avc 0.1.1 → 0.3.1
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 +152 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -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/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -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 +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -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/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -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 +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -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/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -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/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -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 +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -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-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -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 +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -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 +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -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 +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -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 +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -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 +384 -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 +177 -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 +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -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 +123 -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 +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -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/TaskRunnerService.js +261 -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/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
package/cli/init.js
CHANGED
|
@@ -6,6 +6,77 @@ 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
|
+
* Trace log written directly to /tmp/avc-init.log so we can diagnose mkdir
|
|
16
|
+
* failures that occur before .avc/ exists (where normal logging goes).
|
|
17
|
+
*/
|
|
18
|
+
function initTrace(message, data = null) {
|
|
19
|
+
try {
|
|
20
|
+
const ts = new Date().toISOString();
|
|
21
|
+
const line = data
|
|
22
|
+
? `[${ts}] ${message} ${JSON.stringify(data)}\n`
|
|
23
|
+
: `[${ts}] ${message}\n`;
|
|
24
|
+
fs.appendFileSync('/tmp/avc-init.log', line);
|
|
25
|
+
} catch { /* never let tracing break init */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reliable recursive mkdir for WSL2 /mnt/ Windows paths.
|
|
30
|
+
*
|
|
31
|
+
* Two-layer strategy:
|
|
32
|
+
* 1. Walk the tree manually and call plain mkdirSync() for each segment
|
|
33
|
+
* (avoids Node's { recursive:true } which can race on DrvFS/9P).
|
|
34
|
+
* 2. If mkdirSync() still fails with ENOENT (VFS dentry-cache stale —
|
|
35
|
+
* stat() says parent exists but mkdir() syscall disagrees), fall back
|
|
36
|
+
* to a subprocess `mkdir -p`. The subprocess starts with a clean
|
|
37
|
+
* dentry cache and sees the filesystem state the kernel actually has.
|
|
38
|
+
*/
|
|
39
|
+
function mkdirp(dirPath) {
|
|
40
|
+
initTrace('mkdirp enter', { dirPath, exists: fs.existsSync(dirPath) });
|
|
41
|
+
if (fs.existsSync(dirPath)) return;
|
|
42
|
+
const parent = path.dirname(dirPath);
|
|
43
|
+
if (parent !== dirPath) mkdirp(parent);
|
|
44
|
+
try {
|
|
45
|
+
fs.mkdirSync(dirPath);
|
|
46
|
+
initTrace('mkdirp created via mkdirSync', { dirPath });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err.code === 'EEXIST') return;
|
|
49
|
+
if (err.code === 'ENOENT') {
|
|
50
|
+
// WSL2/DrvFS: VFS dentry cache reports parent as present but the 9P
|
|
51
|
+
// server disagrees at mkdir() time. Spawn a fresh process whose cache
|
|
52
|
+
// is clean — shell mkdir -p goes straight to the 9P server correctly.
|
|
53
|
+
initTrace('mkdirp mkdirSync ENOENT — falling back to shell mkdir -p', { dirPath, parentExists: fs.existsSync(parent) });
|
|
54
|
+
try {
|
|
55
|
+
execSync(`mkdir -p ${JSON.stringify(dirPath)}`, { stdio: 'pipe' });
|
|
56
|
+
initTrace('mkdirp created via shell mkdir -p', { dirPath });
|
|
57
|
+
return;
|
|
58
|
+
} catch (shellErr) {
|
|
59
|
+
initTrace('mkdirp shell mkdir -p also failed', { dirPath, shellErr: shellErr.message });
|
|
60
|
+
if (!fs.existsSync(dirPath)) throw err; // throw original ENOENT
|
|
61
|
+
return; // shell failed but dir now exists — tolerate
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Write a structured entry to the active command log file only.
|
|
70
|
+
* Uses [DEBUG] prefix so ConsoleOutputManager routes to file, never terminal.
|
|
71
|
+
*/
|
|
72
|
+
function fileLog(level, message, data = null) {
|
|
73
|
+
const ts = new Date().toISOString();
|
|
74
|
+
if (data !== null) {
|
|
75
|
+
console.log(`[DEBUG] [${level}] [${ts}] ${message}`, JSON.stringify(data, null, 2));
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`[DEBUG] [${level}] [${ts}] ${message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
9
80
|
|
|
10
81
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
82
|
const __dirname = path.dirname(__filename);
|
|
@@ -23,11 +94,16 @@ class ProjectInitiator {
|
|
|
23
94
|
constructor(projectRoot = null) {
|
|
24
95
|
this.projectRoot = projectRoot || process.cwd();
|
|
25
96
|
this.avcDir = path.join(this.projectRoot, '.avc');
|
|
97
|
+
this.srcDir = path.join(this.projectRoot, 'src');
|
|
98
|
+
this.worktreesDir = path.join(this.avcDir, 'worktrees');
|
|
26
99
|
this.avcConfigPath = path.join(this.avcDir, 'avc.json');
|
|
27
100
|
// Progress files are ceremony-specific
|
|
28
101
|
this.initProgressPath = path.join(this.avcDir, 'init-progress.json');
|
|
29
102
|
this.sponsorCallProgressPath = path.join(this.avcDir, 'sponsor-call-progress.json');
|
|
30
103
|
|
|
104
|
+
// Template processor for token usage tracking
|
|
105
|
+
this._lastTemplateProcessor = null;
|
|
106
|
+
|
|
31
107
|
// Load environment variables from project .env file
|
|
32
108
|
// Use override: true to reload even if already set (user may have edited .env)
|
|
33
109
|
dotenv.config({
|
|
@@ -103,11 +179,45 @@ class ProjectInitiator {
|
|
|
103
179
|
*/
|
|
104
180
|
createAvcFolder() {
|
|
105
181
|
if (!this.hasAvcFolder()) {
|
|
106
|
-
|
|
107
|
-
|
|
182
|
+
mkdirp(this.avcDir);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if src folder exists
|
|
190
|
+
*/
|
|
191
|
+
hasSrcFolder() {
|
|
192
|
+
return fs.existsSync(this.srcDir);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create src folder for AVC-managed code
|
|
197
|
+
*/
|
|
198
|
+
createSrcFolder() {
|
|
199
|
+
if (!this.hasSrcFolder()) {
|
|
200
|
+
mkdirp(this.srcDir);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if worktrees folder exists
|
|
208
|
+
*/
|
|
209
|
+
hasWorktreesFolder() {
|
|
210
|
+
return fs.existsSync(this.worktreesDir);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create worktrees folder for git worktree management
|
|
215
|
+
*/
|
|
216
|
+
createWorktreesFolder() {
|
|
217
|
+
if (!this.hasWorktreesFolder()) {
|
|
218
|
+
mkdirp(this.worktreesDir);
|
|
108
219
|
return true;
|
|
109
220
|
}
|
|
110
|
-
console.log('✓ .avc/ folder already exists');
|
|
111
221
|
return false;
|
|
112
222
|
}
|
|
113
223
|
|
|
@@ -123,7 +233,6 @@ class ProjectInitiator {
|
|
|
123
233
|
framework: 'avc',
|
|
124
234
|
created: new Date().toISOString(),
|
|
125
235
|
settings: {
|
|
126
|
-
contextScopes: ['epic', 'story', 'task', 'subtask'],
|
|
127
236
|
workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
|
|
128
237
|
agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
|
|
129
238
|
documentation: {
|
|
@@ -132,20 +241,569 @@ class ProjectInitiator {
|
|
|
132
241
|
ceremonies: [
|
|
133
242
|
{
|
|
134
243
|
name: 'sponsor-call',
|
|
135
|
-
defaultModel: 'claude-sonnet-4-5-20250929',
|
|
136
244
|
provider: 'claude',
|
|
245
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
246
|
+
stages: {
|
|
247
|
+
suggestions: {
|
|
248
|
+
provider: 'claude',
|
|
249
|
+
model: 'claude-sonnet-4-6'
|
|
250
|
+
},
|
|
251
|
+
documentation: {
|
|
252
|
+
provider: 'claude',
|
|
253
|
+
model: 'claude-sonnet-4-6'
|
|
254
|
+
},
|
|
255
|
+
'architecture-recommendation': {
|
|
256
|
+
provider: 'claude',
|
|
257
|
+
model: 'claude-sonnet-4-6'
|
|
258
|
+
},
|
|
259
|
+
'question-prefilling': {
|
|
260
|
+
provider: 'claude',
|
|
261
|
+
model: 'claude-sonnet-4-6'
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
providerPresets: {
|
|
265
|
+
claude: {
|
|
266
|
+
provider: 'claude', defaultModel: 'claude-sonnet-4-6',
|
|
267
|
+
stages: {
|
|
268
|
+
suggestions: { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
269
|
+
documentation: { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
270
|
+
'architecture-recommendation': { provider: 'claude', model: 'claude-opus-4-6' },
|
|
271
|
+
'question-prefilling': { provider: 'claude', model: 'claude-haiku-4-5-20251001' }
|
|
272
|
+
},
|
|
273
|
+
validation: {
|
|
274
|
+
provider: 'claude', model: 'claude-haiku-4-5-20251001',
|
|
275
|
+
documentation: { provider: 'claude', model: 'claude-haiku-4-5-20251001' },
|
|
276
|
+
refinement: { provider: 'claude', model: 'claude-sonnet-4-6' }
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
gemini: {
|
|
280
|
+
provider: 'gemini', defaultModel: 'gemini-2.5-flash',
|
|
281
|
+
stages: {
|
|
282
|
+
suggestions: { provider: 'gemini', model: 'gemini-2.5-flash' },
|
|
283
|
+
documentation: { provider: 'gemini', model: 'gemini-2.5-flash' },
|
|
284
|
+
'architecture-recommendation': { provider: 'gemini', model: 'gemini-2.5-pro' },
|
|
285
|
+
'question-prefilling': { provider: 'gemini', model: 'gemini-2.5-flash-lite' }
|
|
286
|
+
},
|
|
287
|
+
validation: {
|
|
288
|
+
provider: 'gemini', model: 'gemini-2.5-flash-lite',
|
|
289
|
+
documentation: { provider: 'gemini', model: 'gemini-2.5-flash-lite' },
|
|
290
|
+
refinement: { provider: 'gemini', model: 'gemini-2.5-flash' }
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
openai: {
|
|
294
|
+
provider: 'openai', defaultModel: 'gpt-5.4',
|
|
295
|
+
stages: {
|
|
296
|
+
suggestions: { provider: 'openai', model: 'gpt-5.4' },
|
|
297
|
+
documentation: { provider: 'openai', model: 'gpt-5.4' },
|
|
298
|
+
'architecture-recommendation': { provider: 'openai', model: 'gpt-5.4' },
|
|
299
|
+
'question-prefilling': { provider: 'openai', model: 'gpt-5-mini' }
|
|
300
|
+
},
|
|
301
|
+
validation: {
|
|
302
|
+
provider: 'openai', model: 'gpt-5.4',
|
|
303
|
+
documentation: { provider: 'openai', model: 'gpt-5.4' },
|
|
304
|
+
refinement: { provider: 'openai', model: 'gpt-5.4' }
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
xiaomi: {
|
|
308
|
+
provider: 'xiaomi', defaultModel: 'mimo-v2-pro',
|
|
309
|
+
stages: {
|
|
310
|
+
suggestions: { provider: 'xiaomi', model: 'mimo-v2-flash' },
|
|
311
|
+
documentation: { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
312
|
+
'architecture-recommendation': { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
313
|
+
'question-prefilling': { provider: 'xiaomi', model: 'mimo-v2-pro' }
|
|
314
|
+
},
|
|
315
|
+
validation: {
|
|
316
|
+
provider: 'xiaomi', model: 'mimo-v2-flash',
|
|
317
|
+
documentation: { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
318
|
+
refinement: { provider: 'xiaomi', model: 'mimo-v2-pro' }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
},
|
|
137
322
|
agents: [
|
|
138
323
|
{
|
|
139
|
-
name: 'documentation',
|
|
140
|
-
instruction: 'documentation.md',
|
|
141
|
-
stage: '
|
|
142
|
-
}
|
|
324
|
+
name: 'project-documentation-creator',
|
|
325
|
+
instruction: 'project-documentation-creator.md',
|
|
326
|
+
stage: 'documentation-generation'
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: 'validator-documentation',
|
|
330
|
+
instruction: 'validator-documentation.md',
|
|
331
|
+
stage: 'documentation-validation',
|
|
332
|
+
group: 'validators'
|
|
333
|
+
},
|
|
143
334
|
],
|
|
144
|
-
|
|
145
|
-
|
|
335
|
+
validation: {
|
|
336
|
+
enabled: true,
|
|
337
|
+
maxIterations: 100,
|
|
338
|
+
acceptanceThreshold: 95,
|
|
339
|
+
skipOnCriticalIssues: false,
|
|
340
|
+
provider: 'claude',
|
|
341
|
+
model: 'claude-haiku-4-5-20251001',
|
|
342
|
+
documentation: {
|
|
343
|
+
provider: 'claude',
|
|
344
|
+
model: 'claude-haiku-4-5-20251001'
|
|
345
|
+
},
|
|
346
|
+
refinement: {
|
|
347
|
+
provider: 'claude',
|
|
348
|
+
model: 'claude-sonnet-4-6'
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
crossValidation: {
|
|
352
|
+
enabled: true,
|
|
353
|
+
maxIterations: 3
|
|
146
354
|
}
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'sprint-planning',
|
|
358
|
+
provider: 'claude',
|
|
359
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
360
|
+
stages: {
|
|
361
|
+
decomposition: {
|
|
362
|
+
provider: 'claude',
|
|
363
|
+
model: 'claude-opus-4-6'
|
|
364
|
+
},
|
|
365
|
+
validation: {
|
|
366
|
+
provider: 'claude',
|
|
367
|
+
model: 'claude-sonnet-4-6',
|
|
368
|
+
useContextualSelection: true,
|
|
369
|
+
epicConcurrency: 2,
|
|
370
|
+
concurrency: 5,
|
|
371
|
+
maxFixAttempts: 3
|
|
372
|
+
},
|
|
373
|
+
'context-generation': {
|
|
374
|
+
provider: 'claude',
|
|
375
|
+
model: 'claude-sonnet-4-6'
|
|
376
|
+
},
|
|
377
|
+
'doc-generation': {
|
|
378
|
+
provider: 'claude',
|
|
379
|
+
model: 'claude-sonnet-4-6'
|
|
380
|
+
},
|
|
381
|
+
enrichment: {
|
|
382
|
+
provider: 'claude',
|
|
383
|
+
model: 'claude-sonnet-4-6'
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
providerPresets: {
|
|
387
|
+
claude: {
|
|
388
|
+
provider: 'claude', defaultModel: 'claude-sonnet-4-6',
|
|
389
|
+
stages: {
|
|
390
|
+
decomposition: { provider: 'claude', model: 'claude-opus-4-6' },
|
|
391
|
+
validation: { provider: 'claude', model: 'claude-sonnet-4-6', useContextualSelection: true, epicConcurrency: 2, concurrency: 5, maxFixAttempts: 3 },
|
|
392
|
+
'context-generation': { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
393
|
+
'doc-generation': { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
394
|
+
enrichment: { provider: 'claude', model: 'claude-sonnet-4-6' }
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
gemini: {
|
|
398
|
+
provider: 'gemini', defaultModel: 'gemini-2.5-flash',
|
|
399
|
+
stages: {
|
|
400
|
+
decomposition: { provider: 'gemini', model: 'gemini-2.5-pro' },
|
|
401
|
+
validation: { provider: 'gemini', model: 'gemini-2.5-flash', useContextualSelection: true, epicConcurrency: 2, concurrency: 5, maxFixAttempts: 3 },
|
|
402
|
+
'context-generation': { provider: 'gemini', model: 'gemini-2.5-flash' },
|
|
403
|
+
'doc-generation': { provider: 'gemini', model: 'gemini-2.5-flash' },
|
|
404
|
+
enrichment: { provider: 'gemini', model: 'gemini-2.5-flash' }
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
openai: {
|
|
408
|
+
provider: 'openai', defaultModel: 'gpt-5.4',
|
|
409
|
+
stages: {
|
|
410
|
+
decomposition: { provider: 'openai', model: 'gpt-5.4' },
|
|
411
|
+
validation: { provider: 'openai', model: 'gpt-5.4', useContextualSelection: true, epicConcurrency: 2, concurrency: 5, maxFixAttempts: 3 },
|
|
412
|
+
'context-generation': { provider: 'openai', model: 'gpt-5.4' },
|
|
413
|
+
'doc-generation': { provider: 'openai', model: 'gpt-5.4' },
|
|
414
|
+
enrichment: { provider: 'openai', model: 'gpt-5.4' }
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
xiaomi: {
|
|
418
|
+
provider: 'xiaomi', defaultModel: 'mimo-v2-pro',
|
|
419
|
+
stages: {
|
|
420
|
+
decomposition: { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
421
|
+
validation: { provider: 'xiaomi', model: 'mimo-v2-flash', useContextualSelection: true, epicConcurrency: 2, concurrency: 5, maxFixAttempts: 3 },
|
|
422
|
+
'context-generation': { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
423
|
+
'doc-generation': { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
424
|
+
enrichment: { provider: 'xiaomi', model: 'mimo-v2-pro' }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
agents: [
|
|
429
|
+
{
|
|
430
|
+
name: 'epic-story-decomposer',
|
|
431
|
+
instruction: 'epic-story-decomposer.md',
|
|
432
|
+
stage: 'decomposition'
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: 'project-context-extractor',
|
|
436
|
+
instruction: 'project-context-extractor.md',
|
|
437
|
+
stage: 'validation'
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: 'agent-selector',
|
|
441
|
+
instruction: 'agent-selector.md',
|
|
442
|
+
stage: 'validation'
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: 'context-writer-epic',
|
|
446
|
+
instruction: 'context-writer-epic.md',
|
|
447
|
+
stage: 'context-generation'
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: 'context-writer-story',
|
|
451
|
+
instruction: 'context-writer-story.md',
|
|
452
|
+
stage: 'context-generation'
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: 'doc-writer-epic',
|
|
456
|
+
instruction: 'doc-writer-epic.md',
|
|
457
|
+
stage: 'doc-generation'
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: 'doc-writer-story',
|
|
461
|
+
instruction: 'doc-writer-story.md',
|
|
462
|
+
stage: 'doc-generation'
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: 'story-doc-enricher',
|
|
466
|
+
instruction: 'story-doc-enricher.md',
|
|
467
|
+
stage: 'enrichment'
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: 'seed',
|
|
473
|
+
provider: 'claude',
|
|
474
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
475
|
+
stages: {
|
|
476
|
+
decomposition: {
|
|
477
|
+
provider: 'claude',
|
|
478
|
+
model: 'claude-opus-4-6'
|
|
479
|
+
},
|
|
480
|
+
'context-generation': {
|
|
481
|
+
provider: 'claude',
|
|
482
|
+
model: 'claude-sonnet-4-6'
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
providerPresets: {
|
|
486
|
+
claude: {
|
|
487
|
+
provider: 'claude', defaultModel: 'claude-sonnet-4-6',
|
|
488
|
+
stages: {
|
|
489
|
+
decomposition: { provider: 'claude', model: 'claude-opus-4-6' },
|
|
490
|
+
'context-generation': { provider: 'claude', model: 'claude-sonnet-4-6' }
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
gemini: {
|
|
494
|
+
provider: 'gemini', defaultModel: 'gemini-2.5-flash',
|
|
495
|
+
stages: {
|
|
496
|
+
decomposition: { provider: 'gemini', model: 'gemini-2.5-pro' },
|
|
497
|
+
'context-generation': { provider: 'gemini', model: 'gemini-2.5-flash' }
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
openai: {
|
|
501
|
+
provider: 'openai', defaultModel: 'gpt-5.4',
|
|
502
|
+
stages: {
|
|
503
|
+
decomposition: { provider: 'openai', model: 'gpt-5.4' },
|
|
504
|
+
'context-generation': { provider: 'openai', model: 'gpt-5.4' }
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
xiaomi: {
|
|
508
|
+
provider: 'xiaomi', defaultModel: 'mimo-v2-pro',
|
|
509
|
+
stages: {
|
|
510
|
+
decomposition: { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
511
|
+
'context-generation': { provider: 'xiaomi', model: 'mimo-v2-pro' }
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
local: {
|
|
515
|
+
provider: 'local', defaultModel: 'qwen/qwen3-coder-next',
|
|
516
|
+
stages: {
|
|
517
|
+
decomposition: { provider: 'local', model: 'qwen/qwen3-coder-next' },
|
|
518
|
+
'context-generation': { provider: 'local', model: 'qwen/qwen3-coder-next' }
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
agents: [
|
|
523
|
+
{
|
|
524
|
+
name: 'task-subtask-decomposer',
|
|
525
|
+
instruction: 'task-subtask-decomposer.md',
|
|
526
|
+
stage: 'decomposition'
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: 'doc-distributor',
|
|
530
|
+
instruction: 'doc-distributor.md',
|
|
531
|
+
stage: 'doc-distribution'
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: 'feature-context-generator',
|
|
535
|
+
instruction: 'feature-context-generator.md',
|
|
536
|
+
stage: 'decomposition'
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: 'run',
|
|
542
|
+
provider: 'claude',
|
|
543
|
+
defaultModel: 'claude-sonnet-4-6',
|
|
544
|
+
stages: {
|
|
545
|
+
'code-generation': {
|
|
546
|
+
provider: 'claude',
|
|
547
|
+
model: 'claude-sonnet-4-6'
|
|
548
|
+
},
|
|
549
|
+
'code-validation': {
|
|
550
|
+
provider: 'claude',
|
|
551
|
+
model: 'claude-sonnet-4-6'
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
providerPresets: {
|
|
555
|
+
claude: {
|
|
556
|
+
provider: 'claude', defaultModel: 'claude-sonnet-4-6',
|
|
557
|
+
stages: {
|
|
558
|
+
'code-generation': { provider: 'claude', model: 'claude-sonnet-4-6' },
|
|
559
|
+
'code-validation': { provider: 'claude', model: 'claude-sonnet-4-6' }
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
gemini: {
|
|
563
|
+
provider: 'gemini', defaultModel: 'gemini-2.5-flash',
|
|
564
|
+
stages: {
|
|
565
|
+
'code-generation': { provider: 'gemini', model: 'gemini-2.5-flash' },
|
|
566
|
+
'code-validation': { provider: 'gemini', model: 'gemini-2.5-flash' }
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
openai: {
|
|
570
|
+
provider: 'openai', defaultModel: 'gpt-5.4',
|
|
571
|
+
stages: {
|
|
572
|
+
'code-generation': { provider: 'openai', model: 'gpt-5.4' },
|
|
573
|
+
'code-validation': { provider: 'openai', model: 'gpt-5.4' }
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
xiaomi: {
|
|
577
|
+
provider: 'xiaomi', defaultModel: 'mimo-v2-pro',
|
|
578
|
+
stages: {
|
|
579
|
+
'code-generation': { provider: 'xiaomi', model: 'mimo-v2-pro' },
|
|
580
|
+
'code-validation': { provider: 'xiaomi', model: 'mimo-v2-flash' }
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
local: {
|
|
584
|
+
provider: 'local', defaultModel: 'qwen/qwen3-coder-next',
|
|
585
|
+
stages: {
|
|
586
|
+
'code-generation': { provider: 'local', model: 'qwen/qwen3-coder-next' },
|
|
587
|
+
'code-validation': { provider: 'local', model: 'qwen/qwen3-coder-next' }
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
maxValidationIterations: 3,
|
|
592
|
+
acceptanceThreshold: 80,
|
|
593
|
+
agents: [
|
|
594
|
+
{
|
|
595
|
+
name: 'code-implementer',
|
|
596
|
+
instruction: 'code-implementer.md',
|
|
597
|
+
stage: 'code-generation'
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: 'code-validator',
|
|
601
|
+
instruction: 'code-validator.md',
|
|
602
|
+
stage: 'code-validation'
|
|
603
|
+
}
|
|
604
|
+
]
|
|
147
605
|
}
|
|
148
|
-
]
|
|
606
|
+
],
|
|
607
|
+
missionGenerator: {
|
|
608
|
+
validation: {
|
|
609
|
+
maxIterations: 3,
|
|
610
|
+
acceptanceThreshold: 95
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
costThresholds: {
|
|
614
|
+
'sponsor-call': 2,
|
|
615
|
+
'sprint-planning': 2,
|
|
616
|
+
'seed': 2,
|
|
617
|
+
'run': 2
|
|
618
|
+
},
|
|
619
|
+
questionnaire: {
|
|
620
|
+
defaults: {
|
|
621
|
+
MISSION_STATEMENT: null,
|
|
622
|
+
TARGET_USERS: null,
|
|
623
|
+
INITIAL_SCOPE: null,
|
|
624
|
+
DEPLOYMENT_TARGET: null,
|
|
625
|
+
TECHNICAL_CONSIDERATIONS: null,
|
|
626
|
+
SECURITY_AND_COMPLIANCE_REQUIREMENTS: null
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
models: {
|
|
630
|
+
// Anthropic Claude models (prices per 1M tokens in USD)
|
|
631
|
+
// Source: https://www.anthropic.com/pricing
|
|
632
|
+
'claude-opus-4-6': {
|
|
633
|
+
provider: 'claude',
|
|
634
|
+
displayName: 'Claude Opus 4.6',
|
|
635
|
+
pricing: {
|
|
636
|
+
input: 5.00,
|
|
637
|
+
output: 25.00,
|
|
638
|
+
unit: 'million',
|
|
639
|
+
source: 'https://www.anthropic.com/pricing',
|
|
640
|
+
lastUpdated: '2026-02-24'
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
'claude-sonnet-4-6': {
|
|
644
|
+
provider: 'claude',
|
|
645
|
+
displayName: 'Claude Sonnet 4.6',
|
|
646
|
+
pricing: {
|
|
647
|
+
input: 3.00,
|
|
648
|
+
output: 15.00,
|
|
649
|
+
unit: 'million',
|
|
650
|
+
source: 'https://www.anthropic.com/pricing',
|
|
651
|
+
lastUpdated: '2026-02-24'
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
'claude-haiku-4-5-20251001': {
|
|
655
|
+
provider: 'claude',
|
|
656
|
+
displayName: 'Claude Haiku 4.5',
|
|
657
|
+
pricing: {
|
|
658
|
+
input: 1.00,
|
|
659
|
+
output: 5.00,
|
|
660
|
+
unit: 'million',
|
|
661
|
+
source: 'https://www.anthropic.com/pricing',
|
|
662
|
+
lastUpdated: '2026-02-24'
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
// Google Gemini models (prices per 1M tokens in USD)
|
|
667
|
+
// Source: https://ai.google.dev/pricing
|
|
668
|
+
'gemini-3.1-pro-preview': {
|
|
669
|
+
provider: 'gemini',
|
|
670
|
+
displayName: 'Gemini 3.1 Pro Preview',
|
|
671
|
+
pricing: {
|
|
672
|
+
input: 2.00,
|
|
673
|
+
output: 12.00,
|
|
674
|
+
unit: 'million',
|
|
675
|
+
source: 'https://ai.google.dev/pricing',
|
|
676
|
+
lastUpdated: '2026-03-05'
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
'gemini-3-flash-preview': {
|
|
680
|
+
provider: 'gemini',
|
|
681
|
+
displayName: 'Gemini 3 Flash Preview',
|
|
682
|
+
pricing: {
|
|
683
|
+
input: 0.50,
|
|
684
|
+
output: 3.00,
|
|
685
|
+
unit: 'million',
|
|
686
|
+
source: 'https://ai.google.dev/pricing',
|
|
687
|
+
lastUpdated: '2026-03-05'
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
'gemini-2.5-pro': {
|
|
691
|
+
provider: 'gemini',
|
|
692
|
+
displayName: 'Gemini 2.5 Pro',
|
|
693
|
+
pricing: {
|
|
694
|
+
input: 1.25,
|
|
695
|
+
output: 10.00,
|
|
696
|
+
unit: 'million',
|
|
697
|
+
source: 'https://ai.google.dev/pricing',
|
|
698
|
+
lastUpdated: '2026-02-24'
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
'gemini-2.5-flash': {
|
|
702
|
+
provider: 'gemini',
|
|
703
|
+
displayName: 'Gemini 2.5 Flash',
|
|
704
|
+
pricing: {
|
|
705
|
+
input: 0.30,
|
|
706
|
+
output: 2.50,
|
|
707
|
+
unit: 'million',
|
|
708
|
+
source: 'https://ai.google.dev/pricing',
|
|
709
|
+
lastUpdated: '2026-02-24'
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
'gemini-2.5-flash-lite': {
|
|
713
|
+
provider: 'gemini',
|
|
714
|
+
displayName: 'Gemini 2.5 Flash-Lite',
|
|
715
|
+
pricing: {
|
|
716
|
+
input: 0.10,
|
|
717
|
+
output: 0.40,
|
|
718
|
+
unit: 'million',
|
|
719
|
+
source: 'https://ai.google.dev/pricing',
|
|
720
|
+
lastUpdated: '2026-02-24'
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
// OpenAI models (prices per 1M tokens in USD)
|
|
725
|
+
// Source: https://openai.com/api/pricing
|
|
726
|
+
'gpt-5.4': {
|
|
727
|
+
provider: 'openai',
|
|
728
|
+
displayName: 'GPT-5.4',
|
|
729
|
+
pricing: {
|
|
730
|
+
input: 2.50,
|
|
731
|
+
output: 15.00,
|
|
732
|
+
unit: 'million',
|
|
733
|
+
source: 'https://openai.com/api/pricing',
|
|
734
|
+
lastUpdated: '2026-03-06'
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
'gpt-5.4-pro': {
|
|
738
|
+
provider: 'openai',
|
|
739
|
+
displayName: 'GPT-5.4 Pro',
|
|
740
|
+
pricing: {
|
|
741
|
+
input: 30.00,
|
|
742
|
+
output: 180.00,
|
|
743
|
+
unit: 'million',
|
|
744
|
+
source: 'https://openai.com/api/pricing',
|
|
745
|
+
lastUpdated: '2026-03-06'
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
'gpt-5-mini': {
|
|
749
|
+
provider: 'openai',
|
|
750
|
+
displayName: 'GPT-5 mini',
|
|
751
|
+
pricing: {
|
|
752
|
+
input: 0.25,
|
|
753
|
+
output: 2.00,
|
|
754
|
+
unit: 'million',
|
|
755
|
+
source: 'https://openai.com/api/pricing',
|
|
756
|
+
lastUpdated: '2026-02-24'
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
'gpt-5-nano': {
|
|
760
|
+
provider: 'openai',
|
|
761
|
+
displayName: 'GPT-5 nano',
|
|
762
|
+
pricing: {
|
|
763
|
+
input: 0.05,
|
|
764
|
+
output: 0.40,
|
|
765
|
+
unit: 'million',
|
|
766
|
+
source: 'https://openai.com/api/pricing',
|
|
767
|
+
lastUpdated: '2026-03-06'
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
// Xiaomi MiMo models (prices per 1M tokens in USD)
|
|
772
|
+
// Source: https://platform.xiaomimimo.com
|
|
773
|
+
'mimo-v2-flash': {
|
|
774
|
+
provider: 'xiaomi',
|
|
775
|
+
displayName: 'MiMo V2 Flash',
|
|
776
|
+
pricing: {
|
|
777
|
+
input: 0.09,
|
|
778
|
+
output: 0.29,
|
|
779
|
+
unit: 'million',
|
|
780
|
+
source: 'https://platform.xiaomimimo.com',
|
|
781
|
+
lastUpdated: '2026-03-25'
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
'mimo-v2-pro': {
|
|
785
|
+
provider: 'xiaomi',
|
|
786
|
+
displayName: 'MiMo V2 Pro',
|
|
787
|
+
pricing: {
|
|
788
|
+
input: 1.00,
|
|
789
|
+
output: 3.00,
|
|
790
|
+
unit: 'million',
|
|
791
|
+
source: 'https://platform.xiaomimimo.com',
|
|
792
|
+
lastUpdated: '2026-03-25'
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
'mimo-v2-omni': {
|
|
796
|
+
provider: 'xiaomi',
|
|
797
|
+
displayName: 'MiMo V2 Omni',
|
|
798
|
+
pricing: {
|
|
799
|
+
input: 0.40,
|
|
800
|
+
output: 2.00,
|
|
801
|
+
unit: 'million',
|
|
802
|
+
source: 'https://platform.xiaomimimo.com',
|
|
803
|
+
lastUpdated: '2026-03-25'
|
|
804
|
+
}
|
|
805
|
+
},
|
|
806
|
+
}
|
|
149
807
|
}
|
|
150
808
|
};
|
|
151
809
|
|
|
@@ -156,7 +814,6 @@ class ProjectInitiator {
|
|
|
156
814
|
JSON.stringify(defaultConfig, null, 2),
|
|
157
815
|
'utf8'
|
|
158
816
|
);
|
|
159
|
-
console.log('✓ Created .avc/avc.json configuration file');
|
|
160
817
|
return true;
|
|
161
818
|
}
|
|
162
819
|
|
|
@@ -167,6 +824,15 @@ class ProjectInitiator {
|
|
|
167
824
|
// Merge: add new keys, keep existing values
|
|
168
825
|
const mergedConfig = this.deepMerge(existingConfig, defaultConfig);
|
|
169
826
|
|
|
827
|
+
// Upgrade null cost thresholds to default (2 USD)
|
|
828
|
+
if (mergedConfig.settings?.costThresholds) {
|
|
829
|
+
for (const ceremony of ['sponsor-call', 'sprint-planning', 'seed', 'run']) {
|
|
830
|
+
if (mergedConfig.settings.costThresholds[ceremony] === null) {
|
|
831
|
+
mergedConfig.settings.costThresholds[ceremony] = 2;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
170
836
|
// Update avcVersion to track CLI version
|
|
171
837
|
mergedConfig.avcVersion = this.getAvcVersion();
|
|
172
838
|
mergedConfig.updated = new Date().toISOString();
|
|
@@ -177,15 +843,12 @@ class ProjectInitiator {
|
|
|
177
843
|
|
|
178
844
|
if (existingJson !== mergedJson) {
|
|
179
845
|
fs.writeFileSync(this.avcConfigPath, mergedJson, 'utf8');
|
|
180
|
-
console.log('✓ Updated .avc/avc.json with new configuration attributes');
|
|
181
846
|
return true;
|
|
182
847
|
}
|
|
183
848
|
|
|
184
|
-
console.log('✓ .avc/avc.json is up to date');
|
|
185
849
|
return false;
|
|
186
850
|
} catch (error) {
|
|
187
|
-
console.error(
|
|
188
|
-
console.log('✓ .avc/avc.json already exists (merge skipped)');
|
|
851
|
+
console.error(`Warning: Could not merge avc.json: ${error.message}`);
|
|
189
852
|
return false;
|
|
190
853
|
}
|
|
191
854
|
}
|
|
@@ -204,24 +867,75 @@ class ProjectInitiator {
|
|
|
204
867
|
|
|
205
868
|
/**
|
|
206
869
|
* Create .env file for API keys
|
|
870
|
+
* If .env exists, check and add any missing API key variables
|
|
207
871
|
*/
|
|
208
872
|
createEnvFile() {
|
|
209
873
|
const envPath = path.join(this.projectRoot, '.env');
|
|
210
874
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
ANTHROPIC_API_KEY
|
|
875
|
+
// Define required API key variables with metadata
|
|
876
|
+
const requiredApiKeys = [
|
|
877
|
+
{
|
|
878
|
+
key: 'ANTHROPIC_API_KEY',
|
|
879
|
+
comment: 'Anthropic API Key for AI-powered Sponsor Call ceremony',
|
|
880
|
+
url: 'https://console.anthropic.com/settings/keys'
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
key: 'GEMINI_API_KEY',
|
|
884
|
+
comment: 'Google Gemini API Key (alternative LLM provider)',
|
|
885
|
+
url: 'https://aistudio.google.com/app/apikey'
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
key: 'OPENAI_API_KEY',
|
|
889
|
+
comment: 'OpenAI API Key (alternative LLM provider)',
|
|
890
|
+
url: 'https://platform.openai.com/api-keys'
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
key: 'XIAOMI_API_KEY',
|
|
894
|
+
comment: 'Xiaomi MiMo API Key (alternative LLM provider)',
|
|
895
|
+
url: 'https://platform.xiaomimimo.com/#/console/api-keys'
|
|
896
|
+
}
|
|
897
|
+
];
|
|
215
898
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
899
|
+
if (!fs.existsSync(envPath)) {
|
|
900
|
+
// Create new .env file with all API keys
|
|
901
|
+
let envContent = '';
|
|
902
|
+
requiredApiKeys.forEach(({ key, comment, url }, index) => {
|
|
903
|
+
if (index > 0) envContent += '\n';
|
|
904
|
+
envContent += `# ${comment}\n`;
|
|
905
|
+
envContent += `# Get your key at: ${url}\n`;
|
|
906
|
+
envContent += `${key}=\n`;
|
|
907
|
+
});
|
|
220
908
|
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
221
|
-
console.log('✓ Created .env file for API keys');
|
|
222
909
|
return true;
|
|
223
910
|
}
|
|
224
|
-
|
|
911
|
+
|
|
912
|
+
// .env exists - check for missing API keys
|
|
913
|
+
const existingContent = fs.readFileSync(envPath, 'utf8');
|
|
914
|
+
const missingKeys = [];
|
|
915
|
+
|
|
916
|
+
// Check which API keys are missing
|
|
917
|
+
requiredApiKeys.forEach(({ key }) => {
|
|
918
|
+
const keyPattern = new RegExp(`^${key}=`, 'm');
|
|
919
|
+
if (!keyPattern.test(existingContent)) {
|
|
920
|
+
missingKeys.push(key);
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
if (missingKeys.length > 0) {
|
|
925
|
+
// Add missing API keys to .env file
|
|
926
|
+
let appendContent = '\n';
|
|
927
|
+
requiredApiKeys.forEach(({ key, comment, url }) => {
|
|
928
|
+
if (missingKeys.includes(key)) {
|
|
929
|
+
appendContent += `\n# ${comment}\n`;
|
|
930
|
+
appendContent += `# Get your key at: ${url}\n`;
|
|
931
|
+
appendContent += `${key}=\n`;
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
fs.appendFileSync(envPath, appendContent, 'utf8');
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
|
|
225
939
|
return false;
|
|
226
940
|
}
|
|
227
941
|
|
|
@@ -245,7 +959,9 @@ GEMINI_API_KEY=
|
|
|
245
959
|
{ pattern: '.env', comment: 'Environment variables' },
|
|
246
960
|
{ pattern: '.avc/documentation/.vitepress/dist', comment: 'VitePress build output' },
|
|
247
961
|
{ pattern: '.avc/documentation/.vitepress/cache', comment: 'VitePress cache' },
|
|
248
|
-
{ pattern: '.avc/logs', comment: 'Command execution logs' }
|
|
962
|
+
{ pattern: '.avc/logs', comment: 'Command execution logs' },
|
|
963
|
+
{ pattern: '.avc/token-history.json', comment: 'Token usage tracking' },
|
|
964
|
+
{ pattern: '.avc/ceremonies-history.json', comment: 'Ceremony execution history' }
|
|
249
965
|
];
|
|
250
966
|
|
|
251
967
|
let newContent = gitignoreContent;
|
|
@@ -266,33 +982,25 @@ GEMINI_API_KEY=
|
|
|
266
982
|
|
|
267
983
|
if (addedItems.length > 0) {
|
|
268
984
|
fs.writeFileSync(gitignorePath, newContent, 'utf8');
|
|
269
|
-
console.log(`✓ Added to .gitignore: ${addedItems.join(', ')}`);
|
|
270
|
-
} else {
|
|
271
|
-
console.log('✓ .gitignore already up to date');
|
|
272
985
|
}
|
|
273
986
|
}
|
|
274
987
|
|
|
275
988
|
/**
|
|
276
|
-
* Create VitePress documentation
|
|
989
|
+
* Create VitePress documentation structure (folders and config files)
|
|
990
|
+
* Note: VitePress is bundled with AVC, no need to modify user's package.json
|
|
277
991
|
*/
|
|
278
|
-
|
|
992
|
+
createVitePressStructure() {
|
|
279
993
|
const docsDir = path.join(this.avcDir, 'documentation');
|
|
280
994
|
const vitepressDir = path.join(docsDir, '.vitepress');
|
|
281
995
|
const publicDir = path.join(docsDir, 'public');
|
|
282
996
|
|
|
283
997
|
// Create directory structure
|
|
284
998
|
if (!fs.existsSync(vitepressDir)) {
|
|
285
|
-
|
|
286
|
-
console.log('✓ Created .avc/documentation/.vitepress/ folder');
|
|
287
|
-
} else {
|
|
288
|
-
console.log('✓ .avc/documentation/.vitepress/ folder already exists');
|
|
999
|
+
mkdirp(vitepressDir);
|
|
289
1000
|
}
|
|
290
1001
|
|
|
291
1002
|
if (!fs.existsSync(publicDir)) {
|
|
292
|
-
|
|
293
|
-
console.log('✓ Created .avc/documentation/public/ folder');
|
|
294
|
-
} else {
|
|
295
|
-
console.log('✓ .avc/documentation/public/ folder already exists');
|
|
1003
|
+
mkdirp(publicDir);
|
|
296
1004
|
}
|
|
297
1005
|
|
|
298
1006
|
// Create VitePress config
|
|
@@ -302,9 +1010,6 @@ GEMINI_API_KEY=
|
|
|
302
1010
|
let configContent = fs.readFileSync(templatePath, 'utf8');
|
|
303
1011
|
configContent = configContent.replace('{{PROJECT_NAME}}', this.getProjectName());
|
|
304
1012
|
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
1013
|
}
|
|
309
1014
|
|
|
310
1015
|
// Create initial index.md
|
|
@@ -312,107 +1017,9 @@ GEMINI_API_KEY=
|
|
|
312
1017
|
if (!fs.existsSync(indexPath)) {
|
|
313
1018
|
const indexContent = `# ${this.getProjectName()}
|
|
314
1019
|
|
|
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)*
|
|
1020
|
+
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
1021
|
`;
|
|
346
1022
|
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
1023
|
}
|
|
417
1024
|
}
|
|
418
1025
|
|
|
@@ -440,7 +1047,7 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
440
1047
|
*/
|
|
441
1048
|
writeProgress(progress, progressPath) {
|
|
442
1049
|
if (!fs.existsSync(this.avcDir)) {
|
|
443
|
-
|
|
1050
|
+
mkdirp(this.avcDir);
|
|
444
1051
|
}
|
|
445
1052
|
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf8');
|
|
446
1053
|
}
|
|
@@ -455,6 +1062,74 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
455
1062
|
}
|
|
456
1063
|
|
|
457
1064
|
|
|
1065
|
+
/**
|
|
1066
|
+
* Parse and simplify API error messages for better UX
|
|
1067
|
+
* @param {string|object} error - Error from API call
|
|
1068
|
+
* @returns {string} - Human-readable error message
|
|
1069
|
+
*/
|
|
1070
|
+
parseApiError(error) {
|
|
1071
|
+
let errorStr = typeof error === 'string' ? error : JSON.stringify(error);
|
|
1072
|
+
|
|
1073
|
+
// Try to parse as JSON to extract meaningful information
|
|
1074
|
+
let errorObj = null;
|
|
1075
|
+
try {
|
|
1076
|
+
errorObj = typeof error === 'object' ? error : JSON.parse(error);
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
// Not JSON, use as-is
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Check for common error patterns
|
|
1082
|
+
if (errorStr.includes('quota') || errorStr.includes('RESOURCE_EXHAUSTED')) {
|
|
1083
|
+
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.';
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (errorStr.includes('rate limit') || errorStr.includes('429')) {
|
|
1087
|
+
const retryMatch = errorStr.match(/retry.*?(\d+)\.?\d*s/i);
|
|
1088
|
+
const retryTime = retryMatch ? ` Try again in ${Math.ceil(parseFloat(retryMatch[1]))} seconds.` : '';
|
|
1089
|
+
return `Rate limit exceeded.${retryTime}\n\n Please wait before making more requests.`;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (errorStr.includes('401') || errorStr.includes('authentication') || errorStr.includes('unauthorized')) {
|
|
1093
|
+
return 'Invalid API key or authentication failed.\n\n Please verify your API key is correct.';
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (errorStr.includes('403') || errorStr.includes('forbidden')) {
|
|
1097
|
+
return 'Access forbidden. Your API key may not have permission for this operation.\n\n Check your API key permissions or contact support.';
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
if (errorStr.includes('404') || errorStr.includes('not found')) {
|
|
1101
|
+
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.';
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (errorStr.includes('timeout') || errorStr.includes('ETIMEDOUT')) {
|
|
1105
|
+
return 'Request timed out.\n\n Check your internet connection and try again.';
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (errorStr.includes('ENOTFOUND') || errorStr.includes('DNS')) {
|
|
1109
|
+
return 'Network error: Could not reach API server.\n\n Check your internet connection.';
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Extract just the error message if it's an object with a message field
|
|
1113
|
+
if (errorObj) {
|
|
1114
|
+
if (errorObj.error?.message) {
|
|
1115
|
+
// Take first line or first 150 characters of the message
|
|
1116
|
+
const msg = errorObj.error.message.split('\n')[0];
|
|
1117
|
+
return msg.length > 150 ? msg.substring(0, 150) + '...' : msg;
|
|
1118
|
+
}
|
|
1119
|
+
if (errorObj.message) {
|
|
1120
|
+
const msg = errorObj.message.split('\n')[0];
|
|
1121
|
+
return msg.length > 150 ? msg.substring(0, 150) + '...' : msg;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Fallback: truncate the error if it's too long
|
|
1126
|
+
if (errorStr.length > 200) {
|
|
1127
|
+
return errorStr.substring(0, 200) + '...\n\n (Full error logged to console)';
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return errorStr;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
458
1133
|
/**
|
|
459
1134
|
* Validate that the configured provider's API key is present and working
|
|
460
1135
|
*/
|
|
@@ -470,7 +1145,7 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
470
1145
|
};
|
|
471
1146
|
}
|
|
472
1147
|
|
|
473
|
-
// Read
|
|
1148
|
+
// Read ceremony config
|
|
474
1149
|
const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
475
1150
|
const ceremony = config.settings?.ceremonies?.[0];
|
|
476
1151
|
|
|
@@ -481,62 +1156,291 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
481
1156
|
};
|
|
482
1157
|
}
|
|
483
1158
|
|
|
484
|
-
const
|
|
485
|
-
const
|
|
1159
|
+
const mainProvider = ceremony.provider || 'claude';
|
|
1160
|
+
const mainModel = ceremony.defaultModel || 'claude-sonnet-4-6';
|
|
1161
|
+
|
|
1162
|
+
// Check validation provider if validation is enabled
|
|
1163
|
+
const validationEnabled = ceremony.validation?.enabled !== false;
|
|
1164
|
+
const validationProvider = ceremony.validation?.provider || null;
|
|
1165
|
+
const validationModel = ceremony.validation?.model || null;
|
|
486
1166
|
|
|
487
|
-
// Check which env var is required
|
|
488
1167
|
const envVarMap = {
|
|
489
1168
|
'claude': 'ANTHROPIC_API_KEY',
|
|
490
|
-
'gemini': 'GEMINI_API_KEY'
|
|
1169
|
+
'gemini': 'GEMINI_API_KEY',
|
|
1170
|
+
'openai': 'OPENAI_API_KEY',
|
|
1171
|
+
'xiaomi': 'XIAOMI_API_KEY'
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
const urlMap = {
|
|
1175
|
+
'claude': 'https://console.anthropic.com/settings/keys',
|
|
1176
|
+
'gemini': 'https://aistudio.google.com/app/apikey',
|
|
1177
|
+
'openai': 'https://platform.openai.com/api-keys',
|
|
1178
|
+
'xiaomi': 'https://platform.xiaomimimo.com/#/console/api-keys'
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// Check if a provider has any valid auth credential (API key OR oauth token)
|
|
1182
|
+
const hasProviderAuth = (provider) => {
|
|
1183
|
+
if (provider === 'openai') {
|
|
1184
|
+
const oauthFile = path.join(this.projectRoot, '.avc', 'openai-oauth.json');
|
|
1185
|
+
return !!(process.env.OPENAI_API_KEY ||
|
|
1186
|
+
(process.env.OPENAI_AUTH_MODE === 'oauth' && fs.existsSync(oauthFile)));
|
|
1187
|
+
}
|
|
1188
|
+
return !!process.env[envVarMap[provider]];
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
const authLabel = (provider) => {
|
|
1192
|
+
if (provider === 'openai' && process.env.OPENAI_AUTH_MODE === 'oauth') return 'OpenAI OAuth token';
|
|
1193
|
+
return envVarMap[provider];
|
|
491
1194
|
};
|
|
492
1195
|
|
|
493
|
-
|
|
494
|
-
if (
|
|
1196
|
+
// Local provider needs no API key — skip validation entirely
|
|
1197
|
+
if (mainProvider === 'local') {
|
|
1198
|
+
// Still validate the validation provider if it's a cloud provider
|
|
1199
|
+
if (validationEnabled && validationProvider && validationProvider !== 'local') {
|
|
1200
|
+
const validationEnvVar = envVarMap[validationProvider];
|
|
1201
|
+
if (!validationEnvVar) {
|
|
1202
|
+
return {
|
|
1203
|
+
valid: false,
|
|
1204
|
+
message: `Unknown validation provider "${validationProvider}".\n Supported providers: claude, gemini, openai, xiaomi, local`
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
if (!hasProviderAuth(validationProvider)) {
|
|
1208
|
+
return {
|
|
1209
|
+
valid: false,
|
|
1210
|
+
validationProviderMissing: true,
|
|
1211
|
+
message: `${validationEnvVar} not found in .env file (needed for validation provider).`
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return { valid: true };
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Validate main provider
|
|
1219
|
+
const mainEnvVar = envVarMap[mainProvider];
|
|
1220
|
+
if (!mainEnvVar) {
|
|
495
1221
|
return {
|
|
496
1222
|
valid: false,
|
|
497
|
-
message: `Unknown provider "${
|
|
1223
|
+
message: `Unknown provider "${mainProvider}".\n Supported providers: claude, gemini, openai, xiaomi, local`
|
|
498
1224
|
};
|
|
499
1225
|
}
|
|
500
1226
|
|
|
501
|
-
//
|
|
502
|
-
|
|
1227
|
+
// Helper: switch ALL ceremonies in avc.json to the fallback provider/model.
|
|
1228
|
+
// Updates ceremony-level provider + defaultModel and every stage override.
|
|
1229
|
+
const switchCeremonyProvider = (resolved) => {
|
|
1230
|
+
try {
|
|
1231
|
+
const freshConfig = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
1232
|
+
for (const c of (freshConfig.settings?.ceremonies || [])) {
|
|
1233
|
+
const preset = c.providerPresets?.[resolved.provider];
|
|
1234
|
+
const fallbackModel = preset?.defaultModel || resolved.model;
|
|
1235
|
+
|
|
1236
|
+
c.provider = resolved.provider;
|
|
1237
|
+
c.defaultModel = fallbackModel;
|
|
1238
|
+
|
|
1239
|
+
// Apply preset stage config (model + extra props like concurrency)
|
|
1240
|
+
if (c.stages && typeof c.stages === 'object') {
|
|
1241
|
+
for (const stageName of Object.keys(c.stages)) {
|
|
1242
|
+
const presetStage = preset?.stages?.[stageName];
|
|
1243
|
+
if (presetStage) {
|
|
1244
|
+
Object.assign(c.stages[stageName], presetStage);
|
|
1245
|
+
} else {
|
|
1246
|
+
c.stages[stageName].provider = resolved.provider;
|
|
1247
|
+
c.stages[stageName].model = fallbackModel;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Apply preset validation models or fall back to default model
|
|
1253
|
+
if (c.validation && typeof c.validation === 'object') {
|
|
1254
|
+
const presetVal = preset?.validation;
|
|
1255
|
+
c.validation.provider = resolved.provider;
|
|
1256
|
+
c.validation.model = presetVal?.model || fallbackModel;
|
|
1257
|
+
for (const [k, v] of Object.entries(c.validation)) {
|
|
1258
|
+
if (v && typeof v === 'object' && typeof v.provider === 'string') {
|
|
1259
|
+
const presetSub = presetVal?.[k];
|
|
1260
|
+
v.provider = resolved.provider;
|
|
1261
|
+
v.model = presetSub?.model || presetVal?.model || fallbackModel;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
fs.writeFileSync(this.avcConfigPath, JSON.stringify(freshConfig, null, 2), 'utf8');
|
|
1267
|
+
fileLog('INFO', `Switched all ceremony models to ${resolved.provider} using presets in avc.json`);
|
|
1268
|
+
} catch (writeErr) {
|
|
1269
|
+
fileLog('WARN', `Could not persist provider switch: ${writeErr.message}`);
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1273
|
+
if (!hasProviderAuth(mainProvider)) {
|
|
1274
|
+
// Try to find any available fallback provider
|
|
1275
|
+
const resolved = await LLMProvider.resolveAvailableProvider(mainProvider, mainModel);
|
|
1276
|
+
if (resolved.fellBack) {
|
|
1277
|
+
fileLog('WARN', `Pre-ceremony fallback: ${mainProvider}→${resolved.provider} (no API key for ${mainProvider})`);
|
|
1278
|
+
switchCeremonyProvider(resolved);
|
|
1279
|
+
return { valid: true };
|
|
1280
|
+
}
|
|
503
1281
|
return {
|
|
504
1282
|
valid: false,
|
|
505
|
-
message: `${
|
|
1283
|
+
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
1284
|
};
|
|
507
1285
|
}
|
|
508
1286
|
|
|
509
|
-
console.log(`\n🔑 Validating ${providerName} API key...`);
|
|
510
|
-
|
|
511
1287
|
// Test the API key with a minimal call
|
|
512
1288
|
let result;
|
|
513
1289
|
try {
|
|
514
|
-
result = await LLMProvider.validate(
|
|
1290
|
+
result = await LLMProvider.validate(mainProvider, mainModel);
|
|
515
1291
|
} catch (error) {
|
|
1292
|
+
// API key exists but validation failed — try fallback
|
|
1293
|
+
const resolved = await LLMProvider.resolveAvailableProvider(mainProvider, mainModel);
|
|
1294
|
+
if (resolved.fellBack) {
|
|
1295
|
+
fileLog('WARN', `Pre-ceremony fallback after validation failure: ${mainProvider}→${resolved.provider}`);
|
|
1296
|
+
switchCeremonyProvider(resolved);
|
|
1297
|
+
return { valid: true };
|
|
1298
|
+
}
|
|
1299
|
+
const parsedError = this.parseApiError(error.message || error);
|
|
516
1300
|
return {
|
|
517
1301
|
valid: false,
|
|
518
|
-
message: `${
|
|
1302
|
+
message: `${mainEnvVar} validation failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[mainProvider]}`
|
|
519
1303
|
};
|
|
520
1304
|
}
|
|
521
1305
|
|
|
522
1306
|
if (!result.valid) {
|
|
523
|
-
|
|
1307
|
+
// API call failed — try fallback
|
|
1308
|
+
const resolved = await LLMProvider.resolveAvailableProvider(mainProvider, mainModel);
|
|
1309
|
+
if (resolved.fellBack) {
|
|
1310
|
+
fileLog('WARN', `Pre-ceremony fallback after API failure: ${mainProvider}→${resolved.provider}`);
|
|
1311
|
+
switchCeremonyProvider(resolved);
|
|
1312
|
+
return { valid: true };
|
|
1313
|
+
}
|
|
1314
|
+
const parsedError = this.parseApiError(result.error || 'Unknown error');
|
|
524
1315
|
return {
|
|
525
1316
|
valid: false,
|
|
526
|
-
message: `${
|
|
1317
|
+
message: `${mainEnvVar} is set but API call failed.\n\n ${parsedError}\n\n Get a new API key if needed:\n • ${urlMap[mainProvider]}`
|
|
527
1318
|
};
|
|
528
1319
|
}
|
|
529
1320
|
|
|
530
|
-
|
|
1321
|
+
// Validate validation provider if enabled and different from main
|
|
1322
|
+
if (validationEnabled && validationProvider && validationProvider !== mainProvider && validationProvider !== 'local') {
|
|
1323
|
+
const validationEnvVar = envVarMap[validationProvider];
|
|
1324
|
+
|
|
1325
|
+
if (!validationEnvVar) {
|
|
1326
|
+
return {
|
|
1327
|
+
valid: false,
|
|
1328
|
+
message: `Unknown validation provider "${validationProvider}".\n Supported providers: claude, gemini, openai, xiaomi, local`
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if (!hasProviderAuth(validationProvider)) {
|
|
1333
|
+
// Auto-switch validation to use the main provider (which we already verified works)
|
|
1334
|
+
fileLog('WARN', `Validation provider ${validationProvider} has no key — switching to main provider ${mainProvider}`);
|
|
1335
|
+
try {
|
|
1336
|
+
const freshConfig = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
1337
|
+
for (const c of (freshConfig.settings?.ceremonies || [])) {
|
|
1338
|
+
if (c.validation && typeof c.validation === 'object') {
|
|
1339
|
+
c.validation.provider = mainProvider;
|
|
1340
|
+
c.validation.model = mainModel;
|
|
1341
|
+
for (const [k, v] of Object.entries(c.validation)) {
|
|
1342
|
+
if (v && typeof v === 'object' && typeof v.provider === 'string') {
|
|
1343
|
+
v.provider = mainProvider;
|
|
1344
|
+
v.model = mainModel;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
fs.writeFileSync(this.avcConfigPath, JSON.stringify(freshConfig, null, 2), 'utf8');
|
|
1350
|
+
} catch {}
|
|
1351
|
+
return { valid: true };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
try {
|
|
1355
|
+
result = await LLMProvider.validate(validationProvider, validationModel);
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
// Validation provider failed — auto-switch to main provider
|
|
1358
|
+
fileLog('WARN', `Validation provider ${validationProvider} failed — switching to main provider ${mainProvider}`);
|
|
1359
|
+
try {
|
|
1360
|
+
const freshConfig = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
1361
|
+
for (const c of (freshConfig.settings?.ceremonies || [])) {
|
|
1362
|
+
if (c.validation && typeof c.validation === 'object') {
|
|
1363
|
+
c.validation.provider = mainProvider;
|
|
1364
|
+
c.validation.model = mainModel;
|
|
1365
|
+
for (const [k, v] of Object.entries(c.validation)) {
|
|
1366
|
+
if (v && typeof v === 'object' && typeof v.provider === 'string') {
|
|
1367
|
+
v.provider = mainProvider;
|
|
1368
|
+
v.model = mainModel;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
fs.writeFileSync(this.avcConfigPath, JSON.stringify(freshConfig, null, 2), 'utf8');
|
|
1374
|
+
} catch {}
|
|
1375
|
+
return { valid: true };
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (!result.valid) {
|
|
1379
|
+
// Validation provider API call failed — auto-switch to main provider
|
|
1380
|
+
fileLog('WARN', `Validation provider ${validationProvider} API call failed — switching to main provider ${mainProvider}`);
|
|
1381
|
+
try {
|
|
1382
|
+
const freshConfig = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
1383
|
+
for (const c of (freshConfig.settings?.ceremonies || [])) {
|
|
1384
|
+
if (c.validation && typeof c.validation === 'object') {
|
|
1385
|
+
c.validation.provider = mainProvider;
|
|
1386
|
+
c.validation.model = mainModel;
|
|
1387
|
+
for (const [k, v] of Object.entries(c.validation)) {
|
|
1388
|
+
if (v && typeof v === 'object' && typeof v.provider === 'string') {
|
|
1389
|
+
v.provider = mainProvider;
|
|
1390
|
+
v.model = mainModel;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
fs.writeFileSync(this.avcConfigPath, JSON.stringify(freshConfig, null, 2), 'utf8');
|
|
1396
|
+
} catch {}
|
|
1397
|
+
return { valid: true };
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
}
|
|
1401
|
+
|
|
531
1402
|
return { valid: true };
|
|
532
1403
|
}
|
|
533
1404
|
|
|
534
1405
|
/**
|
|
535
1406
|
* Generate project document via Sponsor Call ceremony
|
|
536
1407
|
*/
|
|
537
|
-
async generateProjectDocument(progress = null, progressPath = null, nonInteractive = false) {
|
|
538
|
-
const processor = new TemplateProcessor(progressPath || this.sponsorCallProgressPath, nonInteractive);
|
|
539
|
-
|
|
1408
|
+
async generateProjectDocument(progress = null, progressPath = null, nonInteractive = false, progressCallback = null, options = {}) {
|
|
1409
|
+
const processor = new TemplateProcessor('sponsor-call', progressPath || this.sponsorCallProgressPath, nonInteractive, options);
|
|
1410
|
+
|
|
1411
|
+
// Set before await so processor is reachable even if processTemplate throws
|
|
1412
|
+
this._lastTemplateProcessor = processor;
|
|
1413
|
+
|
|
1414
|
+
if (progressCallback) {
|
|
1415
|
+
processor.setProgressCallback(progressCallback);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
return await processor.processTemplate(progress);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Get token usage from last template processor execution
|
|
1423
|
+
* @returns {Object|null} Token usage object or null
|
|
1424
|
+
*/
|
|
1425
|
+
getLastTokenUsage() {
|
|
1426
|
+
if (this._lastTemplateProcessor) {
|
|
1427
|
+
return this._lastTemplateProcessor.getLastTokenUsage();
|
|
1428
|
+
}
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Read ceremony configuration from avc.json
|
|
1434
|
+
* @param {string} ceremonyName - Name of the ceremony
|
|
1435
|
+
* @returns {Object|null} Ceremony config or null
|
|
1436
|
+
*/
|
|
1437
|
+
readCeremonyConfig(ceremonyName) {
|
|
1438
|
+
try {
|
|
1439
|
+
const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
1440
|
+
return config.settings?.ceremonies?.find(c => c.name === ceremonyName);
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
540
1444
|
}
|
|
541
1445
|
|
|
542
1446
|
/**
|
|
@@ -551,73 +1455,225 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
551
1455
|
* Creates .avc folder, avc.json config, .env file, and gitignore entry
|
|
552
1456
|
*/
|
|
553
1457
|
async init() {
|
|
554
|
-
|
|
555
|
-
|
|
1458
|
+
const startTime = Date.now();
|
|
1459
|
+
fileLog('INFO', 'init() started', { projectRoot: this.projectRoot });
|
|
556
1460
|
|
|
557
1461
|
if (this.isAvcProject()) {
|
|
558
1462
|
// Project already initialized
|
|
559
|
-
|
|
560
|
-
|
|
1463
|
+
fileLog('INFO', 'Project already initialized — skipping structure creation');
|
|
1464
|
+
sendOutput('Project already initialized.');
|
|
1465
|
+
sendOutput('');
|
|
561
1466
|
return;
|
|
562
1467
|
}
|
|
563
1468
|
|
|
1469
|
+
fileLog('INFO', 'New project — creating structure');
|
|
1470
|
+
fileLog('DEBUG', 'Creating components: .avc/, src/, worktrees/, avc.json, .env, .gitignore, VitePress');
|
|
1471
|
+
|
|
564
1472
|
// Suppress all console output during initialization
|
|
565
1473
|
const originalLog = console.log;
|
|
566
|
-
console.log = () => {};
|
|
1474
|
+
console.log = () => { };
|
|
567
1475
|
|
|
1476
|
+
let initError = null;
|
|
568
1477
|
try {
|
|
569
1478
|
// Create project structure silently
|
|
570
1479
|
this.createAvcFolder();
|
|
1480
|
+
this.createSrcFolder();
|
|
1481
|
+
this.createWorktreesFolder();
|
|
571
1482
|
this.createAvcConfig();
|
|
572
1483
|
this.createEnvFile();
|
|
573
1484
|
this.addToGitignore();
|
|
574
|
-
this.
|
|
1485
|
+
this.createVitePressStructure();
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
initError = err;
|
|
575
1488
|
} finally {
|
|
576
1489
|
console.log = originalLog;
|
|
577
1490
|
}
|
|
578
1491
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
1492
|
+
if (initError) {
|
|
1493
|
+
fileLog('ERROR', 'Structure creation failed', { error: initError.message, stack: initError.stack });
|
|
1494
|
+
throw initError;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
const duration = Date.now() - startTime;
|
|
1498
|
+
fileLog('INFO', 'Structure creation complete', {
|
|
1499
|
+
duration: `${duration}ms`,
|
|
1500
|
+
avcFolder: this.hasAvcFolder(),
|
|
1501
|
+
srcFolder: this.hasSrcFolder(),
|
|
1502
|
+
worktreesFolder: this.hasWorktreesFolder(),
|
|
1503
|
+
avcConfig: this.hasAvcConfig(),
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
sendOutput('Project initialized — open the Kanban board to get started.');
|
|
1507
|
+
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* Configure models command
|
|
1513
|
+
* Shows current model configuration and offers interactive editing
|
|
1514
|
+
*/
|
|
1515
|
+
async models() {
|
|
1516
|
+
sendOutput('Model Configuration\n');
|
|
1517
|
+
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');
|
|
1518
|
+
|
|
1519
|
+
// Check if project is initialized
|
|
1520
|
+
if (!this.isAvcProject()) {
|
|
1521
|
+
sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
|
|
1522
|
+
sendOutput('');
|
|
1523
|
+
sendOutput(MESSAGES.PROJECT_NOT_INITIALIZED.help);
|
|
1524
|
+
sendOutput('');
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// Use the shared configuration method
|
|
1529
|
+
return this.configureModelsInteractively();
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Interactive model configuration flow
|
|
1534
|
+
* Shared by both /init and /models commands
|
|
1535
|
+
*/
|
|
1536
|
+
configureModelsInteractively() {
|
|
1537
|
+
fileLog('INFO', 'configureModels() called', { projectRoot: this.projectRoot });
|
|
1538
|
+
|
|
1539
|
+
const configurator = new ModelConfigurator(this.projectRoot);
|
|
1540
|
+
|
|
1541
|
+
// Detect available providers (used for model indicators)
|
|
1542
|
+
configurator.availableProviders = configurator.detectAvailableProviders();
|
|
1543
|
+
configurator.readConfig();
|
|
1544
|
+
|
|
1545
|
+
fileLog('DEBUG', 'Model configurator loaded', {
|
|
1546
|
+
availableProviders: configurator.availableProviders,
|
|
1547
|
+
configPath: this.avcConfigPath,
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
// Show current configuration
|
|
1551
|
+
const ceremonies = configurator.getCeremonies();
|
|
1552
|
+
fileLog('INFO', 'Ceremony model configs', {
|
|
1553
|
+
count: ceremonies.length,
|
|
1554
|
+
names: ceremonies.map(c => c.name),
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
ceremonies.forEach(c => {
|
|
1558
|
+
const hasMainKey = configurator.availableProviders.includes(c.mainProvider);
|
|
1559
|
+
const stageDetails = {};
|
|
1560
|
+
Object.keys(c.stages).forEach(stageName => {
|
|
1561
|
+
const stage = c.stages[stageName];
|
|
1562
|
+
stageDetails[stageName] = {
|
|
1563
|
+
model: stage.model,
|
|
1564
|
+
provider: stage.provider,
|
|
1565
|
+
hasApiKey: configurator.availableProviders.includes(stage.provider),
|
|
1566
|
+
};
|
|
1567
|
+
});
|
|
1568
|
+
fileLog('DEBUG', `Ceremony: ${c.name}`, {
|
|
1569
|
+
mainModel: c.mainModel,
|
|
1570
|
+
mainProvider: c.mainProvider,
|
|
1571
|
+
hasMainKey,
|
|
1572
|
+
validationModel: c.validationModel || null,
|
|
1573
|
+
validationProvider: c.validationProvider || null,
|
|
1574
|
+
hasValidationKey: c.validationProvider ? configurator.availableProviders.includes(c.validationProvider) : null,
|
|
1575
|
+
stages: stageDetails,
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
sendOutput(boldCyan(c.name));
|
|
1579
|
+
sendOutput(`${yellow('default')}: ${green(c.mainModel)} (${c.mainProvider})`);
|
|
1580
|
+
if (c.validationProvider) {
|
|
1581
|
+
const hasValidationKey = configurator.availableProviders.includes(c.validationProvider);
|
|
1582
|
+
const keyWarning = hasValidationKey ? '' : ' [no API key]';
|
|
1583
|
+
sendOutput(`${yellow('validation')}: ${green(c.validationModel)} (${c.validationProvider})${keyWarning}`);
|
|
1584
|
+
}
|
|
1585
|
+
Object.keys(c.stages).forEach(stageName => {
|
|
1586
|
+
const stage = c.stages[stageName];
|
|
1587
|
+
const hasStageKey = configurator.availableProviders.includes(stage.provider);
|
|
1588
|
+
const keyWarning = hasStageKey ? '' : ' [no API key]';
|
|
1589
|
+
sendOutput(`${yellow(stageName)}: ${green(stage.model)} (${stage.provider})${keyWarning}`);
|
|
1590
|
+
});
|
|
1591
|
+
sendOutput('');
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
fileLog('INFO', 'configureModels() complete');
|
|
1595
|
+
// Return configurator for REPL to use
|
|
1596
|
+
return {
|
|
1597
|
+
shouldConfigure: true,
|
|
1598
|
+
configurator,
|
|
1599
|
+
ceremonies: ceremonies.map(c => c.name) // List of ceremony names for selection
|
|
1600
|
+
};
|
|
585
1601
|
}
|
|
586
1602
|
|
|
587
1603
|
/**
|
|
588
1604
|
* Run Sponsor Call ceremony with pre-filled answers from REPL questionnaire
|
|
589
1605
|
* Used when all answers are collected via REPL UI
|
|
590
1606
|
*/
|
|
591
|
-
async sponsorCallWithAnswers(answers) {
|
|
592
|
-
|
|
593
|
-
|
|
1607
|
+
async sponsorCallWithAnswers(answers, progressCallback = null, options = {}) {
|
|
1608
|
+
const startTime = Date.now();
|
|
1609
|
+
fileLog('INFO', 'sponsorCallWithAnswers() called', {
|
|
1610
|
+
answerKeys: Object.keys(answers || {}),
|
|
1611
|
+
answeredCount: Object.values(answers || {}).filter(v => v !== null && v !== '').length,
|
|
1612
|
+
hasProgressCallback: !!progressCallback,
|
|
1613
|
+
projectRoot: this.projectRoot,
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
// Remove initial ceremony banner - will be shown in summary
|
|
594
1617
|
|
|
595
1618
|
// Check if project is initialized
|
|
596
1619
|
if (!this.isAvcProject()) {
|
|
597
|
-
|
|
598
|
-
|
|
1620
|
+
fileLog('ERROR', 'Project not initialized — aborting sponsor call');
|
|
1621
|
+
sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
|
|
599
1622
|
return;
|
|
600
1623
|
}
|
|
601
1624
|
|
|
602
1625
|
const progressPath = this.sponsorCallProgressPath;
|
|
603
1626
|
|
|
604
|
-
|
|
1627
|
+
// Initialize ceremony history
|
|
1628
|
+
const { CeremonyHistory } = await import('./ceremony-history.js');
|
|
1629
|
+
const history = new CeremonyHistory(this.avcDir);
|
|
1630
|
+
history.init();
|
|
1631
|
+
|
|
1632
|
+
// Get or create execution record
|
|
1633
|
+
let executionId;
|
|
1634
|
+
const lastExecution = history.getLastExecution('sponsor-call');
|
|
605
1635
|
|
|
606
|
-
|
|
1636
|
+
if (lastExecution && lastExecution.status === 'in-progress' && lastExecution.stage === 'llm-generation') {
|
|
1637
|
+
// Resume existing execution (from REPL flow)
|
|
1638
|
+
executionId = lastExecution.id;
|
|
1639
|
+
} else {
|
|
1640
|
+
// This shouldn't normally happen if called from REPL, but handle it
|
|
1641
|
+
executionId = history.startExecution('sponsor-call', 'llm-generation');
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// Count answers provided (for logging)
|
|
607
1645
|
const answeredCount = Object.values(answers).filter(v => v !== null && v !== '').length;
|
|
608
|
-
|
|
1646
|
+
fileLog('DEBUG', 'Ceremony history state', {
|
|
1647
|
+
executionId,
|
|
1648
|
+
lastExecutionStatus: lastExecution?.status,
|
|
1649
|
+
lastExecutionStage: lastExecution?.stage,
|
|
1650
|
+
answeredCount,
|
|
1651
|
+
totalQuestions: Object.keys(answers).length,
|
|
1652
|
+
});
|
|
609
1653
|
|
|
610
1654
|
// Validate API key before starting ceremony
|
|
611
|
-
|
|
1655
|
+
fileLog('INFO', 'Validating API key before ceremony start');
|
|
612
1656
|
const validationResult = await this.validateProviderApiKey();
|
|
1657
|
+
fileLog(validationResult.valid ? 'INFO' : 'ERROR', 'API key validation result', {
|
|
1658
|
+
valid: validationResult.valid,
|
|
1659
|
+
message: validationResult.message || 'OK',
|
|
1660
|
+
});
|
|
613
1661
|
if (!validationResult.valid) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1662
|
+
// Mark execution as aborted
|
|
1663
|
+
history.completeExecution('sponsor-call', executionId, 'abrupt-termination', {
|
|
1664
|
+
answers,
|
|
1665
|
+
stage: 'llm-generation',
|
|
1666
|
+
error: 'API key validation failed'
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
// Return error for REPL to display
|
|
1670
|
+
return {
|
|
1671
|
+
error: true,
|
|
1672
|
+
message: `API Key Validation Failed: ${validationResult.message}`
|
|
1673
|
+
};
|
|
617
1674
|
}
|
|
618
1675
|
|
|
619
1676
|
// Create progress with pre-filled answers
|
|
620
|
-
console.log('Step 2/3: Processing questionnaire answers...');
|
|
621
1677
|
const progress = {
|
|
622
1678
|
stage: 'questionnaire',
|
|
623
1679
|
totalQuestions: 5,
|
|
@@ -627,20 +1683,226 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
627
1683
|
};
|
|
628
1684
|
this.writeProgress(progress, progressPath);
|
|
629
1685
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1686
|
+
try {
|
|
1687
|
+
// Generate project document with pre-filled answers
|
|
1688
|
+
const result = await this.generateProjectDocument(progress, progressPath, true, progressCallback, options);
|
|
633
1689
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1690
|
+
fileLog('INFO', 'generateProjectDocument() complete', { resultKeys: result ? Object.keys(result) : [] });
|
|
1691
|
+
|
|
1692
|
+
// Notify progress during cleanup
|
|
1693
|
+
if (progressCallback) await progressCallback(null, 'Calculating token usage costs...');
|
|
1694
|
+
|
|
1695
|
+
// Get token usage from template processor
|
|
1696
|
+
const tokenUsage = this.getLastTokenUsage();
|
|
1697
|
+
|
|
1698
|
+
// Get model ID from ceremony config
|
|
1699
|
+
const ceremony = this.readCeremonyConfig('sponsor-call');
|
|
1700
|
+
const modelId = ceremony?.defaultModel || 'claude-sonnet-4-6';
|
|
1701
|
+
|
|
1702
|
+
// Calculate cost using token tracker
|
|
1703
|
+
const { TokenTracker } = await import('./token-tracker.js');
|
|
1704
|
+
const tracker = new TokenTracker(this.avcDir);
|
|
1705
|
+
const cost = tracker.calculateCost(
|
|
1706
|
+
tokenUsage?.inputTokens || 0,
|
|
1707
|
+
tokenUsage?.outputTokens || 0,
|
|
1708
|
+
modelId
|
|
1709
|
+
);
|
|
639
1710
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1711
|
+
// Mark execution as completed with metadata
|
|
1712
|
+
if (progressCallback) await progressCallback(null, 'Saving ceremony history...');
|
|
1713
|
+
history.completeExecution('sponsor-call', executionId, 'success', {
|
|
1714
|
+
answers,
|
|
1715
|
+
filesGenerated: [
|
|
1716
|
+
path.join(this.avcDir, 'project/doc.md')
|
|
1717
|
+
],
|
|
1718
|
+
tokenUsage: {
|
|
1719
|
+
input: tokenUsage?.inputTokens || 0,
|
|
1720
|
+
output: tokenUsage?.outputTokens || 0,
|
|
1721
|
+
total: tokenUsage?.totalTokens || 0
|
|
1722
|
+
},
|
|
1723
|
+
model: modelId,
|
|
1724
|
+
cost: cost,
|
|
1725
|
+
stage: 'completed'
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
// Mark progress as completed and clean up
|
|
1729
|
+
if (progressCallback) await progressCallback(null, 'Finalizing ceremony...');
|
|
1730
|
+
progress.stage = 'completed';
|
|
1731
|
+
progress.lastUpdate = new Date().toISOString();
|
|
1732
|
+
this.writeProgress(progress, progressPath);
|
|
1733
|
+
this.clearProgress(progressPath);
|
|
1734
|
+
|
|
1735
|
+
// Emit a final main progress message so the UI log clearly shows completion
|
|
1736
|
+
if (progressCallback) await progressCallback('✓ Documentation generated successfully!');
|
|
1737
|
+
|
|
1738
|
+
fileLog('INFO', 'sponsorCallWithAnswers() complete', {
|
|
1739
|
+
duration: `${Date.now() - startTime}ms`,
|
|
1740
|
+
outputPath: result?.outputPath,
|
|
1741
|
+
tokenInput: result?.tokenUsage?.input,
|
|
1742
|
+
tokenOutput: result?.tokenUsage?.output,
|
|
1743
|
+
estimatedCost: result?.cost?.total,
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
// Persist answers and generate Q&A documentation page (non-fatal)
|
|
1747
|
+
this.saveProjectBriefAnswers(answers);
|
|
1748
|
+
|
|
1749
|
+
// Return result for display in REPL
|
|
1750
|
+
return result;
|
|
1751
|
+
|
|
1752
|
+
} catch (error) {
|
|
1753
|
+
const isCancelled = error.message === 'CEREMONY_CANCELLED';
|
|
1754
|
+
|
|
1755
|
+
// Save any tokens spent before cancellation/error
|
|
1756
|
+
try {
|
|
1757
|
+
this._lastTemplateProcessor?.saveCurrentTokenTracking();
|
|
1758
|
+
} catch (trackErr) {
|
|
1759
|
+
fileLog('WARN', 'Could not save partial token tracking', { error: trackErr.message });
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
if (!isCancelled) {
|
|
1763
|
+
fileLog('ERROR', 'sponsorCallWithAnswers() failed', {
|
|
1764
|
+
error: error.message,
|
|
1765
|
+
stack: error.stack,
|
|
1766
|
+
duration: `${Date.now() - startTime}ms`,
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// Mark execution as aborted on error/cancel
|
|
1771
|
+
history.completeExecution('sponsor-call', executionId, 'abrupt-termination', {
|
|
1772
|
+
answers,
|
|
1773
|
+
stage: isCancelled ? 'cancelled' : 'llm-generation',
|
|
1774
|
+
error: error.message
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
throw error;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
/**
|
|
1782
|
+
* Display token usage statistics and costs
|
|
1783
|
+
*/
|
|
1784
|
+
async showTokenStats() {
|
|
1785
|
+
fileLog('INFO', 'showTokenStats() called', { avcDir: this.avcDir });
|
|
1786
|
+
|
|
1787
|
+
const { TokenTracker } = await import('./token-tracker.js');
|
|
1788
|
+
const tracker = new TokenTracker(this.avcDir);
|
|
1789
|
+
tracker.init();
|
|
1790
|
+
tracker.load();
|
|
1791
|
+
|
|
1792
|
+
const data = tracker.data;
|
|
1793
|
+
fileLog('DEBUG', 'Token history loaded', {
|
|
1794
|
+
version: data.version,
|
|
1795
|
+
lastUpdated: data.lastUpdated,
|
|
1796
|
+
ceremonyTypes: Object.keys(data).filter(k => !['version', 'lastUpdated', 'totals'].includes(k)),
|
|
1797
|
+
allTimeTotal: data.totals?.allTime?.total,
|
|
1798
|
+
allTimeExecutions: data.totals?.allTime?.executions,
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
const allTime = data.totals.allTime;
|
|
1802
|
+
const costStr = (allTime.cost && allTime.cost.total > 0) ? ` / $${allTime.cost.total.toFixed(4)}` : '';
|
|
1803
|
+
sendOutput(`All-time: ${allTime.total.toLocaleString()} tokens / ${allTime.executions} executions${costStr}`);
|
|
1804
|
+
|
|
1805
|
+
const ceremonyTypes = Object.keys(data).filter(k => !['version', 'lastUpdated', 'totals'].includes(k));
|
|
1806
|
+
for (const ceremonyType of ceremonyTypes) {
|
|
1807
|
+
const ceremony = data[ceremonyType];
|
|
1808
|
+
if (ceremony.allTime && ceremony.allTime.executions > 0) {
|
|
1809
|
+
const cCostStr = (ceremony.allTime.cost && ceremony.allTime.cost.total > 0) ? ` / $${ceremony.allTime.cost.total.toFixed(4)}` : '';
|
|
1810
|
+
sendIndented(`${ceremonyType}: ${ceremony.allTime.total.toLocaleString()} tokens / ${ceremony.allTime.executions} runs${cCostStr}`, 1);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
fileLog('INFO', 'showTokenStats() complete', {
|
|
1815
|
+
allTimeInput: data.totals?.allTime?.input,
|
|
1816
|
+
allTimeOutput: data.totals?.allTime?.output,
|
|
1817
|
+
allTimeTotal: data.totals?.allTime?.total,
|
|
1818
|
+
estimatedCost: data.totals?.allTime?.cost?.total,
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/**
|
|
1823
|
+
* Run Sprint Planning ceremony to create/expand Epics and Stories
|
|
1824
|
+
*/
|
|
1825
|
+
async sprintPlanning() {
|
|
1826
|
+
const startTime = Date.now();
|
|
1827
|
+
fileLog('INFO', 'sprintPlanning() called', { projectRoot: this.projectRoot });
|
|
1828
|
+
|
|
1829
|
+
if (!this.isAvcProject()) {
|
|
1830
|
+
fileLog('ERROR', 'Project not initialized — aborting sprint planning');
|
|
1831
|
+
sendError('Project not initialized. Run /init first.');
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
fileLog('INFO', 'Loading SprintPlanningProcessor');
|
|
1836
|
+
const { SprintPlanningProcessor } = await import('./sprint-planning-processor.js');
|
|
1837
|
+
const processor = new SprintPlanningProcessor();
|
|
1838
|
+
fileLog('DEBUG', 'SprintPlanningProcessor created', {
|
|
1839
|
+
projectPath: processor.projectPath,
|
|
1840
|
+
provider: processor._providerName,
|
|
1841
|
+
model: processor._modelName,
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
await processor.execute();
|
|
1845
|
+
|
|
1846
|
+
// Non-fatal docs sync after sprint planning
|
|
1847
|
+
try {
|
|
1848
|
+
const { DocsSyncProcessor } = await import('./docs-sync.js');
|
|
1849
|
+
const syncer = new DocsSyncProcessor(process.cwd());
|
|
1850
|
+
if (fs.existsSync(syncer.docsDir)) {
|
|
1851
|
+
await syncer.sync();
|
|
1852
|
+
sendInfo('Documentation synced.');
|
|
1853
|
+
}
|
|
1854
|
+
} catch (_) { /* non-fatal */ }
|
|
1855
|
+
|
|
1856
|
+
fileLog('INFO', 'sprintPlanning() complete', { duration: `${Date.now() - startTime}ms` });
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* Run Sprint Planning ceremony with a progress callback (used by kanban board)
|
|
1861
|
+
* @param {Function|null} progressCallback - Called with (msg, substep, meta) on each stage
|
|
1862
|
+
* @returns {Promise<object>} Result with epicsCreated, storiesCreated, tokenUsage, model
|
|
1863
|
+
*/
|
|
1864
|
+
async sprintPlanningWithCallback(progressCallback = null, options = {}) {
|
|
1865
|
+
fileLog('INFO', 'sprintPlanningWithCallback() called', { projectRoot: this.projectRoot, resumeFrom: options.resumeFrom || null });
|
|
1866
|
+
if (!this.isAvcProject()) {
|
|
1867
|
+
throw new Error('Project not initialized. Run /init first.');
|
|
1868
|
+
}
|
|
1869
|
+
const { SprintPlanningProcessor } = await import('./sprint-planning-processor.js');
|
|
1870
|
+
const processor = new SprintPlanningProcessor(options);
|
|
1871
|
+
return await processor.execute(progressCallback, { resumeFrom: options.resumeFrom || null });
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* Run Seed ceremony to decompose a Story into Tasks and Subtasks
|
|
1876
|
+
* @param {string} storyId - Story ID (e.g., context-0001-0001)
|
|
1877
|
+
*/
|
|
1878
|
+
async seed(storyId) {
|
|
1879
|
+
const startTime = Date.now();
|
|
1880
|
+
fileLog('INFO', 'seed() called', { storyId, projectRoot: this.projectRoot });
|
|
1881
|
+
|
|
1882
|
+
if (!this.isAvcProject()) {
|
|
1883
|
+
fileLog('ERROR', 'Project not initialized — aborting seed');
|
|
1884
|
+
sendError('Project not initialized. Run /init first.');
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
if (!storyId) {
|
|
1889
|
+
fileLog('ERROR', 'No story ID provided — aborting seed');
|
|
1890
|
+
sendError('Story ID required. Usage: /seed <story-id>');
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
fileLog('INFO', 'Loading SeedProcessor', { storyId });
|
|
1895
|
+
const { SeedProcessor } = await import('./seed-processor.js');
|
|
1896
|
+
const processor = new SeedProcessor(storyId);
|
|
1897
|
+
fileLog('DEBUG', 'SeedProcessor created', {
|
|
1898
|
+
storyId,
|
|
1899
|
+
storyPath: processor.storyPath,
|
|
1900
|
+
provider: processor._providerName,
|
|
1901
|
+
model: processor._modelName,
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
await processor.execute();
|
|
1905
|
+
fileLog('INFO', 'seed() complete', { storyId, duration: `${Date.now() - startTime}ms` });
|
|
644
1906
|
}
|
|
645
1907
|
|
|
646
1908
|
/**
|
|
@@ -648,25 +1910,46 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
648
1910
|
* Requires API keys to be configured in .env file
|
|
649
1911
|
*/
|
|
650
1912
|
async sponsorCall() {
|
|
651
|
-
|
|
652
|
-
|
|
1913
|
+
const header = getCeremonyHeader('sponsor-call');
|
|
1914
|
+
sendOutput('');
|
|
1915
|
+
sendOutput(header.title);
|
|
1916
|
+
sendOutput('');
|
|
1917
|
+
sendOutput(`Project directory: ${this.projectRoot}`);
|
|
1918
|
+
sendOutput('');
|
|
653
1919
|
|
|
654
1920
|
// Check if running in REPL mode
|
|
655
1921
|
const isReplMode = process.env.AVC_REPL_MODE === 'true';
|
|
656
1922
|
if (isReplMode) {
|
|
657
1923
|
// REPL mode is handled by repl-ink.js questionnaire display
|
|
658
1924
|
// This code path shouldn't be reached from REPL
|
|
659
|
-
|
|
1925
|
+
sendWarning('Unexpected: Ceremony called directly from REPL');
|
|
660
1926
|
return;
|
|
661
1927
|
}
|
|
662
1928
|
|
|
663
1929
|
// Check if project is initialized
|
|
664
1930
|
if (!this.isAvcProject()) {
|
|
665
|
-
|
|
666
|
-
|
|
1931
|
+
sendError(MESSAGES.PROJECT_NOT_INITIALIZED.error);
|
|
1932
|
+
sendOutput('');
|
|
1933
|
+
sendOutput(MESSAGES.PROJECT_NOT_INITIALIZED.help);
|
|
1934
|
+
sendOutput('');
|
|
667
1935
|
return; // Don't exit in REPL mode
|
|
668
1936
|
}
|
|
669
1937
|
|
|
1938
|
+
// Check if sponsor call has already completed successfully
|
|
1939
|
+
const { CeremonyHistory } = await import('./ceremony-history.js');
|
|
1940
|
+
const history = new CeremonyHistory(this.avcDir);
|
|
1941
|
+
|
|
1942
|
+
if (history.hasSuccessfulCompletion('sponsor-call')) {
|
|
1943
|
+
sendError('Sponsor Call has already completed successfully');
|
|
1944
|
+
sendOutput('');
|
|
1945
|
+
sendOutput('Project documentation already exists at .avc/project/doc.md');
|
|
1946
|
+
sendOutput('');
|
|
1947
|
+
sendOutput('To regenerate documentation, first run /remove to clear the project,');
|
|
1948
|
+
sendOutput('then run /init followed by /sponsor-call again.');
|
|
1949
|
+
sendOutput('');
|
|
1950
|
+
return; // Don't allow re-running
|
|
1951
|
+
}
|
|
1952
|
+
|
|
670
1953
|
let progress = null;
|
|
671
1954
|
const progressPath = this.sponsorCallProgressPath;
|
|
672
1955
|
|
|
@@ -675,22 +1958,28 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
675
1958
|
progress = this.readProgress(progressPath);
|
|
676
1959
|
|
|
677
1960
|
if (progress && progress.stage !== 'completed') {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1961
|
+
sendWarning('Found incomplete ceremony from previous session');
|
|
1962
|
+
sendIndented(`Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`, 1);
|
|
1963
|
+
sendIndented(`Stage: ${progress.stage}`, 1);
|
|
1964
|
+
sendIndented(`Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`, 1);
|
|
1965
|
+
sendOutput('');
|
|
1966
|
+
sendInfo('Continuing from where you left off...');
|
|
1967
|
+
sendOutput('');
|
|
683
1968
|
}
|
|
684
1969
|
} else {
|
|
685
1970
|
// Fresh start
|
|
686
|
-
|
|
1971
|
+
sendOutput('Starting Sponsor Call ceremony...');
|
|
1972
|
+
sendOutput('');
|
|
687
1973
|
}
|
|
688
1974
|
|
|
689
1975
|
// Validate API key before starting ceremony
|
|
690
1976
|
const validationResult = await this.validateProviderApiKey();
|
|
691
1977
|
if (!validationResult.valid) {
|
|
692
|
-
|
|
693
|
-
|
|
1978
|
+
sendOutput('');
|
|
1979
|
+
sendError('API Key Validation Failed');
|
|
1980
|
+
sendOutput('');
|
|
1981
|
+
sendIndented(validationResult.message, 1);
|
|
1982
|
+
sendOutput('');
|
|
694
1983
|
return; // Don't exit in REPL mode
|
|
695
1984
|
}
|
|
696
1985
|
|
|
@@ -715,30 +2004,44 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
715
2004
|
this.writeProgress(progress, progressPath);
|
|
716
2005
|
this.clearProgress(progressPath);
|
|
717
2006
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
2007
|
+
sendOutput('');
|
|
2008
|
+
sendSuccess('Project defined successfully!');
|
|
2009
|
+
sendOutput('');
|
|
2010
|
+
sendOutput('Next steps:');
|
|
2011
|
+
sendIndented('1. Review .avc/project/doc.md for your project definition', 1);
|
|
2012
|
+
sendIndented('2. Review .avc/avc.json configuration', 1);
|
|
2013
|
+
sendIndented('3. Create your project documentation and work items', 1);
|
|
2014
|
+
sendIndented('4. Use AI agents to implement features', 1);
|
|
724
2015
|
}
|
|
725
2016
|
|
|
726
2017
|
/**
|
|
727
2018
|
* Display current project status
|
|
728
2019
|
*/
|
|
729
2020
|
status() {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
2021
|
+
fileLog('INFO', 'status() called', { projectRoot: this.projectRoot });
|
|
2022
|
+
|
|
2023
|
+
const hasAvc = this.hasAvcFolder();
|
|
2024
|
+
const hasSrc = this.hasSrcFolder();
|
|
2025
|
+
const hasWorktrees = this.hasWorktreesFolder();
|
|
2026
|
+
const hasConfig = this.hasAvcConfig();
|
|
2027
|
+
const isInitialized = this.isAvcProject();
|
|
2028
|
+
const projectName = this.getProjectName();
|
|
2029
|
+
|
|
2030
|
+
fileLog('DEBUG', 'Component check results', {
|
|
2031
|
+
'.avc/': hasAvc,
|
|
2032
|
+
'src/': hasSrc,
|
|
2033
|
+
'worktrees/': hasWorktrees,
|
|
2034
|
+
'avc.json': hasConfig,
|
|
2035
|
+
isAvcProject: isInitialized,
|
|
2036
|
+
projectName,
|
|
2037
|
+
});
|
|
739
2038
|
|
|
740
|
-
if (
|
|
741
|
-
|
|
2039
|
+
if (isInitialized) {
|
|
2040
|
+
sendOutput(`${projectName}: Initialized`);
|
|
2041
|
+
fileLog('INFO', 'status() complete — project is initialized');
|
|
2042
|
+
} else {
|
|
2043
|
+
fileLog('WARNING', 'Project not initialized — components missing', { hasAvc, hasConfig });
|
|
2044
|
+
sendOutput(`${projectName}: Not initialized. Run /init to start.`);
|
|
742
2045
|
}
|
|
743
2046
|
}
|
|
744
2047
|
|
|
@@ -747,40 +2050,63 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
747
2050
|
* Requires confirmation by typing "delete all"
|
|
748
2051
|
*/
|
|
749
2052
|
async remove() {
|
|
750
|
-
|
|
751
|
-
|
|
2053
|
+
sendSectionHeader('Remove AVC Project Structure');
|
|
2054
|
+
sendOutput('');
|
|
2055
|
+
sendOutput(`Project directory: ${this.projectRoot}`);
|
|
2056
|
+
sendOutput('');
|
|
752
2057
|
|
|
753
2058
|
// Check if project is initialized
|
|
754
2059
|
if (!this.isAvcProject()) {
|
|
755
|
-
|
|
756
|
-
|
|
2060
|
+
sendWarning('No AVC project found in this directory.');
|
|
2061
|
+
sendOutput('Nothing to remove.');
|
|
2062
|
+
sendOutput('');
|
|
757
2063
|
return;
|
|
758
2064
|
}
|
|
759
2065
|
|
|
760
2066
|
// Show what will be deleted
|
|
761
|
-
|
|
762
|
-
|
|
2067
|
+
sendWarning('WARNING: This is a DESTRUCTIVE operation!');
|
|
2068
|
+
sendOutput('');
|
|
2069
|
+
sendOutput('The following will be PERMANENTLY DELETED:');
|
|
2070
|
+
sendOutput('');
|
|
763
2071
|
|
|
764
2072
|
// List contents of .avc folder
|
|
765
2073
|
const avcContents = this.getAvcContents();
|
|
766
2074
|
if (avcContents.length > 0) {
|
|
767
|
-
|
|
2075
|
+
sendOutput('.avc/ folder contents:');
|
|
2076
|
+
sendOutput('');
|
|
768
2077
|
avcContents.forEach(item => {
|
|
769
|
-
|
|
2078
|
+
sendIndented(`• ${item}`, 1);
|
|
770
2079
|
});
|
|
771
|
-
|
|
2080
|
+
sendOutput('');
|
|
772
2081
|
}
|
|
773
2082
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
2083
|
+
sendError('All project definitions, epics, stories, tasks, and documentation will be lost.');
|
|
2084
|
+
sendError('All VitePress documentation will be deleted.');
|
|
2085
|
+
sendError('This action CANNOT be undone.');
|
|
2086
|
+
sendOutput('');
|
|
777
2087
|
|
|
778
2088
|
// Check for .env file
|
|
779
2089
|
const envPath = path.join(this.projectRoot, '.env');
|
|
780
2090
|
const hasEnvFile = fs.existsSync(envPath);
|
|
781
2091
|
if (hasEnvFile) {
|
|
782
|
-
|
|
783
|
-
|
|
2092
|
+
sendInfo('Note: The .env file will NOT be deleted.');
|
|
2093
|
+
sendOutput('You may want to manually remove API keys if no longer needed.');
|
|
2094
|
+
sendOutput('');
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// Check for src folder
|
|
2098
|
+
const hasSrcFolder = this.hasSrcFolder();
|
|
2099
|
+
if (hasSrcFolder) {
|
|
2100
|
+
sendSuccess('IMPORTANT: The src/ folder will NOT be deleted.');
|
|
2101
|
+
sendOutput('All your AVC-managed code will be preserved.');
|
|
2102
|
+
sendOutput('');
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// Check for worktrees folder
|
|
2106
|
+
const hasWorktreesFolder = this.hasWorktreesFolder();
|
|
2107
|
+
if (hasWorktreesFolder) {
|
|
2108
|
+
sendWarning('The .avc/worktrees/ folder and all git worktrees inside it WILL be deleted.');
|
|
2109
|
+
sendOutput('');
|
|
784
2110
|
}
|
|
785
2111
|
|
|
786
2112
|
// Check if running in REPL mode
|
|
@@ -789,16 +2115,16 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
789
2115
|
if (isReplMode) {
|
|
790
2116
|
// In REPL mode, interactive confirmation is handled by repl-ink.js
|
|
791
2117
|
// This code path shouldn't be reached from REPL
|
|
792
|
-
|
|
793
|
-
|
|
2118
|
+
sendWarning('Unexpected: Remove called directly from REPL');
|
|
2119
|
+
sendOutput('Interactive confirmation should be handled by REPL interface.');
|
|
794
2120
|
return;
|
|
795
2121
|
}
|
|
796
2122
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
2123
|
+
sendOutput('─'.repeat(60));
|
|
2124
|
+
sendOutput('To confirm deletion, type exactly: delete all');
|
|
2125
|
+
sendOutput('To cancel, type anything else or press Ctrl+C');
|
|
2126
|
+
sendOutput('─'.repeat(60));
|
|
2127
|
+
sendOutput('');
|
|
802
2128
|
|
|
803
2129
|
// Create readline interface for confirmation
|
|
804
2130
|
const readline = await import('readline');
|
|
@@ -810,11 +2136,12 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
810
2136
|
return new Promise((resolve) => {
|
|
811
2137
|
rl.question('Confirmation: ', (answer) => {
|
|
812
2138
|
rl.close();
|
|
813
|
-
|
|
2139
|
+
sendOutput('');
|
|
814
2140
|
|
|
815
2141
|
if (answer.trim() === 'delete all') {
|
|
816
2142
|
// Proceed with deletion
|
|
817
|
-
|
|
2143
|
+
sendOutput('Deleting AVC project structure...');
|
|
2144
|
+
sendOutput('');
|
|
818
2145
|
|
|
819
2146
|
try {
|
|
820
2147
|
// Get list of what's being deleted before deletion
|
|
@@ -823,39 +2150,60 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
823
2150
|
// Delete .avc folder
|
|
824
2151
|
fs.rmSync(this.avcDir, { recursive: true, force: true });
|
|
825
2152
|
|
|
826
|
-
|
|
827
|
-
|
|
2153
|
+
sendSuccess('Successfully deleted:');
|
|
2154
|
+
sendOutput('');
|
|
2155
|
+
sendOutput('.avc/ folder and all contents:');
|
|
2156
|
+
sendOutput('');
|
|
828
2157
|
deletedItems.forEach(item => {
|
|
829
|
-
|
|
2158
|
+
sendIndented(`• ${item}`, 3);
|
|
830
2159
|
});
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
// Reminder about
|
|
834
|
-
if (hasEnvFile) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
2160
|
+
sendOutput('');
|
|
2161
|
+
|
|
2162
|
+
// Reminder about preserved files
|
|
2163
|
+
if (hasEnvFile || hasSrcFolder) {
|
|
2164
|
+
sendInfo('Preserved files:');
|
|
2165
|
+
sendOutput('');
|
|
2166
|
+
|
|
2167
|
+
if (hasEnvFile) {
|
|
2168
|
+
sendOutput('The .env file was NOT deleted and still contains:');
|
|
2169
|
+
sendOutput('');
|
|
2170
|
+
sendIndented('• ANTHROPIC_API_KEY', 1);
|
|
2171
|
+
sendIndented('• GEMINI_API_KEY', 1);
|
|
2172
|
+
sendIndented('• OPENAI_API_KEY', 1);
|
|
2173
|
+
sendIndented('• XIAOMI_API_KEY', 1);
|
|
2174
|
+
sendIndented('• (and any other API keys you added)', 1);
|
|
2175
|
+
sendOutput('If these API keys are not used elsewhere in your project,');
|
|
2176
|
+
sendOutput('you may want to manually delete the .env file or remove');
|
|
2177
|
+
sendOutput('the unused keys.');
|
|
2178
|
+
sendOutput('');
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
if (hasSrcFolder) {
|
|
2182
|
+
sendSuccess('The src/ folder was NOT deleted.');
|
|
2183
|
+
sendOutput('All your AVC-managed code has been preserved.');
|
|
2184
|
+
sendOutput('');
|
|
2185
|
+
}
|
|
2186
|
+
|
|
843
2187
|
}
|
|
844
2188
|
|
|
845
|
-
|
|
846
|
-
|
|
2189
|
+
sendSuccess('AVC project structure has been completely removed.');
|
|
2190
|
+
sendOutput('You can re-initialize anytime by running /init');
|
|
2191
|
+
sendOutput('');
|
|
847
2192
|
|
|
848
2193
|
resolve();
|
|
849
2194
|
} catch (error) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
2195
|
+
sendError(`Error during deletion: ${error.message}`);
|
|
2196
|
+
sendOutput('');
|
|
2197
|
+
sendOutput('The .avc folder may be partially deleted.');
|
|
2198
|
+
sendOutput('You may need to manually remove it.');
|
|
2199
|
+
sendOutput('');
|
|
853
2200
|
resolve();
|
|
854
2201
|
}
|
|
855
2202
|
} else {
|
|
856
2203
|
// Cancellation
|
|
857
|
-
|
|
858
|
-
|
|
2204
|
+
sendError('Operation cancelled.');
|
|
2205
|
+
sendOutput('No files were deleted.');
|
|
2206
|
+
sendOutput('');
|
|
859
2207
|
resolve();
|
|
860
2208
|
}
|
|
861
2209
|
});
|
|
@@ -895,6 +2243,114 @@ If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilev
|
|
|
895
2243
|
return contents;
|
|
896
2244
|
}
|
|
897
2245
|
|
|
2246
|
+
/**
|
|
2247
|
+
* Save sponsor-call Q&A answers to avc.json and generate VitePress Q&A page.
|
|
2248
|
+
* Wrapped in try/catch — failures are non-fatal.
|
|
2249
|
+
*/
|
|
2250
|
+
saveProjectBriefAnswers(answers) {
|
|
2251
|
+
try {
|
|
2252
|
+
// Normalize: CLI path has top-level keys; Kanban path may nest under .requirements
|
|
2253
|
+
const qa = answers.MISSION_STATEMENT ? answers : (answers.requirements || answers);
|
|
2254
|
+
|
|
2255
|
+
// Read current avc.json and persist answers
|
|
2256
|
+
const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
2257
|
+
if (!config.settings) config.settings = {};
|
|
2258
|
+
if (!config.settings.projectBrief) config.settings.projectBrief = {};
|
|
2259
|
+
config.settings.projectBrief.answers = {
|
|
2260
|
+
MISSION_STATEMENT: qa.MISSION_STATEMENT || null,
|
|
2261
|
+
TARGET_USERS: qa.TARGET_USERS || null,
|
|
2262
|
+
INITIAL_SCOPE: qa.INITIAL_SCOPE || null,
|
|
2263
|
+
DEPLOYMENT_TARGET: qa.DEPLOYMENT_TARGET || null,
|
|
2264
|
+
TECHNICAL_CONSIDERATIONS: qa.TECHNICAL_CONSIDERATIONS || null,
|
|
2265
|
+
TECHNICAL_EXCLUSIONS: qa.TECHNICAL_EXCLUSIONS || null,
|
|
2266
|
+
SECURITY_AND_COMPLIANCE_REQUIREMENTS: qa.SECURITY_AND_COMPLIANCE_REQUIREMENTS || null,
|
|
2267
|
+
};
|
|
2268
|
+
config.settings.projectBrief.savedAt = new Date().toISOString();
|
|
2269
|
+
fs.writeFileSync(this.avcConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
2270
|
+
|
|
2271
|
+
fileLog('INFO', 'saveProjectBriefAnswers() — answers persisted to avc.json');
|
|
2272
|
+
|
|
2273
|
+
this.generateQADocumentationPage(qa);
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
fileLog('WARN', 'saveProjectBriefAnswers() failed (non-fatal)', { error: error.message });
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
/**
|
|
2280
|
+
* Generate .avc/documentation/questions-and-answers.md with the sponsor-call answers.
|
|
2281
|
+
* No-ops silently if the documentation directory doesn't exist yet.
|
|
2282
|
+
*/
|
|
2283
|
+
generateQADocumentationPage(qa) {
|
|
2284
|
+
const docDir = path.join(this.avcDir, 'documentation');
|
|
2285
|
+
if (!fs.existsSync(docDir)) {
|
|
2286
|
+
fileLog('INFO', 'generateQADocumentationPage() — documentation dir not found, skipping');
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
const lines = ['# Questions & Answers', ''];
|
|
2291
|
+
lines.push('Sponsor-call questionnaire answers captured during project brief generation.', '');
|
|
2292
|
+
|
|
2293
|
+
lines.push('## Mission Statement', '');
|
|
2294
|
+
lines.push(qa.MISSION_STATEMENT || '_Not provided._', '');
|
|
2295
|
+
|
|
2296
|
+
lines.push('## Initial Scope', '');
|
|
2297
|
+
lines.push(qa.INITIAL_SCOPE || '_Not provided._', '');
|
|
2298
|
+
|
|
2299
|
+
lines.push('## Target Users', '');
|
|
2300
|
+
lines.push(qa.TARGET_USERS || '_Not provided._', '');
|
|
2301
|
+
|
|
2302
|
+
lines.push('## Deployment Target', '');
|
|
2303
|
+
lines.push(qa.DEPLOYMENT_TARGET || '_Not provided._', '');
|
|
2304
|
+
|
|
2305
|
+
lines.push('## Technical Considerations', '');
|
|
2306
|
+
lines.push(qa.TECHNICAL_CONSIDERATIONS || '_Not provided._', '');
|
|
2307
|
+
|
|
2308
|
+
if (qa.TECHNICAL_EXCLUSIONS) {
|
|
2309
|
+
lines.push('## Technical Exclusions', '');
|
|
2310
|
+
lines.push(qa.TECHNICAL_EXCLUSIONS, '');
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
lines.push('## Security & Compliance Requirements', '');
|
|
2314
|
+
lines.push(qa.SECURITY_AND_COMPLIANCE_REQUIREMENTS || '_Not provided._', '');
|
|
2315
|
+
|
|
2316
|
+
const qaPath = path.join(docDir, 'questions-and-answers.md');
|
|
2317
|
+
fs.writeFileSync(qaPath, lines.join('\n'), 'utf8');
|
|
2318
|
+
|
|
2319
|
+
fileLog('INFO', 'generateQADocumentationPage() — written', { qaPath });
|
|
2320
|
+
|
|
2321
|
+
this.addQAToVitePressConfig();
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
/**
|
|
2325
|
+
* Insert "Questions & Answers" into the VitePress sidebar, immediately after "Project Brief".
|
|
2326
|
+
* Idempotent — skips if already present.
|
|
2327
|
+
*/
|
|
2328
|
+
addQAToVitePressConfig() {
|
|
2329
|
+
const configPath = path.join(this.avcDir, 'documentation', '.vitepress', 'config.mts');
|
|
2330
|
+
if (!fs.existsSync(configPath)) {
|
|
2331
|
+
fileLog('INFO', 'addQAToVitePressConfig() — config.mts not found, skipping');
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
let content = fs.readFileSync(configPath, 'utf8');
|
|
2336
|
+
|
|
2337
|
+
if (content.includes('questions-and-answers')) {
|
|
2338
|
+
fileLog('INFO', 'addQAToVitePressConfig() — already present, skipping');
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// Insert after the Project Brief link line
|
|
2343
|
+
const projectBriefLine = "{ text: 'Project Brief', link: '/' }";
|
|
2344
|
+
const qaLine = "{ text: 'Questions & Answers', link: '/questions-and-answers' }";
|
|
2345
|
+
content = content.replace(
|
|
2346
|
+
projectBriefLine,
|
|
2347
|
+
`${projectBriefLine},\n ${qaLine}`
|
|
2348
|
+
);
|
|
2349
|
+
|
|
2350
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
2351
|
+
fileLog('INFO', 'addQAToVitePressConfig() — sidebar updated', { configPath });
|
|
2352
|
+
}
|
|
2353
|
+
|
|
898
2354
|
/**
|
|
899
2355
|
* Recursively count items in a directory
|
|
900
2356
|
*/
|
|
@@ -939,11 +2395,14 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
939
2395
|
case 'status':
|
|
940
2396
|
initiator.status();
|
|
941
2397
|
break;
|
|
2398
|
+
case 'models':
|
|
2399
|
+
initiator.models();
|
|
2400
|
+
break;
|
|
942
2401
|
case 'remove':
|
|
943
2402
|
initiator.remove();
|
|
944
2403
|
break;
|
|
945
2404
|
default:
|
|
946
|
-
|
|
2405
|
+
sendError('Unknown command. Available commands: init, sponsor-call, status, models, remove');
|
|
947
2406
|
process.exit(1);
|
|
948
2407
|
}
|
|
949
2408
|
}
|