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.
Files changed (182) hide show
  1. package/dashboard/dist/components/AuditPane.d.ts +30 -0
  2. package/dashboard/dist/components/AuditPane.js +127 -0
  3. package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
  4. package/dashboard/dist/components/AuditPane.test.js +339 -0
  5. package/dashboard/dist/components/CompliancePane.d.ts +39 -0
  6. package/dashboard/dist/components/CompliancePane.js +96 -0
  7. package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
  8. package/dashboard/dist/components/CompliancePane.test.js +183 -0
  9. package/dashboard/dist/components/SSOPane.d.ts +36 -0
  10. package/dashboard/dist/components/SSOPane.js +71 -0
  11. package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
  12. package/dashboard/dist/components/SSOPane.test.js +155 -0
  13. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  14. package/dashboard/dist/components/UsagePane.js +51 -0
  15. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  16. package/dashboard/dist/components/UsagePane.test.js +142 -0
  17. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  18. package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
  19. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  20. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  21. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  22. package/dashboard/dist/components/WorkspacePane.js +17 -0
  23. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  24. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  25. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  26. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  27. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  28. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  29. package/package.json +1 -1
  30. package/server/lib/access-control-doc.js +541 -0
  31. package/server/lib/access-control-doc.test.js +672 -0
  32. package/server/lib/adr-generator.js +423 -0
  33. package/server/lib/adr-generator.test.js +586 -0
  34. package/server/lib/agent-progress-monitor.js +223 -0
  35. package/server/lib/agent-progress-monitor.test.js +202 -0
  36. package/server/lib/architecture-command.js +450 -0
  37. package/server/lib/architecture-command.test.js +754 -0
  38. package/server/lib/ast-analyzer.js +324 -0
  39. package/server/lib/ast-analyzer.test.js +437 -0
  40. package/server/lib/audit-attribution.js +191 -0
  41. package/server/lib/audit-attribution.test.js +359 -0
  42. package/server/lib/audit-classifier.js +202 -0
  43. package/server/lib/audit-classifier.test.js +209 -0
  44. package/server/lib/audit-command.js +275 -0
  45. package/server/lib/audit-command.test.js +325 -0
  46. package/server/lib/audit-exporter.js +380 -0
  47. package/server/lib/audit-exporter.test.js +464 -0
  48. package/server/lib/audit-logger.js +236 -0
  49. package/server/lib/audit-logger.test.js +364 -0
  50. package/server/lib/audit-query.js +257 -0
  51. package/server/lib/audit-query.test.js +352 -0
  52. package/server/lib/audit-storage.js +269 -0
  53. package/server/lib/audit-storage.test.js +272 -0
  54. package/server/lib/auth-system.test.js +4 -1
  55. package/server/lib/boundary-detector.js +427 -0
  56. package/server/lib/boundary-detector.test.js +320 -0
  57. package/server/lib/budget-alerts.js +138 -0
  58. package/server/lib/budget-alerts.test.js +235 -0
  59. package/server/lib/bulk-repo-init.js +342 -0
  60. package/server/lib/bulk-repo-init.test.js +388 -0
  61. package/server/lib/candidates-tracker.js +210 -0
  62. package/server/lib/candidates-tracker.test.js +300 -0
  63. package/server/lib/checkpoint-manager.js +251 -0
  64. package/server/lib/checkpoint-manager.test.js +474 -0
  65. package/server/lib/circular-detector.js +337 -0
  66. package/server/lib/circular-detector.test.js +353 -0
  67. package/server/lib/cohesion-analyzer.js +310 -0
  68. package/server/lib/cohesion-analyzer.test.js +447 -0
  69. package/server/lib/compliance-checklist.js +866 -0
  70. package/server/lib/compliance-checklist.test.js +476 -0
  71. package/server/lib/compliance-command.js +616 -0
  72. package/server/lib/compliance-command.test.js +551 -0
  73. package/server/lib/compliance-reporter.js +692 -0
  74. package/server/lib/compliance-reporter.test.js +707 -0
  75. package/server/lib/contract-testing.js +625 -0
  76. package/server/lib/contract-testing.test.js +342 -0
  77. package/server/lib/conversion-planner.js +469 -0
  78. package/server/lib/conversion-planner.test.js +361 -0
  79. package/server/lib/convert-command.js +351 -0
  80. package/server/lib/convert-command.test.js +608 -0
  81. package/server/lib/coupling-calculator.js +189 -0
  82. package/server/lib/coupling-calculator.test.js +509 -0
  83. package/server/lib/data-flow-doc.js +665 -0
  84. package/server/lib/data-flow-doc.test.js +659 -0
  85. package/server/lib/dependency-graph.js +367 -0
  86. package/server/lib/dependency-graph.test.js +516 -0
  87. package/server/lib/duplication-detector.js +349 -0
  88. package/server/lib/duplication-detector.test.js +401 -0
  89. package/server/lib/ephemeral-storage.js +249 -0
  90. package/server/lib/ephemeral-storage.test.js +254 -0
  91. package/server/lib/evidence-collector.js +627 -0
  92. package/server/lib/evidence-collector.test.js +901 -0
  93. package/server/lib/example-service.js +616 -0
  94. package/server/lib/example-service.test.js +397 -0
  95. package/server/lib/flow-diagram-generator.js +474 -0
  96. package/server/lib/flow-diagram-generator.test.js +446 -0
  97. package/server/lib/idp-manager.js +626 -0
  98. package/server/lib/idp-manager.test.js +587 -0
  99. package/server/lib/impact-scorer.js +184 -0
  100. package/server/lib/impact-scorer.test.js +211 -0
  101. package/server/lib/memory-exclusion.js +326 -0
  102. package/server/lib/memory-exclusion.test.js +241 -0
  103. package/server/lib/mermaid-generator.js +358 -0
  104. package/server/lib/mermaid-generator.test.js +301 -0
  105. package/server/lib/messaging-patterns.js +750 -0
  106. package/server/lib/messaging-patterns.test.js +213 -0
  107. package/server/lib/mfa-handler.js +452 -0
  108. package/server/lib/mfa-handler.test.js +490 -0
  109. package/server/lib/microservice-template.js +386 -0
  110. package/server/lib/microservice-template.test.js +325 -0
  111. package/server/lib/new-project-microservice.js +450 -0
  112. package/server/lib/new-project-microservice.test.js +600 -0
  113. package/server/lib/oauth-flow.js +375 -0
  114. package/server/lib/oauth-flow.test.js +487 -0
  115. package/server/lib/oauth-registry.js +190 -0
  116. package/server/lib/oauth-registry.test.js +306 -0
  117. package/server/lib/readme-generator.js +490 -0
  118. package/server/lib/readme-generator.test.js +493 -0
  119. package/server/lib/refactor-command.js +326 -0
  120. package/server/lib/refactor-command.test.js +528 -0
  121. package/server/lib/refactor-executor.js +254 -0
  122. package/server/lib/refactor-executor.test.js +305 -0
  123. package/server/lib/refactor-observer.js +292 -0
  124. package/server/lib/refactor-observer.test.js +422 -0
  125. package/server/lib/refactor-progress.js +193 -0
  126. package/server/lib/refactor-progress.test.js +251 -0
  127. package/server/lib/refactor-reporter.js +237 -0
  128. package/server/lib/refactor-reporter.test.js +247 -0
  129. package/server/lib/repo-dependency-tracker.js +261 -0
  130. package/server/lib/repo-dependency-tracker.test.js +350 -0
  131. package/server/lib/retention-policy.js +281 -0
  132. package/server/lib/retention-policy.test.js +486 -0
  133. package/server/lib/role-mapper.js +236 -0
  134. package/server/lib/role-mapper.test.js +395 -0
  135. package/server/lib/saml-provider.js +765 -0
  136. package/server/lib/saml-provider.test.js +643 -0
  137. package/server/lib/security-policy-generator.js +682 -0
  138. package/server/lib/security-policy-generator.test.js +544 -0
  139. package/server/lib/semantic-analyzer.js +198 -0
  140. package/server/lib/semantic-analyzer.test.js +474 -0
  141. package/server/lib/sensitive-detector.js +112 -0
  142. package/server/lib/sensitive-detector.test.js +209 -0
  143. package/server/lib/service-interaction-diagram.js +700 -0
  144. package/server/lib/service-interaction-diagram.test.js +638 -0
  145. package/server/lib/service-scaffold.js +486 -0
  146. package/server/lib/service-scaffold.test.js +373 -0
  147. package/server/lib/service-summary.js +553 -0
  148. package/server/lib/service-summary.test.js +619 -0
  149. package/server/lib/session-purge.js +460 -0
  150. package/server/lib/session-purge.test.js +312 -0
  151. package/server/lib/shared-kernel.js +578 -0
  152. package/server/lib/shared-kernel.test.js +255 -0
  153. package/server/lib/sso-command.js +544 -0
  154. package/server/lib/sso-command.test.js +552 -0
  155. package/server/lib/sso-session.js +492 -0
  156. package/server/lib/sso-session.test.js +670 -0
  157. package/server/lib/traefik-config.js +282 -0
  158. package/server/lib/traefik-config.test.js +312 -0
  159. package/server/lib/usage-command.js +218 -0
  160. package/server/lib/usage-command.test.js +391 -0
  161. package/server/lib/usage-formatter.js +192 -0
  162. package/server/lib/usage-formatter.test.js +267 -0
  163. package/server/lib/usage-history.js +122 -0
  164. package/server/lib/usage-history.test.js +206 -0
  165. package/server/lib/workspace-command.js +249 -0
  166. package/server/lib/workspace-command.test.js +264 -0
  167. package/server/lib/workspace-config.js +270 -0
  168. package/server/lib/workspace-config.test.js +312 -0
  169. package/server/lib/workspace-docs-command.js +547 -0
  170. package/server/lib/workspace-docs-command.test.js +692 -0
  171. package/server/lib/workspace-memory.js +451 -0
  172. package/server/lib/workspace-memory.test.js +403 -0
  173. package/server/lib/workspace-scanner.js +452 -0
  174. package/server/lib/workspace-scanner.test.js +677 -0
  175. package/server/lib/workspace-test-runner.js +315 -0
  176. package/server/lib/workspace-test-runner.test.js +294 -0
  177. package/server/lib/zero-retention-command.js +439 -0
  178. package/server/lib/zero-retention-command.test.js +448 -0
  179. package/server/lib/zero-retention.js +322 -0
  180. package/server/lib/zero-retention.test.js +258 -0
  181. package/server/package-lock.json +14 -0
  182. 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
+ });