@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.
@@ -0,0 +1,491 @@
1
+ import dotenv from 'dotenv';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import readline from 'readline';
5
+ import Anthropic from '@anthropic-ai/sdk';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ /**
12
+ * TemplateProcessor - Handles interactive template processing with AI suggestions
13
+ *
14
+ * Core workflow:
15
+ * 1. Parse template to extract variables
16
+ * 2. Prompt user for each variable (with singular/plural detection)
17
+ * 3. Generate AI suggestions for skipped variables
18
+ * 4. Replace variables in template
19
+ * 5. Enhance final document with AI
20
+ * 6. Write to .avc/project/doc.md
21
+ */
22
+ class TemplateProcessor {
23
+ constructor(progressPath = null) {
24
+ // Load environment variables from project .env
25
+ dotenv.config({ path: path.join(process.cwd(), '.env') });
26
+
27
+ this.templatePath = path.join(__dirname, 'templates/project.md');
28
+ this.outputDir = path.join(process.cwd(), '.avc/project');
29
+ this.outputPath = path.join(this.outputDir, 'doc.md');
30
+ this.avcConfigPath = path.join(process.cwd(), '.avc/avc.json');
31
+ this.progressPath = progressPath;
32
+
33
+ // Read model configuration from avc.json
34
+ this.model = this.readModelConfig();
35
+ this.apiKey = process.env.ANTHROPIC_API_KEY;
36
+ this.claudeClient = null;
37
+ }
38
+
39
+ /**
40
+ * Read model configuration from avc.json
41
+ */
42
+ readModelConfig() {
43
+ try {
44
+ const config = JSON.parse(fs.readFileSync(this.avcConfigPath, 'utf8'));
45
+ return config.settings?.model || 'claude-sonnet-4-5-20250929';
46
+ } catch (error) {
47
+ console.warn('⚠️ Could not read model config, using default');
48
+ return 'claude-sonnet-4-5-20250929';
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Extract variables from template content
54
+ * Returns array of variable objects with metadata
55
+ */
56
+ extractVariables(content) {
57
+ const pattern = /\{\{([A-Z_]+)\}\}/g;
58
+ const matches = [...content.matchAll(pattern)];
59
+
60
+ return Array.from(new Set(matches.map(m => m[1]))).map(name => ({
61
+ name,
62
+ placeholder: `{{${name}}}`,
63
+ isPlural: this.isPlural(name),
64
+ displayName: this.toDisplayName(name),
65
+ guidance: this.getGuidance(name)
66
+ }));
67
+ }
68
+
69
+ /**
70
+ * Detect if variable expects plural values based on naming conventions
71
+ */
72
+ isPlural(variableName) {
73
+ const pluralIndicators = [
74
+ 'REQUIREMENTS', 'OBJECTIVES', 'FEATURES', 'USERS',
75
+ 'WORKFLOWS', 'CONSIDERATIONS'
76
+ ];
77
+ return pluralIndicators.some(indicator => variableName.includes(indicator));
78
+ }
79
+
80
+ /**
81
+ * Convert variable name to display format
82
+ * Example: "BUSINESS_CONTEXT" -> "Business Context"
83
+ */
84
+ toDisplayName(variableName) {
85
+ return variableName
86
+ .split('_')
87
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
88
+ .join(' ');
89
+ }
90
+
91
+ /**
92
+ * Get guidance text for each variable
93
+ */
94
+ getGuidance(variableName) {
95
+ const guidance = {
96
+ 'MISSION_STATEMENT': 'A concise statement describing the core purpose and value proposition of your application.\n Example: "Enable small businesses to manage inventory and sales through an intuitive mobile-first platform."',
97
+
98
+ 'TARGET_USERS': 'Who will use this application? List different user types and their roles.\n Examples: "Small business owners", "Inventory managers", "Sales staff", "Administrators"',
99
+
100
+ 'INITIAL_SCOPE': 'Describe the initial scope of your application: key features, main workflows, and core functionality.\n What will users be able to do? What are the essential capabilities?\n Example: "Users can create tasks, assign them to team members, track progress, set deadlines, and receive notifications."',
101
+
102
+ 'TECHNICAL_CONSIDERATIONS': 'Technical requirements, constraints, or preferences for your application.\n Examples: "Mobile-first responsive design", "Must work offline", "Real-time data sync", "PostgreSQL database"',
103
+
104
+ 'SECURITY_AND_COMPLIANCE_REQUIREMENTS': 'Security, privacy, or regulatory requirements your application must meet.\n Examples: "GDPR compliance for EU users", "PCI DSS for payment data", "Two-factor authentication", "Data encryption at rest"'
105
+ };
106
+
107
+ return guidance[variableName] || '';
108
+ }
109
+
110
+ /**
111
+ * Create readline interface
112
+ */
113
+ createInterface() {
114
+ return readline.createInterface({
115
+ input: process.stdin,
116
+ output: process.stdout
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Promisified readline question
122
+ */
123
+ question(rl, query) {
124
+ return new Promise(resolve => rl.question(query, resolve));
125
+ }
126
+
127
+ /**
128
+ * Prompt user for singular value
129
+ * Returns string or null if skipped
130
+ */
131
+ async promptSingular(name, guidance) {
132
+ const rl = this.createInterface();
133
+
134
+ console.log(`\n📝 ${name}`);
135
+ if (guidance) {
136
+ console.log(` ${guidance}`);
137
+ }
138
+ console.log(' Enter response (press Enter twice when done, or Enter immediately to skip):\n');
139
+
140
+ const lines = [];
141
+ let emptyLineCount = 0;
142
+ let firstInput = true;
143
+
144
+ return new Promise((resolve) => {
145
+ rl.on('line', (line) => {
146
+ // If first input is empty, user wants to skip
147
+ if (firstInput && line.trim() === '') {
148
+ rl.close();
149
+ resolve(null);
150
+ return;
151
+ }
152
+
153
+ firstInput = false;
154
+
155
+ if (line.trim() === '') {
156
+ emptyLineCount++;
157
+ if (emptyLineCount >= 1) {
158
+ rl.close();
159
+ resolve(lines.join('\n').trim());
160
+ return;
161
+ }
162
+ } else {
163
+ emptyLineCount = 0;
164
+ lines.push(line);
165
+ }
166
+ });
167
+
168
+ rl.on('close', () => {
169
+ if (lines.length === 0) {
170
+ resolve(null);
171
+ }
172
+ });
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Prompt user for plural values (list)
178
+ * Returns array or null if skipped
179
+ */
180
+ async promptPlural(name, guidance) {
181
+ const rl = this.createInterface();
182
+
183
+ console.log(`\n📝 ${name}`);
184
+ if (guidance) {
185
+ console.log(` ${guidance}`);
186
+ }
187
+ console.log(' Enter items one per line (empty line to finish, or Enter immediately to skip):\n');
188
+
189
+ const items = [];
190
+ let itemNumber = 1;
191
+ let firstInput = true;
192
+
193
+ return new Promise((resolve) => {
194
+ const promptForItem = () => {
195
+ rl.question(` ${itemNumber}. `, (answer) => {
196
+ // If first input is empty, user wants to skip
197
+ if (firstInput && answer.trim() === '') {
198
+ rl.close();
199
+ resolve(null);
200
+ return;
201
+ }
202
+
203
+ firstInput = false;
204
+
205
+ if (answer.trim() === '') {
206
+ rl.close();
207
+ resolve(items.length > 0 ? items : null);
208
+ return;
209
+ }
210
+
211
+ items.push(answer.trim());
212
+ itemNumber++;
213
+ promptForItem();
214
+ });
215
+ };
216
+
217
+ promptForItem();
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Initialize Claude client
223
+ */
224
+ initializeClaudeClient() {
225
+ if (!process.env.ANTHROPIC_API_KEY) {
226
+ console.log('⚠️ ANTHROPIC_API_KEY not found - AI suggestions will be skipped');
227
+ return null;
228
+ }
229
+
230
+ this.claudeClient = new Anthropic({
231
+ apiKey: process.env.ANTHROPIC_API_KEY
232
+ });
233
+ return this.claudeClient;
234
+ }
235
+
236
+ /**
237
+ * Build context-aware prompt for AI suggestions
238
+ */
239
+ buildPrompt(variableName, isPlural, context) {
240
+ const displayName = this.toDisplayName(variableName);
241
+
242
+ // Build context section from previously collected values
243
+ let contextSection = '';
244
+ if (Object.keys(context).length > 0) {
245
+ contextSection = 'Project context so far:\n\n';
246
+ for (const [key, value] of Object.entries(context)) {
247
+ const keyDisplay = this.toDisplayName(key);
248
+ if (Array.isArray(value)) {
249
+ contextSection += `${keyDisplay}:\n${value.map(v => `- ${v}`).join('\n')}\n`;
250
+ } else {
251
+ contextSection += `${keyDisplay}: ${value}\n`;
252
+ }
253
+ }
254
+ contextSection += '\n';
255
+ }
256
+
257
+ if (isPlural) {
258
+ return `${contextSection}Suggest 3-5 appropriate values for "${displayName}".\n\nReturn only the suggestions, one per line, no numbering or bullets.`;
259
+ } else {
260
+ return `${contextSection}Suggest an appropriate value for "${displayName}".\n\nReturn only the suggestion text, concise (1-3 sentences).`;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Parse Claude's response into structured format
266
+ */
267
+ parseClaudeResponse(response, isPlural) {
268
+ if (isPlural) {
269
+ return response.split('\n')
270
+ .map(line => line.trim())
271
+ .filter(line => line.length > 0 && !line.match(/^[0-9\-*.]+\s/));
272
+ }
273
+ return response.trim();
274
+ }
275
+
276
+ /**
277
+ * Generate AI suggestions for a variable
278
+ */
279
+ async generateSuggestions(variableName, isPlural, context) {
280
+ if (!this.claudeClient && !this.initializeClaudeClient()) {
281
+ return null;
282
+ }
283
+
284
+ try {
285
+ const prompt = this.buildPrompt(variableName, isPlural, context);
286
+
287
+ const response = await this.claudeClient.messages.create({
288
+ model: this.model,
289
+ max_tokens: isPlural ? 512 : 256,
290
+ messages: [{ role: "user", content: prompt }]
291
+ });
292
+
293
+ return this.parseClaudeResponse(response.content[0].text, isPlural);
294
+ } catch (error) {
295
+ console.warn(`⚠️ Could not generate suggestions: ${error.message}`);
296
+ return null;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Prompt user for a variable value
302
+ * Returns { variable, value, source, skipped }
303
+ */
304
+ async promptUser(variable, context) {
305
+ let value;
306
+
307
+ if (variable.isPlural) {
308
+ value = await this.promptPlural(variable.displayName, variable.guidance);
309
+ } else {
310
+ value = await this.promptSingular(variable.displayName, variable.guidance);
311
+ }
312
+
313
+ // If user skipped, try to generate AI suggestions
314
+ if (value === null) {
315
+ console.log(' ✨ Generating AI suggestion...');
316
+ value = await this.generateSuggestions(variable.name, variable.isPlural, context);
317
+
318
+ if (value) {
319
+ console.log(' ✅ AI suggestion:');
320
+ if (Array.isArray(value)) {
321
+ value.forEach((item, idx) => console.log(` ${idx + 1}. ${item}`));
322
+ } else {
323
+ console.log(` ${value}`);
324
+ }
325
+ return { variable: variable.name, value, source: 'ai', skipped: true };
326
+ } else {
327
+ // No AI available or error
328
+ return { variable: variable.name, value: '', source: 'empty', skipped: true };
329
+ }
330
+ }
331
+
332
+ return { variable: variable.name, value, source: 'user', skipped: false };
333
+ }
334
+
335
+ /**
336
+ * Replace variables in template with collected values
337
+ */
338
+ replaceVariables(template, variables) {
339
+ let result = template;
340
+
341
+ for (const [variableName, value] of Object.entries(variables)) {
342
+ const placeholder = `{{${variableName}}}`;
343
+
344
+ let replacement;
345
+ if (Array.isArray(value)) {
346
+ replacement = value.map(item => `- ${item}`).join('\n');
347
+ } else {
348
+ replacement = value || '(Not specified)';
349
+ }
350
+
351
+ result = result.replace(
352
+ new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'),
353
+ replacement
354
+ );
355
+ }
356
+
357
+ return result;
358
+ }
359
+
360
+ /**
361
+ * Generate final document with LLM enhancement
362
+ */
363
+ async generateFinalDocument(templateWithValues) {
364
+ if (!this.claudeClient && !this.initializeClaudeClient()) {
365
+ // No API key - save template as-is
366
+ return templateWithValues;
367
+ }
368
+
369
+ console.log('\n🤖 Enhancing document with AI...');
370
+
371
+ try {
372
+ const response = await this.claudeClient.messages.create({
373
+ model: this.model,
374
+ max_tokens: 4096,
375
+ messages: [{
376
+ role: "user",
377
+ content: `You are creating a project definition document for an Agile Vibe Coding (AVC) project.
378
+
379
+ Here is the project information with all variables filled in:
380
+
381
+ ${templateWithValues}
382
+
383
+ Please review and enhance this document to ensure:
384
+ 1. All sections are well-formatted and clear
385
+ 2. Content is professional and actionable
386
+ 3. Sections flow logically
387
+ 4. Any incomplete sections are identified
388
+
389
+ Return the enhanced markdown document.`
390
+ }]
391
+ });
392
+
393
+ return response.content[0].text;
394
+ } catch (error) {
395
+ console.warn(`⚠️ Could not enhance document: ${error.message}`);
396
+ return templateWithValues;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Save progress to file
402
+ */
403
+ saveProgress(progress) {
404
+ if (this.progressPath) {
405
+ fs.writeFileSync(this.progressPath, JSON.stringify(progress, null, 2), 'utf8');
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Write document to file
411
+ */
412
+ async writeDocument(content) {
413
+ // Create .avc/project/ directory
414
+ if (!fs.existsSync(this.outputDir)) {
415
+ fs.mkdirSync(this.outputDir, { recursive: true });
416
+ }
417
+
418
+ // Write doc.md
419
+ fs.writeFileSync(this.outputPath, content, 'utf8');
420
+
421
+ console.log(`\n✅ Project document generated!`);
422
+ console.log(` Location: ${this.outputPath}`);
423
+ }
424
+
425
+ /**
426
+ * Main workflow - process template and generate document
427
+ */
428
+ async processTemplate(initialProgress = null) {
429
+ console.log('\n📋 Project Setup Questionnaire\n');
430
+
431
+ // 1. Read template
432
+ const templateContent = fs.readFileSync(this.templatePath, 'utf8');
433
+
434
+ // 2. Extract variables
435
+ const variables = this.extractVariables(templateContent);
436
+
437
+ // 3. Initialize or restore progress
438
+ let collectedValues = {};
439
+ let answeredCount = 0;
440
+
441
+ if (initialProgress && initialProgress.collectedValues) {
442
+ collectedValues = { ...initialProgress.collectedValues };
443
+ answeredCount = Object.keys(collectedValues).length;
444
+ console.log(`Resuming with ${answeredCount}/${variables.length} questions already answered.\n`);
445
+ } else {
446
+ console.log(`Found ${variables.length} sections to complete.\n`);
447
+ }
448
+
449
+ // 4. Collect values with context accumulation
450
+ for (const variable of variables) {
451
+ // Skip already answered questions when resuming
452
+ if (collectedValues[variable.name] !== undefined) {
453
+ console.log(`\n✓ ${variable.displayName}`);
454
+ console.log(` Using previous answer: ${
455
+ Array.isArray(collectedValues[variable.name])
456
+ ? `${collectedValues[variable.name].length} items`
457
+ : `"${collectedValues[variable.name].substring(0, 60)}${collectedValues[variable.name].length > 60 ? '...' : ''}"`
458
+ }`);
459
+ continue;
460
+ }
461
+
462
+ const result = await this.promptUser(variable, collectedValues);
463
+ collectedValues[result.variable] = result.value;
464
+ answeredCount++;
465
+
466
+ // Save progress after each question
467
+ if (this.progressPath) {
468
+ const progress = {
469
+ stage: 'questionnaire',
470
+ totalQuestions: variables.length,
471
+ answeredQuestions: answeredCount,
472
+ collectedValues: collectedValues,
473
+ lastUpdate: new Date().toISOString()
474
+ };
475
+ this.saveProgress(progress);
476
+ }
477
+ }
478
+
479
+ // 5. Replace variables in template
480
+ console.log('\n🔄 Preparing project document...');
481
+ const templateWithValues = this.replaceVariables(templateContent, collectedValues);
482
+
483
+ // 6. Enhance document with LLM
484
+ const finalDocument = await this.generateFinalDocument(templateWithValues);
485
+
486
+ // 7. Write to file
487
+ await this.writeDocument(finalDocument);
488
+ }
489
+ }
490
+
491
+ export { TemplateProcessor };
@@ -0,0 +1,62 @@
1
+ You will be helping to create a comprehensive software application definition document. This document will serve as a foundational specification that can later be broken down into domain-specific details and related prompts for development agents.
2
+
3
+ ## Project Information
4
+
5
+ ### Mission Statement
6
+
7
+ {{MISSION_STATEMENT}}
8
+
9
+ ### Target Users
10
+
11
+ {{TARGET_USERS}}
12
+
13
+ ### Initial Scope
14
+
15
+ {{INITIAL_SCOPE}}
16
+
17
+ ### Technical Considerations
18
+
19
+ {{TECHNICAL_CONSIDERATIONS}}
20
+
21
+ ### Security and Compliance
22
+
23
+ {{SECURITY_AND_COMPLIANCE_REQUIREMENTS}}
24
+
25
+ ---
26
+
27
+ Your task is to generate a detailed, structured application definition that expands on the information provided above and creates a comprehensive project specification for AI-powered development.
28
+
29
+ Before writing your final application definition, use the scratchpad below to think through the key aspects:
30
+
31
+ <scratchpad>
32
+ In your scratchpad, consider:
33
+ - What is the core purpose and value proposition of this application?
34
+ - Who are the primary users/stakeholders and what are their needs?
35
+ - What are the main functional areas or domains this application will need to address?
36
+ - What are the critical features versus nice-to-have features?
37
+ - Are there any technical, security, or compliance considerations evident from the requirements?
38
+ - What integrations or external systems might be needed?
39
+ - What are the primary user journeys through the application?
40
+ </scratchpad>
41
+
42
+ Now, create a comprehensive application definition that includes the following sections:
43
+
44
+ 1. **Application Overview**: A clear, concise summary of what the application does and its primary purpose
45
+
46
+ 2. **Target Users and Stakeholders**: Identify the different types of users who will interact with this application, their roles, and their key needs
47
+
48
+ 3. **Key Features and Functionality**: Describe the essential features organized by functional area or domain (e.g., user management, data processing, reporting, etc.)
49
+
50
+ 4. **User Workflows**: Outline the primary user journeys or workflows through the application with step-by-step descriptions
51
+
52
+ 5. **Technical Architecture**: Note any important technical requirements, constraints, or preferences (e.g., platform, technology stack, scalability needs, performance requirements)
53
+
54
+ 6. **Integration Requirements**: Identify any external systems, APIs, or data sources the application needs to connect with
55
+
56
+ 7. **Security and Compliance**: Highlight any security, privacy, or regulatory compliance requirements
57
+
58
+ 8. **Success Criteria**: Define what success looks like for this application
59
+
60
+ Your final output should be a complete, well-structured application definition document that provides enough detail to serve as a foundation for creating domain-specific specifications and work items for AI agents, while remaining at a high enough level to give a comprehensive view of the entire application.
61
+
62
+ Use clear markdown formatting with headers, bullet points, and emphasis where appropriate. Do not include the scratchpad in your final output - only include the sections listed above.