@iservu-inc/adf-cli 0.4.36 → 0.5.0
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/2025-10-05_MULTI-IDE-IMPROVEMENTS.md +415 -0
- package/.project/docs/ROADMAP.md +150 -17
- package/CHANGELOG.md +159 -0
- package/lib/analysis/answer-analyzer.js +304 -0
- package/lib/analysis/dynamic-pipeline.js +262 -0
- package/lib/analysis/knowledge-graph.js +227 -0
- package/lib/analysis/question-mapper.js +293 -0
- package/lib/frameworks/interviewer.js +59 -0
- package/package.json +1 -1
- package/tests/answer-analyzer.test.js +262 -0
- package/tests/dynamic-pipeline.test.js +332 -0
- package/tests/knowledge-graph.test.js +322 -0
- package/tests/question-mapper.test.js +342 -0
- /package/.project/chats/{current → complete}/2025-10-04_CRITICAL-MODEL-FETCHING-BUG.md +0 -0
- /package/.project/chats/{current → complete}/2025-10-04_PHASE-4-2-COMPLETION-AND-ROADMAP.md +0 -0
- /package/.project/chats/{current → complete}/2025-10-04_PHASE-4-2-LEARNING-SYSTEM.md +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
const QuestionMapper = require('../lib/analysis/question-mapper');
|
|
2
|
+
const KnowledgeGraph = require('../lib/analysis/knowledge-graph');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
describe('QuestionMapper', () => {
|
|
8
|
+
let mapper;
|
|
9
|
+
let kg;
|
|
10
|
+
let tempDir;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
mapper = new QuestionMapper();
|
|
14
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'qm-test-'));
|
|
15
|
+
kg = new KnowledgeGraph(tempDir);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await fs.remove(tempDir);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('mapQuestion', () => {
|
|
23
|
+
it('should map tech stack question correctly', () => {
|
|
24
|
+
const question = {
|
|
25
|
+
id: 'tech-stack',
|
|
26
|
+
text: 'What tech stack will you use?'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const mapping = mapper.mapQuestion(question);
|
|
30
|
+
|
|
31
|
+
expect(mapping.types).toContain('tech_stack');
|
|
32
|
+
expect(mapping.priority).toBeLessThanOrEqual(2); // High priority
|
|
33
|
+
expect(mapping.confidence).toBeGreaterThan(70);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should map goal question correctly', () => {
|
|
37
|
+
const question = {
|
|
38
|
+
id: 'project-goal',
|
|
39
|
+
text: 'What are you building?'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const mapping = mapper.mapQuestion(question);
|
|
43
|
+
|
|
44
|
+
expect(mapping.types).toContain('project_goal');
|
|
45
|
+
expect(mapping.priority).toBe(1); // Highest priority
|
|
46
|
+
expect(mapping.confidence).toBeGreaterThan(70);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should map user question correctly', () => {
|
|
50
|
+
const question = {
|
|
51
|
+
id: 'target-users',
|
|
52
|
+
text: 'Who are your target users?'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const mapping = mapper.mapQuestion(question);
|
|
56
|
+
|
|
57
|
+
expect(mapping.types).toContain('target_users');
|
|
58
|
+
expect(mapping.confidence).toBeGreaterThan(70);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should map architecture question correctly', () => {
|
|
62
|
+
const question = {
|
|
63
|
+
id: 'architecture',
|
|
64
|
+
text: 'How will you structure your application?'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const mapping = mapper.mapQuestion(question);
|
|
68
|
+
|
|
69
|
+
expect(mapping.types).toContain('architecture');
|
|
70
|
+
expect(mapping.confidence).toBeGreaterThan(70);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should map platform question correctly', () => {
|
|
74
|
+
const question = {
|
|
75
|
+
id: 'platform',
|
|
76
|
+
text: 'What platform will this run on?'
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const mapping = mapper.mapQuestion(question);
|
|
80
|
+
|
|
81
|
+
expect(mapping.types).toContain('platform');
|
|
82
|
+
expect(mapping.confidence).toBeGreaterThan(70);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return default mapping for unrecognized questions', () => {
|
|
86
|
+
const question = {
|
|
87
|
+
id: 'random',
|
|
88
|
+
text: 'Something totally unrelated?'
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const mapping = mapper.mapQuestion(question);
|
|
92
|
+
|
|
93
|
+
expect(mapping.types.length).toBeGreaterThan(0); // Has some type
|
|
94
|
+
expect(mapping.confidence).toBeGreaterThan(0); // Has some confidence
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should map multiple information types for comprehensive questions', () => {
|
|
98
|
+
const question = {
|
|
99
|
+
id: 'goal-and-tech',
|
|
100
|
+
text: 'What are you building and what technology will you use?'
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const mapping = mapper.mapQuestion(question);
|
|
104
|
+
|
|
105
|
+
expect(mapping.types).toContain('project_goal');
|
|
106
|
+
expect(mapping.types).toContain('tech_stack');
|
|
107
|
+
expect(mapping.types.length).toBeGreaterThan(1);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('canSkipQuestion', () => {
|
|
112
|
+
it('should allow skipping when all information types are satisfied', () => {
|
|
113
|
+
const question = {
|
|
114
|
+
id: 'tech-stack',
|
|
115
|
+
text: 'What tech stack will you use?'
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Add tech stack knowledge
|
|
119
|
+
kg.add([
|
|
120
|
+
{ type: 'tech_stack', content: 'React and Node.js', confidence: 90, source: 'prev-q' }
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
const result = mapper.canSkipQuestion(question, kg, 70);
|
|
124
|
+
|
|
125
|
+
expect(result.canSkip).toBe(true);
|
|
126
|
+
expect(result.reason).toContain('tech stack');
|
|
127
|
+
expect(result.satisfiedTypes).toHaveLength(1);
|
|
128
|
+
expect(result.missingTypes).toHaveLength(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should not skip when confidence is below threshold', () => {
|
|
132
|
+
const question = {
|
|
133
|
+
id: 'tech-stack',
|
|
134
|
+
text: 'What tech stack?'
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
kg.add([
|
|
138
|
+
{ type: 'tech_stack', content: 'React', confidence: 60, source: 'prev-q' }
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const result = mapper.canSkipQuestion(question, kg, 70);
|
|
142
|
+
|
|
143
|
+
expect(result.canSkip).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should not skip when information is missing', () => {
|
|
147
|
+
const question = {
|
|
148
|
+
id: 'tech-stack',
|
|
149
|
+
text: 'What tech stack will you use?'
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// No knowledge added
|
|
153
|
+
|
|
154
|
+
const result = mapper.canSkipQuestion(question, kg, 70);
|
|
155
|
+
|
|
156
|
+
expect(result.canSkip).toBe(false);
|
|
157
|
+
expect(result.satisfiedTypes).toHaveLength(0);
|
|
158
|
+
expect(result.missingTypes.length).toBeGreaterThan(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle partial satisfaction', () => {
|
|
162
|
+
const question = {
|
|
163
|
+
id: 'goal-and-tech',
|
|
164
|
+
text: 'What are you building and what technology?'
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Only have goal, missing tech
|
|
168
|
+
kg.add([
|
|
169
|
+
{ type: 'project_goal', content: 'Building a CRM', confidence: 90, source: 'prev-q' }
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
const result = mapper.canSkipQuestion(question, kg, 70);
|
|
173
|
+
|
|
174
|
+
expect(result.canSkip).toBe(false);
|
|
175
|
+
expect(result.satisfiedTypes.length).toBeGreaterThan(0);
|
|
176
|
+
expect(result.missingTypes.length).toBeGreaterThan(0);
|
|
177
|
+
expect(result.reason).toContain('Partial');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should respect custom minConfidence threshold', () => {
|
|
181
|
+
const question = {
|
|
182
|
+
id: 'tech-stack',
|
|
183
|
+
text: 'What tech stack?'
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
kg.add([
|
|
187
|
+
{ type: 'tech_stack', content: 'React', confidence: 75, source: 'prev-q' }
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
// Should skip with 70 threshold
|
|
191
|
+
expect(mapper.canSkipQuestion(question, kg, 70).canSkip).toBe(true);
|
|
192
|
+
|
|
193
|
+
// Should not skip with 80 threshold
|
|
194
|
+
expect(mapper.canSkipQuestion(question, kg, 80).canSkip).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('reorderQuestions', () => {
|
|
199
|
+
it('should prioritize questions with missing information', () => {
|
|
200
|
+
const questions = [
|
|
201
|
+
{ id: 'tech-stack', text: 'What tech stack?' },
|
|
202
|
+
{ id: 'timeline', text: 'What is the timeline?' },
|
|
203
|
+
{ id: 'platform', text: 'What platform?' }
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
// Already have tech stack
|
|
207
|
+
kg.add([
|
|
208
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' }
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
const reordered = mapper.reorderQuestions(questions, kg);
|
|
212
|
+
|
|
213
|
+
// Questions without satisfied info should come first
|
|
214
|
+
expect(reordered[0].question.id).not.toBe('tech-stack');
|
|
215
|
+
expect(reordered[2].question.id).toBe('tech-stack'); // Should be last
|
|
216
|
+
expect(reordered[2].relevanceScore).toBe(0); // Can skip
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should assign high scores to unsatisfied questions', () => {
|
|
220
|
+
const questions = [
|
|
221
|
+
{ id: 'tech-stack', text: 'What tech stack?' }
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const reordered = mapper.reorderQuestions(questions, kg);
|
|
225
|
+
|
|
226
|
+
expect(reordered[0].relevanceScore).toBe(100);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should assign zero score to fully satisfied questions', () => {
|
|
230
|
+
const questions = [
|
|
231
|
+
{ id: 'tech-stack', text: 'What tech stack?' }
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
kg.add([
|
|
235
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' },
|
|
236
|
+
{ type: 'architecture', content: 'SPA', confidence: 85, source: 'prev' }
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
const reordered = mapper.reorderQuestions(questions, kg);
|
|
240
|
+
|
|
241
|
+
expect(reordered[0].relevanceScore).toBe(0);
|
|
242
|
+
expect(reordered[0].skipInfo.canSkip).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should boost high-priority questions', () => {
|
|
246
|
+
const questions = [
|
|
247
|
+
{ id: 'timeline', text: 'Timeline?' }, // Priority 4
|
|
248
|
+
{ id: 'goal', text: 'What building?' } // Priority 1
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
// Add some knowledge so we can see priority effects
|
|
252
|
+
kg.add([
|
|
253
|
+
{ type: 'timeline', content: '2 weeks', confidence: 60, source: 'prev' }
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
const reordered = mapper.reorderQuestions(questions, kg);
|
|
257
|
+
|
|
258
|
+
// Goal question should have higher score due to priority boost
|
|
259
|
+
const goalItem = reordered.find(r => r.question.id === 'goal');
|
|
260
|
+
const timelineItem = reordered.find(r => r.question.id === 'timeline');
|
|
261
|
+
|
|
262
|
+
expect(goalItem.relevanceScore).toBeGreaterThanOrEqual(timelineItem.relevanceScore);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should handle partially satisfied questions', () => {
|
|
266
|
+
const questions = [
|
|
267
|
+
{ id: 'goal', text: 'What building?' },
|
|
268
|
+
{ id: 'tech', text: 'What tech?' }
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
// Partial knowledge - only have goal
|
|
272
|
+
kg.add([
|
|
273
|
+
{ type: 'project_goal', content: 'CRM', confidence: 90, source: 'prev' }
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
const reordered = mapper.reorderQuestions(questions, kg);
|
|
277
|
+
|
|
278
|
+
// Tech should have full score (100), goal should be skippable (0)
|
|
279
|
+
const techItem = reordered.find(r => r.question.id === 'tech');
|
|
280
|
+
const goalItem = reordered.find(r => r.question.id === 'goal');
|
|
281
|
+
|
|
282
|
+
expect(techItem.relevanceScore).toBeGreaterThan(goalItem.relevanceScore);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('getStats', () => {
|
|
287
|
+
it('should calculate statistics correctly', () => {
|
|
288
|
+
const questions = [
|
|
289
|
+
{ id: 'tech-stack', text: 'What tech stack?' },
|
|
290
|
+
{ id: 'platform', text: 'What platform?' },
|
|
291
|
+
{ id: 'timeline', text: 'Timeline?' },
|
|
292
|
+
{ id: 'users', text: 'Who are users?' }
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
// Add some knowledge
|
|
296
|
+
kg.add([
|
|
297
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' },
|
|
298
|
+
{ type: 'platform', content: 'Web', confidence: 85, source: 'prev' }
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
const stats = mapper.getStats(questions, kg);
|
|
302
|
+
|
|
303
|
+
expect(stats.total).toBe(4);
|
|
304
|
+
expect(stats.canSkip).toBeGreaterThanOrEqual(2); // tech_stack and platform
|
|
305
|
+
expect(stats.needed).toBeGreaterThan(0);
|
|
306
|
+
expect(stats.estimatedTimeSaved).toBeGreaterThan(0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should handle empty knowledge graph', () => {
|
|
310
|
+
const questions = [
|
|
311
|
+
{ id: 'tech-stack', text: 'What tech stack?' },
|
|
312
|
+
{ id: 'platform', text: 'What platform?' }
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
const stats = mapper.getStats(questions, kg);
|
|
316
|
+
|
|
317
|
+
expect(stats.total).toBe(2);
|
|
318
|
+
expect(stats.canSkip).toBe(0);
|
|
319
|
+
expect(stats.needed).toBe(2);
|
|
320
|
+
expect(stats.estimatedTimeSaved).toBe(0);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should estimate time saved correctly', () => {
|
|
324
|
+
const questions = [
|
|
325
|
+
{ id: 'tech-stack', text: 'What tech stack?' },
|
|
326
|
+
{ id: 'platform', text: 'What platform?' },
|
|
327
|
+
{ id: 'timeline', text: 'What is the timeline?' }
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
// Can skip 2 questions
|
|
331
|
+
kg.add([
|
|
332
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' },
|
|
333
|
+
{ type: 'platform', content: 'Web', confidence: 90, source: 'prev' }
|
|
334
|
+
]);
|
|
335
|
+
|
|
336
|
+
const stats = mapper.getStats(questions, kg);
|
|
337
|
+
|
|
338
|
+
// If 2 can be skipped, time saved should be around 3 minutes (1.5 * 2)
|
|
339
|
+
expect(stats.estimatedTimeSaved).toBeGreaterThanOrEqual(1.5);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|