@iservu-inc/adf-cli 0.3.6 → 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.
@@ -0,0 +1,297 @@
1
+ const { filterQuestions, scoreQuestionRelevance } = require('../lib/filters/question-filter');
2
+ const { PROJECT_TYPES } = require('../lib/analyzers/project-analyzer');
3
+
4
+ describe('Question Filter', () => {
5
+ const sampleQuestions = [
6
+ {
7
+ id: 'q1',
8
+ text: 'What is the user interface design approach?',
9
+ guidance: 'Describe your UI design',
10
+ category: 'frontend',
11
+ phase: 'design'
12
+ },
13
+ {
14
+ id: 'q2',
15
+ text: 'What API endpoints will you need?',
16
+ guidance: 'List your REST/GraphQL endpoints',
17
+ category: 'api',
18
+ phase: 'architecture'
19
+ },
20
+ {
21
+ id: 'q3',
22
+ text: 'What command-line arguments will your CLI accept?',
23
+ guidance: 'Define CLI interface',
24
+ category: 'cli',
25
+ phase: 'design'
26
+ },
27
+ {
28
+ id: 'q4',
29
+ text: 'What database will you use?',
30
+ guidance: 'Choose your database',
31
+ category: 'backend',
32
+ phase: 'architecture'
33
+ }
34
+ ];
35
+
36
+ describe('filterQuestions', () => {
37
+ test('should skip UI questions for CLI tools', () => {
38
+ const projectContext = {
39
+ type: PROJECT_TYPES.CLI_TOOL,
40
+ frameworks: [],
41
+ languages: ['JavaScript/TypeScript'],
42
+ confidence: 80
43
+ };
44
+
45
+ const result = filterQuestions(sampleQuestions, projectContext, {
46
+ enableSmartFiltering: true,
47
+ minRelevanceScore: 50
48
+ });
49
+
50
+ // UI question should be skipped
51
+ const uiQuestion = result.questions.find(q => q.id === 'q1');
52
+ expect(uiQuestion).toBeUndefined();
53
+
54
+ // CLI question should be kept
55
+ const cliQuestion = result.questions.find(q => q.id === 'q3');
56
+ expect(cliQuestion).toBeDefined();
57
+
58
+ expect(result.summary.skipped).toBeGreaterThan(0);
59
+ });
60
+
61
+ test('should skip frontend questions for API servers', () => {
62
+ const projectContext = {
63
+ type: PROJECT_TYPES.API_SERVER,
64
+ frameworks: ['Express'],
65
+ languages: ['JavaScript/TypeScript'],
66
+ confidence: 85
67
+ };
68
+
69
+ const result = filterQuestions(sampleQuestions, projectContext, {
70
+ enableSmartFiltering: true,
71
+ minRelevanceScore: 50
72
+ });
73
+
74
+ // UI question should be skipped
75
+ const uiQuestion = result.questions.find(q => q.id === 'q1');
76
+ expect(uiQuestion).toBeUndefined();
77
+
78
+ // API question should be kept
79
+ const apiQuestion = result.questions.find(q => q.id === 'q2');
80
+ expect(apiQuestion).toBeDefined();
81
+ });
82
+
83
+ test('should keep all questions when smart filtering disabled', () => {
84
+ const projectContext = {
85
+ type: PROJECT_TYPES.CLI_TOOL,
86
+ frameworks: [],
87
+ languages: ['JavaScript/TypeScript'],
88
+ confidence: 80
89
+ };
90
+
91
+ const result = filterQuestions(sampleQuestions, projectContext, {
92
+ enableSmartFiltering: false
93
+ });
94
+
95
+ expect(result.questions.length).toBe(sampleQuestions.length);
96
+ expect(result.summary.skipped).toBe(0);
97
+ });
98
+
99
+ test('should estimate time saved correctly', () => {
100
+ const projectContext = {
101
+ type: PROJECT_TYPES.CLI_TOOL,
102
+ frameworks: [],
103
+ languages: ['JavaScript/TypeScript'],
104
+ confidence: 80
105
+ };
106
+
107
+ const result = filterQuestions(sampleQuestions, projectContext, {
108
+ enableSmartFiltering: true,
109
+ minRelevanceScore: 50
110
+ });
111
+
112
+ expect(result.summary.timeSaved).toBeGreaterThan(0);
113
+ // 2 minutes per skipped question
114
+ expect(result.summary.timeSaved).toBe(result.summary.skipped * 2);
115
+ });
116
+ });
117
+
118
+ describe('scoreQuestionRelevance', () => {
119
+ test('should score UI question low for CLI projects', () => {
120
+ const question = {
121
+ text: 'What is your responsive design approach?',
122
+ guidance: 'Describe mobile/desktop layouts'
123
+ };
124
+
125
+ const projectContext = {
126
+ type: PROJECT_TYPES.CLI_TOOL,
127
+ frameworks: [],
128
+ languages: ['JavaScript/TypeScript']
129
+ };
130
+
131
+ const relevance = scoreQuestionRelevance(question, projectContext);
132
+
133
+ expect(relevance.score).toBeLessThan(70);
134
+ expect(relevance.skipReason).toBeTruthy();
135
+ });
136
+
137
+ test('should score API question high for API servers', () => {
138
+ const question = {
139
+ text: 'What REST API endpoints will you need?',
140
+ guidance: 'Define your API routes'
141
+ };
142
+
143
+ const projectContext = {
144
+ type: PROJECT_TYPES.API_SERVER,
145
+ frameworks: ['Express'],
146
+ languages: ['JavaScript/TypeScript']
147
+ };
148
+
149
+ const relevance = scoreQuestionRelevance(question, projectContext);
150
+
151
+ expect(relevance.score).toBeGreaterThanOrEqual(50);
152
+ });
153
+
154
+ test('should boost relevance for framework-specific questions', () => {
155
+ const question = {
156
+ text: 'How will you structure your React components?',
157
+ guidance: 'Describe component architecture'
158
+ };
159
+
160
+ const projectContext = {
161
+ type: PROJECT_TYPES.WEB_APP,
162
+ frameworks: ['React'],
163
+ languages: ['JavaScript/TypeScript']
164
+ };
165
+
166
+ const relevance = scoreQuestionRelevance(question, projectContext);
167
+
168
+ expect(relevance.score).toBeGreaterThan(80);
169
+ });
170
+
171
+ test('should reduce relevance for competing framework questions', () => {
172
+ const question = {
173
+ text: 'How will you set up Angular modules?',
174
+ guidance: 'Configure Angular app'
175
+ };
176
+
177
+ const projectContext = {
178
+ type: PROJECT_TYPES.WEB_APP,
179
+ frameworks: ['React'],
180
+ languages: ['JavaScript/TypeScript']
181
+ };
182
+
183
+ const relevance = scoreQuestionRelevance(question, projectContext);
184
+
185
+ expect(relevance.score).toBeLessThanOrEqual(70);
186
+ });
187
+
188
+ test('should handle projects with no framework gracefully', () => {
189
+ const question = {
190
+ text: 'What is your project goal?',
191
+ guidance: 'Describe main objective'
192
+ };
193
+
194
+ const projectContext = {
195
+ type: PROJECT_TYPES.UNKNOWN,
196
+ frameworks: [],
197
+ languages: []
198
+ };
199
+
200
+ const relevance = scoreQuestionRelevance(question, projectContext);
201
+
202
+ expect(relevance.score).toBeGreaterThanOrEqual(0);
203
+ expect(relevance.score).toBeLessThanOrEqual(100);
204
+ });
205
+
206
+ test('should provide helpful reasons for kept questions', () => {
207
+ const question = {
208
+ text: 'What CLI commands will you support?',
209
+ guidance: 'Define command structure'
210
+ };
211
+
212
+ const projectContext = {
213
+ type: PROJECT_TYPES.CLI_TOOL,
214
+ frameworks: [],
215
+ languages: ['JavaScript/TypeScript']
216
+ };
217
+
218
+ const relevance = scoreQuestionRelevance(question, projectContext);
219
+
220
+ expect(relevance.reason).toBeTruthy();
221
+ expect(relevance.reason.toLowerCase()).toContain('cli');
222
+ });
223
+
224
+ test('should provide helpful reasons for skipped questions', () => {
225
+ const question = {
226
+ text: 'What browser compatibility do you need?',
227
+ guidance: 'List supported browsers'
228
+ };
229
+
230
+ const projectContext = {
231
+ type: PROJECT_TYPES.CLI_TOOL,
232
+ frameworks: [],
233
+ languages: ['JavaScript/TypeScript']
234
+ };
235
+
236
+ const relevance = scoreQuestionRelevance(question, projectContext);
237
+
238
+ if (relevance.score < 50) {
239
+ expect(relevance.skipReason).toBeTruthy();
240
+ }
241
+ });
242
+ });
243
+
244
+ describe('Edge cases', () => {
245
+ test('should handle empty question list', () => {
246
+ const projectContext = {
247
+ type: PROJECT_TYPES.WEB_APP,
248
+ frameworks: ['React'],
249
+ languages: ['JavaScript/TypeScript'],
250
+ confidence: 80
251
+ };
252
+
253
+ const result = filterQuestions([], projectContext);
254
+
255
+ expect(result.questions).toEqual([]);
256
+ expect(result.summary.total).toBe(0);
257
+ expect(result.summary.skipped).toBe(0);
258
+ });
259
+
260
+ test('should handle unknown project type gracefully', () => {
261
+ const projectContext = {
262
+ type: PROJECT_TYPES.UNKNOWN,
263
+ frameworks: [],
264
+ languages: [],
265
+ confidence: 20
266
+ };
267
+
268
+ const result = filterQuestions(sampleQuestions, projectContext, {
269
+ enableSmartFiltering: true,
270
+ minRelevanceScore: 50
271
+ });
272
+
273
+ // Should keep most questions when uncertain
274
+ expect(result.questions.length).toBeGreaterThan(0);
275
+ });
276
+
277
+ test('should handle missing category in question', () => {
278
+ const questionWithoutCategory = {
279
+ id: 'q5',
280
+ text: 'General question',
281
+ guidance: 'Answer this'
282
+ };
283
+
284
+ const projectContext = {
285
+ type: PROJECT_TYPES.WEB_APP,
286
+ frameworks: [],
287
+ languages: [],
288
+ confidence: 80
289
+ };
290
+
291
+ const relevance = scoreQuestionRelevance(questionWithoutCategory, projectContext);
292
+
293
+ expect(relevance.score).toBeGreaterThanOrEqual(0);
294
+ expect(relevance.score).toBeLessThanOrEqual(100);
295
+ });
296
+ });
297
+ });
@@ -0,0 +1,198 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { SkipTracker, analyzeSkipPatterns } = require('../lib/learning/skip-tracker');
4
+
5
+ describe('Skip Tracker', () => {
6
+ const tempDir = path.join(__dirname, 'temp-skip-tracker-test');
7
+
8
+ beforeEach(async () => {
9
+ await fs.ensureDir(tempDir);
10
+ });
11
+
12
+ afterEach(async () => {
13
+ await fs.remove(tempDir);
14
+ });
15
+
16
+ describe('SkipTracker', () => {
17
+ test('should initialize with project metadata', () => {
18
+ const tracker = new SkipTracker(tempDir, {
19
+ projectType: 'cli-tool',
20
+ frameworks: ['commander'],
21
+ languages: ['JavaScript']
22
+ });
23
+
24
+ expect(tracker.projectPath).toBe(tempDir);
25
+ expect(tracker.sessionData.projectType).toBe('cli-tool');
26
+ expect(tracker.sessionData.frameworks).toContain('commander');
27
+ expect(tracker.sessionId).toBeTruthy();
28
+ });
29
+
30
+ test('should record skip events', () => {
31
+ const tracker = new SkipTracker(tempDir, { projectType: 'web-app' });
32
+
33
+ const question = {
34
+ id: 'q1',
35
+ text: 'What is your deployment strategy?',
36
+ category: 'deployment',
37
+ phase: 'architecture'
38
+ };
39
+
40
+ tracker.recordSkip(question, 'manual');
41
+
42
+ expect(tracker.sessionData.skips.length).toBe(1);
43
+ expect(tracker.sessionData.skips[0].questionId).toBe('q1');
44
+ expect(tracker.sessionData.skips[0].reason).toBe('manual');
45
+ expect(tracker.sessionData.skips[0].category).toBe('deployment');
46
+ });
47
+
48
+ test('should record answer events', () => {
49
+ const tracker = new SkipTracker(tempDir, { projectType: 'api-server' });
50
+
51
+ const question = {
52
+ id: 'q1',
53
+ text: 'What database will you use?',
54
+ category: 'backend'
55
+ };
56
+
57
+ tracker.recordAnswer(question, 'PostgreSQL with TypeORM', {
58
+ qualityScore: 85,
59
+ richness: 'comprehensive'
60
+ });
61
+
62
+ expect(tracker.sessionData.answers.length).toBe(1);
63
+ expect(tracker.sessionData.answers[0].questionId).toBe('q1');
64
+ expect(tracker.sessionData.answers[0].wordCount).toBe(3);
65
+ expect(tracker.sessionData.answers[0].qualityScore).toBe(85);
66
+ });
67
+
68
+ test('should track time spent on questions', async () => {
69
+ const tracker = new SkipTracker(tempDir, { projectType: 'web-app' });
70
+
71
+ const question = { id: 'q1', text: 'Test question' };
72
+
73
+ tracker.startQuestion('q1');
74
+ await new Promise(resolve => setTimeout(resolve, 100)); // Wait 100ms
75
+ const timeSpent = tracker.getTimeSpent('q1');
76
+
77
+ expect(timeSpent).toBeGreaterThan(0);
78
+ expect(timeSpent).toBeLessThan(1); // Should be fraction of a second
79
+ });
80
+
81
+ test('should record filtered questions in bulk', () => {
82
+ const tracker = new SkipTracker(tempDir, { projectType: 'cli-tool' });
83
+
84
+ const filteredQuestions = [
85
+ { id: 'q1', text: 'Question 1', category: 'frontend' },
86
+ { id: 'q2', text: 'Question 2', category: 'ui' }
87
+ ];
88
+
89
+ tracker.recordFilteredQuestions(filteredQuestions);
90
+
91
+ expect(tracker.sessionData.skips.length).toBe(2);
92
+ expect(tracker.sessionData.skips.every(s => s.reason === 'filtered')).toBe(true);
93
+ });
94
+
95
+ test('should generate session summary', () => {
96
+ const tracker = new SkipTracker(tempDir, { projectType: 'web-app' });
97
+
98
+ tracker.recordSkip({ id: 'q1', text: 'Q1' }, 'manual');
99
+ tracker.recordSkip({ id: 'q2', text: 'Q2' }, 'filtered');
100
+ tracker.recordAnswer({ id: 'q3', text: 'Q3' }, 'Answer', {});
101
+
102
+ const summary = tracker.getSessionSummary();
103
+
104
+ expect(summary.totalSkips).toBe(2);
105
+ expect(summary.manualSkips).toBe(1);
106
+ expect(summary.filteredSkips).toBe(1);
107
+ expect(summary.totalAnswers).toBe(1);
108
+ });
109
+
110
+ test('should save session data', async () => {
111
+ const tracker = new SkipTracker(tempDir, { projectType: 'cli-tool' });
112
+
113
+ tracker.recordSkip({ id: 'q1', text: 'Test question' }, 'manual');
114
+ const result = await tracker.saveSession();
115
+
116
+ expect(result).toBe(true);
117
+
118
+ // Verify data was saved
119
+ const skipHistoryPath = path.join(tempDir, '.adf', 'learning', 'skip-history.json');
120
+ const exists = await fs.pathExists(skipHistoryPath);
121
+ expect(exists).toBe(true);
122
+
123
+ const data = await fs.readJSON(skipHistoryPath);
124
+ expect(data.sessions.length).toBe(1);
125
+ expect(data.sessions[0].skips.length).toBe(1);
126
+ });
127
+ });
128
+
129
+ describe('analyzeSkipPatterns', () => {
130
+ test('should return empty analysis for no history', async () => {
131
+ const analysis = await analyzeSkipPatterns(tempDir);
132
+
133
+ expect(analysis.totalSessions).toBe(0);
134
+ expect(analysis.totalSkips).toBe(0);
135
+ expect(analysis.mostSkippedQuestions).toEqual([]);
136
+ });
137
+
138
+ test('should analyze skip patterns from history', async () => {
139
+ // Create mock history
140
+ const history = {
141
+ version: '1.0',
142
+ sessions: [
143
+ {
144
+ sessionId: 's1',
145
+ skips: [
146
+ { questionId: 'q1', text: 'Q1', category: 'deployment', reason: 'manual' },
147
+ { questionId: 'q2', text: 'Q2', category: 'testing', reason: 'manual' }
148
+ ]
149
+ },
150
+ {
151
+ sessionId: 's2',
152
+ skips: [
153
+ { questionId: 'q1', text: 'Q1', category: 'deployment', reason: 'manual' },
154
+ { questionId: 'q3', text: 'Q3', category: 'deployment', reason: 'filtered' }
155
+ ]
156
+ }
157
+ ]
158
+ };
159
+
160
+ await fs.ensureDir(path.join(tempDir, '.adf', 'learning'));
161
+ await fs.writeJSON(path.join(tempDir, '.adf', 'learning', 'skip-history.json'), history);
162
+
163
+ const analysis = await analyzeSkipPatterns(tempDir);
164
+
165
+ expect(analysis.totalSessions).toBe(2);
166
+ expect(analysis.manualSkips).toBe(3);
167
+ expect(analysis.filteredSkips).toBe(1);
168
+ expect(analysis.mostSkippedQuestions.length).toBeGreaterThan(0);
169
+ expect(analysis.mostSkippedQuestions[0].questionId).toBe('q1'); // Most skipped
170
+ expect(analysis.mostSkippedQuestions[0].count).toBe(2);
171
+ });
172
+
173
+ test('should identify most skipped categories', async () => {
174
+ const history = {
175
+ version: '1.0',
176
+ sessions: [
177
+ {
178
+ sessionId: 's1',
179
+ skips: [
180
+ { questionId: 'q1', text: 'Q1', category: 'deployment', reason: 'manual' },
181
+ { questionId: 'q2', text: 'Q2', category: 'deployment', reason: 'manual' },
182
+ { questionId: 'q3', text: 'Q3', category: 'testing', reason: 'manual' }
183
+ ]
184
+ }
185
+ ]
186
+ };
187
+
188
+ await fs.ensureDir(path.join(tempDir, '.adf', 'learning'));
189
+ await fs.writeJSON(path.join(tempDir, '.adf', 'learning', 'skip-history.json'), history);
190
+
191
+ const analysis = await analyzeSkipPatterns(tempDir);
192
+
193
+ expect(analysis.mostSkippedCategories.length).toBeGreaterThan(0);
194
+ expect(analysis.mostSkippedCategories[0].category).toBe('deployment');
195
+ expect(analysis.mostSkippedCategories[0].count).toBe(2);
196
+ });
197
+ });
198
+ });