@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.
- package/.project/chats/{current → complete}/2025-10-03_AGENTS-MD-AND-TOOL-GENERATORS.md +82 -17
- package/.project/chats/complete/2025-10-03_AI-PROVIDER-INTEGRATION.md +568 -0
- package/.project/chats/complete/2025-10-03_FRAMEWORK-UPDATE-SYSTEM.md +497 -0
- package/.project/chats/complete/2025-10-04_CONFIG-COMMAND.md +503 -0
- package/.project/chats/current/2025-10-04_PHASE-4-1-SMART-FILTERING.md +381 -0
- package/.project/chats/current/SESSION-STATUS.md +168 -0
- package/.project/docs/AI-PROVIDER-INTEGRATION.md +600 -0
- package/.project/docs/FRAMEWORK-UPDATE-INTEGRATION.md +421 -0
- package/.project/docs/FRAMEWORK-UPDATE-SYSTEM.md +832 -0
- package/.project/docs/PHASE-4-2-LEARNING-SYSTEM.md +881 -0
- package/.project/docs/PROJECT-STRUCTURE-EXPLANATION.md +500 -0
- package/.project/docs/SMART-FILTERING-SYSTEM.md +385 -0
- package/.project/docs/architecture/SYSTEM-DESIGN.md +122 -1
- package/.project/docs/goals/PROJECT-VISION.md +61 -34
- package/CHANGELOG.md +257 -1
- package/README.md +476 -292
- package/bin/adf.js +7 -0
- package/lib/ai/ai-client.js +328 -0
- package/lib/ai/ai-config.js +398 -0
- package/lib/analyzers/project-analyzer.js +380 -0
- package/lib/commands/config.js +221 -0
- package/lib/commands/init.js +56 -10
- package/lib/filters/question-filter.js +480 -0
- package/lib/frameworks/interviewer.js +271 -12
- package/lib/frameworks/progress-tracker.js +8 -1
- package/lib/learning/learning-manager.js +447 -0
- package/lib/learning/pattern-detector.js +376 -0
- package/lib/learning/rule-generator.js +304 -0
- package/lib/learning/skip-tracker.js +260 -0
- package/lib/learning/storage.js +296 -0
- package/package.json +70 -57
- package/tests/learning-storage.test.js +184 -0
- package/tests/pattern-detector.test.js +297 -0
- package/tests/project-analyzer.test.js +221 -0
- package/tests/question-filter.test.js +297 -0
- 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
|
+
};
|