tlc-claude-code 1.2.29 → 1.3.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/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +146 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/package.json +1 -1
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Analyzer Tests
|
|
3
|
+
* Task 3: Use AI to detect naming issues and semantic problems
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
|
|
8
|
+
describe('SemanticAnalyzer', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.resetAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('poor naming detection', () => {
|
|
14
|
+
it('flags single-letter variable names', async () => {
|
|
15
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
16
|
+
|
|
17
|
+
const mockAdapter = {
|
|
18
|
+
name: 'mock',
|
|
19
|
+
canAfford: () => true,
|
|
20
|
+
review: vi.fn().mockResolvedValue({
|
|
21
|
+
model: 'mock',
|
|
22
|
+
issues: [
|
|
23
|
+
{
|
|
24
|
+
type: 'naming',
|
|
25
|
+
severity: 'warning',
|
|
26
|
+
message: "Variable 'x' has unclear name",
|
|
27
|
+
line: 2,
|
|
28
|
+
suggestion: 'Consider renaming to a descriptive name like userCount',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
cost: 0.001,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
36
|
+
|
|
37
|
+
const code = `
|
|
38
|
+
function process(x) {
|
|
39
|
+
const y = x * 2;
|
|
40
|
+
return y;
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const result = await analyzer.analyze(code, 'test.js');
|
|
45
|
+
|
|
46
|
+
expect(result.issues.some(i => i.type === 'naming')).toBe(true);
|
|
47
|
+
expect(result.issues.some(i => i.message.includes('x'))).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('flags cryptic function names', async () => {
|
|
51
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
52
|
+
|
|
53
|
+
const mockAdapter = {
|
|
54
|
+
name: 'mock',
|
|
55
|
+
canAfford: () => true,
|
|
56
|
+
review: vi.fn().mockResolvedValue({
|
|
57
|
+
model: 'mock',
|
|
58
|
+
issues: [
|
|
59
|
+
{
|
|
60
|
+
type: 'naming',
|
|
61
|
+
severity: 'warning',
|
|
62
|
+
message: "Function 'fn1' has unclear name",
|
|
63
|
+
line: 1,
|
|
64
|
+
suggestion: 'Rename to describe what it does, e.g., calculateTotal',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'naming',
|
|
68
|
+
severity: 'warning',
|
|
69
|
+
message: "Function 'doIt' is too vague",
|
|
70
|
+
line: 5,
|
|
71
|
+
suggestion: 'Be specific about what action is performed',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
cost: 0.001,
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
79
|
+
|
|
80
|
+
const code = `
|
|
81
|
+
function fn1() { return 1; }
|
|
82
|
+
function doIt() { return 2; }
|
|
83
|
+
function proc() { return 3; }
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const result = await analyzer.analyze(code, 'test.js');
|
|
87
|
+
|
|
88
|
+
expect(result.issues.some(i => i.message.includes('fn1'))).toBe(true);
|
|
89
|
+
expect(result.issues.some(i => i.message.includes('doIt'))).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('suggests descriptive alternatives', async () => {
|
|
93
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
94
|
+
|
|
95
|
+
const mockAdapter = {
|
|
96
|
+
name: 'mock',
|
|
97
|
+
canAfford: () => true,
|
|
98
|
+
review: vi.fn().mockResolvedValue({
|
|
99
|
+
model: 'mock',
|
|
100
|
+
issues: [
|
|
101
|
+
{
|
|
102
|
+
type: 'naming',
|
|
103
|
+
severity: 'warning',
|
|
104
|
+
message: "Variable 'd' in date context",
|
|
105
|
+
line: 2,
|
|
106
|
+
suggestion: 'Rename to currentDate or dateValue',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
cost: 0.001,
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
114
|
+
|
|
115
|
+
const code = `
|
|
116
|
+
function formatDate(d) {
|
|
117
|
+
return d.toISOString();
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
const result = await analyzer.analyze(code, 'test.js');
|
|
122
|
+
|
|
123
|
+
expect(result.issues[0].suggestion).toBeDefined();
|
|
124
|
+
expect(result.issues[0].suggestion.length).toBeGreaterThan(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('unclear function purpose', () => {
|
|
129
|
+
it('identifies unclear function purposes', async () => {
|
|
130
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
131
|
+
|
|
132
|
+
const mockAdapter = {
|
|
133
|
+
name: 'mock',
|
|
134
|
+
canAfford: () => true,
|
|
135
|
+
review: vi.fn().mockResolvedValue({
|
|
136
|
+
model: 'mock',
|
|
137
|
+
issues: [
|
|
138
|
+
{
|
|
139
|
+
type: 'clarity',
|
|
140
|
+
severity: 'info',
|
|
141
|
+
message: 'Function does multiple unrelated things',
|
|
142
|
+
line: 1,
|
|
143
|
+
suggestion: 'Consider splitting into separate functions',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
cost: 0.001,
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
151
|
+
|
|
152
|
+
const code = `
|
|
153
|
+
function handle(data) {
|
|
154
|
+
validate(data);
|
|
155
|
+
save(data);
|
|
156
|
+
notify(data);
|
|
157
|
+
log(data);
|
|
158
|
+
return transform(data);
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const result = await analyzer.analyze(code, 'test.js');
|
|
163
|
+
|
|
164
|
+
expect(result.issues.some(i => i.type === 'clarity')).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('multi-model consensus', () => {
|
|
169
|
+
it('uses consensus engine for multi-model analysis', async () => {
|
|
170
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
171
|
+
|
|
172
|
+
const adapter1 = {
|
|
173
|
+
name: 'model1',
|
|
174
|
+
canAfford: () => true,
|
|
175
|
+
review: vi.fn().mockResolvedValue({
|
|
176
|
+
model: 'model1',
|
|
177
|
+
issues: [
|
|
178
|
+
{ type: 'naming', message: "Variable 'x' unclear", line: 1 },
|
|
179
|
+
],
|
|
180
|
+
cost: 0.01,
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const adapter2 = {
|
|
185
|
+
name: 'model2',
|
|
186
|
+
canAfford: () => true,
|
|
187
|
+
review: vi.fn().mockResolvedValue({
|
|
188
|
+
model: 'model2',
|
|
189
|
+
issues: [
|
|
190
|
+
{ type: 'naming', message: "Variable 'x' unclear", line: 1 },
|
|
191
|
+
{ type: 'naming', message: "Variable 'y' unclear", line: 2 },
|
|
192
|
+
],
|
|
193
|
+
cost: 0.02,
|
|
194
|
+
}),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const analyzer = new SemanticAnalyzer({
|
|
198
|
+
adapters: [adapter1, adapter2],
|
|
199
|
+
useConsensus: true,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const code = 'const x = 1; const y = 2;';
|
|
203
|
+
const result = await analyzer.analyze(code, 'test.js');
|
|
204
|
+
|
|
205
|
+
expect(result.consensus).toBeDefined();
|
|
206
|
+
expect(result.consensus.issues.length).toBeGreaterThan(0);
|
|
207
|
+
|
|
208
|
+
// Issue found by both models should have higher confidence
|
|
209
|
+
const xIssue = result.consensus.issues.find(i => i.message.includes('x'));
|
|
210
|
+
expect(xIssue.confidence).toBe(1); // 2/2 models
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('shows confidence scores based on agreement', async () => {
|
|
214
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
215
|
+
|
|
216
|
+
const adapter1 = {
|
|
217
|
+
name: 'model1',
|
|
218
|
+
canAfford: () => true,
|
|
219
|
+
review: vi.fn().mockResolvedValue({
|
|
220
|
+
model: 'model1',
|
|
221
|
+
issues: [
|
|
222
|
+
{ type: 'naming', message: 'Issue A', line: 1 },
|
|
223
|
+
],
|
|
224
|
+
cost: 0.01,
|
|
225
|
+
}),
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const adapter2 = {
|
|
229
|
+
name: 'model2',
|
|
230
|
+
canAfford: () => true,
|
|
231
|
+
review: vi.fn().mockResolvedValue({
|
|
232
|
+
model: 'model2',
|
|
233
|
+
issues: [
|
|
234
|
+
{ type: 'naming', message: 'Issue A', line: 1 },
|
|
235
|
+
{ type: 'naming', message: 'Issue B', line: 2 },
|
|
236
|
+
],
|
|
237
|
+
cost: 0.02,
|
|
238
|
+
}),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const adapter3 = {
|
|
242
|
+
name: 'model3',
|
|
243
|
+
canAfford: () => true,
|
|
244
|
+
review: vi.fn().mockResolvedValue({
|
|
245
|
+
model: 'model3',
|
|
246
|
+
issues: [
|
|
247
|
+
{ type: 'naming', message: 'Issue A', line: 1 },
|
|
248
|
+
],
|
|
249
|
+
cost: 0.015,
|
|
250
|
+
}),
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const analyzer = new SemanticAnalyzer({
|
|
254
|
+
adapters: [adapter1, adapter2, adapter3],
|
|
255
|
+
useConsensus: true,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = await analyzer.analyze('code', 'test.js');
|
|
259
|
+
|
|
260
|
+
// Issue A: 3/3 = 100% confidence
|
|
261
|
+
// Issue B: 1/3 = 33% confidence
|
|
262
|
+
const issueA = result.consensus.issues.find(i => i.message === 'Issue A');
|
|
263
|
+
const issueB = result.consensus.issues.find(i => i.message === 'Issue B');
|
|
264
|
+
|
|
265
|
+
expect(issueA.confidence).toBe(1);
|
|
266
|
+
expect(issueB.confidence).toBeCloseTo(0.33, 1);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('budget limits', () => {
|
|
271
|
+
it('respects budget limits', async () => {
|
|
272
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
273
|
+
|
|
274
|
+
const expensiveAdapter = {
|
|
275
|
+
name: 'expensive',
|
|
276
|
+
canAfford: () => false,
|
|
277
|
+
review: vi.fn(),
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const cheapAdapter = {
|
|
281
|
+
name: 'cheap',
|
|
282
|
+
canAfford: () => true,
|
|
283
|
+
review: vi.fn().mockResolvedValue({
|
|
284
|
+
model: 'cheap',
|
|
285
|
+
issues: [],
|
|
286
|
+
cost: 0.001,
|
|
287
|
+
}),
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const analyzer = new SemanticAnalyzer({
|
|
291
|
+
adapters: [expensiveAdapter, cheapAdapter],
|
|
292
|
+
budgetAware: true,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await analyzer.analyze('const x = 1;', 'test.js');
|
|
296
|
+
|
|
297
|
+
expect(expensiveAdapter.review).not.toHaveBeenCalled();
|
|
298
|
+
expect(cheapAdapter.review).toHaveBeenCalled();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('tracks costs across analysis', async () => {
|
|
302
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
303
|
+
|
|
304
|
+
const mockAdapter = {
|
|
305
|
+
name: 'mock',
|
|
306
|
+
canAfford: () => true,
|
|
307
|
+
review: vi.fn().mockResolvedValue({
|
|
308
|
+
model: 'mock',
|
|
309
|
+
issues: [],
|
|
310
|
+
cost: 0.05,
|
|
311
|
+
}),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
315
|
+
|
|
316
|
+
const result = await analyzer.analyze('code', 'test.js');
|
|
317
|
+
|
|
318
|
+
expect(result.cost).toBe(0.05);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('error handling', () => {
|
|
323
|
+
it('gracefully handles model failures', async () => {
|
|
324
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
325
|
+
|
|
326
|
+
const failingAdapter = {
|
|
327
|
+
name: 'failing',
|
|
328
|
+
canAfford: () => true,
|
|
329
|
+
review: vi.fn().mockRejectedValue(new Error('API error')),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const workingAdapter = {
|
|
333
|
+
name: 'working',
|
|
334
|
+
canAfford: () => true,
|
|
335
|
+
review: vi.fn().mockResolvedValue({
|
|
336
|
+
model: 'working',
|
|
337
|
+
issues: [{ type: 'naming', message: 'Found issue', line: 1 }],
|
|
338
|
+
cost: 0.01,
|
|
339
|
+
}),
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const analyzer = new SemanticAnalyzer({
|
|
343
|
+
adapters: [failingAdapter, workingAdapter],
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const result = await analyzer.analyze('code', 'test.js');
|
|
347
|
+
|
|
348
|
+
expect(result.warnings).toContain('failing failed: API error');
|
|
349
|
+
expect(result.issues).toHaveLength(1);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('returns empty results when all models fail', async () => {
|
|
353
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
354
|
+
|
|
355
|
+
const failingAdapter = {
|
|
356
|
+
name: 'failing',
|
|
357
|
+
canAfford: () => true,
|
|
358
|
+
review: vi.fn().mockRejectedValue(new Error('API error')),
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const analyzer = new SemanticAnalyzer({
|
|
362
|
+
adapters: [failingAdapter],
|
|
363
|
+
requireMinimum: 0,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const result = await analyzer.analyze('code', 'test.js');
|
|
367
|
+
|
|
368
|
+
expect(result.issues).toEqual([]);
|
|
369
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('handles empty code gracefully', async () => {
|
|
373
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
374
|
+
|
|
375
|
+
const mockAdapter = {
|
|
376
|
+
name: 'mock',
|
|
377
|
+
canAfford: () => true,
|
|
378
|
+
review: vi.fn().mockResolvedValue({
|
|
379
|
+
model: 'mock',
|
|
380
|
+
issues: [],
|
|
381
|
+
cost: 0,
|
|
382
|
+
}),
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
386
|
+
|
|
387
|
+
const result = await analyzer.analyze('', 'test.js');
|
|
388
|
+
|
|
389
|
+
expect(result.issues).toEqual([]);
|
|
390
|
+
expect(result.error).toBeUndefined();
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('context passing', () => {
|
|
395
|
+
it('passes file context to adapters', async () => {
|
|
396
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
397
|
+
|
|
398
|
+
const mockAdapter = {
|
|
399
|
+
name: 'mock',
|
|
400
|
+
canAfford: () => true,
|
|
401
|
+
review: vi.fn().mockResolvedValue({
|
|
402
|
+
model: 'mock',
|
|
403
|
+
issues: [],
|
|
404
|
+
cost: 0.01,
|
|
405
|
+
}),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
409
|
+
|
|
410
|
+
await analyzer.analyze('code', 'src/utils/helpers.js');
|
|
411
|
+
|
|
412
|
+
expect(mockAdapter.review).toHaveBeenCalledWith(
|
|
413
|
+
'code',
|
|
414
|
+
expect.objectContaining({
|
|
415
|
+
filename: 'src/utils/helpers.js',
|
|
416
|
+
type: 'semantic',
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('passes custom context to adapters', async () => {
|
|
422
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
423
|
+
|
|
424
|
+
const mockAdapter = {
|
|
425
|
+
name: 'mock',
|
|
426
|
+
canAfford: () => true,
|
|
427
|
+
review: vi.fn().mockResolvedValue({
|
|
428
|
+
model: 'mock',
|
|
429
|
+
issues: [],
|
|
430
|
+
cost: 0.01,
|
|
431
|
+
}),
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
435
|
+
|
|
436
|
+
await analyzer.analyze('code', 'test.js', { projectType: 'react' });
|
|
437
|
+
|
|
438
|
+
expect(mockAdapter.review).toHaveBeenCalledWith(
|
|
439
|
+
'code',
|
|
440
|
+
expect.objectContaining({
|
|
441
|
+
projectType: 'react',
|
|
442
|
+
})
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe('issue categorization', () => {
|
|
448
|
+
it('categorizes issues by type', async () => {
|
|
449
|
+
const { SemanticAnalyzer } = await import('./semantic-analyzer.js');
|
|
450
|
+
|
|
451
|
+
const mockAdapter = {
|
|
452
|
+
name: 'mock',
|
|
453
|
+
canAfford: () => true,
|
|
454
|
+
review: vi.fn().mockResolvedValue({
|
|
455
|
+
model: 'mock',
|
|
456
|
+
issues: [
|
|
457
|
+
{ type: 'naming', message: 'Poor name', line: 1 },
|
|
458
|
+
{ type: 'clarity', message: 'Unclear purpose', line: 2 },
|
|
459
|
+
{ type: 'naming', message: 'Another poor name', line: 3 },
|
|
460
|
+
],
|
|
461
|
+
cost: 0.01,
|
|
462
|
+
}),
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const analyzer = new SemanticAnalyzer({ adapters: [mockAdapter] });
|
|
466
|
+
|
|
467
|
+
const result = await analyzer.analyze('code', 'test.js');
|
|
468
|
+
|
|
469
|
+
expect(result.byType).toBeDefined();
|
|
470
|
+
expect(result.byType.naming).toHaveLength(2);
|
|
471
|
+
expect(result.byType.clarity).toHaveLength(1);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
});
|