@iservu-inc/adf-cli 0.2.0 → 0.3.6

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.
Files changed (33) hide show
  1. package/.project/chats/complete/2025-10-03_AGENTS-MD-AND-TOOL-GENERATORS.md +764 -0
  2. package/.project/chats/current/2025-10-03_AI-PROVIDER-INTEGRATION.md +569 -0
  3. package/.project/chats/current/2025-10-03_FRAMEWORK-UPDATE-SYSTEM.md +497 -0
  4. package/.project/chats/current/SESSION-STATUS.md +127 -0
  5. package/.project/docs/AI-PROVIDER-INTEGRATION.md +600 -0
  6. package/.project/docs/FRAMEWORK-UPDATE-INTEGRATION.md +421 -0
  7. package/.project/docs/FRAMEWORK-UPDATE-SYSTEM.md +832 -0
  8. package/.project/docs/PROJECT-STRUCTURE-EXPLANATION.md +500 -0
  9. package/.project/docs/architecture/SYSTEM-DESIGN.md +122 -1
  10. package/.project/docs/goals/PROJECT-VISION.md +33 -28
  11. package/.project/docs/tool-integrations/RESEARCH-FINDINGS.md +828 -0
  12. package/CHANGELOG.md +325 -0
  13. package/README.md +100 -11
  14. package/bin/adf.js +7 -0
  15. package/lib/ai/ai-client.js +328 -0
  16. package/lib/ai/ai-config.js +398 -0
  17. package/lib/commands/config.js +154 -0
  18. package/lib/commands/deploy.js +122 -3
  19. package/lib/commands/init.js +56 -10
  20. package/lib/frameworks/interviewer.js +89 -11
  21. package/lib/frameworks/progress-tracker.js +8 -1
  22. package/lib/generators/agents-md-generator.js +388 -0
  23. package/lib/generators/cursor-generator.js +374 -0
  24. package/lib/generators/index.js +98 -0
  25. package/lib/generators/tool-config-generator.js +188 -0
  26. package/lib/generators/vscode-generator.js +403 -0
  27. package/lib/generators/windsurf-generator.js +596 -0
  28. package/package.json +15 -3
  29. package/tests/agents-md-generator.test.js +245 -0
  30. package/tests/cursor-generator.test.js +326 -0
  31. package/tests/vscode-generator.test.js +436 -0
  32. package/tests/windsurf-generator.test.js +320 -0
  33. /package/.project/chats/{current → complete}/2025-10-03_ADF-CLI-QUALITY-BASED-PROGRESS-AND-RESUME.md +0 -0
@@ -2,6 +2,12 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const ora = require('ora');
5
+ const {
6
+ generateAgentsMd,
7
+ generateWindsurf,
8
+ generateCursor,
9
+ generateVSCode
10
+ } = require('../generators');
5
11
 
6
12
  const TOOLS = {
7
13
  windsurf: { name: 'Windsurf', configFile: '.windsurfrules' },
@@ -15,6 +21,60 @@ const TOOLS = {
15
21
  'codex-cli': { name: 'Codex CLI', configFile: '.codex/config.json' }
16
22
  };
17
23
 
24
+ /**
25
+ * Find the latest completed session
26
+ */
27
+ async function findLatestSession(cwd) {
28
+ const sessionsDir = path.join(cwd, '.adf', 'sessions');
29
+
30
+ if (!await fs.pathExists(sessionsDir)) {
31
+ return null;
32
+ }
33
+
34
+ const sessions = await fs.readdir(sessionsDir);
35
+ if (sessions.length === 0) {
36
+ return null;
37
+ }
38
+
39
+ // Find most recent session with outputs
40
+ let latestSession = null;
41
+ let latestTime = 0;
42
+
43
+ for (const sessionId of sessions) {
44
+ const sessionPath = path.join(sessionsDir, sessionId);
45
+ const outputsPath = path.join(sessionPath, 'outputs');
46
+
47
+ if (await fs.pathExists(outputsPath)) {
48
+ const stats = await fs.stat(sessionPath);
49
+ if (stats.mtimeMs > latestTime) {
50
+ latestTime = stats.mtimeMs;
51
+ latestSession = sessionPath;
52
+ }
53
+ }
54
+ }
55
+
56
+ return latestSession;
57
+ }
58
+
59
+ /**
60
+ * Get framework type from session metadata
61
+ */
62
+ async function getFrameworkFromSession(sessionPath) {
63
+ const metadataPath = path.join(sessionPath, '_metadata.json');
64
+ if (await fs.pathExists(metadataPath)) {
65
+ const metadata = await fs.readJson(metadataPath);
66
+ return metadata.framework;
67
+ }
68
+
69
+ const progressPath = path.join(sessionPath, '_progress.json');
70
+ if (await fs.pathExists(progressPath)) {
71
+ const progress = await fs.readJson(progressPath);
72
+ return progress.framework;
73
+ }
74
+
75
+ return 'balanced'; // Default fallback
76
+ }
77
+
18
78
  async function deployToTool(tool, options = {}) {
19
79
  const cwd = process.cwd();
20
80
  const adfDir = path.join(cwd, '.adf');
@@ -26,22 +86,80 @@ async function deployToTool(tool, options = {}) {
26
86
  process.exit(1);
27
87
  }
28
88
 
29
- // Read context to get workflow
89
+ // Find latest session
90
+ const sessionPath = await findLatestSession(cwd);
91
+ if (!sessionPath) {
92
+ console.error(chalk.red('\n❌ Error: No completed sessions found.'));
93
+ console.log(chalk.yellow('Run "adf init" to create requirements first.\n'));
94
+ process.exit(1);
95
+ }
96
+
97
+ const framework = await getFrameworkFromSession(sessionPath);
98
+
99
+ // Read context to get workflow (legacy support)
30
100
  const contextPath = path.join(adfDir, 'context.json');
31
101
  let context = {};
32
102
  if (await fs.pathExists(contextPath)) {
33
103
  context = await fs.readJson(contextPath);
34
104
  }
35
105
 
36
- const workflow = context.workflow || 'balanced';
106
+ const workflow = framework || context.workflow || 'balanced';
37
107
 
38
- // Deploy logic (simplified - will be implemented based on actual deploy script)
39
108
  const spinner = options.silent ? null : ora(`Deploying to ${TOOLS[tool]?.name || tool}...`).start();
40
109
 
41
110
  try {
111
+ // Generate AGENTS.md (universal standard)
112
+ if (!options.silent) {
113
+ if (spinner) spinner.text = 'Generating AGENTS.md...';
114
+ }
115
+
116
+ try {
117
+ await generateAgentsMd(sessionPath, cwd, framework);
118
+ if (!options.silent && !spinner) {
119
+ console.log(chalk.green('✓ Generated AGENTS.md'));
120
+ }
121
+ } catch (error) {
122
+ console.warn(chalk.yellow(`\n⚠️ Warning: Could not generate AGENTS.md: ${error.message}`));
123
+ }
124
+
125
+ // Generate tool-specific configurations
126
+ if (spinner) spinner.text = `Generating ${TOOLS[tool]?.name || tool} configurations...`;
127
+
128
+ try {
129
+ let generatedFiles = {};
130
+
131
+ if (tool === 'windsurf') {
132
+ generatedFiles = await generateWindsurf(sessionPath, cwd, framework);
133
+ if (!options.silent && !spinner) {
134
+ console.log(chalk.green('✓ Generated Windsurf configurations'));
135
+ console.log(chalk.gray(` - .windsurfrules (legacy)`));
136
+ console.log(chalk.gray(` - .windsurf/rules/*.md (${generatedFiles.rules?.length || 0} files)`));
137
+ console.log(chalk.gray(` - .windsurf/workflows/*.md (${generatedFiles.workflows?.length || 0} files)`));
138
+ }
139
+ } else if (tool === 'cursor') {
140
+ generatedFiles = await generateCursor(sessionPath, cwd, framework);
141
+ if (!options.silent && !spinner) {
142
+ console.log(chalk.green('✓ Generated Cursor configurations'));
143
+ console.log(chalk.gray(` - .cursor/rules`));
144
+ console.log(chalk.gray(` - .cursorrules (deprecation notice)`));
145
+ }
146
+ } else if (tool === 'vscode' || tool === 'vscode-insider') {
147
+ generatedFiles = await generateVSCode(sessionPath, cwd, framework);
148
+ if (!options.silent && !spinner) {
149
+ console.log(chalk.green('✓ Generated VS Code configurations'));
150
+ console.log(chalk.gray(` - .github/copilot-instructions.md`));
151
+ console.log(chalk.gray(` - .vscode/settings.json (custom chat modes)`));
152
+ }
153
+ }
154
+ } catch (error) {
155
+ console.warn(chalk.yellow(`\n⚠️ Warning: Could not generate ${TOOLS[tool]?.name || tool} configurations: ${error.message}`));
156
+ }
157
+
42
158
  // Get agents based on workflow
43
159
  const agentsList = getAgentsForWorkflow(workflow);
44
160
 
161
+ if (spinner) spinner.text = `Deploying to ${TOOLS[tool]?.name || tool}...`;
162
+
45
163
  // Copy agents
46
164
  const agentsDir = path.join(cwd, '.framework', 'agents');
47
165
  await fs.ensureDir(agentsDir);
@@ -82,6 +200,7 @@ async function deployToTool(tool, options = {}) {
82
200
  }
83
201
 
84
202
  if (!options.silent) {
203
+ console.log(chalk.gray(` Generated: AGENTS.md (universal AI agent config)`));
85
204
  console.log(chalk.gray(` Agents: ${agentsList.join(', ')}`));
86
205
  console.log(chalk.gray(` Config: ${toolConfig?.configFile || 'N/A'}\n`));
87
206
  }
@@ -10,6 +10,7 @@ const {
10
10
  const Interviewer = require('../frameworks/interviewer');
11
11
  const SessionManager = require('../frameworks/session-manager');
12
12
  const { deployToTool } = require('./deploy');
13
+ const { configureAIProvider, loadEnvIntoProcess, getEnvFilePath } = require('../ai/ai-config');
13
14
 
14
15
  async function init(options) {
15
16
  console.log(chalk.cyan.bold('\n🚀 AgentDevFramework - Software Development Requirements\n'));
@@ -17,13 +18,41 @@ async function init(options) {
17
18
  const cwd = process.cwd();
18
19
  const adfDir = path.join(cwd, '.adf');
19
20
 
21
+ // Load .env file if it exists (for API keys)
22
+ const envPath = getEnvFilePath(cwd);
23
+ if (await fs.pathExists(envPath)) {
24
+ loadEnvIntoProcess(envPath);
25
+ }
26
+
20
27
  // Check for resumable sessions FIRST (before asking to overwrite)
21
28
  const sessionManager = new SessionManager(cwd);
22
29
  const existingSession = await sessionManager.promptToResume();
23
30
 
24
31
  if (existingSession) {
25
32
  // Resume existing session
26
- const interviewer = new Interviewer(existingSession.progress.framework || 'balanced', cwd, existingSession);
33
+ // Check if session has AI config (from resumed session)
34
+ let aiConfig = existingSession.progress.aiConfig;
35
+
36
+ if (aiConfig) {
37
+ // We have AI config from session, but need to verify API key exists
38
+ const apiKey = process.env[aiConfig.envVar];
39
+ if (!apiKey) {
40
+ console.log(chalk.yellow(`\n⚠️ Previous session used ${aiConfig.providerName}`));
41
+ console.log(chalk.yellow(`Please configure API key to resume...\n`));
42
+ aiConfig = await configureAIProvider(cwd);
43
+ } else {
44
+ // Add API key to config (it's not stored in session for security)
45
+ aiConfig.apiKey = apiKey;
46
+ console.log(chalk.green(`\n✓ Resuming with ${aiConfig.providerName} (${aiConfig.model})\n`));
47
+ }
48
+ } else {
49
+ // Old session without AI config, configure now
50
+ console.log(chalk.yellow('\n⚠️ This session was created before AI provider integration.'));
51
+ console.log(chalk.yellow('Please configure AI provider to continue...\n'));
52
+ aiConfig = await configureAIProvider(cwd);
53
+ }
54
+
55
+ const interviewer = new Interviewer(existingSession.progress.framework || 'balanced', cwd, existingSession, aiConfig);
27
56
  const sessionPath = await interviewer.start();
28
57
 
29
58
  console.log(chalk.green.bold('\n✨ Requirements gathering complete!\n'));
@@ -76,17 +105,34 @@ async function init(options) {
76
105
  // Create .adf directory
77
106
  await fs.ensureDir(adfDir);
78
107
 
79
- // Start AI-guided interview
108
+ // Configure AI Provider (OPTIONAL - can be done later with 'adf config')
109
+ let aiConfig = null;
110
+
111
+ const { configureAI } = await inquirer.prompt([
112
+ {
113
+ type: 'confirm',
114
+ name: 'configureAI',
115
+ message: 'Configure AI provider now? (Enables intelligent follow-up questions)',
116
+ default: true
117
+ }
118
+ ]);
119
+
120
+ if (configureAI) {
121
+ aiConfig = await configureAIProvider(cwd);
122
+ } else {
123
+ console.log(chalk.yellow('\n💡 You can configure AI later by running: adf config\n'));
124
+ }
125
+
126
+ // Start interview (with or without AI)
80
127
  console.log(chalk.gray('\n' + '━'.repeat(60)) + '\n');
81
128
 
82
- const interviewer = new Interviewer(workflow, cwd);
129
+ const interviewer = new Interviewer(workflow, cwd, null, aiConfig);
83
130
  const sessionPath = await interviewer.start();
84
131
 
85
- // Show next steps
86
- console.log(chalk.cyan('📋 Next Steps:\n'));
87
- console.log(chalk.gray(` 1. Review your requirements: ${sessionPath}/outputs/`));
88
- console.log(chalk.gray(` 2. Share the output files with your AI coding assistant`));
89
- console.log(chalk.gray(` 3. Start building based on the detailed requirements\n`));
132
+ // Show completion message
133
+ console.log(chalk.cyan('📋 Requirements Complete!\n'));
134
+ console.log(chalk.gray(` Files saved to: ${sessionPath}/outputs/`));
135
+ console.log(chalk.gray(` You can review your requirements anytime\n`));
90
136
 
91
137
  // Optional: Deploy to tool
92
138
  if (options.tool) {
@@ -97,8 +143,8 @@ async function init(options) {
97
143
  {
98
144
  type: 'confirm',
99
145
  name: 'deployNow',
100
- message: 'Deploy framework to a development tool?',
101
- default: false
146
+ message: 'Automatically deploy to your IDE? (I\'ll configure everything for you)',
147
+ default: true
102
148
  }
103
149
  ]);
104
150
 
@@ -13,9 +13,10 @@ const AnswerQualityAnalyzer = require('./answer-quality-analyzer');
13
13
  */
14
14
 
15
15
  class Interviewer {
16
- constructor(framework, projectPath, existingSession = null) {
16
+ constructor(framework, projectPath, existingSession = null, aiConfig = null) {
17
17
  this.framework = framework;
18
18
  this.projectPath = projectPath;
19
+ this.aiConfig = aiConfig; // Store AI configuration
19
20
 
20
21
  if (existingSession) {
21
22
  // Resuming existing session
@@ -24,6 +25,8 @@ class Interviewer {
24
25
  this.answers = existingSession.progress.answers || {};
25
26
  this.transcript = existingSession.progress.transcript || [];
26
27
  this.isResuming = true;
28
+ // Use stored AI config from session if available
29
+ this.aiConfig = existingSession.progress.aiConfig || aiConfig;
27
30
  } else {
28
31
  // New session
29
32
  this.sessionId = this.generateSessionId();
@@ -34,6 +37,7 @@ class Interviewer {
34
37
  }
35
38
 
36
39
  this.progressTracker = null;
40
+ this.aiClient = null; // Will be initialized in start()
37
41
  }
38
42
 
39
43
  generateSessionId() {
@@ -48,6 +52,16 @@ class Interviewer {
48
52
  console.log(chalk.gray(`Session: ${this.sessionId}`));
49
53
  console.log(chalk.gray(`Files will be saved to: .adf/sessions/${this.sessionId}/\n`));
50
54
 
55
+ // Initialize AI Client
56
+ if (!this.aiClient && this.aiConfig) {
57
+ const AIClient = require('../ai/ai-client');
58
+ this.aiClient = new AIClient(this.aiConfig);
59
+ console.log(chalk.green(`✓ AI Provider: ${this.aiConfig.providerName} (${this.aiConfig.model})\n`));
60
+ } else if (!this.aiConfig) {
61
+ console.log(chalk.red('\n✖ Error: No AI configuration provided. Cannot start interview.\n'));
62
+ process.exit(1);
63
+ }
64
+
51
65
  // Create session directory
52
66
  await fs.ensureDir(this.sessionPath);
53
67
  await fs.ensureDir(path.join(this.sessionPath, 'qa-responses'));
@@ -60,7 +74,7 @@ class Interviewer {
60
74
  const questionBlocks = this.groupQuestionsIntoBlocks(questions);
61
75
 
62
76
  // Initialize progress tracker
63
- this.progressTracker = new ProgressTracker(this.sessionPath, questionBlocks.length, this.framework);
77
+ this.progressTracker = new ProgressTracker(this.sessionPath, questionBlocks.length, this.framework, this.aiConfig);
64
78
  const isResumable = await this.progressTracker.initialize();
65
79
 
66
80
  if (isResumable && this.isResuming) {
@@ -250,7 +264,8 @@ class Interviewer {
250
264
  // Show guidance
251
265
  console.log(chalk.gray(`💡 ${question.guidance}`));
252
266
  console.log(chalk.green(` ✓ Good: ${question.goodExample}`));
253
- console.log(chalk.red(` ✗ Bad: ${question.badExample}\n`));
267
+ console.log(chalk.red(` ✗ Bad: ${question.badExample}`));
268
+ console.log(chalk.gray(` (Type "skip" to skip remaining questions in this block)\n`));
254
269
 
255
270
  // Get answer
256
271
  const { answer } = await inquirer.prompt([
@@ -260,7 +275,7 @@ class Interviewer {
260
275
  message: 'Your answer:',
261
276
  validate: (input) => {
262
277
  if (!input || input.trim().length === 0) {
263
- return 'Please provide an answer or type "skip" to skip remaining questions in this block';
278
+ return 'Please provide an answer or type "skip"';
264
279
  }
265
280
  return true;
266
281
  }
@@ -271,13 +286,41 @@ class Interviewer {
271
286
  return null; // Signal to skip remaining questions
272
287
  }
273
288
 
274
- // Analyze answer quality
275
- const qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
289
+ // Analyze answer quality (use AI if available, fallback to heuristic)
290
+ let qualityMetrics;
291
+ try {
292
+ if (this.aiClient) {
293
+ const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, answer);
294
+ qualityMetrics = {
295
+ wordCount: answer.trim().split(/\s+/).length,
296
+ qualityScore: aiAnalysis.score,
297
+ isComprehensive: aiAnalysis.score >= 70,
298
+ canSkipFollowUps: aiAnalysis.score >= 85,
299
+ issues: aiAnalysis.issues,
300
+ suggestions: aiAnalysis.suggestions,
301
+ missingElements: aiAnalysis.missingElements
302
+ };
303
+ } else {
304
+ // Fallback to heuristic analysis
305
+ qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
306
+ }
307
+ } catch (error) {
308
+ // If AI analysis fails, use heuristic
309
+ console.log(chalk.yellow(`⚠️ AI analysis failed, using fallback method`));
310
+ qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
311
+ }
276
312
 
277
313
  // Show quality feedback
278
- const feedback = AnswerQualityAnalyzer.getFeedback(qualityMetrics);
279
- if (feedback) {
280
- console.log(chalk.cyan(`${feedback}`));
314
+ if (qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
315
+ console.log(chalk.yellow(`\n💡 Quality: ${qualityMetrics.qualityScore}/100`));
316
+ if (qualityMetrics.suggestions && qualityMetrics.suggestions.length > 0) {
317
+ console.log(chalk.gray(` Suggestion: ${qualityMetrics.suggestions[0]}`));
318
+ }
319
+ } else {
320
+ const feedback = AnswerQualityAnalyzer.getFeedback(qualityMetrics);
321
+ if (feedback) {
322
+ console.log(chalk.cyan(`${feedback}`));
323
+ }
281
324
  }
282
325
 
283
326
  // Track answer with quality metrics
@@ -290,7 +333,26 @@ class Interviewer {
290
333
  }
291
334
 
292
335
  // Check if answer needs follow-up
293
- const followUp = this.determineFollowUp(question, answer);
336
+ let followUp = null;
337
+
338
+ // Try AI-generated follow-up first
339
+ if (this.aiClient && qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
340
+ try {
341
+ const aiFollowUp = await this.aiClient.generateFollowUp(question.text, answer, qualityMetrics.issues);
342
+ if (aiFollowUp) {
343
+ followUp = {
344
+ message: "Let me ask a more specific question:",
345
+ question: aiFollowUp
346
+ };
347
+ }
348
+ } catch (error) {
349
+ // If AI fails, use heuristic fallback
350
+ followUp = this.determineFollowUp(question, answer);
351
+ }
352
+ } else {
353
+ // Use heuristic follow-up
354
+ followUp = this.determineFollowUp(question, answer);
355
+ }
294
356
 
295
357
  if (followUp) {
296
358
  console.log(chalk.yellow(`\n🤖 ${followUp.message}\n`));
@@ -308,7 +370,23 @@ class Interviewer {
308
370
  const combined = `${answer} | Follow-up: ${followUpAnswer}`;
309
371
 
310
372
  // Re-analyze combined answer
311
- const combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
373
+ let combinedMetrics;
374
+ try {
375
+ if (this.aiClient) {
376
+ const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, combined);
377
+ combinedMetrics = {
378
+ wordCount: combined.trim().split(/\s+/).length,
379
+ qualityScore: aiAnalysis.score,
380
+ isComprehensive: aiAnalysis.score >= 70,
381
+ canSkipFollowUps: aiAnalysis.score >= 85
382
+ };
383
+ } else {
384
+ combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
385
+ }
386
+ } catch {
387
+ combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
388
+ }
389
+
312
390
  await this.progressTracker.answerQuestion(question.id, question.text, combined, combinedMetrics);
313
391
 
314
392
  console.log(chalk.green('\n✓ Saved\n'));
@@ -8,7 +8,7 @@ const chalk = require('chalk');
8
8
  */
9
9
 
10
10
  class ProgressTracker {
11
- constructor(sessionPath, totalBlocks, framework = null) {
11
+ constructor(sessionPath, totalBlocks, framework = null, aiConfig = null) {
12
12
  this.sessionPath = sessionPath;
13
13
  this.progressFile = path.join(sessionPath, '_progress.json');
14
14
  this.progressBackupFile = path.join(sessionPath, '_progress.backup.json');
@@ -17,6 +17,13 @@ class ProgressTracker {
17
17
  this.progress = {
18
18
  sessionId: path.basename(sessionPath),
19
19
  framework: framework, // Store framework for resume
20
+ aiConfig: aiConfig ? {
21
+ provider: aiConfig.provider,
22
+ providerName: aiConfig.providerName,
23
+ model: aiConfig.model,
24
+ envVar: aiConfig.envVar
25
+ // Note: We don't store the API key for security
26
+ } : null,
20
27
  status: 'in-progress',
21
28
  startedAt: new Date().toISOString(),
22
29
  lastUpdated: new Date().toISOString(),