@iservu-inc/adf-cli 0.3.0 → 0.4.12

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 (36) hide show
  1. package/.project/chats/{current → complete}/2025-10-03_AGENTS-MD-AND-TOOL-GENERATORS.md +82 -17
  2. package/.project/chats/complete/2025-10-03_AI-PROVIDER-INTEGRATION.md +568 -0
  3. package/.project/chats/complete/2025-10-03_FRAMEWORK-UPDATE-SYSTEM.md +497 -0
  4. package/.project/chats/complete/2025-10-04_CONFIG-COMMAND.md +503 -0
  5. package/.project/chats/current/2025-10-04_PHASE-4-1-SMART-FILTERING.md +381 -0
  6. package/.project/chats/current/SESSION-STATUS.md +168 -0
  7. package/.project/docs/AI-PROVIDER-INTEGRATION.md +600 -0
  8. package/.project/docs/FRAMEWORK-UPDATE-INTEGRATION.md +421 -0
  9. package/.project/docs/FRAMEWORK-UPDATE-SYSTEM.md +832 -0
  10. package/.project/docs/PHASE-4-2-LEARNING-SYSTEM.md +881 -0
  11. package/.project/docs/PROJECT-STRUCTURE-EXPLANATION.md +500 -0
  12. package/.project/docs/SMART-FILTERING-SYSTEM.md +385 -0
  13. package/.project/docs/architecture/SYSTEM-DESIGN.md +122 -1
  14. package/.project/docs/goals/PROJECT-VISION.md +61 -34
  15. package/CHANGELOG.md +257 -1
  16. package/README.md +476 -292
  17. package/bin/adf.js +7 -0
  18. package/lib/ai/ai-client.js +328 -0
  19. package/lib/ai/ai-config.js +398 -0
  20. package/lib/analyzers/project-analyzer.js +380 -0
  21. package/lib/commands/config.js +221 -0
  22. package/lib/commands/init.js +56 -10
  23. package/lib/filters/question-filter.js +480 -0
  24. package/lib/frameworks/interviewer.js +271 -12
  25. package/lib/frameworks/progress-tracker.js +8 -1
  26. package/lib/learning/learning-manager.js +447 -0
  27. package/lib/learning/pattern-detector.js +376 -0
  28. package/lib/learning/rule-generator.js +304 -0
  29. package/lib/learning/skip-tracker.js +260 -0
  30. package/lib/learning/storage.js +296 -0
  31. package/package.json +70 -57
  32. package/tests/learning-storage.test.js +184 -0
  33. package/tests/pattern-detector.test.js +297 -0
  34. package/tests/project-analyzer.test.js +221 -0
  35. package/tests/question-filter.test.js +297 -0
  36. package/tests/skip-tracker.test.js +198 -0
@@ -2,9 +2,16 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const inquirer = require('inquirer');
4
4
  const chalk = require('chalk');
5
+ const ora = require('ora');
5
6
  const { getQuestionsForFramework } = require('./questions');
6
7
  const ProgressTracker = require('./progress-tracker');
7
8
  const AnswerQualityAnalyzer = require('./answer-quality-analyzer');
9
+ const { analyzeProject, getProjectSummary } = require('../analyzers/project-analyzer');
10
+ const { filterQuestions, getFilteringSummary } = require('../filters/question-filter');
11
+ const { SkipTracker } = require('../learning/skip-tracker');
12
+ const { detectPatterns } = require('../learning/pattern-detector');
13
+ const { updateLearnedRules, getActiveRules, getRuleExplanations } = require('../learning/rule-generator');
14
+ const { getLearningConfig } = require('../learning/storage');
8
15
 
9
16
  /**
10
17
  * Conversational AI Interviewer
@@ -13,9 +20,10 @@ const AnswerQualityAnalyzer = require('./answer-quality-analyzer');
13
20
  */
14
21
 
15
22
  class Interviewer {
16
- constructor(framework, projectPath, existingSession = null) {
23
+ constructor(framework, projectPath, existingSession = null, aiConfig = null) {
17
24
  this.framework = framework;
18
25
  this.projectPath = projectPath;
26
+ this.aiConfig = aiConfig; // Store AI configuration
19
27
 
20
28
  if (existingSession) {
21
29
  // Resuming existing session
@@ -24,6 +32,8 @@ class Interviewer {
24
32
  this.answers = existingSession.progress.answers || {};
25
33
  this.transcript = existingSession.progress.transcript || [];
26
34
  this.isResuming = true;
35
+ // Use stored AI config from session if available
36
+ this.aiConfig = existingSession.progress.aiConfig || aiConfig;
27
37
  } else {
28
38
  // New session
29
39
  this.sessionId = this.generateSessionId();
@@ -34,6 +44,9 @@ class Interviewer {
34
44
  }
35
45
 
36
46
  this.progressTracker = null;
47
+ this.aiClient = null; // Will be initialized in start()
48
+ this.skipTracker = null; // Will be initialized in start() with project context
49
+ this.learnedRules = []; // Will be loaded in start()
37
50
  }
38
51
 
39
52
  generateSessionId() {
@@ -48,19 +61,162 @@ class Interviewer {
48
61
  console.log(chalk.gray(`Session: ${this.sessionId}`));
49
62
  console.log(chalk.gray(`Files will be saved to: .adf/sessions/${this.sessionId}/\n`));
50
63
 
64
+ // Initialize AI Client
65
+ if (!this.aiClient && this.aiConfig) {
66
+ const AIClient = require('../ai/ai-client');
67
+ this.aiClient = new AIClient(this.aiConfig);
68
+ console.log(chalk.green(`✓ AI Provider: ${this.aiConfig.providerName} (${this.aiConfig.model})\n`));
69
+ } else if (!this.aiConfig) {
70
+ console.log(chalk.yellow('\n⚠️ No AI configuration - continuing without AI assistance\n'));
71
+ // Allow interview to continue without AI (graceful degradation)
72
+ }
73
+
51
74
  // Create session directory
52
75
  await fs.ensureDir(this.sessionPath);
53
76
  await fs.ensureDir(path.join(this.sessionPath, 'qa-responses'));
54
77
  await fs.ensureDir(path.join(this.sessionPath, 'outputs'));
55
78
 
79
+ // Analyze project for smart filtering
80
+ const spinner = ora('Analyzing your project for context...').start();
81
+ let projectContext = null;
82
+ let enableSmartFiltering = false;
83
+
84
+ try {
85
+ projectContext = await analyzeProject(this.projectPath);
86
+ spinner.succeed(chalk.green(`✓ Project analyzed: ${getProjectSummary(projectContext)}`));
87
+
88
+ // Initialize skip tracker with project context
89
+ this.skipTracker = new SkipTracker(this.projectPath, {
90
+ projectType: projectContext.type,
91
+ frameworks: projectContext.frameworks,
92
+ languages: projectContext.languages
93
+ });
94
+
95
+ // Load learning configuration and learned rules
96
+ const learningConfig = await getLearningConfig(this.projectPath);
97
+ if (learningConfig.enabled && learningConfig.applyLearnedFilters) {
98
+ this.learnedRules = await getActiveRules(this.projectPath);
99
+ }
100
+
101
+ if (projectContext.confidence >= 50) {
102
+ // Show project context
103
+ console.log(chalk.gray(`\n📊 Project Context:`));
104
+ console.log(chalk.gray(` Type: ${projectContext.type}`));
105
+ if (projectContext.frameworks.length > 0) {
106
+ console.log(chalk.gray(` Frameworks: ${projectContext.frameworks.join(', ')}`));
107
+ }
108
+ if (projectContext.languages.length > 0) {
109
+ console.log(chalk.gray(` Languages: ${projectContext.languages.join(', ')}`));
110
+ }
111
+ console.log(chalk.gray(` Confidence: ${projectContext.confidence}%`));
112
+
113
+ // Show learning status
114
+ if (this.learnedRules.length > 0) {
115
+ console.log(chalk.gray(` Learning: ${this.learnedRules.length} learned rule${this.learnedRules.length > 1 ? 's' : ''} active\n`));
116
+ } else {
117
+ console.log('');
118
+ }
119
+
120
+ // Ask user if they want smart filtering
121
+ const { useSmartFiltering } = await inquirer.prompt([
122
+ {
123
+ type: 'confirm',
124
+ name: 'useSmartFiltering',
125
+ message: 'Enable smart filtering to skip irrelevant questions? (Recommended)',
126
+ default: true
127
+ }
128
+ ]);
129
+
130
+ enableSmartFiltering = useSmartFiltering;
131
+
132
+ // If user enables filtering and we have learned rules, ask about applying them
133
+ if (enableSmartFiltering && this.learnedRules.length > 0) {
134
+ const ruleExplanations = getRuleExplanations(this.learnedRules);
135
+ console.log(chalk.cyan('\n🧠 I\'ve learned from your previous sessions:'));
136
+ for (const exp of ruleExplanations.slice(0, 3)) { // Show top 3
137
+ console.log(chalk.gray(` • ${exp.message}`));
138
+ }
139
+
140
+ const { applyLearning } = await inquirer.prompt([
141
+ {
142
+ type: 'confirm',
143
+ name: 'applyLearning',
144
+ message: 'Apply learned preferences? (You can manage these in: adf config learning)',
145
+ default: true
146
+ }
147
+ ]);
148
+
149
+ if (!applyLearning) {
150
+ this.learnedRules = []; // User declined
151
+ console.log(chalk.gray('\n○ Learning disabled for this session\n'));
152
+ } else {
153
+ console.log(chalk.green('\n✓ Learning enabled - applying your preferences\n'));
154
+ }
155
+ } else if (enableSmartFiltering) {
156
+ console.log(chalk.green('\n✓ Smart filtering enabled - I\'ll focus on relevant questions\n'));
157
+ } else {
158
+ console.log(chalk.gray('\n○ Smart filtering disabled - I\'ll ask all questions\n'));
159
+ }
160
+ } else {
161
+ spinner.warn(chalk.yellow(`Project analysis incomplete (${projectContext.confidence}% confidence) - asking all questions`));
162
+ console.log('');
163
+ }
164
+ } catch (error) {
165
+ spinner.fail(chalk.yellow('Could not analyze project - asking all questions'));
166
+ console.log('');
167
+ projectContext = { type: 'unknown', frameworks: [], languages: [], confidence: 0 };
168
+
169
+ // Still initialize skip tracker for basic tracking
170
+ this.skipTracker = new SkipTracker(this.projectPath, {
171
+ projectType: 'unknown',
172
+ frameworks: [],
173
+ languages: []
174
+ });
175
+ }
176
+
56
177
  // Get questions for framework
57
- const questions = getQuestionsForFramework(this.framework);
178
+ let questions = getQuestionsForFramework(this.framework);
179
+
180
+ // Apply smart filtering if enabled
181
+ let filteringResult = null;
182
+ if (enableSmartFiltering && projectContext.confidence >= 50) {
183
+ filteringResult = filterQuestions(questions, projectContext, {
184
+ enableSmartFiltering: true,
185
+ minRelevanceScore: 50,
186
+ aiClient: this.aiClient,
187
+ learnedRules: this.learnedRules // Pass learned rules
188
+ });
189
+
190
+ questions = filteringResult.questions.map(q => ({
191
+ ...q,
192
+ relevance: q.relevance // Keep relevance score
193
+ }));
194
+
195
+ // Record filtered questions to skip tracker
196
+ if (this.skipTracker && filteringResult.skipped.length > 0) {
197
+ this.skipTracker.recordFilteredQuestions(filteringResult.skipped);
198
+ }
199
+
200
+ // Show filtering summary
201
+ if (filteringResult.summary.skipped > 0) {
202
+ console.log(chalk.cyan(`🎯 Smart Filtering Results:`));
203
+ console.log(chalk.green(` ✓ ${filteringResult.summary.kept} relevant questions selected`));
204
+ console.log(chalk.gray(` ○ ${filteringResult.summary.skipped} questions skipped`));
205
+
206
+ // Show learning-based skips if any
207
+ if (filteringResult.summary.learningActive && filteringResult.summary.skippedByLearning > 0) {
208
+ console.log(chalk.magenta(` 🧠 ${filteringResult.summary.skippedByLearning} skipped based on your learning history`));
209
+ }
210
+
211
+ console.log(chalk.blue(` ⏱️ Estimated time saved: ~${filteringResult.summary.timeSaved} minutes\n`));
212
+ }
213
+ }
58
214
 
59
215
  // Group questions into blocks by phase
60
216
  const questionBlocks = this.groupQuestionsIntoBlocks(questions);
61
217
 
62
218
  // Initialize progress tracker
63
- this.progressTracker = new ProgressTracker(this.sessionPath, questionBlocks.length, this.framework);
219
+ this.progressTracker = new ProgressTracker(this.sessionPath, questionBlocks.length, this.framework, this.aiConfig);
64
220
  const isResumable = await this.progressTracker.initialize();
65
221
 
66
222
  if (isResumable && this.isResuming) {
@@ -119,6 +275,28 @@ class Interviewer {
119
275
  // Save session metadata
120
276
  await this.saveMetadata();
121
277
 
278
+ // Save learning data and update patterns (Phase 4.2)
279
+ if (this.skipTracker) {
280
+ try {
281
+ // Save skip tracker session
282
+ await this.skipTracker.saveSession();
283
+
284
+ // Detect patterns and update learned rules
285
+ const learningConfig = await getLearningConfig(this.projectPath);
286
+ if (learningConfig.enabled && learningConfig.detectPatterns) {
287
+ const patterns = await detectPatterns(this.projectPath);
288
+ const updateResult = await updateLearnedRules(this.projectPath, patterns);
289
+
290
+ if (updateResult.added > 0) {
291
+ console.log(chalk.cyan(`\n🧠 Learning System: Detected ${updateResult.added} new pattern${updateResult.added > 1 ? 's' : ''}`));
292
+ console.log(chalk.gray(` View with: adf config learning\n`));
293
+ }
294
+ }
295
+ } catch (error) {
296
+ console.warn(chalk.yellow('⚠️ Could not save learning data:', error.message));
297
+ }
298
+ }
299
+
122
300
  // Mark session as complete
123
301
  await this.progressTracker.complete();
124
302
 
@@ -250,7 +428,13 @@ class Interviewer {
250
428
  // Show guidance
251
429
  console.log(chalk.gray(`💡 ${question.guidance}`));
252
430
  console.log(chalk.green(` ✓ Good: ${question.goodExample}`));
253
- console.log(chalk.red(` ✗ Bad: ${question.badExample}\n`));
431
+ console.log(chalk.red(` ✗ Bad: ${question.badExample}`));
432
+ console.log(chalk.gray(` (Type "skip" to skip remaining questions in this block)\n`));
433
+
434
+ // Start tracking time for this question
435
+ if (this.skipTracker) {
436
+ this.skipTracker.startQuestion(question.id);
437
+ }
254
438
 
255
439
  // Get answer
256
440
  const { answer } = await inquirer.prompt([
@@ -260,7 +444,7 @@ class Interviewer {
260
444
  message: 'Your answer:',
261
445
  validate: (input) => {
262
446
  if (!input || input.trim().length === 0) {
263
- return 'Please provide an answer or type "skip" to skip remaining questions in this block';
447
+ return 'Please provide an answer or type "skip"';
264
448
  }
265
449
  return true;
266
450
  }
@@ -268,21 +452,61 @@ class Interviewer {
268
452
  ]);
269
453
 
270
454
  if (answer.toLowerCase() === 'skip') {
455
+ // Record skip event
456
+ if (this.skipTracker) {
457
+ this.skipTracker.recordSkip(question, 'manual');
458
+ }
271
459
  return null; // Signal to skip remaining questions
272
460
  }
273
461
 
274
- // Analyze answer quality
275
- const qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
462
+ // Analyze answer quality (use AI if available, fallback to heuristic)
463
+ let qualityMetrics;
464
+ try {
465
+ if (this.aiClient) {
466
+ const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, answer);
467
+ qualityMetrics = {
468
+ wordCount: answer.trim().split(/\s+/).length,
469
+ qualityScore: aiAnalysis.score,
470
+ isComprehensive: aiAnalysis.score >= 70,
471
+ canSkipFollowUps: aiAnalysis.score >= 85,
472
+ issues: aiAnalysis.issues,
473
+ suggestions: aiAnalysis.suggestions,
474
+ missingElements: aiAnalysis.missingElements
475
+ };
476
+ } else {
477
+ // Fallback to heuristic analysis
478
+ qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
479
+ }
480
+ } catch (error) {
481
+ // If AI analysis fails, use heuristic
482
+ console.log(chalk.yellow(`⚠️ AI analysis failed, using fallback method`));
483
+ qualityMetrics = AnswerQualityAnalyzer.analyze(answer, question);
484
+ }
276
485
 
277
486
  // Show quality feedback
278
- const feedback = AnswerQualityAnalyzer.getFeedback(qualityMetrics);
279
- if (feedback) {
280
- console.log(chalk.cyan(`${feedback}`));
487
+ if (qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
488
+ console.log(chalk.yellow(`\n💡 Quality: ${qualityMetrics.qualityScore}/100`));
489
+ if (qualityMetrics.suggestions && qualityMetrics.suggestions.length > 0) {
490
+ console.log(chalk.gray(` Suggestion: ${qualityMetrics.suggestions[0]}`));
491
+ }
492
+ } else {
493
+ const feedback = AnswerQualityAnalyzer.getFeedback(qualityMetrics);
494
+ if (feedback) {
495
+ console.log(chalk.cyan(`${feedback}`));
496
+ }
281
497
  }
282
498
 
283
499
  // Track answer with quality metrics
284
500
  await this.progressTracker.answerQuestion(question.id, question.text, answer, qualityMetrics);
285
501
 
502
+ // Record answer to skip tracker (Phase 4.2)
503
+ if (this.skipTracker) {
504
+ this.skipTracker.recordAnswer(question, answer, {
505
+ qualityScore: qualityMetrics.qualityScore,
506
+ richness: qualityMetrics.isComprehensive ? 'comprehensive' : 'basic'
507
+ });
508
+ }
509
+
286
510
  // Check if answer is comprehensive enough to skip follow-ups
287
511
  if (qualityMetrics.canSkipFollowUps) {
288
512
  console.log(chalk.green('\n✓ Saved\n'));
@@ -290,7 +514,26 @@ class Interviewer {
290
514
  }
291
515
 
292
516
  // Check if answer needs follow-up
293
- const followUp = this.determineFollowUp(question, answer);
517
+ let followUp = null;
518
+
519
+ // Try AI-generated follow-up first
520
+ if (this.aiClient && qualityMetrics.issues && qualityMetrics.issues.length > 0 && qualityMetrics.qualityScore < 70) {
521
+ try {
522
+ const aiFollowUp = await this.aiClient.generateFollowUp(question.text, answer, qualityMetrics.issues);
523
+ if (aiFollowUp) {
524
+ followUp = {
525
+ message: "Let me ask a more specific question:",
526
+ question: aiFollowUp
527
+ };
528
+ }
529
+ } catch (error) {
530
+ // If AI fails, use heuristic fallback
531
+ followUp = this.determineFollowUp(question, answer);
532
+ }
533
+ } else {
534
+ // Use heuristic follow-up
535
+ followUp = this.determineFollowUp(question, answer);
536
+ }
294
537
 
295
538
  if (followUp) {
296
539
  console.log(chalk.yellow(`\n🤖 ${followUp.message}\n`));
@@ -308,7 +551,23 @@ class Interviewer {
308
551
  const combined = `${answer} | Follow-up: ${followUpAnswer}`;
309
552
 
310
553
  // Re-analyze combined answer
311
- const combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
554
+ let combinedMetrics;
555
+ try {
556
+ if (this.aiClient) {
557
+ const aiAnalysis = await this.aiClient.analyzeAnswerQuality(question.text, combined);
558
+ combinedMetrics = {
559
+ wordCount: combined.trim().split(/\s+/).length,
560
+ qualityScore: aiAnalysis.score,
561
+ isComprehensive: aiAnalysis.score >= 70,
562
+ canSkipFollowUps: aiAnalysis.score >= 85
563
+ };
564
+ } else {
565
+ combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
566
+ }
567
+ } catch {
568
+ combinedMetrics = AnswerQualityAnalyzer.analyze(combined, question);
569
+ }
570
+
312
571
  await this.progressTracker.answerQuestion(question.id, question.text, combined, combinedMetrics);
313
572
 
314
573
  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(),