@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
@@ -0,0 +1,480 @@
1
+ const { PROJECT_TYPES } = require('../analyzers/project-analyzer');
2
+ const { applyLearnedRules } = require('../learning/rule-generator');
3
+
4
+ /**
5
+ * Question Filter - Smart filtering based on project context
6
+ *
7
+ * This module provides intelligent question filtering to:
8
+ * - Skip irrelevant questions based on project type
9
+ * - Score question relevance (0-100)
10
+ * - Reduce interview time by 40-60%
11
+ * - Maintain answer quality
12
+ */
13
+
14
+ /**
15
+ * Question relevance rules by project type
16
+ * Each rule specifies which questions to skip or prioritize
17
+ */
18
+ const RELEVANCE_RULES = {
19
+ [PROJECT_TYPES.CLI_TOOL]: {
20
+ skip: [
21
+ 'user interface design',
22
+ 'responsive design',
23
+ 'browser compatibility',
24
+ 'frontend framework',
25
+ 'ui/ux',
26
+ 'styling',
27
+ 'css',
28
+ 'accessibility features'
29
+ ],
30
+ prioritize: [
31
+ 'command-line',
32
+ 'cli',
33
+ 'terminal',
34
+ 'arguments',
35
+ 'flags',
36
+ 'options',
37
+ 'npm',
38
+ 'package'
39
+ ]
40
+ },
41
+
42
+ [PROJECT_TYPES.API_SERVER]: {
43
+ skip: [
44
+ 'user interface',
45
+ 'frontend',
46
+ 'styling',
47
+ 'responsive design',
48
+ 'browser',
49
+ 'ui/ux'
50
+ ],
51
+ prioritize: [
52
+ 'api',
53
+ 'endpoint',
54
+ 'rest',
55
+ 'graphql',
56
+ 'authentication',
57
+ 'authorization',
58
+ 'database',
59
+ 'backend',
60
+ 'server',
61
+ 'security'
62
+ ]
63
+ },
64
+
65
+ [PROJECT_TYPES.LIBRARY]: {
66
+ skip: [
67
+ 'deployment',
68
+ 'hosting',
69
+ 'server',
70
+ 'user interface',
71
+ 'frontend'
72
+ ],
73
+ prioritize: [
74
+ 'api',
75
+ 'documentation',
76
+ 'npm',
77
+ 'package',
78
+ 'version',
79
+ 'dependencies',
80
+ 'exports',
81
+ 'typescript'
82
+ ]
83
+ },
84
+
85
+ [PROJECT_TYPES.WEB_APP]: {
86
+ skip: [], // Web apps might need most questions
87
+ prioritize: [
88
+ 'frontend',
89
+ 'backend',
90
+ 'user interface',
91
+ 'responsive',
92
+ 'browser',
93
+ 'deployment',
94
+ 'hosting'
95
+ ]
96
+ },
97
+
98
+ [PROJECT_TYPES.FULLSTACK]: {
99
+ skip: [], // Fullstack needs comprehensive questions
100
+ prioritize: [
101
+ 'frontend',
102
+ 'backend',
103
+ 'api',
104
+ 'database',
105
+ 'authentication',
106
+ 'deployment'
107
+ ]
108
+ }
109
+ };
110
+
111
+ /**
112
+ * Framework-specific question relevance
113
+ */
114
+ const FRAMEWORK_RULES = {
115
+ 'React': {
116
+ skip: ['angular', 'vue', 'svelte'],
117
+ prioritize: ['component', 'hooks', 'state management', 'jsx']
118
+ },
119
+ 'Vue': {
120
+ skip: ['react', 'angular', 'svelte'],
121
+ prioritize: ['component', 'composition api', 'vuex']
122
+ },
123
+ 'Angular': {
124
+ skip: ['react', 'vue', 'svelte'],
125
+ prioritize: ['component', 'typescript', 'rxjs', 'dependency injection']
126
+ },
127
+ 'Express': {
128
+ skip: ['fastify', 'koa', 'hapi'],
129
+ prioritize: ['middleware', 'routes', 'request', 'response']
130
+ },
131
+ 'NestJS': {
132
+ skip: ['express routing'],
133
+ prioritize: ['modules', 'controllers', 'services', 'dependency injection']
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Filter questions based on project context
139
+ * @param {Array} questions - Array of question objects
140
+ * @param {Object} projectContext - Project analysis context
141
+ * @param {Object} options - Filtering options
142
+ * @returns {Object} Filtered questions with metadata
143
+ */
144
+ function filterQuestions(questions, projectContext, options = {}) {
145
+ const {
146
+ enableSmartFiltering = true,
147
+ minRelevanceScore = 50, // Skip questions below this score
148
+ aiClient = null, // Optional AI client for enhanced filtering
149
+ learnedRules = [] // Optional learned rules (Phase 4.2)
150
+ } = options;
151
+
152
+ if (!enableSmartFiltering) {
153
+ return {
154
+ questions,
155
+ skipped: [],
156
+ summary: { total: questions.length, kept: questions.length, skipped: 0, skippedByLearning: 0 }
157
+ };
158
+ }
159
+
160
+ const kept = [];
161
+ const skipped = [];
162
+ let skippedByLearning = 0;
163
+
164
+ for (const question of questions) {
165
+ const relevance = scoreQuestionRelevance(question, projectContext, learnedRules);
166
+
167
+ if (relevance.score >= minRelevanceScore) {
168
+ kept.push({
169
+ ...question,
170
+ relevance: relevance.score,
171
+ reason: relevance.reason,
172
+ learningApplied: relevance.learningApplied
173
+ });
174
+ } else {
175
+ skipped.push({
176
+ ...question,
177
+ relevance: relevance.score,
178
+ reason: relevance.skipReason,
179
+ learningApplied: relevance.learningApplied
180
+ });
181
+
182
+ // Track if this was skipped due to learning
183
+ if (relevance.learningApplied && relevance.learningAdjustment < 0) {
184
+ skippedByLearning++;
185
+ }
186
+ }
187
+ }
188
+
189
+ return {
190
+ questions: kept,
191
+ skipped,
192
+ summary: {
193
+ total: questions.length,
194
+ kept: kept.length,
195
+ skipped: skipped.length,
196
+ skippedByLearning,
197
+ timeSaved: estimateTimeSaved(skipped.length),
198
+ learningActive: learnedRules.length > 0
199
+ }
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Score question relevance (0-100)
205
+ * @param {Object} question - Question object
206
+ * @param {Object} projectContext - Project analysis context
207
+ * @param {Array} learnedRules - Optional learned rules to apply
208
+ * @returns {Object} Relevance score and reasoning
209
+ */
210
+ function scoreQuestionRelevance(question, projectContext, learnedRules = []) {
211
+ let score = 100; // Start with perfect relevance
212
+ let reasons = [];
213
+ let skipReasons = [];
214
+
215
+ const questionText = (question.text + ' ' + (question.guidance || '')).toLowerCase();
216
+ const projectType = projectContext.type;
217
+ const frameworks = projectContext.frameworks || [];
218
+
219
+ // Apply project type rules
220
+ const typeRules = RELEVANCE_RULES[projectType];
221
+ if (typeRules) {
222
+ // Check skip keywords
223
+ for (const skipKeyword of typeRules.skip) {
224
+ if (questionText.includes(skipKeyword.toLowerCase())) {
225
+ score -= 40;
226
+ skipReasons.push(`Not relevant for ${projectType} (mentions ${skipKeyword})`);
227
+ }
228
+ }
229
+
230
+ // Check prioritize keywords (boost relevance)
231
+ for (const prioritizeKeyword of typeRules.prioritize) {
232
+ if (questionText.includes(prioritizeKeyword.toLowerCase())) {
233
+ score += 10;
234
+ reasons.push(`Highly relevant for ${projectType} (${prioritizeKeyword})`);
235
+ }
236
+ }
237
+ }
238
+
239
+ // Apply framework-specific rules
240
+ for (const framework of frameworks) {
241
+ const frameworkRules = FRAMEWORK_RULES[framework];
242
+ if (frameworkRules) {
243
+ // Check skip keywords
244
+ for (const skipKeyword of frameworkRules.skip) {
245
+ if (questionText.includes(skipKeyword.toLowerCase())) {
246
+ score -= 30;
247
+ skipReasons.push(`Using ${framework}, not ${skipKeyword}`);
248
+ }
249
+ }
250
+
251
+ // Check prioritize keywords
252
+ for (const prioritizeKeyword of frameworkRules.prioritize) {
253
+ if (questionText.includes(prioritizeKeyword.toLowerCase())) {
254
+ score += 5;
255
+ reasons.push(`Relevant for ${framework}`);
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ // Question category specific rules
262
+ if (question.category) {
263
+ const categoryScore = scoreCategoryRelevance(question.category, projectContext);
264
+ score = score * (categoryScore / 100);
265
+ if (categoryScore < 50) {
266
+ skipReasons.push(`Category '${question.category}' less relevant for this project`);
267
+ }
268
+ }
269
+
270
+ // Apply learned rules (Phase 4.2)
271
+ let learningAdjustment = null;
272
+ if (learnedRules && learnedRules.length > 0) {
273
+ learningAdjustment = applyLearnedRules(question, projectContext, learnedRules);
274
+ score += learningAdjustment.adjustment;
275
+
276
+ // Add learning reasons
277
+ for (const applied of learningAdjustment.appliedRules) {
278
+ if (applied.adjustment < 0) {
279
+ skipReasons.push(`Learned: ${applied.reason}`);
280
+ } else {
281
+ reasons.push(`Learned: ${applied.reason}`);
282
+ }
283
+ }
284
+ }
285
+
286
+ // Normalize score to 0-100 range
287
+ score = Math.max(0, Math.min(100, score));
288
+
289
+ return {
290
+ score: Math.round(score),
291
+ reason: reasons.join('; ') || 'Standard relevance',
292
+ skipReason: skipReasons.join('; ') || 'Low relevance score',
293
+ learningApplied: learningAdjustment?.hasLearning || false,
294
+ learningAdjustment: learningAdjustment?.adjustment || 0
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Score category relevance for project type
300
+ */
301
+ function scoreCategoryRelevance(category, projectContext) {
302
+ const categoryLower = category.toLowerCase();
303
+ const projectType = projectContext.type;
304
+
305
+ // Category relevance by project type
306
+ const relevanceMap = {
307
+ [PROJECT_TYPES.CLI_TOOL]: {
308
+ 'frontend': 0,
309
+ 'ui/ux': 0,
310
+ 'design': 0,
311
+ 'deployment': 70,
312
+ 'architecture': 80,
313
+ 'testing': 90,
314
+ 'cli': 100,
315
+ 'package': 100
316
+ },
317
+ [PROJECT_TYPES.API_SERVER]: {
318
+ 'frontend': 0,
319
+ 'ui/ux': 0,
320
+ 'design': 0,
321
+ 'api': 100,
322
+ 'backend': 100,
323
+ 'security': 100,
324
+ 'database': 100,
325
+ 'deployment': 90
326
+ },
327
+ [PROJECT_TYPES.LIBRARY]: {
328
+ 'frontend': 30,
329
+ 'backend': 30,
330
+ 'api': 100,
331
+ 'documentation': 100,
332
+ 'testing': 100,
333
+ 'package': 100,
334
+ 'deployment': 50
335
+ },
336
+ [PROJECT_TYPES.WEB_APP]: {
337
+ 'frontend': 100,
338
+ 'ui/ux': 100,
339
+ 'design': 90,
340
+ 'backend': 80,
341
+ 'deployment': 90
342
+ },
343
+ [PROJECT_TYPES.FULLSTACK]: {
344
+ 'frontend': 100,
345
+ 'backend': 100,
346
+ 'api': 90,
347
+ 'database': 90,
348
+ 'deployment': 100
349
+ }
350
+ };
351
+
352
+ const typeMap = relevanceMap[projectType];
353
+ if (!typeMap) return 100; // Default to relevant if unknown
354
+
355
+ // Find matching category
356
+ for (const [cat, score] of Object.entries(typeMap)) {
357
+ if (categoryLower.includes(cat)) {
358
+ return score;
359
+ }
360
+ }
361
+
362
+ return 70; // Default moderate relevance
363
+ }
364
+
365
+ /**
366
+ * Estimate time saved by skipping questions (in minutes)
367
+ */
368
+ function estimateTimeSaved(skippedCount) {
369
+ // Assume avg 2 minutes per question (read + think + answer + AI analysis)
370
+ const avgTimePerQuestion = 2;
371
+ return skippedCount * avgTimePerQuestion;
372
+ }
373
+
374
+ /**
375
+ * Check if specific question should be asked based on previous answers
376
+ * @param {Object} question - Question to check
377
+ * @param {Object} previousAnswers - Map of previous answers
378
+ * @param {Object} projectContext - Project context
379
+ * @returns {boolean} Whether to ask the question
380
+ */
381
+ function shouldAskQuestion(question, previousAnswers, projectContext) {
382
+ // If question has conditional logic
383
+ if (question.condition) {
384
+ return evaluateCondition(question.condition, previousAnswers, projectContext);
385
+ }
386
+
387
+ // Default to asking
388
+ return true;
389
+ }
390
+
391
+ /**
392
+ * Evaluate conditional logic for questions
393
+ */
394
+ function evaluateCondition(condition, previousAnswers, projectContext) {
395
+ if (condition.requiresAnswer) {
396
+ // Question depends on a previous answer
397
+ const required = condition.requiresAnswer;
398
+ return previousAnswers[required] && previousAnswers[required].trim().length > 0;
399
+ }
400
+
401
+ if (condition.requiresProjectType) {
402
+ // Question depends on project type
403
+ return condition.requiresProjectType === projectContext.type;
404
+ }
405
+
406
+ if (condition.requiresFramework) {
407
+ // Question depends on framework
408
+ return projectContext.frameworks.includes(condition.requiresFramework);
409
+ }
410
+
411
+ return true;
412
+ }
413
+
414
+ /**
415
+ * Get summary of filtering decisions
416
+ */
417
+ function getFilteringSummary(filterResult, projectContext) {
418
+ const { summary } = filterResult;
419
+
420
+ return {
421
+ message: `Smart filtering enabled: Showing ${summary.kept} of ${summary.total} questions`,
422
+ projectType: projectContext.type,
423
+ frameworks: projectContext.frameworks,
424
+ timeSaved: `~${summary.timeSaved} minutes`,
425
+ confidence: projectContext.confidence
426
+ };
427
+ }
428
+
429
+ /**
430
+ * AI-powered relevance scoring (optional enhancement)
431
+ * @param {Object} question - Question to score
432
+ * @param {Object} projectContext - Project context
433
+ * @param {Object} aiClient - AI client instance
434
+ * @returns {Promise<Object>} AI relevance assessment
435
+ */
436
+ async function scoreQuestionRelevanceWithAI(question, projectContext, aiClient) {
437
+ if (!aiClient) {
438
+ return scoreQuestionRelevance(question, projectContext);
439
+ }
440
+
441
+ try {
442
+ const prompt = `Analyze if this question is relevant for the given project:
443
+
444
+ Project Type: ${projectContext.type}
445
+ Frameworks: ${projectContext.frameworks.join(', ')}
446
+ Languages: ${projectContext.languages.join(', ')}
447
+
448
+ Question: ${question.text}
449
+ Guidance: ${question.guidance || 'None'}
450
+
451
+ Score relevance 0-100 and explain briefly.
452
+ Respond with JSON: { "score": 0-100, "reason": "why relevant/not relevant" }`;
453
+
454
+ const response = await aiClient.sendMessage(prompt, { maxTokens: 150 });
455
+ const match = response.content.match(/\{[\s\S]*\}/);
456
+
457
+ if (match) {
458
+ const analysis = JSON.parse(match[0]);
459
+ return {
460
+ score: analysis.score,
461
+ reason: analysis.reason,
462
+ skipReason: analysis.score < 50 ? analysis.reason : 'Low relevance',
463
+ aiPowered: true
464
+ };
465
+ }
466
+ } catch (error) {
467
+ // Fallback to rule-based scoring
468
+ console.warn('AI scoring failed, using rule-based approach');
469
+ }
470
+
471
+ return scoreQuestionRelevance(question, projectContext);
472
+ }
473
+
474
+ module.exports = {
475
+ filterQuestions,
476
+ scoreQuestionRelevance,
477
+ shouldAskQuestion,
478
+ getFilteringSummary,
479
+ scoreQuestionRelevanceWithAI
480
+ };