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,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Code Analyzer
|
|
3
|
+
* Parse JavaScript/TypeScript files and extract complexity metrics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ts = require('typescript');
|
|
7
|
+
|
|
8
|
+
class AstAnalyzer {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
longFunctionThreshold: options.longFunctionThreshold || 50,
|
|
12
|
+
deepNestingThreshold: options.deepNestingThreshold || 4,
|
|
13
|
+
highComplexityThreshold: options.highComplexityThreshold || 10,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Analyze code and extract metrics
|
|
19
|
+
* @param {string} code - Source code to analyze
|
|
20
|
+
* @param {string} filename - Filename (used to determine language)
|
|
21
|
+
* @returns {Object} Analysis result with functions and metrics
|
|
22
|
+
*/
|
|
23
|
+
analyze(code, filename) {
|
|
24
|
+
if (!code || code.trim() === '') {
|
|
25
|
+
return {
|
|
26
|
+
functions: [],
|
|
27
|
+
fileMetrics: {
|
|
28
|
+
totalFunctions: 0,
|
|
29
|
+
averageComplexity: 0,
|
|
30
|
+
maxComplexity: 0,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const scriptKind = this.getScriptKind(filename);
|
|
37
|
+
const sourceFile = ts.createSourceFile(
|
|
38
|
+
filename,
|
|
39
|
+
code,
|
|
40
|
+
ts.ScriptTarget.Latest,
|
|
41
|
+
true,
|
|
42
|
+
scriptKind
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Check for parse errors
|
|
46
|
+
const diagnostics = this.getParseErrors(sourceFile);
|
|
47
|
+
if (diagnostics.length > 0) {
|
|
48
|
+
return {
|
|
49
|
+
error: diagnostics[0].messageText.toString(),
|
|
50
|
+
functions: [],
|
|
51
|
+
fileMetrics: {
|
|
52
|
+
totalFunctions: 0,
|
|
53
|
+
averageComplexity: 0,
|
|
54
|
+
maxComplexity: 0,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const functions = [];
|
|
60
|
+
this.visitNode(sourceFile, sourceFile, functions, 0);
|
|
61
|
+
|
|
62
|
+
const fileMetrics = this.calculateFileMetrics(functions);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
functions,
|
|
66
|
+
fileMetrics,
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
error: error.message,
|
|
71
|
+
functions: [],
|
|
72
|
+
fileMetrics: {
|
|
73
|
+
totalFunctions: 0,
|
|
74
|
+
averageComplexity: 0,
|
|
75
|
+
maxComplexity: 0,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get parse errors from source file
|
|
83
|
+
*/
|
|
84
|
+
getParseErrors(sourceFile) {
|
|
85
|
+
// TypeScript parser is error-tolerant, but we can check for obvious issues
|
|
86
|
+
const errors = [];
|
|
87
|
+
|
|
88
|
+
const visit = (node) => {
|
|
89
|
+
// Check for missing tokens or syntax issues
|
|
90
|
+
if (node.kind === ts.SyntaxKind.Unknown) {
|
|
91
|
+
errors.push({ messageText: 'Syntax error: unknown token' });
|
|
92
|
+
}
|
|
93
|
+
ts.forEachChild(node, visit);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Check if file has unbalanced braces/parens by looking at structure
|
|
97
|
+
const text = sourceFile.text;
|
|
98
|
+
const openBraces = (text.match(/\{/g) || []).length;
|
|
99
|
+
const closeBraces = (text.match(/\}/g) || []).length;
|
|
100
|
+
const openParens = (text.match(/\(/g) || []).length;
|
|
101
|
+
const closeParens = (text.match(/\)/g) || []).length;
|
|
102
|
+
|
|
103
|
+
if (openBraces !== closeBraces) {
|
|
104
|
+
errors.push({ messageText: 'Syntax error: unbalanced braces' });
|
|
105
|
+
}
|
|
106
|
+
if (openParens !== closeParens) {
|
|
107
|
+
errors.push({ messageText: 'Syntax error: unbalanced parentheses' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return errors;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get TypeScript script kind based on filename
|
|
115
|
+
*/
|
|
116
|
+
getScriptKind(filename) {
|
|
117
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
118
|
+
switch (ext) {
|
|
119
|
+
case 'tsx':
|
|
120
|
+
return ts.ScriptKind.TSX;
|
|
121
|
+
case 'ts':
|
|
122
|
+
return ts.ScriptKind.TS;
|
|
123
|
+
case 'jsx':
|
|
124
|
+
return ts.ScriptKind.JSX;
|
|
125
|
+
case 'js':
|
|
126
|
+
default:
|
|
127
|
+
return ts.ScriptKind.JS;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Visit AST nodes recursively
|
|
133
|
+
*/
|
|
134
|
+
visitNode(node, sourceFile, functions, depth) {
|
|
135
|
+
if (this.isFunctionNode(node)) {
|
|
136
|
+
const funcInfo = this.analyzeFunctionNode(node, sourceFile);
|
|
137
|
+
functions.push(funcInfo);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
ts.forEachChild(node, (child) => {
|
|
141
|
+
this.visitNode(child, sourceFile, functions, depth + 1);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if node is a function-like node
|
|
147
|
+
*/
|
|
148
|
+
isFunctionNode(node) {
|
|
149
|
+
return (
|
|
150
|
+
ts.isFunctionDeclaration(node) ||
|
|
151
|
+
ts.isFunctionExpression(node) ||
|
|
152
|
+
ts.isArrowFunction(node) ||
|
|
153
|
+
ts.isMethodDeclaration(node)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Analyze a function node and extract metrics
|
|
159
|
+
*/
|
|
160
|
+
analyzeFunctionNode(node, sourceFile) {
|
|
161
|
+
const name = this.getFunctionName(node, sourceFile);
|
|
162
|
+
const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
163
|
+
const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
164
|
+
const lineCount = endLine - startLine + 1;
|
|
165
|
+
|
|
166
|
+
const complexity = this.calculateComplexity(node);
|
|
167
|
+
const maxNesting = this.calculateMaxNesting(node);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
name,
|
|
171
|
+
startLine: startLine + 1,
|
|
172
|
+
endLine: endLine + 1,
|
|
173
|
+
lineCount,
|
|
174
|
+
complexity,
|
|
175
|
+
maxNesting,
|
|
176
|
+
isLong: lineCount >= this.options.longFunctionThreshold,
|
|
177
|
+
isDeeplyNested: maxNesting > this.options.deepNestingThreshold,
|
|
178
|
+
isComplex: complexity > this.options.highComplexityThreshold,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get function name from node
|
|
184
|
+
*/
|
|
185
|
+
getFunctionName(node, sourceFile) {
|
|
186
|
+
// Function declaration with name
|
|
187
|
+
if (node.name) {
|
|
188
|
+
return node.name.getText(sourceFile);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Arrow function or function expression assigned to variable
|
|
192
|
+
if (node.parent && ts.isVariableDeclaration(node.parent)) {
|
|
193
|
+
return node.parent.name.getText(sourceFile);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Method in class
|
|
197
|
+
if (ts.isMethodDeclaration(node) && node.name) {
|
|
198
|
+
return node.name.getText(sourceFile);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return '<anonymous>';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Calculate cyclomatic complexity
|
|
206
|
+
* Base of 1 + decision points
|
|
207
|
+
*/
|
|
208
|
+
calculateComplexity(node) {
|
|
209
|
+
let complexity = 1;
|
|
210
|
+
|
|
211
|
+
const visit = (n) => {
|
|
212
|
+
// Conditional statements
|
|
213
|
+
if (ts.isIfStatement(n)) complexity++;
|
|
214
|
+
if (ts.isConditionalExpression(n)) complexity++; // ternary
|
|
215
|
+
|
|
216
|
+
// Loops
|
|
217
|
+
if (ts.isForStatement(n)) complexity++;
|
|
218
|
+
if (ts.isForInStatement(n)) complexity++;
|
|
219
|
+
if (ts.isForOfStatement(n)) complexity++;
|
|
220
|
+
if (ts.isWhileStatement(n)) complexity++;
|
|
221
|
+
if (ts.isDoStatement(n)) complexity++;
|
|
222
|
+
|
|
223
|
+
// Switch cases (each case except default)
|
|
224
|
+
if (ts.isCaseClause(n)) complexity++;
|
|
225
|
+
|
|
226
|
+
// Logical operators
|
|
227
|
+
if (ts.isBinaryExpression(n)) {
|
|
228
|
+
if (
|
|
229
|
+
n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
230
|
+
n.operatorToken.kind === ts.SyntaxKind.BarBarToken
|
|
231
|
+
) {
|
|
232
|
+
complexity++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Catch clauses
|
|
237
|
+
if (ts.isCatchClause(n)) complexity++;
|
|
238
|
+
|
|
239
|
+
// Nullish coalescing
|
|
240
|
+
if (ts.isBinaryExpression(n) && n.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
241
|
+
complexity++;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
ts.forEachChild(n, visit);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (node.body) {
|
|
248
|
+
visit(node.body);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return complexity;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Calculate maximum nesting depth
|
|
256
|
+
*/
|
|
257
|
+
calculateMaxNesting(node) {
|
|
258
|
+
let maxDepth = 0;
|
|
259
|
+
|
|
260
|
+
const visit = (n, depth) => {
|
|
261
|
+
// Track nesting for control structures
|
|
262
|
+
if (
|
|
263
|
+
ts.isIfStatement(n) ||
|
|
264
|
+
ts.isForStatement(n) ||
|
|
265
|
+
ts.isForInStatement(n) ||
|
|
266
|
+
ts.isForOfStatement(n) ||
|
|
267
|
+
ts.isWhileStatement(n) ||
|
|
268
|
+
ts.isDoStatement(n) ||
|
|
269
|
+
ts.isTryStatement(n) ||
|
|
270
|
+
ts.isSwitchStatement(n)
|
|
271
|
+
) {
|
|
272
|
+
const newDepth = depth + 1;
|
|
273
|
+
maxDepth = Math.max(maxDepth, newDepth);
|
|
274
|
+
ts.forEachChild(n, (child) => visit(child, newDepth));
|
|
275
|
+
} else {
|
|
276
|
+
ts.forEachChild(n, (child) => visit(child, depth));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
if (node.body) {
|
|
281
|
+
visit(node.body, 0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return maxDepth;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Calculate file-level metrics
|
|
289
|
+
*/
|
|
290
|
+
calculateFileMetrics(functions) {
|
|
291
|
+
if (functions.length === 0) {
|
|
292
|
+
return {
|
|
293
|
+
totalFunctions: 0,
|
|
294
|
+
averageComplexity: 0,
|
|
295
|
+
maxComplexity: 0,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const complexities = functions.map((f) => f.complexity);
|
|
300
|
+
const totalComplexity = complexities.reduce((a, b) => a + b, 0);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
totalFunctions: functions.length,
|
|
304
|
+
averageComplexity: totalComplexity / functions.length,
|
|
305
|
+
maxComplexity: Math.max(...complexities),
|
|
306
|
+
longFunctions: functions.filter((f) => f.isLong).length,
|
|
307
|
+
deeplyNestedFunctions: functions.filter((f) => f.isDeeplyNested).length,
|
|
308
|
+
complexFunctions: functions.filter((f) => f.isComplex).length,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Analyze a file from path
|
|
314
|
+
* @param {string} filePath - Path to file
|
|
315
|
+
* @returns {Object} Analysis result
|
|
316
|
+
*/
|
|
317
|
+
async analyzeFile(filePath) {
|
|
318
|
+
const fs = require('fs').promises;
|
|
319
|
+
const code = await fs.readFile(filePath, 'utf-8');
|
|
320
|
+
return this.analyze(code, filePath);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = { AstAnalyzer };
|