@agile-vibe-coding/avc 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nacho Coll (@NachoColl)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ⚠️ UNDER DEVELOPMENT - DO NOT USE ⚠️
2
+
3
+ This package is currently under active development and is not ready for production use.
package/cli/index.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startRepl, executeCommand } from './repl-ink.js';
4
+
5
+ /**
6
+ * AVC CLI - Main entry point
7
+ *
8
+ * Supports both interactive REPL mode and direct command execution
9
+ *
10
+ * Usage:
11
+ * avc - Start interactive REPL
12
+ * avc help - Show help and exit
13
+ * avc init - Run init command and exit
14
+ * avc status - Run status command and exit
15
+ */
16
+
17
+ const args = process.argv.slice(2);
18
+
19
+ if (args.length === 0) {
20
+ // No arguments - start interactive REPL
21
+ startRepl();
22
+ } else {
23
+ // Command provided - execute non-interactively
24
+ const command = args[0].startsWith('/') ? args[0] : `/${args[0]}`;
25
+ executeCommand(command).catch(() => {
26
+ process.exit(1);
27
+ });
28
+ }
package/cli/init.js ADDED
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { execSync } from 'child_process';
7
+ import { TemplateProcessor } from './template-processor.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ /**
13
+ * AVC Project Initiator
14
+ *
15
+ * Checks if an AVC project exists in the current directory and creates
16
+ * the necessary files and folders if they don't exist:
17
+ * - .avc/ folder
18
+ * - .avc/avc.json settings file
19
+ */
20
+
21
+ class ProjectInitiator {
22
+ constructor() {
23
+ this.projectRoot = process.cwd();
24
+ this.avcDir = path.join(this.projectRoot, '.avc');
25
+ this.avcConfigPath = path.join(this.avcDir, 'avc.json');
26
+ this.progressPath = path.join(this.avcDir, 'init-progress.json');
27
+ }
28
+
29
+ /**
30
+ * Get the project name from the current folder name
31
+ */
32
+ getProjectName() {
33
+ return path.basename(this.projectRoot);
34
+ }
35
+
36
+ /**
37
+ * Check if .avc folder exists
38
+ */
39
+ hasAvcFolder() {
40
+ return fs.existsSync(this.avcDir);
41
+ }
42
+
43
+ /**
44
+ * Check if avc.json exists
45
+ */
46
+ hasAvcConfig() {
47
+ return fs.existsSync(this.avcConfigPath);
48
+ }
49
+
50
+ /**
51
+ * Create .avc folder
52
+ */
53
+ createAvcFolder() {
54
+ if (!this.hasAvcFolder()) {
55
+ fs.mkdirSync(this.avcDir, { recursive: true });
56
+ console.log('✓ Created .avc/ folder');
57
+ return true;
58
+ }
59
+ console.log('✓ .avc/ folder already exists');
60
+ return false;
61
+ }
62
+
63
+ /**
64
+ * Create avc.json with default settings
65
+ */
66
+ createAvcConfig() {
67
+ if (!this.hasAvcConfig()) {
68
+ const defaultConfig = {
69
+ version: '1.0.0',
70
+ projectName: this.getProjectName(),
71
+ framework: 'avc',
72
+ created: new Date().toISOString(),
73
+ settings: {
74
+ contextScopes: ['epic', 'story', 'task', 'subtask'],
75
+ workItemStatuses: ['ready', 'pending', 'implementing', 'implemented', 'testing', 'completed', 'blocked', 'feedback'],
76
+ agentTypes: ['product-owner', 'server', 'client', 'infrastructure', 'testing'],
77
+ model: 'claude-sonnet-4-5-20250929'
78
+ }
79
+ };
80
+
81
+ fs.writeFileSync(
82
+ this.avcConfigPath,
83
+ JSON.stringify(defaultConfig, null, 2),
84
+ 'utf8'
85
+ );
86
+ console.log('✓ Created .avc/avc.json configuration file');
87
+ return true;
88
+ }
89
+ console.log('✓ .avc/avc.json already exists');
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * Check if current directory is a git repository
95
+ */
96
+ isGitRepository() {
97
+ try {
98
+ execSync('git rev-parse --git-dir', { stdio: 'ignore' });
99
+ return true;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create .env file for API keys
107
+ */
108
+ createEnvFile() {
109
+ const envPath = path.join(this.projectRoot, '.env');
110
+
111
+ if (!fs.existsSync(envPath)) {
112
+ const envContent = `# Anthropic API Key for AI-powered Sponsor Call ceremony
113
+ # Get your key at: https://console.anthropic.com/settings/keys
114
+ ANTHROPIC_API_KEY=
115
+
116
+ # Add other API keys below as needed
117
+ `;
118
+ fs.writeFileSync(envPath, envContent, 'utf8');
119
+ console.log('✓ Created .env file for API keys');
120
+ return true;
121
+ }
122
+ console.log('✓ .env file already exists');
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Add .env to .gitignore if git repository
128
+ */
129
+ addToGitignore() {
130
+ if (!this.isGitRepository()) {
131
+ return;
132
+ }
133
+
134
+ const gitignorePath = path.join(this.projectRoot, '.gitignore');
135
+
136
+ let gitignoreContent = '';
137
+ if (fs.existsSync(gitignorePath)) {
138
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
139
+ }
140
+
141
+ // Check if .env is already in .gitignore
142
+ if (gitignoreContent.includes('.env')) {
143
+ console.log('✓ .env already in .gitignore');
144
+ return;
145
+ }
146
+
147
+ // Add .env to .gitignore
148
+ const newContent = gitignoreContent
149
+ ? `${gitignoreContent}\n# Environment variables\n.env\n`
150
+ : '# Environment variables\n.env\n';
151
+
152
+ fs.writeFileSync(gitignorePath, newContent, 'utf8');
153
+ console.log('✓ Added .env to .gitignore');
154
+ }
155
+
156
+ /**
157
+ * Check if there's an incomplete init in progress
158
+ */
159
+ hasIncompleteInit() {
160
+ return fs.existsSync(this.progressPath);
161
+ }
162
+
163
+ /**
164
+ * Read progress from file
165
+ */
166
+ readProgress() {
167
+ try {
168
+ const content = fs.readFileSync(this.progressPath, 'utf8');
169
+ return JSON.parse(content);
170
+ } catch (error) {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Write progress to file
177
+ */
178
+ writeProgress(progress) {
179
+ if (!fs.existsSync(this.avcDir)) {
180
+ fs.mkdirSync(this.avcDir, { recursive: true });
181
+ }
182
+ fs.writeFileSync(this.progressPath, JSON.stringify(progress, null, 2), 'utf8');
183
+ }
184
+
185
+ /**
186
+ * Clear progress file (init completed successfully)
187
+ */
188
+ clearProgress() {
189
+ if (fs.existsSync(this.progressPath)) {
190
+ fs.unlinkSync(this.progressPath);
191
+ }
192
+ }
193
+
194
+
195
+ /**
196
+ * Generate project document via Sponsor Call ceremony
197
+ */
198
+ async generateProjectDocument(progress = null) {
199
+ const processor = new TemplateProcessor(this.progressPath);
200
+ await processor.processTemplate(progress);
201
+ }
202
+
203
+ /**
204
+ * Check if the current directory is an AVC project
205
+ */
206
+ isAvcProject() {
207
+ return this.hasAvcFolder() && this.hasAvcConfig();
208
+ }
209
+
210
+ /**
211
+ * Initialize the AVC project
212
+ */
213
+ async init() {
214
+ console.log('\n🚀 AVC Project Initiator - Sponsor Call Ceremony\n');
215
+ console.log(`Project directory: ${this.projectRoot}\n`);
216
+
217
+ let progress = null;
218
+
219
+ // Check for incomplete initialization
220
+ if (this.hasIncompleteInit()) {
221
+ progress = this.readProgress();
222
+
223
+ if (progress && progress.stage !== 'completed') {
224
+ console.log('⚠️ Found incomplete initialization from previous session');
225
+ console.log(` Last activity: ${new Date(progress.lastUpdate).toLocaleString()}`);
226
+ console.log(` Stage: ${progress.stage}`);
227
+ console.log(` Progress: ${progress.answeredQuestions || 0}/${progress.totalQuestions || 0} questions answered`);
228
+ console.log('\n▶️ Continuing from where you left off...\n');
229
+ }
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
+ } else {
236
+ // Fresh start
237
+ console.log('Initializing AVC project...\n');
238
+ }
239
+
240
+ // Create .avc folder
241
+ this.createAvcFolder();
242
+
243
+ // Create avc.json
244
+ this.createAvcConfig();
245
+
246
+ // Create .env file for API keys
247
+ this.createEnvFile();
248
+
249
+ // Add .env to .gitignore if git repository
250
+ this.addToGitignore();
251
+
252
+ // Save initial progress
253
+ if (!progress) {
254
+ progress = {
255
+ stage: 'questionnaire',
256
+ totalQuestions: 5,
257
+ answeredQuestions: 0,
258
+ collectedValues: {},
259
+ lastUpdate: new Date().toISOString()
260
+ };
261
+ this.writeProgress(progress);
262
+ }
263
+
264
+ // Generate project document via Sponsor Call ceremony
265
+ await this.generateProjectDocument(progress);
266
+
267
+ // Mark as completed and clean up
268
+ progress.stage = 'completed';
269
+ progress.lastUpdate = new Date().toISOString();
270
+ this.writeProgress(progress);
271
+ this.clearProgress();
272
+
273
+ console.log('\n✅ AVC project initialized successfully!');
274
+ console.log('\nNext steps:');
275
+ console.log(' 1. Add your ANTHROPIC_API_KEY to .env file');
276
+ console.log(' 2. Review .avc/project/doc.md for your project definition');
277
+ console.log(' 3. Review .avc/avc.json configuration');
278
+ console.log(' 4. Create your project context and work items');
279
+ console.log(' 5. Use AI agents to implement features');
280
+ }
281
+
282
+ /**
283
+ * Display current project status
284
+ */
285
+ status() {
286
+ console.log('\n📊 AVC Project Status\n');
287
+ console.log(`Project directory: ${this.projectRoot}`);
288
+ console.log(`Project name: ${this.getProjectName()}\n`);
289
+
290
+ console.log('Components:');
291
+ console.log(` .avc/ folder: ${this.hasAvcFolder() ? '✓' : '✗'}`);
292
+ console.log(` avc.json: ${this.hasAvcConfig() ? '✓' : '✗'}`);
293
+
294
+ console.log(`\nStatus: ${this.isAvcProject() ? '✅ Initialized' : '⚠️ Not initialized'}`);
295
+
296
+ if (!this.isAvcProject()) {
297
+ console.log('\nRun "avc init" to initialize the project.');
298
+ }
299
+ }
300
+ }
301
+
302
+ // Export for use in REPL
303
+ export { ProjectInitiator };
304
+
305
+ // CLI execution (only when run directly, not when imported)
306
+ if (import.meta.url === `file://${process.argv[1]}`) {
307
+ const command = process.argv[2] || 'init';
308
+ const initiator = new ProjectInitiator();
309
+
310
+ switch (command) {
311
+ case 'init':
312
+ initiator.init();
313
+ break;
314
+ case 'status':
315
+ initiator.status();
316
+ break;
317
+ default:
318
+ console.log('Unknown command. Available commands: init, status');
319
+ process.exit(1);
320
+ }
321
+ }
package/cli/logger.js ADDED
@@ -0,0 +1,138 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ /**
6
+ * Logger - Writes debug and error logs to file
7
+ *
8
+ * Log location: ~/.avc/logs/avc.log
9
+ * Log rotation: Keeps last 10MB, rotates to avc.log.old
10
+ */
11
+ export class Logger {
12
+ constructor(componentName = 'AVC') {
13
+ this.componentName = componentName;
14
+ this.logDir = path.join(os.homedir(), '.avc', 'logs');
15
+ this.logFile = path.join(this.logDir, 'avc.log');
16
+ this.maxLogSize = 10 * 1024 * 1024; // 10MB
17
+
18
+ this.ensureLogDir();
19
+ }
20
+
21
+ ensureLogDir() {
22
+ try {
23
+ if (!fs.existsSync(this.logDir)) {
24
+ fs.mkdirSync(this.logDir, { recursive: true });
25
+ }
26
+ } catch (error) {
27
+ // Silently fail if we can't create log directory
28
+ console.error('Failed to create log directory:', error.message);
29
+ }
30
+ }
31
+
32
+ rotateLogIfNeeded() {
33
+ try {
34
+ if (fs.existsSync(this.logFile)) {
35
+ const stats = fs.statSync(this.logFile);
36
+ if (stats.size > this.maxLogSize) {
37
+ const oldLogFile = this.logFile + '.old';
38
+ if (fs.existsSync(oldLogFile)) {
39
+ fs.unlinkSync(oldLogFile);
40
+ }
41
+ fs.renameSync(this.logFile, oldLogFile);
42
+ }
43
+ }
44
+ } catch (error) {
45
+ // Silently fail rotation
46
+ }
47
+ }
48
+
49
+ formatMessage(level, message, data = null) {
50
+ const timestamp = new Date().toISOString();
51
+ let logLine = `[${timestamp}] [${level}] [${this.componentName}] ${message}`;
52
+
53
+ if (data) {
54
+ if (data instanceof Error) {
55
+ logLine += `\n Error: ${data.message}`;
56
+ if (data.stack) {
57
+ logLine += `\n Stack: ${data.stack}`;
58
+ }
59
+ } else if (typeof data === 'object') {
60
+ try {
61
+ logLine += `\n Data: ${JSON.stringify(data, null, 2)}`;
62
+ } catch (e) {
63
+ logLine += `\n Data: [Unable to stringify]`;
64
+ }
65
+ } else {
66
+ logLine += `\n Data: ${data}`;
67
+ }
68
+ }
69
+
70
+ return logLine + '\n';
71
+ }
72
+
73
+ writeLog(level, message, data = null) {
74
+ try {
75
+ this.rotateLogIfNeeded();
76
+ const logMessage = this.formatMessage(level, message, data);
77
+ fs.appendFileSync(this.logFile, logMessage, 'utf8');
78
+ } catch (error) {
79
+ // Silently fail if we can't write to log
80
+ console.error('Failed to write log:', error.message);
81
+ }
82
+ }
83
+
84
+ debug(message, data = null) {
85
+ this.writeLog('DEBUG', message, data);
86
+ }
87
+
88
+ info(message, data = null) {
89
+ this.writeLog('INFO', message, data);
90
+ }
91
+
92
+ warn(message, data = null) {
93
+ this.writeLog('WARN', message, data);
94
+ }
95
+
96
+ error(message, data = null) {
97
+ this.writeLog('ERROR', message, data);
98
+ }
99
+
100
+ // Read recent logs (last N lines)
101
+ readRecentLogs(lines = 50) {
102
+ try {
103
+ if (!fs.existsSync(this.logFile)) {
104
+ return 'No logs available yet.';
105
+ }
106
+
107
+ const content = fs.readFileSync(this.logFile, 'utf8');
108
+ const allLines = content.split('\n').filter(line => line.trim());
109
+ const recentLines = allLines.slice(-lines);
110
+
111
+ return recentLines.join('\n');
112
+ } catch (error) {
113
+ return `Error reading logs: ${error.message}`;
114
+ }
115
+ }
116
+
117
+ // Clear all logs
118
+ clearLogs() {
119
+ try {
120
+ if (fs.existsSync(this.logFile)) {
121
+ fs.unlinkSync(this.logFile);
122
+ }
123
+ const oldLogFile = this.logFile + '.old';
124
+ if (fs.existsSync(oldLogFile)) {
125
+ fs.unlinkSync(oldLogFile);
126
+ }
127
+ this.info('Logs cleared');
128
+ } catch (error) {
129
+ console.error('Failed to clear logs:', error.message);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Export singleton instances for common components
135
+ export const updateLogger = new Logger('UpdateChecker');
136
+ export const installerLogger = new Logger('UpdateInstaller');
137
+ export const replLogger = new Logger('REPL');
138
+ export const initLogger = new Logger('Init');