@iservu-inc/adf-cli 0.4.36 → 0.5.1
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_INTELLIGENT-ANSWER-ANALYSIS.md +649 -0
- package/.project/chats/current/2025-10-05_MULTI-IDE-IMPROVEMENTS.md +415 -0
- package/.project/chats/current/SESSION-STATUS.md +166 -71
- package/.project/docs/ROADMAP.md +150 -17
- package/CHANGELOG.md +243 -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/commands/init.js +137 -8
- 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,322 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const KnowledgeGraph = require('../lib/analysis/knowledge-graph');
|
|
5
|
+
|
|
6
|
+
describe('KnowledgeGraph', () => {
|
|
7
|
+
let tempDir;
|
|
8
|
+
let kg;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kg-test-'));
|
|
12
|
+
kg = new KnowledgeGraph(tempDir);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await fs.remove(tempDir);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('add', () => {
|
|
20
|
+
it('should add new information', () => {
|
|
21
|
+
const extractedInfo = [
|
|
22
|
+
{
|
|
23
|
+
type: 'tech_stack',
|
|
24
|
+
content: 'React and Node.js',
|
|
25
|
+
confidence: 90,
|
|
26
|
+
source: 'tech-question'
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
kg.add(extractedInfo);
|
|
31
|
+
|
|
32
|
+
expect(kg.has('tech_stack', 70)).toBe(true);
|
|
33
|
+
expect(kg.getConfidence('tech_stack')).toBe(90);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should update existing information with higher confidence', () => {
|
|
37
|
+
const info1 = [
|
|
38
|
+
{ type: 'tech_stack', content: 'React for frontend development', confidence: 70, source: 'q1' }
|
|
39
|
+
];
|
|
40
|
+
const info2 = [
|
|
41
|
+
{ type: 'tech_stack', content: 'React for the frontend', confidence: 90, source: 'q2' }
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
kg.add(info1);
|
|
45
|
+
kg.add(info2);
|
|
46
|
+
|
|
47
|
+
expect(kg.getConfidence('tech_stack')).toBe(90);
|
|
48
|
+
const items = kg.get('tech_stack');
|
|
49
|
+
// Should have merged or both sources tracked
|
|
50
|
+
const allSources = items.flatMap(i => i.sources);
|
|
51
|
+
expect(allSources).toContain('q1');
|
|
52
|
+
expect(allSources).toContain('q2');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should not replace existing information with lower confidence', () => {
|
|
56
|
+
const info1 = [
|
|
57
|
+
{ type: 'tech_stack', content: 'React and Node', confidence: 90, source: 'q1' }
|
|
58
|
+
];
|
|
59
|
+
const info2 = [
|
|
60
|
+
{ type: 'tech_stack', content: 'React', confidence: 70, source: 'q2' }
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
kg.add(info1);
|
|
64
|
+
kg.add(info2);
|
|
65
|
+
|
|
66
|
+
expect(kg.getConfidence('tech_stack')).toBe(90);
|
|
67
|
+
const items = kg.get('tech_stack');
|
|
68
|
+
expect(items[0].content).toContain('Node');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should add dissimilar information as separate items', () => {
|
|
72
|
+
const info1 = [
|
|
73
|
+
{ type: 'features', content: 'User authentication', confidence: 85, source: 'q1' }
|
|
74
|
+
];
|
|
75
|
+
const info2 = [
|
|
76
|
+
{ type: 'features', content: 'Data export to CSV', confidence: 80, source: 'q2' }
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
kg.add(info1);
|
|
80
|
+
kg.add(info2);
|
|
81
|
+
|
|
82
|
+
const items = kg.get('features');
|
|
83
|
+
expect(items).toHaveLength(2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should merge similar information', () => {
|
|
87
|
+
const info1 = [
|
|
88
|
+
{ type: 'tech_stack', content: 'React for frontend', confidence: 85, source: 'q1' }
|
|
89
|
+
];
|
|
90
|
+
const info2 = [
|
|
91
|
+
{ type: 'tech_stack', content: 'React for the frontend', confidence: 80, source: 'q2' }
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
kg.add(info1);
|
|
95
|
+
kg.add(info2);
|
|
96
|
+
|
|
97
|
+
const items = kg.get('tech_stack');
|
|
98
|
+
// Should merge because very similar
|
|
99
|
+
expect(items.length).toBeLessThanOrEqual(2);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('has', () => {
|
|
104
|
+
it('should return true if information exists with sufficient confidence', () => {
|
|
105
|
+
const info = [
|
|
106
|
+
{ type: 'project_goal', content: 'Build a CRM', confidence: 85, source: 'q1' }
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
kg.add(info);
|
|
110
|
+
|
|
111
|
+
expect(kg.has('project_goal', 80)).toBe(true);
|
|
112
|
+
expect(kg.has('project_goal', 90)).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should return false for missing information types', () => {
|
|
116
|
+
expect(kg.has('tech_stack')).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should use default minConfidence of 70', () => {
|
|
120
|
+
const info = [
|
|
121
|
+
{ type: 'platform', content: 'Web', confidence: 75, source: 'q1' }
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
kg.add(info);
|
|
125
|
+
|
|
126
|
+
expect(kg.has('platform')).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('get', () => {
|
|
131
|
+
it('should return all items of a type', () => {
|
|
132
|
+
const info = [
|
|
133
|
+
{ type: 'features', content: 'Auth', confidence: 85, source: 'q1' },
|
|
134
|
+
{ type: 'features', content: 'Export', confidence: 80, source: 'q2' }
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
kg.add(info);
|
|
138
|
+
|
|
139
|
+
const items = kg.get('features');
|
|
140
|
+
expect(items).toHaveLength(2);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return empty array for non-existent type', () => {
|
|
144
|
+
const items = kg.get('nonexistent');
|
|
145
|
+
expect(items).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('getConfidence', () => {
|
|
150
|
+
it('should return highest confidence for a type', () => {
|
|
151
|
+
const info = [
|
|
152
|
+
{ type: 'tech_stack', content: 'React', confidence: 80, source: 'q1' },
|
|
153
|
+
{ type: 'tech_stack', content: 'Node', confidence: 90, source: 'q2' }
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
kg.add(info);
|
|
157
|
+
|
|
158
|
+
expect(kg.getConfidence('tech_stack')).toBe(90);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should return 0 for non-existent type', () => {
|
|
162
|
+
expect(kg.getConfidence('nonexistent')).toBe(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('getSummary', () => {
|
|
167
|
+
it('should return summary of all knowledge', () => {
|
|
168
|
+
const info = [
|
|
169
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' },
|
|
170
|
+
{ type: 'architecture', content: 'SPA', confidence: 85, source: 'q2' },
|
|
171
|
+
{ type: 'platform', content: 'Web', confidence: 80, source: 'q3' }
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
kg.add(info);
|
|
175
|
+
|
|
176
|
+
const summary = kg.getSummary();
|
|
177
|
+
|
|
178
|
+
expect(summary.tech_stack.maxConfidence).toBe(90);
|
|
179
|
+
expect(summary.architecture.maxConfidence).toBe(85);
|
|
180
|
+
expect(summary.platform.maxConfidence).toBe(80);
|
|
181
|
+
expect(summary.tech_stack.sources).toContain('q1');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('getDisplaySummary', () => {
|
|
186
|
+
it('should return displayable summary with icons', () => {
|
|
187
|
+
const info = [
|
|
188
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' },
|
|
189
|
+
{ type: 'project_goal', content: 'Build CRM', confidence: 95, source: 'q2' }
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
kg.add(info);
|
|
193
|
+
|
|
194
|
+
const display = kg.getDisplaySummary();
|
|
195
|
+
|
|
196
|
+
expect(display.length).toBe(2);
|
|
197
|
+
expect(display[0].confidence).toBe(95); // Sorted by confidence
|
|
198
|
+
expect(display[0].icon).toBe('🎯');
|
|
199
|
+
expect(display[1].icon).toBe('🔧');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should filter out low confidence items', () => {
|
|
203
|
+
const info = [
|
|
204
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' },
|
|
205
|
+
{ type: 'timeline', content: 'Soon', confidence: 50, source: 'q2' }
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
kg.add(info);
|
|
209
|
+
|
|
210
|
+
const display = kg.getDisplaySummary();
|
|
211
|
+
|
|
212
|
+
// Should only include tech_stack (90% > 60%)
|
|
213
|
+
expect(display).toHaveLength(1);
|
|
214
|
+
expect(display[0].type).toBe('tech stack');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('calculateSimilarity', () => {
|
|
219
|
+
it('should calculate high similarity for similar texts', () => {
|
|
220
|
+
const similarity = kg.calculateSimilarity(
|
|
221
|
+
'React for frontend development',
|
|
222
|
+
'React for the frontend'
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(similarity).toBeGreaterThan(0.5);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should calculate low similarity for different texts', () => {
|
|
229
|
+
const similarity = kg.calculateSimilarity(
|
|
230
|
+
'React for frontend',
|
|
231
|
+
'PostgreSQL database'
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(similarity).toBeLessThan(0.5);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should return 1 for identical texts', () => {
|
|
238
|
+
const similarity = kg.calculateSimilarity(
|
|
239
|
+
'same text',
|
|
240
|
+
'same text'
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
expect(similarity).toBe(1);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('save and load', () => {
|
|
248
|
+
it('should save knowledge graph to disk', async () => {
|
|
249
|
+
const info = [
|
|
250
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' }
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
kg.add(info);
|
|
254
|
+
await kg.save();
|
|
255
|
+
|
|
256
|
+
const filePath = path.join(tempDir, '_knowledge_graph.json');
|
|
257
|
+
expect(await fs.pathExists(filePath)).toBe(true);
|
|
258
|
+
|
|
259
|
+
const data = await fs.readJson(filePath);
|
|
260
|
+
expect(data.knowledge).toBeDefined();
|
|
261
|
+
expect(data.knowledge.length).toBeGreaterThan(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should load knowledge graph from disk', async () => {
|
|
265
|
+
const info = [
|
|
266
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' },
|
|
267
|
+
{ type: 'platform', content: 'Web', confidence: 85, source: 'q2' }
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
kg.add(info);
|
|
271
|
+
await kg.save();
|
|
272
|
+
|
|
273
|
+
// Create new instance and load
|
|
274
|
+
const kg2 = new KnowledgeGraph(tempDir);
|
|
275
|
+
const loaded = await kg2.load();
|
|
276
|
+
|
|
277
|
+
expect(loaded).toBe(true);
|
|
278
|
+
expect(kg2.has('tech_stack')).toBe(true);
|
|
279
|
+
expect(kg2.has('platform')).toBe(true);
|
|
280
|
+
expect(kg2.getConfidence('tech_stack')).toBe(90);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should return false when loading from non-existent file', async () => {
|
|
284
|
+
const loaded = await kg.load();
|
|
285
|
+
expect(loaded).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('getStats', () => {
|
|
290
|
+
it('should return statistics about knowledge graph', () => {
|
|
291
|
+
const info = [
|
|
292
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' },
|
|
293
|
+
{ type: 'tech_stack', content: 'Node', confidence: 85, source: 'q2' },
|
|
294
|
+
{ type: 'platform', content: 'Web', confidence: 75, source: 'q3' },
|
|
295
|
+
{ type: 'architecture', content: 'SPA', confidence: 80, source: 'q4' }
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
kg.add(info);
|
|
299
|
+
|
|
300
|
+
const stats = kg.getStats();
|
|
301
|
+
|
|
302
|
+
expect(stats.totalItems).toBe(4);
|
|
303
|
+
expect(stats.highConfidenceItems).toBe(3); // 90, 85, 80 are >= 80
|
|
304
|
+
expect(stats.types).toBe(3); // tech_stack, platform, architecture
|
|
305
|
+
expect(stats.typeList).toContain('tech_stack');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('clear', () => {
|
|
310
|
+
it('should clear all knowledge', () => {
|
|
311
|
+
const info = [
|
|
312
|
+
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'q1' }
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
kg.add(info);
|
|
316
|
+
expect(kg.has('tech_stack')).toBe(true);
|
|
317
|
+
|
|
318
|
+
kg.clear();
|
|
319
|
+
expect(kg.has('tech_stack')).toBe(false);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -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
|