@agile-vibe-coding/avc 0.1.0 → 0.1.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/README.md +2 -0
- package/cli/agents/documentation.md +302 -0
- package/cli/build-docs.js +277 -0
- package/cli/command-logger.js +208 -0
- package/cli/index.js +3 -25
- package/cli/init.js +705 -77
- package/cli/llm-claude.js +27 -0
- package/cli/llm-gemini.js +30 -0
- package/cli/llm-provider.js +63 -0
- package/cli/logger.js +32 -5
- package/cli/process-manager.js +261 -0
- package/cli/repl-ink.js +1784 -219
- package/cli/template-processor.js +274 -73
- package/cli/templates/vitepress-config.mts.template +33 -0
- package/package.json +17 -3
package/cli/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import dotenv from 'dotenv';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
@@ -19,11 +20,20 @@ const __dirname = path.dirname(__filename);
|
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
class ProjectInitiator {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.projectRoot = process.cwd();
|
|
23
|
+
constructor(projectRoot = null) {
|
|
24
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
24
25
|
this.avcDir = path.join(this.projectRoot, '.avc');
|
|
25
26
|
this.avcConfigPath = path.join(this.avcDir, 'avc.json');
|
|
26
|
-
|
|
27
|
+
// Progress files are ceremony-specific
|
|
28
|
+
this.initProgressPath = path.join(this.avcDir, 'init-progress.json');
|
|
29
|
+
this.sponsorCallProgressPath = path.join(this.avcDir, 'sponsor-call-progress.json');
|
|
30
|
+
|
|
31
|
+
// Load environment variables from project .env file
|
|
32
|
+
// Use override: true to reload even if already set (user may have edited .env)
|
|
33
|
+
dotenv.config({
|
|
34
|
+
path: path.join(this.projectRoot, '.env'),
|
|
35
|
+
override: true
|
|
36
|
+
});
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/**
|
|
@@ -33,6 +43,47 @@ class ProjectInitiator {
|
|
|
33
43
|
return path.basename(this.projectRoot);
|
|
34
44
|
}
|
|
35
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Get the current AVC package version
|
|
48
|
+
*/
|
|
49
|
+
getAvcVersion() {
|
|
50
|
+
try {
|
|
51
|
+
const packagePath = path.join(__dirname, '../package.json');
|
|
52
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
53
|
+
return packageJson.version;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return 'unknown';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Deep merge objects - adds new keys, preserves existing values
|
|
61
|
+
* @param {Object} target - The target object (user's config)
|
|
62
|
+
* @param {Object} source - The source object (default config)
|
|
63
|
+
* @returns {Object} Merged object
|
|
64
|
+
*/
|
|
65
|
+
deepMerge(target, source) {
|
|
66
|
+
const result = { ...target };
|
|
67
|
+
|
|
68
|
+
for (const key in source) {
|
|
69
|
+
if (source.hasOwnProperty(key)) {
|
|
70
|
+
if (key in result) {
|
|
71
|
+
// Key exists in target
|
|
72
|
+
if (typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key] !== null) {
|
|
73
|
+
// Recursively merge objects
|
|
74
|
+
result[key] = this.deepMerge(result[key], source[key]);
|
|
75
|
+
}
|
|
76
|
+
// else: Keep existing value (don't overwrite)
|
|
77
|
+
} else {
|
|
78
|
+
// New key - add it
|
|
79
|
+
result[key] = source[key];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
36
87
|
/**
|
|
37
88
|
* Check if .avc folder exists
|
|
38
89
|
*/
|
|
@@ -61,23 +112,45 @@ class ProjectInitiator {
|
|
|
61
112
|
}
|
|
62
113
|
|
|
63
114
|
/**
|
|
64
|
-
* Create avc.json with default settings
|
|
115
|
+
* Create or update avc.json with default settings
|
|
116
|
+
* Merges new attributes from version updates while preserving existing values
|
|
65
117
|
*/
|
|
66
118
|
createAvcConfig() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
119
|
+
const defaultConfig = {
|
|
120
|
+
version: '1.0.0',
|
|
121
|
+
avcVersion: this.getAvcVersion(),
|
|
122
|
+
projectName: this.getProjectName(),
|
|
123
|
+
framework: 'avc',
|
|
124
|
+
created: new Date().toISOString(),
|
|
125
|
+
settings: {
|
|
126
|
+
contextScopes: ['epic', 'story', 'task', 'subtask'],
|
|
127
|
+
workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
|
|
128
|
+
agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
|
|
129
|
+
documentation: {
|
|
130
|
+
port: 4173
|
|
131
|
+
},
|
|
132
|
+
ceremonies: [
|
|
133
|
+
{
|
|
134
|
+
name: 'sponsor-call',
|
|
135
|
+
defaultModel: 'claude-sonnet-4-5-20250929',
|
|
136
|
+
provider: 'claude',
|
|
137
|
+
agents: [
|
|
138
|
+
{
|
|
139
|
+
name: 'documentation',
|
|
140
|
+
instruction: 'documentation.md',
|
|
141
|
+
stage: 'enhancement'
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
guidelines: {
|
|
145
|
+
technicalConsiderations: 'Use AWS serverless stack with Lambda functions for compute, API Gateway for REST APIs, DynamoDB for database, S3 for storage. Use CloudFormation for infrastructure definition and AWS CodePipeline/CodeBuild for CI/CD deployment.'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
};
|
|
80
151
|
|
|
152
|
+
if (!this.hasAvcConfig()) {
|
|
153
|
+
// Create new config
|
|
81
154
|
fs.writeFileSync(
|
|
82
155
|
this.avcConfigPath,
|
|
83
156
|
JSON.stringify(defaultConfig, null, 2),
|
|
@@ -86,8 +159,35 @@ class ProjectInitiator {
|
|
|
86
159
|
console.log('✓ Created .avc/avc.json configuration file');
|
|
87
160
|
return true;
|
|
88
161
|
}
|
|
89
|
-
|
|
90
|
-
|
|
162
|
+
|
|
163
|
+
// Config exists - check for merge
|
|
164
|
+
try {
|
|
165
|
+
const existingConfig = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
166
|
+
|
|
167
|
+
// Merge: add new keys, keep existing values
|
|
168
|
+
const mergedConfig = this.deepMerge(existingConfig, defaultConfig);
|
|
169
|
+
|
|
170
|
+
// Update avcVersion to track CLI version
|
|
171
|
+
mergedConfig.avcVersion = this.getAvcVersion();
|
|
172
|
+
mergedConfig.updated = new Date().toISOString();
|
|
173
|
+
|
|
174
|
+
// Check if anything changed
|
|
175
|
+
const existingJson = JSON.stringify(existingConfig, null, 2);
|
|
176
|
+
const mergedJson = JSON.stringify(mergedConfig, null, 2);
|
|
177
|
+
|
|
178
|
+
if (existingJson !== mergedJson) {
|
|
179
|
+
fs.writeFileSync(this.avcConfigPath, mergedJson, 'utf8');
|
|
180
|
+
console.log('✓ Updated .avc/avc.json with new configuration attributes');
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log('✓ .avc/avc.json is up to date');
|
|
185
|
+
return false;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`⚠️ Warning: Could not merge avc.json: ${error.message}`);
|
|
188
|
+
console.log('✓ .avc/avc.json already exists (merge skipped)');
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
91
191
|
}
|
|
92
192
|
|
|
93
193
|
/**
|
|
@@ -113,7 +213,9 @@ class ProjectInitiator {
|
|
|
113
213
|
# Get your key at: https://console.anthropic.com/settings/keys
|
|
114
214
|
ANTHROPIC_API_KEY=
|
|
115
215
|
|
|
116
|
-
#
|
|
216
|
+
# Google Gemini API Key (alternative LLM provider)
|
|
217
|
+
# Get your key at: https://aistudio.google.com/app/apikey
|
|
218
|
+
GEMINI_API_KEY=
|
|
117
219
|
`;
|
|
118
220
|
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
119
221
|
console.log('✓ Created .env file for API keys');
|
|
@@ -138,34 +240,195 @@ ANTHROPIC_API_KEY=
|
|
|
138
240
|
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
139
241
|
}
|
|
140
242
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
243
|
+
// Items to add to gitignore
|
|
244
|
+
const itemsToIgnore = [
|
|
245
|
+
{ pattern: '.env', comment: 'Environment variables' },
|
|
246
|
+
{ pattern: '.avc/documentation/.vitepress/dist', comment: 'VitePress build output' },
|
|
247
|
+
{ pattern: '.avc/documentation/.vitepress/cache', comment: 'VitePress cache' },
|
|
248
|
+
{ pattern: '.avc/logs', comment: 'Command execution logs' }
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
let newContent = gitignoreContent;
|
|
252
|
+
let addedItems = [];
|
|
253
|
+
|
|
254
|
+
for (const item of itemsToIgnore) {
|
|
255
|
+
if (!newContent.includes(item.pattern)) {
|
|
256
|
+
if (!newContent.endsWith('\n') && newContent.length > 0) {
|
|
257
|
+
newContent += '\n';
|
|
258
|
+
}
|
|
259
|
+
if (!newContent.includes(`# ${item.comment}`)) {
|
|
260
|
+
newContent += `\n# ${item.comment}\n`;
|
|
261
|
+
}
|
|
262
|
+
newContent += `${item.pattern}\n`;
|
|
263
|
+
addedItems.push(item.pattern);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (addedItems.length > 0) {
|
|
268
|
+
fs.writeFileSync(gitignorePath, newContent, 'utf8');
|
|
269
|
+
console.log(`✓ Added to .gitignore: ${addedItems.join(', ')}`);
|
|
270
|
+
} else {
|
|
271
|
+
console.log('✓ .gitignore already up to date');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Create VitePress documentation setup
|
|
277
|
+
*/
|
|
278
|
+
createVitePressSetup() {
|
|
279
|
+
const docsDir = path.join(this.avcDir, 'documentation');
|
|
280
|
+
const vitepressDir = path.join(docsDir, '.vitepress');
|
|
281
|
+
const publicDir = path.join(docsDir, 'public');
|
|
282
|
+
|
|
283
|
+
// Create directory structure
|
|
284
|
+
if (!fs.existsSync(vitepressDir)) {
|
|
285
|
+
fs.mkdirSync(vitepressDir, { recursive: true });
|
|
286
|
+
console.log('✓ Created .avc/documentation/.vitepress/ folder');
|
|
287
|
+
} else {
|
|
288
|
+
console.log('✓ .avc/documentation/.vitepress/ folder already exists');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!fs.existsSync(publicDir)) {
|
|
292
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
293
|
+
console.log('✓ Created .avc/documentation/public/ folder');
|
|
294
|
+
} else {
|
|
295
|
+
console.log('✓ .avc/documentation/public/ folder already exists');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Create VitePress config
|
|
299
|
+
const configPath = path.join(vitepressDir, 'config.mts');
|
|
300
|
+
if (!fs.existsSync(configPath)) {
|
|
301
|
+
const templatePath = path.join(__dirname, 'templates/vitepress-config.mts.template');
|
|
302
|
+
let configContent = fs.readFileSync(templatePath, 'utf8');
|
|
303
|
+
configContent = configContent.replace('{{PROJECT_NAME}}', this.getProjectName());
|
|
304
|
+
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
|
+
}
|
|
309
|
+
|
|
310
|
+
// Create initial index.md
|
|
311
|
+
const indexPath = path.join(docsDir, 'index.md');
|
|
312
|
+
if (!fs.existsSync(indexPath)) {
|
|
313
|
+
const indexContent = `# ${this.getProjectName()}
|
|
314
|
+
|
|
315
|
+
## Project Status
|
|
316
|
+
|
|
317
|
+
This project is being developed using the [Agile Vibe Coding](https://agilevibecoding.org) framework.
|
|
318
|
+
|
|
319
|
+
**Current Stage**: Initial Setup
|
|
320
|
+
|
|
321
|
+
Project documentation will be generated automatically as the project is defined and developed.
|
|
322
|
+
|
|
323
|
+
## About This Documentation
|
|
324
|
+
|
|
325
|
+
This site provides comprehensive documentation about **${this.getProjectName()}**, including:
|
|
326
|
+
|
|
327
|
+
- Project overview and objectives
|
|
328
|
+
- Feature specifications organized by epics and stories
|
|
329
|
+
- Technical architecture and design decisions
|
|
330
|
+
- Implementation progress and status
|
|
331
|
+
|
|
332
|
+
Documentation is automatically updated from the AVC project structure as development progresses.
|
|
333
|
+
|
|
334
|
+
## Getting Started with AVC
|
|
335
|
+
|
|
336
|
+
If you're new to Agile Vibe Coding, visit the [AVC Documentation](https://agilevibecoding.org) to learn about:
|
|
337
|
+
|
|
338
|
+
- [CLI Commands](https://agilevibecoding.org/commands) - Available commands and their usage
|
|
339
|
+
- [Installation Guide](https://agilevibecoding.org/install) - Setup instructions
|
|
340
|
+
- [Framework Overview](https://agilevibecoding.org) - Core concepts and workflow
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
*Documentation powered by [Agile Vibe Coding](https://agilevibecoding.org)*
|
|
345
|
+
`;
|
|
346
|
+
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
|
+
};
|
|
145
371
|
}
|
|
146
372
|
|
|
147
|
-
// Add
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
373
|
+
// Add scripts
|
|
374
|
+
if (!packageJson.scripts) {
|
|
375
|
+
packageJson.scripts = {};
|
|
376
|
+
}
|
|
151
377
|
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
}
|
|
154
417
|
}
|
|
155
418
|
|
|
156
419
|
/**
|
|
157
|
-
* Check if there's an incomplete
|
|
420
|
+
* Check if there's an incomplete ceremony in progress
|
|
158
421
|
*/
|
|
159
|
-
|
|
160
|
-
return fs.existsSync(
|
|
422
|
+
hasIncompleteProgress(progressPath) {
|
|
423
|
+
return fs.existsSync(progressPath);
|
|
161
424
|
}
|
|
162
425
|
|
|
163
426
|
/**
|
|
164
427
|
* Read progress from file
|
|
165
428
|
*/
|
|
166
|
-
readProgress() {
|
|
429
|
+
readProgress(progressPath) {
|
|
167
430
|
try {
|
|
168
|
-
const content = fs.readFileSync(
|
|
431
|
+
const content = fs.readFileSync(progressPath, 'utf8');
|
|
169
432
|
return JSON.parse(content);
|
|
170
433
|
} catch (error) {
|
|
171
434
|
return null;
|
|
@@ -175,28 +438,104 @@ ANTHROPIC_API_KEY=
|
|
|
175
438
|
/**
|
|
176
439
|
* Write progress to file
|
|
177
440
|
*/
|
|
178
|
-
writeProgress(progress) {
|
|
441
|
+
writeProgress(progress, progressPath) {
|
|
179
442
|
if (!fs.existsSync(this.avcDir)) {
|
|
180
443
|
fs.mkdirSync(this.avcDir, { recursive: true });
|
|
181
444
|
}
|
|
182
|
-
fs.writeFileSync(
|
|
445
|
+
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf8');
|
|
183
446
|
}
|
|
184
447
|
|
|
185
448
|
/**
|
|
186
|
-
* Clear progress file (
|
|
449
|
+
* Clear progress file (ceremony completed successfully)
|
|
187
450
|
*/
|
|
188
|
-
clearProgress() {
|
|
189
|
-
if (fs.existsSync(
|
|
190
|
-
fs.unlinkSync(
|
|
451
|
+
clearProgress(progressPath) {
|
|
452
|
+
if (fs.existsSync(progressPath)) {
|
|
453
|
+
fs.unlinkSync(progressPath);
|
|
191
454
|
}
|
|
192
455
|
}
|
|
193
456
|
|
|
194
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Validate that the configured provider's API key is present and working
|
|
460
|
+
*/
|
|
461
|
+
async validateProviderApiKey() {
|
|
462
|
+
// Import LLMProvider dynamically to avoid circular dependencies
|
|
463
|
+
const { LLMProvider } = await import('./llm-provider.js');
|
|
464
|
+
|
|
465
|
+
// Check if config file exists
|
|
466
|
+
if (!fs.existsSync(this.avcConfigPath)) {
|
|
467
|
+
return {
|
|
468
|
+
valid: false,
|
|
469
|
+
message: 'Configuration file not found at .avc/avc.json.\n Please run /init first to set up your project.'
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Read provider config from avc.json
|
|
474
|
+
const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
|
|
475
|
+
const ceremony = config.settings?.ceremonies?.[0];
|
|
476
|
+
|
|
477
|
+
if (!ceremony) {
|
|
478
|
+
return {
|
|
479
|
+
valid: false,
|
|
480
|
+
message: 'No ceremonies configured in .avc/avc.json.\n Please check your configuration.'
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const providerName = ceremony.provider || 'claude';
|
|
485
|
+
const modelName = ceremony.defaultModel || 'claude-sonnet-4-5-20250929';
|
|
486
|
+
|
|
487
|
+
// Check which env var is required
|
|
488
|
+
const envVarMap = {
|
|
489
|
+
'claude': 'ANTHROPIC_API_KEY',
|
|
490
|
+
'gemini': 'GEMINI_API_KEY'
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const requiredEnvVar = envVarMap[providerName];
|
|
494
|
+
if (!requiredEnvVar) {
|
|
495
|
+
return {
|
|
496
|
+
valid: false,
|
|
497
|
+
message: `Unknown provider "${providerName}".\n Supported providers: claude, gemini`
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Check if API key is set in environment
|
|
502
|
+
if (!process.env[requiredEnvVar]) {
|
|
503
|
+
return {
|
|
504
|
+
valid: false,
|
|
505
|
+
message: `${requiredEnvVar} not found in .env file.\n\n Steps to fix:\n 1. Open .env file in the current directory\n 2. Add your API key: ${requiredEnvVar}=your-key-here\n 3. Save the file and run /init again\n\n Get your API key:\n ${providerName === 'claude' ? '• https://console.anthropic.com/settings/keys' : '• https://aistudio.google.com/app/apikey'}`
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
console.log(`\n🔑 Validating ${providerName} API key...`);
|
|
510
|
+
|
|
511
|
+
// Test the API key with a minimal call
|
|
512
|
+
let result;
|
|
513
|
+
try {
|
|
514
|
+
result = await LLMProvider.validate(providerName, modelName);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
return {
|
|
517
|
+
valid: false,
|
|
518
|
+
message: `${requiredEnvVar} validation failed.\n\n Error: ${error.message}\n\n This could be due to:\n • Network connectivity issues\n • API service temporarily unavailable\n • Invalid API key\n\n Please check your connection and try again.`
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!result.valid) {
|
|
523
|
+
const errorMsg = result.error || 'Unknown error';
|
|
524
|
+
return {
|
|
525
|
+
valid: false,
|
|
526
|
+
message: `${requiredEnvVar} is set but API call failed.\n\n Error: ${errorMsg}\n\n Steps to fix:\n 1. Verify your API key is correct in .env file\n 2. Check that the key has not expired\n 3. Ensure you have API credits/quota available\n\n Get a new API key if needed:\n ${providerName === 'claude' ? '• https://console.anthropic.com/settings/keys' : '• https://aistudio.google.com/app/apikey'}`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
console.log(`✓ API key validated successfully\n`);
|
|
531
|
+
return { valid: true };
|
|
532
|
+
}
|
|
533
|
+
|
|
195
534
|
/**
|
|
196
535
|
* Generate project document via Sponsor Call ceremony
|
|
197
536
|
*/
|
|
198
|
-
async generateProjectDocument(progress = null) {
|
|
199
|
-
const processor = new TemplateProcessor(this.
|
|
537
|
+
async generateProjectDocument(progress = null, progressPath = null, nonInteractive = false) {
|
|
538
|
+
const processor = new TemplateProcessor(progressPath || this.sponsorCallProgressPath, nonInteractive);
|
|
200
539
|
await processor.processTemplate(progress);
|
|
201
540
|
}
|
|
202
541
|
|
|
@@ -208,46 +547,152 @@ ANTHROPIC_API_KEY=
|
|
|
208
547
|
}
|
|
209
548
|
|
|
210
549
|
/**
|
|
211
|
-
* Initialize the AVC project
|
|
550
|
+
* Initialize the AVC project structure (no API keys required)
|
|
551
|
+
* Creates .avc folder, avc.json config, .env file, and gitignore entry
|
|
212
552
|
*/
|
|
213
553
|
async init() {
|
|
214
|
-
console.log('\n🚀 AVC Project Initiator
|
|
554
|
+
console.log('\n🚀 AVC Project Initiator\n');
|
|
555
|
+
console.log(`Project directory: ${this.projectRoot}\n`);
|
|
556
|
+
|
|
557
|
+
if (this.isAvcProject()) {
|
|
558
|
+
// Project already initialized
|
|
559
|
+
console.log('✓ AVC project already initialized');
|
|
560
|
+
console.log('\nProject is ready to use.');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Suppress all console output during initialization
|
|
565
|
+
const originalLog = console.log;
|
|
566
|
+
console.log = () => {};
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
// Create project structure silently
|
|
570
|
+
this.createAvcFolder();
|
|
571
|
+
this.createAvcConfig();
|
|
572
|
+
this.createEnvFile();
|
|
573
|
+
this.addToGitignore();
|
|
574
|
+
this.createVitePressSetup();
|
|
575
|
+
} finally {
|
|
576
|
+
console.log = originalLog;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
console.log('\n✅ AVC project initialized!\n');
|
|
580
|
+
console.log('Next steps:');
|
|
581
|
+
console.log(' 1. Add your API key(s) to .env file');
|
|
582
|
+
console.log(' • ANTHROPIC_API_KEY for Claude');
|
|
583
|
+
console.log(' • GEMINI_API_KEY for Gemini');
|
|
584
|
+
console.log(' 2. Run /sponsor-call to start\n');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Run Sponsor Call ceremony with pre-filled answers from REPL questionnaire
|
|
589
|
+
* Used when all answers are collected via REPL UI
|
|
590
|
+
*/
|
|
591
|
+
async sponsorCallWithAnswers(answers) {
|
|
592
|
+
console.log('\n🎯 Sponsor Call Ceremony\n');
|
|
215
593
|
console.log(`Project directory: ${this.projectRoot}\n`);
|
|
216
594
|
|
|
595
|
+
// Check if project is initialized
|
|
596
|
+
if (!this.isAvcProject()) {
|
|
597
|
+
console.log('❌ Project not initialized\n');
|
|
598
|
+
console.log(' Please run /init first to create the project structure.\n');
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const progressPath = this.sponsorCallProgressPath;
|
|
603
|
+
|
|
604
|
+
console.log('Starting Sponsor Call ceremony with provided answers...\n');
|
|
605
|
+
|
|
606
|
+
// Count answers provided
|
|
607
|
+
const answeredCount = Object.values(answers).filter(v => v !== null && v !== '').length;
|
|
608
|
+
console.log(`📊 Received ${answeredCount}/5 answers from questionnaire\n`);
|
|
609
|
+
|
|
610
|
+
// Validate API key before starting ceremony
|
|
611
|
+
console.log('Step 1/3: Validating API configuration...');
|
|
612
|
+
const validationResult = await this.validateProviderApiKey();
|
|
613
|
+
if (!validationResult.valid) {
|
|
614
|
+
console.log('\n❌ API Key Validation Failed\n');
|
|
615
|
+
console.log(` ${validationResult.message}\n`);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Create progress with pre-filled answers
|
|
620
|
+
console.log('Step 2/3: Processing questionnaire answers...');
|
|
621
|
+
const progress = {
|
|
622
|
+
stage: 'questionnaire',
|
|
623
|
+
totalQuestions: 5,
|
|
624
|
+
answeredQuestions: 5,
|
|
625
|
+
collectedValues: answers,
|
|
626
|
+
lastUpdate: new Date().toISOString()
|
|
627
|
+
};
|
|
628
|
+
this.writeProgress(progress, progressPath);
|
|
629
|
+
|
|
630
|
+
// Generate project document with pre-filled answers
|
|
631
|
+
console.log('Step 3/3: Generating project document...');
|
|
632
|
+
await this.generateProjectDocument(progress, progressPath, true); // nonInteractive = true
|
|
633
|
+
|
|
634
|
+
// Mark as completed and clean up
|
|
635
|
+
progress.stage = 'completed';
|
|
636
|
+
progress.lastUpdate = new Date().toISOString();
|
|
637
|
+
this.writeProgress(progress, progressPath);
|
|
638
|
+
this.clearProgress(progressPath);
|
|
639
|
+
|
|
640
|
+
console.log('\n✅ Project defined successfully!');
|
|
641
|
+
console.log('\nNext steps:');
|
|
642
|
+
console.log(' 1. Review .avc/project/doc.md for your project definition');
|
|
643
|
+
console.log(' 2. Review .avc/avc.json configuration');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Run Sponsor Call ceremony to define project with AI assistance
|
|
648
|
+
* Requires API keys to be configured in .env file
|
|
649
|
+
*/
|
|
650
|
+
async sponsorCall() {
|
|
651
|
+
console.log('\n🎯 Sponsor Call Ceremony\n');
|
|
652
|
+
console.log(`Project directory: ${this.projectRoot}\n`);
|
|
653
|
+
|
|
654
|
+
// Check if running in REPL mode
|
|
655
|
+
const isReplMode = process.env.AVC_REPL_MODE === 'true';
|
|
656
|
+
if (isReplMode) {
|
|
657
|
+
// REPL mode is handled by repl-ink.js questionnaire display
|
|
658
|
+
// This code path shouldn't be reached from REPL
|
|
659
|
+
console.log('⚠️ Unexpected: Ceremony called directly from REPL');
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Check if project is initialized
|
|
664
|
+
if (!this.isAvcProject()) {
|
|
665
|
+
console.log('❌ Project not initialized\n');
|
|
666
|
+
console.log(' Please run /init first to create the project structure.\n');
|
|
667
|
+
return; // Don't exit in REPL mode
|
|
668
|
+
}
|
|
669
|
+
|
|
217
670
|
let progress = null;
|
|
671
|
+
const progressPath = this.sponsorCallProgressPath;
|
|
218
672
|
|
|
219
|
-
// Check for incomplete
|
|
220
|
-
if (this.
|
|
221
|
-
progress = this.readProgress();
|
|
673
|
+
// Check for incomplete ceremony
|
|
674
|
+
if (this.hasIncompleteProgress(progressPath)) {
|
|
675
|
+
progress = this.readProgress(progressPath);
|
|
222
676
|
|
|
223
677
|
if (progress && progress.stage !== 'completed') {
|
|
224
|
-
console.log('⚠️ Found incomplete
|
|
678
|
+
console.log('⚠️ Found incomplete ceremony from previous session');
|
|
225
679
|
console.log(` Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`);
|
|
226
680
|
console.log(` Stage: ${progress.stage}`);
|
|
227
681
|
console.log(` Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`);
|
|
228
682
|
console.log('\n▶️ Continuing from where you left off...\n');
|
|
229
683
|
}
|
|
230
|
-
} else if (this.isAvcProject()) {
|
|
231
|
-
// No incomplete progress but project exists - already initialized
|
|
232
|
-
console.log('✓ AVC project already initialized');
|
|
233
|
-
console.log('\nProject is ready to use.');
|
|
234
|
-
return;
|
|
235
684
|
} else {
|
|
236
685
|
// Fresh start
|
|
237
|
-
console.log('
|
|
686
|
+
console.log('Starting Sponsor Call ceremony...\n');
|
|
238
687
|
}
|
|
239
688
|
|
|
240
|
-
//
|
|
241
|
-
this.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.createEnvFile();
|
|
248
|
-
|
|
249
|
-
// Add .env to .gitignore if git repository
|
|
250
|
-
this.addToGitignore();
|
|
689
|
+
// Validate API key before starting ceremony
|
|
690
|
+
const validationResult = await this.validateProviderApiKey();
|
|
691
|
+
if (!validationResult.valid) {
|
|
692
|
+
console.log('\n❌ API Key Validation Failed\n');
|
|
693
|
+
console.log(` ${validationResult.message}\n`);
|
|
694
|
+
return; // Don't exit in REPL mode
|
|
695
|
+
}
|
|
251
696
|
|
|
252
697
|
// Save initial progress
|
|
253
698
|
if (!progress) {
|
|
@@ -258,25 +703,24 @@ ANTHROPIC_API_KEY=
|
|
|
258
703
|
collectedValues: {},
|
|
259
704
|
lastUpdate: new Date().toISOString()
|
|
260
705
|
};
|
|
261
|
-
this.writeProgress(progress);
|
|
706
|
+
this.writeProgress(progress, progressPath);
|
|
262
707
|
}
|
|
263
708
|
|
|
264
709
|
// Generate project document via Sponsor Call ceremony
|
|
265
|
-
await this.generateProjectDocument(progress);
|
|
710
|
+
await this.generateProjectDocument(progress, progressPath, isReplMode);
|
|
266
711
|
|
|
267
712
|
// Mark as completed and clean up
|
|
268
713
|
progress.stage = 'completed';
|
|
269
714
|
progress.lastUpdate = new Date().toISOString();
|
|
270
|
-
this.writeProgress(progress);
|
|
271
|
-
this.clearProgress();
|
|
715
|
+
this.writeProgress(progress, progressPath);
|
|
716
|
+
this.clearProgress(progressPath);
|
|
272
717
|
|
|
273
|
-
console.log('\n✅
|
|
718
|
+
console.log('\n✅ Project defined successfully!');
|
|
274
719
|
console.log('\nNext steps:');
|
|
275
|
-
console.log(' 1.
|
|
276
|
-
console.log(' 2. Review .avc/
|
|
277
|
-
console.log(' 3.
|
|
278
|
-
console.log(' 4.
|
|
279
|
-
console.log(' 5. Use AI agents to implement features');
|
|
720
|
+
console.log(' 1. Review .avc/project/doc.md for your project definition');
|
|
721
|
+
console.log(' 2. Review .avc/avc.json configuration');
|
|
722
|
+
console.log(' 3. Create your project context and work items');
|
|
723
|
+
console.log(' 4. Use AI agents to implement features');
|
|
280
724
|
}
|
|
281
725
|
|
|
282
726
|
/**
|
|
@@ -297,6 +741,184 @@ ANTHROPIC_API_KEY=
|
|
|
297
741
|
console.log('\nRun "avc init" to initialize the project.');
|
|
298
742
|
}
|
|
299
743
|
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Remove AVC project structure (destructive operation)
|
|
747
|
+
* Requires confirmation by typing "delete all"
|
|
748
|
+
*/
|
|
749
|
+
async remove() {
|
|
750
|
+
console.log('\n🗑️ Remove AVC Project Structure\n');
|
|
751
|
+
console.log(`Project directory: ${this.projectRoot}\n`);
|
|
752
|
+
|
|
753
|
+
// Check if project is initialized
|
|
754
|
+
if (!this.isAvcProject()) {
|
|
755
|
+
console.log('⚠️ No AVC project found in this directory.\n');
|
|
756
|
+
console.log('Nothing to remove.\n');
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Show what will be deleted
|
|
761
|
+
console.log('⚠️ WARNING: This is a DESTRUCTIVE operation!\n');
|
|
762
|
+
console.log('The following will be PERMANENTLY DELETED:\n');
|
|
763
|
+
|
|
764
|
+
// List contents of .avc folder
|
|
765
|
+
const avcContents = this.getAvcContents();
|
|
766
|
+
if (avcContents.length > 0) {
|
|
767
|
+
console.log('📁 .avc/ folder contents:');
|
|
768
|
+
avcContents.forEach(item => {
|
|
769
|
+
console.log(` • ${item}`);
|
|
770
|
+
});
|
|
771
|
+
console.log('');
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
console.log('❌ All project definitions, epics, stories, tasks, and documentation will be lost.');
|
|
775
|
+
console.log('❌ All VitePress documentation will be deleted.');
|
|
776
|
+
console.log('❌ This action CANNOT be undone.\n');
|
|
777
|
+
|
|
778
|
+
// Check for .env file
|
|
779
|
+
const envPath = path.join(this.projectRoot, '.env');
|
|
780
|
+
const hasEnvFile = fs.existsSync(envPath);
|
|
781
|
+
if (hasEnvFile) {
|
|
782
|
+
console.log('ℹ️ Note: The .env file will NOT be deleted.');
|
|
783
|
+
console.log(' You may want to manually remove API keys if no longer needed.\n');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Check if running in REPL mode
|
|
787
|
+
const isReplMode = process.env.AVC_REPL_MODE === 'true';
|
|
788
|
+
|
|
789
|
+
if (isReplMode) {
|
|
790
|
+
// In REPL mode, interactive confirmation is handled by repl-ink.js
|
|
791
|
+
// This code path shouldn't be reached from REPL
|
|
792
|
+
console.log('⚠️ Unexpected: Remove called directly from REPL');
|
|
793
|
+
console.log('Interactive confirmation should be handled by REPL interface.');
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
console.log('─'.repeat(60));
|
|
798
|
+
console.log('To confirm deletion, type exactly: delete all');
|
|
799
|
+
console.log('To cancel, type anything else or press Ctrl+C');
|
|
800
|
+
console.log('─'.repeat(60));
|
|
801
|
+
console.log('');
|
|
802
|
+
|
|
803
|
+
// Create readline interface for confirmation
|
|
804
|
+
const readline = await import('readline');
|
|
805
|
+
const rl = readline.createInterface({
|
|
806
|
+
input: process.stdin,
|
|
807
|
+
output: process.stdout
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
return new Promise((resolve) => {
|
|
811
|
+
rl.question('Confirmation: ', (answer) => {
|
|
812
|
+
rl.close();
|
|
813
|
+
console.log('');
|
|
814
|
+
|
|
815
|
+
if (answer.trim() === 'delete all') {
|
|
816
|
+
// Proceed with deletion
|
|
817
|
+
console.log('🗑️ Deleting AVC project structure...\n');
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
// Get list of what's being deleted before deletion
|
|
821
|
+
const deletedItems = this.getAvcContents();
|
|
822
|
+
|
|
823
|
+
// Delete .avc folder
|
|
824
|
+
fs.rmSync(this.avcDir, { recursive: true, force: true });
|
|
825
|
+
|
|
826
|
+
console.log('✅ Successfully deleted:\n');
|
|
827
|
+
console.log(' 📁 .avc/ folder and all contents:');
|
|
828
|
+
deletedItems.forEach(item => {
|
|
829
|
+
console.log(` • ${item}`);
|
|
830
|
+
});
|
|
831
|
+
console.log('');
|
|
832
|
+
|
|
833
|
+
// Reminder about .env file
|
|
834
|
+
if (hasEnvFile) {
|
|
835
|
+
console.log('ℹ️ Manual cleanup reminder:\n');
|
|
836
|
+
console.log(' The .env file was NOT deleted and still contains:');
|
|
837
|
+
console.log(' • ANTHROPIC_API_KEY');
|
|
838
|
+
console.log(' • GEMINI_API_KEY');
|
|
839
|
+
console.log(' • (and any other API keys you added)\n');
|
|
840
|
+
console.log(' If these API keys are not used elsewhere in your project,');
|
|
841
|
+
console.log(' you may want to manually delete the .env file or remove');
|
|
842
|
+
console.log(' the unused keys.\n');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
console.log('✅ AVC project structure has been completely removed.\n');
|
|
846
|
+
console.log('You can re-initialize anytime by running /init\n');
|
|
847
|
+
|
|
848
|
+
resolve();
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.log(`❌ Error during deletion: ${error.message}\n`);
|
|
851
|
+
console.log('The .avc folder may be partially deleted.');
|
|
852
|
+
console.log('You may need to manually remove it.\n');
|
|
853
|
+
resolve();
|
|
854
|
+
}
|
|
855
|
+
} else {
|
|
856
|
+
// Cancellation
|
|
857
|
+
console.log('❌ Operation cancelled.\n');
|
|
858
|
+
console.log('No files were deleted.\n');
|
|
859
|
+
resolve();
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Get list of contents in .avc folder for display
|
|
867
|
+
*/
|
|
868
|
+
getAvcContents() {
|
|
869
|
+
const contents = [];
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
if (!fs.existsSync(this.avcDir)) {
|
|
873
|
+
return contents;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Read .avc directory
|
|
877
|
+
const items = fs.readdirSync(this.avcDir);
|
|
878
|
+
|
|
879
|
+
items.forEach(item => {
|
|
880
|
+
const itemPath = path.join(this.avcDir, item);
|
|
881
|
+
const stat = fs.statSync(itemPath);
|
|
882
|
+
|
|
883
|
+
if (stat.isDirectory()) {
|
|
884
|
+
// Count items in subdirectories
|
|
885
|
+
const subItems = this.countItemsRecursive(itemPath);
|
|
886
|
+
contents.push(`${item}/ (${subItems} items)`);
|
|
887
|
+
} else {
|
|
888
|
+
contents.push(item);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
} catch (error) {
|
|
892
|
+
// Ignore errors, return what we have
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return contents;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Recursively count items in a directory
|
|
900
|
+
*/
|
|
901
|
+
countItemsRecursive(dirPath) {
|
|
902
|
+
let count = 0;
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
const items = fs.readdirSync(dirPath);
|
|
906
|
+
count += items.length;
|
|
907
|
+
|
|
908
|
+
items.forEach(item => {
|
|
909
|
+
const itemPath = path.join(dirPath, item);
|
|
910
|
+
const stat = fs.statSync(itemPath);
|
|
911
|
+
|
|
912
|
+
if (stat.isDirectory()) {
|
|
913
|
+
count += this.countItemsRecursive(itemPath);
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
} catch (error) {
|
|
917
|
+
// Ignore errors
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return count;
|
|
921
|
+
}
|
|
300
922
|
}
|
|
301
923
|
|
|
302
924
|
// Export for use in REPL
|
|
@@ -311,11 +933,17 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
311
933
|
case 'init':
|
|
312
934
|
initiator.init();
|
|
313
935
|
break;
|
|
936
|
+
case 'sponsor-call':
|
|
937
|
+
initiator.sponsorCall();
|
|
938
|
+
break;
|
|
314
939
|
case 'status':
|
|
315
940
|
initiator.status();
|
|
316
941
|
break;
|
|
942
|
+
case 'remove':
|
|
943
|
+
initiator.remove();
|
|
944
|
+
break;
|
|
317
945
|
default:
|
|
318
|
-
console.log('Unknown command. Available commands: init, status');
|
|
946
|
+
console.log('Unknown command. Available commands: init, sponsor-call, status, remove');
|
|
319
947
|
process.exit(1);
|
|
320
948
|
}
|
|
321
949
|
}
|