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,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohesion Analyzer
|
|
3
|
+
* Analyze module cohesion based on dependency relationships
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
class CohesionAnalyzer {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.basePath = options.basePath || process.cwd();
|
|
12
|
+
this.lowCohesionThreshold = options.lowCohesionThreshold || 0.3;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Analyze cohesion for a dependency graph
|
|
17
|
+
* @param {Object} graph - DependencyGraph instance
|
|
18
|
+
* @returns {Object} Cohesion analysis results
|
|
19
|
+
*/
|
|
20
|
+
analyze(graph) {
|
|
21
|
+
const graphData = graph.getGraph();
|
|
22
|
+
const modules = this.groupByDirectory(graphData.nodes);
|
|
23
|
+
const moduleAnalysis = {};
|
|
24
|
+
|
|
25
|
+
for (const [modulePath, files] of Object.entries(modules)) {
|
|
26
|
+
moduleAnalysis[modulePath] = this.analyzeModule(modulePath, files, graph);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const lowCohesion = this.identifyLowCohesion(moduleAnalysis);
|
|
30
|
+
const suggestions = this.generateSuggestions(moduleAnalysis, graph);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
modules: moduleAnalysis,
|
|
34
|
+
lowCohesion,
|
|
35
|
+
suggestions,
|
|
36
|
+
summary: {
|
|
37
|
+
totalModules: Object.keys(moduleAnalysis).length,
|
|
38
|
+
averageCohesion: this.calculateAverageCohesion(moduleAnalysis),
|
|
39
|
+
lowCohesionCount: lowCohesion.length,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Group files by their directory (module)
|
|
46
|
+
*/
|
|
47
|
+
groupByDirectory(nodes) {
|
|
48
|
+
const modules = {};
|
|
49
|
+
|
|
50
|
+
for (const node of nodes) {
|
|
51
|
+
const filePath = node.id;
|
|
52
|
+
const relativePath = node.name;
|
|
53
|
+
const dir = path.dirname(relativePath);
|
|
54
|
+
|
|
55
|
+
// Normalize the directory path
|
|
56
|
+
const modulePath = dir === '.' ? '(root)' : dir;
|
|
57
|
+
|
|
58
|
+
if (!modules[modulePath]) {
|
|
59
|
+
modules[modulePath] = [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
modules[modulePath].push({
|
|
63
|
+
path: filePath,
|
|
64
|
+
name: relativePath,
|
|
65
|
+
basename: path.basename(relativePath),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return modules;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Analyze cohesion for a single module (directory)
|
|
74
|
+
*/
|
|
75
|
+
analyzeModule(modulePath, files, graph) {
|
|
76
|
+
if (files.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
path: modulePath,
|
|
79
|
+
files: [],
|
|
80
|
+
cohesion: 1,
|
|
81
|
+
internalDeps: 0,
|
|
82
|
+
externalDeps: 0,
|
|
83
|
+
ratio: 1,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (files.length === 1) {
|
|
88
|
+
// Single file module - check its dependencies
|
|
89
|
+
const file = files[0];
|
|
90
|
+
const imports = graph.getImports(file.path);
|
|
91
|
+
const importers = graph.getImporters(file.path);
|
|
92
|
+
|
|
93
|
+
// A single file with no internal deps is cohesive by definition
|
|
94
|
+
return {
|
|
95
|
+
path: modulePath,
|
|
96
|
+
files: files.map(f => f.name),
|
|
97
|
+
cohesion: 1,
|
|
98
|
+
internalDeps: 0,
|
|
99
|
+
externalDeps: imports.length + importers.length,
|
|
100
|
+
ratio: 1,
|
|
101
|
+
singleFile: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const filePaths = new Set(files.map(f => f.path));
|
|
106
|
+
let internalDeps = 0;
|
|
107
|
+
let externalDeps = 0;
|
|
108
|
+
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
const imports = graph.getImports(file.path);
|
|
111
|
+
const importers = graph.getImporters(file.path);
|
|
112
|
+
|
|
113
|
+
// Count internal vs external dependencies
|
|
114
|
+
for (const imp of imports) {
|
|
115
|
+
if (filePaths.has(imp)) {
|
|
116
|
+
internalDeps++;
|
|
117
|
+
} else {
|
|
118
|
+
externalDeps++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const importer of importers) {
|
|
123
|
+
if (!filePaths.has(importer)) {
|
|
124
|
+
externalDeps++;
|
|
125
|
+
}
|
|
126
|
+
// Internal importers are counted when processing the importer file
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const totalDeps = internalDeps + externalDeps;
|
|
131
|
+
const cohesion = totalDeps === 0 ? 1 : internalDeps / totalDeps;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
path: modulePath,
|
|
135
|
+
files: files.map(f => f.name),
|
|
136
|
+
cohesion: Math.round(cohesion * 1000) / 1000,
|
|
137
|
+
internalDeps,
|
|
138
|
+
externalDeps,
|
|
139
|
+
ratio: totalDeps === 0 ? 1 : Math.round(cohesion * 1000) / 1000,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Identify modules with low cohesion
|
|
145
|
+
*/
|
|
146
|
+
identifyLowCohesion(moduleAnalysis) {
|
|
147
|
+
const lowCohesion = [];
|
|
148
|
+
|
|
149
|
+
for (const [modulePath, analysis] of Object.entries(moduleAnalysis)) {
|
|
150
|
+
if (analysis.cohesion < this.lowCohesionThreshold && !analysis.singleFile) {
|
|
151
|
+
lowCohesion.push({
|
|
152
|
+
module: modulePath,
|
|
153
|
+
cohesion: analysis.cohesion,
|
|
154
|
+
files: analysis.files,
|
|
155
|
+
internalDeps: analysis.internalDeps,
|
|
156
|
+
externalDeps: analysis.externalDeps,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lowCohesion.sort((a, b) => a.cohesion - b.cohesion);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate suggestions for improving cohesion
|
|
166
|
+
*/
|
|
167
|
+
generateSuggestions(moduleAnalysis, graph) {
|
|
168
|
+
const suggestions = [];
|
|
169
|
+
|
|
170
|
+
for (const [modulePath, analysis] of Object.entries(moduleAnalysis)) {
|
|
171
|
+
if (analysis.singleFile || analysis.files.length <= 1) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Find files that might be better placed elsewhere
|
|
176
|
+
const outliers = this.findOutliers(modulePath, analysis, graph);
|
|
177
|
+
|
|
178
|
+
for (const outlier of outliers) {
|
|
179
|
+
suggestions.push({
|
|
180
|
+
type: 'move',
|
|
181
|
+
file: outlier.file,
|
|
182
|
+
from: modulePath,
|
|
183
|
+
to: outlier.suggestedModule,
|
|
184
|
+
reason: outlier.reason,
|
|
185
|
+
impact: outlier.impact,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return suggestions;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Find files that don't fit well in their current module
|
|
195
|
+
*/
|
|
196
|
+
findOutliers(modulePath, analysis, graph) {
|
|
197
|
+
const outliers = [];
|
|
198
|
+
const filePaths = new Set();
|
|
199
|
+
|
|
200
|
+
// Build a set of file paths in this module
|
|
201
|
+
for (const fileName of analysis.files) {
|
|
202
|
+
const fullPath = this.resolveFilePath(fileName, graph);
|
|
203
|
+
if (fullPath) {
|
|
204
|
+
filePaths.add(fullPath);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const fileName of analysis.files) {
|
|
209
|
+
const fullPath = this.resolveFilePath(fileName, graph);
|
|
210
|
+
if (!fullPath) continue;
|
|
211
|
+
|
|
212
|
+
const imports = graph.getImports(fullPath);
|
|
213
|
+
const importers = graph.getImporters(fullPath);
|
|
214
|
+
|
|
215
|
+
// Count dependencies by module
|
|
216
|
+
const depsByModule = {};
|
|
217
|
+
let internalDeps = 0;
|
|
218
|
+
let externalDeps = 0;
|
|
219
|
+
|
|
220
|
+
for (const imp of imports) {
|
|
221
|
+
if (filePaths.has(imp)) {
|
|
222
|
+
internalDeps++;
|
|
223
|
+
} else {
|
|
224
|
+
externalDeps++;
|
|
225
|
+
const impModule = this.getModuleForFile(imp, graph);
|
|
226
|
+
depsByModule[impModule] = (depsByModule[impModule] || 0) + 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const importer of importers) {
|
|
231
|
+
if (!filePaths.has(importer)) {
|
|
232
|
+
const impModule = this.getModuleForFile(importer, graph);
|
|
233
|
+
depsByModule[impModule] = (depsByModule[impModule] || 0) + 1;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check if file has more dependencies with another module
|
|
238
|
+
const totalDeps = internalDeps + externalDeps;
|
|
239
|
+
if (totalDeps === 0) continue;
|
|
240
|
+
|
|
241
|
+
const fileCohesion = internalDeps / totalDeps;
|
|
242
|
+
|
|
243
|
+
// Find the module with most dependencies
|
|
244
|
+
let maxModule = null;
|
|
245
|
+
let maxDeps = 0;
|
|
246
|
+
|
|
247
|
+
for (const [mod, count] of Object.entries(depsByModule)) {
|
|
248
|
+
if (count > maxDeps) {
|
|
249
|
+
maxDeps = count;
|
|
250
|
+
maxModule = mod;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Suggest move if file has more dependencies with another module
|
|
255
|
+
if (maxModule && maxDeps > internalDeps && fileCohesion < 0.5) {
|
|
256
|
+
outliers.push({
|
|
257
|
+
file: fileName,
|
|
258
|
+
suggestedModule: maxModule,
|
|
259
|
+
reason: `File has ${maxDeps} dependencies with ${maxModule} vs ${internalDeps} internal`,
|
|
260
|
+
impact: Math.round((maxDeps / totalDeps) * 100) / 100,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return outliers;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Resolve a file name to its full path
|
|
270
|
+
*/
|
|
271
|
+
resolveFilePath(fileName, graph) {
|
|
272
|
+
const graphData = graph.getGraph();
|
|
273
|
+
for (const node of graphData.nodes) {
|
|
274
|
+
if (node.name === fileName) {
|
|
275
|
+
return node.id;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get the module (directory) for a file path
|
|
283
|
+
*/
|
|
284
|
+
getModuleForFile(filePath, graph) {
|
|
285
|
+
const graphData = graph.getGraph();
|
|
286
|
+
for (const node of graphData.nodes) {
|
|
287
|
+
if (node.id === filePath) {
|
|
288
|
+
const dir = path.dirname(node.name);
|
|
289
|
+
return dir === '.' ? '(root)' : dir;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// If not found in graph, derive from path
|
|
293
|
+
const relativePath = path.relative(this.basePath, filePath);
|
|
294
|
+
const dir = path.dirname(relativePath);
|
|
295
|
+
return dir === '.' ? '(root)' : dir;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Calculate average cohesion across all modules
|
|
300
|
+
*/
|
|
301
|
+
calculateAverageCohesion(moduleAnalysis) {
|
|
302
|
+
const modules = Object.values(moduleAnalysis);
|
|
303
|
+
if (modules.length === 0) return 1;
|
|
304
|
+
|
|
305
|
+
const total = modules.reduce((sum, m) => sum + m.cohesion, 0);
|
|
306
|
+
return Math.round((total / modules.length) * 1000) / 1000;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = { CohesionAnalyzer };
|