tlc-claude-code 1.2.29 → 1.4.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/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- 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 +130 -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/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- 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/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -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/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -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/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -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/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -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/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -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/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -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/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -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/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -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/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Analyzer
|
|
3
|
+
* Use AI to detect naming issues and semantic problems
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ConsensusEngine } = require('./consensus-engine.js');
|
|
7
|
+
|
|
8
|
+
class SemanticAnalyzer {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.adapters = options.adapters || [];
|
|
11
|
+
this.useConsensus = options.useConsensus !== false && this.adapters.length > 1;
|
|
12
|
+
this.budgetAware = options.budgetAware !== false;
|
|
13
|
+
this.requireMinimum = options.requireMinimum ?? 1;
|
|
14
|
+
|
|
15
|
+
if (this.useConsensus) {
|
|
16
|
+
this.consensusEngine = new ConsensusEngine(this.adapters, {
|
|
17
|
+
consensusType: options.consensusType || 'majority',
|
|
18
|
+
requireMinimum: this.requireMinimum,
|
|
19
|
+
budgetAware: this.budgetAware,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Analyze code for semantic issues
|
|
26
|
+
* @param {string} code - Source code to analyze
|
|
27
|
+
* @param {string} filename - Filename for context
|
|
28
|
+
* @param {Object} extraContext - Additional context to pass to adapters
|
|
29
|
+
* @returns {Object} Analysis result with issues
|
|
30
|
+
*/
|
|
31
|
+
async analyze(code, filename, extraContext = {}) {
|
|
32
|
+
if (!code || code.trim() === '') {
|
|
33
|
+
return {
|
|
34
|
+
issues: [],
|
|
35
|
+
byType: {},
|
|
36
|
+
cost: 0,
|
|
37
|
+
warnings: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const context = {
|
|
42
|
+
filename,
|
|
43
|
+
type: 'semantic',
|
|
44
|
+
...extraContext,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Filter adapters by budget if configured
|
|
48
|
+
const availableAdapters = this.budgetAware
|
|
49
|
+
? this.adapters.filter((a) => a.canAfford())
|
|
50
|
+
: this.adapters;
|
|
51
|
+
|
|
52
|
+
if (availableAdapters.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
issues: [],
|
|
55
|
+
byType: {},
|
|
56
|
+
cost: 0,
|
|
57
|
+
warnings: ['No adapters available (budget exceeded or none configured)'],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Use consensus engine for multi-model
|
|
62
|
+
if (this.useConsensus && availableAdapters.length > 1) {
|
|
63
|
+
return this.analyzeWithConsensus(code, context);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Single model analysis
|
|
67
|
+
return this.analyzeWithSingleModel(code, context, availableAdapters[0]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Analyze with consensus engine (multiple models)
|
|
72
|
+
*/
|
|
73
|
+
async analyzeWithConsensus(code, context) {
|
|
74
|
+
try {
|
|
75
|
+
const result = await this.consensusEngine.review(code, context);
|
|
76
|
+
|
|
77
|
+
const issues = [];
|
|
78
|
+
const warnings = result.warnings || [];
|
|
79
|
+
|
|
80
|
+
// Collect all issues from reviews
|
|
81
|
+
for (const review of result.reviews) {
|
|
82
|
+
for (const issue of review.issues || []) {
|
|
83
|
+
issues.push({
|
|
84
|
+
...issue,
|
|
85
|
+
model: review.model,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Categorize by type
|
|
91
|
+
const byType = this.categorizeByType(issues);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
issues,
|
|
95
|
+
byType,
|
|
96
|
+
consensus: result.consensus,
|
|
97
|
+
cost: result.costs.total,
|
|
98
|
+
warnings,
|
|
99
|
+
models: result.reviews.map((r) => r.model),
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
issues: [],
|
|
104
|
+
byType: {},
|
|
105
|
+
cost: 0,
|
|
106
|
+
warnings: [error.message],
|
|
107
|
+
error: error.message,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Analyze with a single model
|
|
114
|
+
*/
|
|
115
|
+
async analyzeWithSingleModel(code, context, adapter) {
|
|
116
|
+
const warnings = [];
|
|
117
|
+
let issues = [];
|
|
118
|
+
let cost = 0;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await adapter.review(code, context);
|
|
122
|
+
issues = result.issues || [];
|
|
123
|
+
cost = result.cost || 0;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
warnings.push(`${adapter.name} failed: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const byType = this.categorizeByType(issues);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
issues,
|
|
132
|
+
byType,
|
|
133
|
+
cost,
|
|
134
|
+
warnings,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Categorize issues by type
|
|
140
|
+
*/
|
|
141
|
+
categorizeByType(issues) {
|
|
142
|
+
const byType = {};
|
|
143
|
+
|
|
144
|
+
for (const issue of issues) {
|
|
145
|
+
const type = issue.type || 'unknown';
|
|
146
|
+
if (!byType[type]) {
|
|
147
|
+
byType[type] = [];
|
|
148
|
+
}
|
|
149
|
+
byType[type].push(issue);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return byType;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create a prompt for semantic analysis
|
|
157
|
+
* Used when calling LLM adapters
|
|
158
|
+
*/
|
|
159
|
+
static createPrompt(code, context) {
|
|
160
|
+
return `Analyze this code for naming and semantic issues.
|
|
161
|
+
|
|
162
|
+
File: ${context.filename}
|
|
163
|
+
|
|
164
|
+
Code:
|
|
165
|
+
\`\`\`
|
|
166
|
+
${code}
|
|
167
|
+
\`\`\`
|
|
168
|
+
|
|
169
|
+
Look for:
|
|
170
|
+
1. Poor variable names (single letters, cryptic abbreviations)
|
|
171
|
+
2. Poor function names (vague like 'doIt', 'process', 'handle')
|
|
172
|
+
3. Unclear function purposes (doing too many things)
|
|
173
|
+
4. Inconsistent naming conventions
|
|
174
|
+
5. Magic numbers without explanation
|
|
175
|
+
|
|
176
|
+
For each issue found, provide:
|
|
177
|
+
- type: "naming" or "clarity"
|
|
178
|
+
- severity: "error", "warning", or "info"
|
|
179
|
+
- line: line number
|
|
180
|
+
- message: description of the issue
|
|
181
|
+
- suggestion: how to fix it
|
|
182
|
+
|
|
183
|
+
Respond in JSON format:
|
|
184
|
+
{
|
|
185
|
+
"issues": [
|
|
186
|
+
{
|
|
187
|
+
"type": "naming",
|
|
188
|
+
"severity": "warning",
|
|
189
|
+
"line": 5,
|
|
190
|
+
"message": "Variable 'x' has unclear name",
|
|
191
|
+
"suggestion": "Rename to descriptive name like 'userCount'"
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = { SemanticAnalyzer };
|
|
@@ -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
|
+
});
|