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,84 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { render } from 'ink-testing-library';
|
|
4
|
+
import { WorkspacePane } from './WorkspacePane.js';
|
|
5
|
+
describe('WorkspacePane', () => {
|
|
6
|
+
describe('repo list', () => {
|
|
7
|
+
it('renders repo list', () => {
|
|
8
|
+
const repos = [
|
|
9
|
+
{ name: 'core', path: 'core', status: 'ready', packageName: '@org/core' },
|
|
10
|
+
{ name: 'api', path: 'api', status: 'ready', packageName: '@org/api' },
|
|
11
|
+
];
|
|
12
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: repos }));
|
|
13
|
+
const output = lastFrame();
|
|
14
|
+
expect(output).toContain('core');
|
|
15
|
+
expect(output).toContain('api');
|
|
16
|
+
});
|
|
17
|
+
it('shows repo names and paths', () => {
|
|
18
|
+
const repos = [
|
|
19
|
+
{ name: 'my-app', path: 'packages/my-app', status: 'ready', packageName: '@scope/my-app' },
|
|
20
|
+
];
|
|
21
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: repos }));
|
|
22
|
+
const output = lastFrame();
|
|
23
|
+
expect(output).toContain('my-app');
|
|
24
|
+
});
|
|
25
|
+
it('shows test status per repo', () => {
|
|
26
|
+
const repos = [
|
|
27
|
+
{ name: 'tested', path: 'tested', status: 'ready', tests: { passed: 10, failed: 2 } },
|
|
28
|
+
];
|
|
29
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: repos }));
|
|
30
|
+
const output = lastFrame();
|
|
31
|
+
expect(output).toContain('10');
|
|
32
|
+
});
|
|
33
|
+
it('shows aggregate totals', () => {
|
|
34
|
+
const repos = [
|
|
35
|
+
{ name: 'a', path: 'a', status: 'ready', tests: { passed: 5, failed: 1 } },
|
|
36
|
+
{ name: 'b', path: 'b', status: 'ready', tests: { passed: 3, failed: 0 } },
|
|
37
|
+
];
|
|
38
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: repos }));
|
|
39
|
+
const output = lastFrame();
|
|
40
|
+
// Should show aggregate
|
|
41
|
+
expect(output).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('dependency graph', () => {
|
|
45
|
+
it('renders dependency graph section', () => {
|
|
46
|
+
const repos = [{ name: 'core', path: 'core', status: 'ready' }];
|
|
47
|
+
const graph = 'graph TD\n core[core]';
|
|
48
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: repos, dependencyGraph: graph }));
|
|
49
|
+
const output = lastFrame();
|
|
50
|
+
expect(output).toContain('Dependencies');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('highlighting', () => {
|
|
54
|
+
it('highlights repos with failing tests', () => {
|
|
55
|
+
const repos = [
|
|
56
|
+
{ name: 'passing', path: 'passing', status: 'ready', tests: { passed: 5, failed: 0 } },
|
|
57
|
+
{ name: 'failing', path: 'failing', status: 'ready', tests: { passed: 3, failed: 2 } },
|
|
58
|
+
];
|
|
59
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: repos }));
|
|
60
|
+
const output = lastFrame();
|
|
61
|
+
// Failed tests should be visible
|
|
62
|
+
expect(output).toContain('failing');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('loading state', () => {
|
|
66
|
+
it('shows loading state', () => {
|
|
67
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { loading: true }));
|
|
68
|
+
const output = lastFrame();
|
|
69
|
+
expect(output).toContain('Loading');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('empty state', () => {
|
|
73
|
+
it('shows empty state when no repos', () => {
|
|
74
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: [] }));
|
|
75
|
+
const output = lastFrame();
|
|
76
|
+
expect(output).toContain('No repos');
|
|
77
|
+
});
|
|
78
|
+
it('shows hint to initialize workspace', () => {
|
|
79
|
+
const { lastFrame } = render(_jsx(WorkspacePane, { repos: [], initialized: false }));
|
|
80
|
+
const output = lastFrame();
|
|
81
|
+
expect(output).toContain('workspace');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture Command
|
|
3
|
+
* Main orchestrator for /tlc:architecture skill
|
|
4
|
+
* Analyzes codebase architecture: dependencies, boundaries, coupling, cohesion, cycles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { DependencyGraph } = require('./dependency-graph.js');
|
|
9
|
+
const { MermaidGenerator } = require('./mermaid-generator.js');
|
|
10
|
+
const { BoundaryDetector } = require('./boundary-detector.js');
|
|
11
|
+
const { CouplingCalculator } = require('./coupling-calculator.js');
|
|
12
|
+
const { CohesionAnalyzer } = require('./cohesion-analyzer.js');
|
|
13
|
+
const { CircularDetector } = require('./circular-detector.js');
|
|
14
|
+
|
|
15
|
+
class ArchitectureCommand {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.basePath = options.basePath || process.cwd();
|
|
19
|
+
|
|
20
|
+
// Dependency injection for all modules
|
|
21
|
+
this.dependencyGraph = options.dependencyGraph || new DependencyGraph({
|
|
22
|
+
basePath: this.basePath,
|
|
23
|
+
...options.graphOptions,
|
|
24
|
+
});
|
|
25
|
+
this.mermaidGenerator = options.mermaidGenerator || new MermaidGenerator(options.mermaidOptions);
|
|
26
|
+
this.boundaryDetector = options.boundaryDetector || new BoundaryDetector(options.boundaryOptions);
|
|
27
|
+
this.couplingCalculator = options.couplingCalculator || null; // Created after graph built
|
|
28
|
+
this.cohesionAnalyzer = options.cohesionAnalyzer || new CohesionAnalyzer({
|
|
29
|
+
basePath: this.basePath,
|
|
30
|
+
...options.cohesionOptions,
|
|
31
|
+
});
|
|
32
|
+
this.circularDetector = options.circularDetector || new CircularDetector({
|
|
33
|
+
basePath: this.basePath,
|
|
34
|
+
...options.circularOptions,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Callbacks for progress reporting
|
|
38
|
+
this.onProgress = options.onProgress || (() => {});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run the architecture command
|
|
43
|
+
* @param {Object} options - Command options
|
|
44
|
+
* @returns {Object} Analysis results
|
|
45
|
+
*/
|
|
46
|
+
async run(options = {}) {
|
|
47
|
+
const {
|
|
48
|
+
analyze = false, // --analyze: full analysis
|
|
49
|
+
boundaries = false, // --boundaries: service boundaries
|
|
50
|
+
diagram = false, // --diagram: Mermaid output
|
|
51
|
+
metrics = false, // --metrics: coupling/cohesion scores
|
|
52
|
+
circular = false, // --circular: dependency cycles
|
|
53
|
+
targetPath = null, // Path targeting for specific modules
|
|
54
|
+
format = 'text', // Output format: 'text', 'json', 'markdown'
|
|
55
|
+
} = options;
|
|
56
|
+
|
|
57
|
+
const result = {
|
|
58
|
+
success: true,
|
|
59
|
+
targetPath,
|
|
60
|
+
graph: null,
|
|
61
|
+
analysis: null,
|
|
62
|
+
boundaries: null,
|
|
63
|
+
diagram: null,
|
|
64
|
+
metrics: null,
|
|
65
|
+
circular: null,
|
|
66
|
+
report: null,
|
|
67
|
+
error: null,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Step 1: Build dependency graph
|
|
72
|
+
this.onProgress({ phase: 'building-graph', message: 'Building dependency graph...' });
|
|
73
|
+
|
|
74
|
+
const scanPath = targetPath
|
|
75
|
+
? path.resolve(this.basePath, targetPath)
|
|
76
|
+
: this.basePath;
|
|
77
|
+
|
|
78
|
+
await this.dependencyGraph.buildFromDirectory(scanPath);
|
|
79
|
+
result.graph = this.dependencyGraph.getGraph();
|
|
80
|
+
|
|
81
|
+
// Create coupling calculator now that graph is built
|
|
82
|
+
if (!this.couplingCalculator) {
|
|
83
|
+
this.couplingCalculator = new CouplingCalculator(this.dependencyGraph);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Step 2: Run requested analyses
|
|
87
|
+
if (analyze || (!boundaries && !diagram && !metrics && !circular)) {
|
|
88
|
+
// Full analysis (default if no specific flags)
|
|
89
|
+
result.analysis = await this.runFullAnalysis(result.graph);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (boundaries || analyze) {
|
|
93
|
+
this.onProgress({ phase: 'analyzing-boundaries', message: 'Detecting service boundaries...' });
|
|
94
|
+
result.boundaries = this.analyzeBoundaries(result.graph);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (diagram || analyze) {
|
|
98
|
+
this.onProgress({ phase: 'generating-diagram', message: 'Generating Mermaid diagram...' });
|
|
99
|
+
result.diagram = this.generateDiagram(result.graph, options);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (metrics || analyze) {
|
|
103
|
+
this.onProgress({ phase: 'calculating-metrics', message: 'Calculating coupling and cohesion metrics...' });
|
|
104
|
+
result.metrics = this.calculateMetrics(result.graph);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (circular || analyze) {
|
|
108
|
+
this.onProgress({ phase: 'detecting-cycles', message: 'Detecting circular dependencies...' });
|
|
109
|
+
result.circular = this.detectCircular(result.graph);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Step 3: Generate report
|
|
113
|
+
result.report = this.generateReport(result, format);
|
|
114
|
+
|
|
115
|
+
this.onProgress({ phase: 'complete', message: 'Analysis complete' });
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
result.success = false;
|
|
119
|
+
result.error = error.message;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Run full architecture analysis
|
|
127
|
+
*/
|
|
128
|
+
async runFullAnalysis(graph) {
|
|
129
|
+
const boundaries = this.analyzeBoundaries(graph);
|
|
130
|
+
const metrics = this.calculateMetrics(graph);
|
|
131
|
+
const circular = this.detectCircular(graph);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
summary: {
|
|
135
|
+
totalFiles: graph.stats.totalFiles,
|
|
136
|
+
totalDependencies: graph.stats.totalEdges,
|
|
137
|
+
externalDependencies: graph.stats.externalDeps,
|
|
138
|
+
suggestedServices: boundaries.services?.length || 0,
|
|
139
|
+
cyclesFound: circular.cycleCount || 0,
|
|
140
|
+
averageCohesion: metrics.cohesion?.summary?.averageCohesion || 0,
|
|
141
|
+
},
|
|
142
|
+
boundaries,
|
|
143
|
+
metrics,
|
|
144
|
+
circular,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Analyze service boundaries
|
|
150
|
+
*/
|
|
151
|
+
analyzeBoundaries(graph) {
|
|
152
|
+
const couplingData = {
|
|
153
|
+
modules: this.couplingCalculator.getAllMetrics().map(m => ({
|
|
154
|
+
name: path.dirname(path.relative(this.basePath, m.file)) || '(root)',
|
|
155
|
+
afferent: m.afferentCoupling,
|
|
156
|
+
efferent: m.efferentCoupling,
|
|
157
|
+
instability: m.instability,
|
|
158
|
+
})),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const cohesionData = this.cohesionAnalyzer.analyze(this.dependencyGraph);
|
|
162
|
+
|
|
163
|
+
return this.boundaryDetector.detect(graph, couplingData, cohesionData);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generate Mermaid diagram
|
|
168
|
+
*/
|
|
169
|
+
generateDiagram(graph, options = {}) {
|
|
170
|
+
const { targetPath, diagramType = 'flowchart' } = options;
|
|
171
|
+
|
|
172
|
+
// If targeting specific module, generate filtered diagram
|
|
173
|
+
if (targetPath) {
|
|
174
|
+
return this.mermaidGenerator.generateModuleDiagram(graph, targetPath, {
|
|
175
|
+
direction: 'LR',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check for cycles to highlight
|
|
180
|
+
const circularResult = this.circularDetector.detect(this.dependencyGraph);
|
|
181
|
+
const cycles = circularResult.hasCycles
|
|
182
|
+
? circularResult.cycles.map(c => c.pathNames)
|
|
183
|
+
: [];
|
|
184
|
+
|
|
185
|
+
return this.mermaidGenerator.generateFlowchart(graph, {
|
|
186
|
+
cycles,
|
|
187
|
+
highlightCycles: cycles.length > 0,
|
|
188
|
+
groupByDirectory: true,
|
|
189
|
+
maxNodes: options.maxNodes || 50,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Calculate coupling and cohesion metrics
|
|
195
|
+
*/
|
|
196
|
+
calculateMetrics(graph) {
|
|
197
|
+
// Coupling metrics
|
|
198
|
+
const allCoupling = this.couplingCalculator.getAllMetrics();
|
|
199
|
+
const hubFiles = this.couplingCalculator.getHubFiles({ threshold: 3 });
|
|
200
|
+
const dependentFiles = this.couplingCalculator.getDependentFiles({ threshold: 3 });
|
|
201
|
+
const isolatedFiles = this.couplingCalculator.getIsolatedFiles();
|
|
202
|
+
const highlyCoupled = this.couplingCalculator.getHighlyCoupledModules({ threshold: 5 });
|
|
203
|
+
|
|
204
|
+
// Cohesion metrics
|
|
205
|
+
const cohesion = this.cohesionAnalyzer.analyze(this.dependencyGraph);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
coupling: {
|
|
209
|
+
files: allCoupling.map(m => ({
|
|
210
|
+
file: path.relative(this.basePath, m.file),
|
|
211
|
+
afferentCoupling: m.afferentCoupling,
|
|
212
|
+
efferentCoupling: m.efferentCoupling,
|
|
213
|
+
instability: Math.round(m.instability * 100) / 100,
|
|
214
|
+
})),
|
|
215
|
+
hubs: hubFiles.map(h => ({
|
|
216
|
+
file: path.relative(this.basePath, h.file),
|
|
217
|
+
dependents: h.afferentCoupling,
|
|
218
|
+
})),
|
|
219
|
+
dependent: dependentFiles.map(d => ({
|
|
220
|
+
file: path.relative(this.basePath, d.file),
|
|
221
|
+
dependencies: d.efferentCoupling,
|
|
222
|
+
})),
|
|
223
|
+
isolated: isolatedFiles.map(f => path.relative(this.basePath, f)),
|
|
224
|
+
highlyCoupled: highlyCoupled.map(h => ({
|
|
225
|
+
file: path.relative(this.basePath, h.file),
|
|
226
|
+
total: h.totalCoupling,
|
|
227
|
+
afferent: h.afferentCoupling,
|
|
228
|
+
efferent: h.efferentCoupling,
|
|
229
|
+
})),
|
|
230
|
+
summary: {
|
|
231
|
+
totalFiles: allCoupling.length,
|
|
232
|
+
hubCount: hubFiles.length,
|
|
233
|
+
isolatedCount: isolatedFiles.length,
|
|
234
|
+
highlyCoupledCount: highlyCoupled.length,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
cohesion,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Detect circular dependencies
|
|
243
|
+
*/
|
|
244
|
+
detectCircular(graph) {
|
|
245
|
+
return this.circularDetector.detect(this.dependencyGraph);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate formatted report
|
|
250
|
+
*/
|
|
251
|
+
generateReport(result, format) {
|
|
252
|
+
switch (format) {
|
|
253
|
+
case 'json':
|
|
254
|
+
return this.generateJsonReport(result);
|
|
255
|
+
case 'markdown':
|
|
256
|
+
return this.generateMarkdownReport(result);
|
|
257
|
+
case 'text':
|
|
258
|
+
default:
|
|
259
|
+
return this.generateTextReport(result);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Generate JSON report
|
|
265
|
+
*/
|
|
266
|
+
generateJsonReport(result) {
|
|
267
|
+
return JSON.stringify({
|
|
268
|
+
success: result.success,
|
|
269
|
+
targetPath: result.targetPath,
|
|
270
|
+
stats: result.graph?.stats,
|
|
271
|
+
analysis: result.analysis,
|
|
272
|
+
boundaries: result.boundaries,
|
|
273
|
+
metrics: result.metrics,
|
|
274
|
+
circular: result.circular,
|
|
275
|
+
diagram: result.diagram,
|
|
276
|
+
error: result.error,
|
|
277
|
+
}, null, 2);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Generate Markdown report
|
|
282
|
+
*/
|
|
283
|
+
generateMarkdownReport(result) {
|
|
284
|
+
const lines = ['# Architecture Analysis Report\n'];
|
|
285
|
+
|
|
286
|
+
// Summary
|
|
287
|
+
if (result.analysis?.summary) {
|
|
288
|
+
const s = result.analysis.summary;
|
|
289
|
+
lines.push('## Summary\n');
|
|
290
|
+
lines.push(`| Metric | Value |`);
|
|
291
|
+
lines.push(`|--------|-------|`);
|
|
292
|
+
lines.push(`| Total Files | ${s.totalFiles} |`);
|
|
293
|
+
lines.push(`| Total Dependencies | ${s.totalDependencies} |`);
|
|
294
|
+
lines.push(`| External Dependencies | ${s.externalDependencies} |`);
|
|
295
|
+
lines.push(`| Suggested Services | ${s.suggestedServices} |`);
|
|
296
|
+
lines.push(`| Circular Dependencies | ${s.cyclesFound} |`);
|
|
297
|
+
lines.push(`| Average Cohesion | ${(s.averageCohesion * 100).toFixed(1)}% |`);
|
|
298
|
+
lines.push('');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Boundaries
|
|
302
|
+
if (result.boundaries?.services) {
|
|
303
|
+
lines.push('## Service Boundaries\n');
|
|
304
|
+
lines.push('### Detected Services\n');
|
|
305
|
+
for (const service of result.boundaries.services.slice(0, 10)) {
|
|
306
|
+
lines.push(`- **${service.name}** (${service.fileCount} files, quality: ${service.quality}/100)`);
|
|
307
|
+
if (service.dependencies.length > 0) {
|
|
308
|
+
lines.push(` - Depends on: ${service.dependencies.join(', ')}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
lines.push('');
|
|
312
|
+
|
|
313
|
+
if (result.boundaries.suggestions?.length > 0) {
|
|
314
|
+
lines.push('### Suggestions\n');
|
|
315
|
+
for (const suggestion of result.boundaries.suggestions.slice(0, 5)) {
|
|
316
|
+
lines.push(`- ${suggestion.message}`);
|
|
317
|
+
}
|
|
318
|
+
lines.push('');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Metrics
|
|
323
|
+
if (result.metrics) {
|
|
324
|
+
lines.push('## Coupling Metrics\n');
|
|
325
|
+
|
|
326
|
+
if (result.metrics.coupling.hubs.length > 0) {
|
|
327
|
+
lines.push('### Hub Files (Most Depended Upon)\n');
|
|
328
|
+
lines.push('| File | Dependents |');
|
|
329
|
+
lines.push('|------|------------|');
|
|
330
|
+
for (const hub of result.metrics.coupling.hubs.slice(0, 10)) {
|
|
331
|
+
lines.push(`| ${hub.file} | ${hub.dependents} |`);
|
|
332
|
+
}
|
|
333
|
+
lines.push('');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (result.metrics.coupling.highlyCoupled.length > 0) {
|
|
337
|
+
lines.push('### Highly Coupled Files\n');
|
|
338
|
+
lines.push('| File | Total | In | Out |');
|
|
339
|
+
lines.push('|------|-------|-----|-----|');
|
|
340
|
+
for (const file of result.metrics.coupling.highlyCoupled.slice(0, 10)) {
|
|
341
|
+
lines.push(`| ${file.file} | ${file.total} | ${file.afferent} | ${file.efferent} |`);
|
|
342
|
+
}
|
|
343
|
+
lines.push('');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
lines.push('## Cohesion Metrics\n');
|
|
347
|
+
if (result.metrics.cohesion?.lowCohesion?.length > 0) {
|
|
348
|
+
lines.push('### Low Cohesion Modules\n');
|
|
349
|
+
lines.push('| Module | Cohesion | Internal | External |');
|
|
350
|
+
lines.push('|--------|----------|----------|----------|');
|
|
351
|
+
for (const mod of result.metrics.cohesion.lowCohesion.slice(0, 10)) {
|
|
352
|
+
lines.push(`| ${mod.module} | ${(mod.cohesion * 100).toFixed(1)}% | ${mod.internalDeps} | ${mod.externalDeps} |`);
|
|
353
|
+
}
|
|
354
|
+
lines.push('');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Circular dependencies
|
|
359
|
+
if (result.circular?.hasCycles) {
|
|
360
|
+
lines.push('## Circular Dependencies\n');
|
|
361
|
+
lines.push(`Found ${result.circular.cycleCount} cycle(s):\n`);
|
|
362
|
+
for (let i = 0; i < Math.min(result.circular.cycles.length, 5); i++) {
|
|
363
|
+
const cycle = result.circular.cycles[i];
|
|
364
|
+
lines.push(`### Cycle ${i + 1}`);
|
|
365
|
+
lines.push('```');
|
|
366
|
+
lines.push(cycle.pathNames.join(' -> ') + ' -> ' + cycle.pathNames[0]);
|
|
367
|
+
lines.push('```');
|
|
368
|
+
if (result.circular.suggestions?.[i]) {
|
|
369
|
+
lines.push(`**Suggestion:** ${result.circular.suggestions[i].reason}`);
|
|
370
|
+
}
|
|
371
|
+
lines.push('');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Diagram
|
|
376
|
+
if (result.diagram) {
|
|
377
|
+
lines.push('## Dependency Diagram\n');
|
|
378
|
+
lines.push('```mermaid');
|
|
379
|
+
lines.push(result.diagram);
|
|
380
|
+
lines.push('```');
|
|
381
|
+
lines.push('');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return lines.join('\n');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Generate plain text report
|
|
389
|
+
*/
|
|
390
|
+
generateTextReport(result) {
|
|
391
|
+
const lines = ['ARCHITECTURE ANALYSIS REPORT', '='.repeat(50), ''];
|
|
392
|
+
|
|
393
|
+
// Summary
|
|
394
|
+
if (result.analysis?.summary) {
|
|
395
|
+
const s = result.analysis.summary;
|
|
396
|
+
lines.push('SUMMARY');
|
|
397
|
+
lines.push('-'.repeat(30));
|
|
398
|
+
lines.push(`Total Files: ${s.totalFiles}`);
|
|
399
|
+
lines.push(`Total Dependencies: ${s.totalDependencies}`);
|
|
400
|
+
lines.push(`External Dependencies: ${s.externalDependencies}`);
|
|
401
|
+
lines.push(`Suggested Services: ${s.suggestedServices}`);
|
|
402
|
+
lines.push(`Circular Dependencies: ${s.cyclesFound}`);
|
|
403
|
+
lines.push(`Average Cohesion: ${(s.averageCohesion * 100).toFixed(1)}%`);
|
|
404
|
+
lines.push('');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Boundaries
|
|
408
|
+
if (result.boundaries?.services) {
|
|
409
|
+
lines.push('SERVICE BOUNDARIES');
|
|
410
|
+
lines.push('-'.repeat(30));
|
|
411
|
+
for (const service of result.boundaries.services.slice(0, 10)) {
|
|
412
|
+
lines.push(` ${service.name} (${service.fileCount} files, quality: ${service.quality}/100)`);
|
|
413
|
+
}
|
|
414
|
+
lines.push('');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Cycles
|
|
418
|
+
if (result.circular?.hasCycles) {
|
|
419
|
+
lines.push('CIRCULAR DEPENDENCIES');
|
|
420
|
+
lines.push('-'.repeat(30));
|
|
421
|
+
lines.push(`Found ${result.circular.cycleCount} cycle(s)`);
|
|
422
|
+
for (const cycle of result.circular.cycles.slice(0, 5)) {
|
|
423
|
+
lines.push(` ${cycle.pathNames.join(' -> ')} -> ${cycle.pathNames[0]}`);
|
|
424
|
+
}
|
|
425
|
+
lines.push('');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Metrics summary
|
|
429
|
+
if (result.metrics?.coupling?.summary) {
|
|
430
|
+
const s = result.metrics.coupling.summary;
|
|
431
|
+
lines.push('COUPLING SUMMARY');
|
|
432
|
+
lines.push('-'.repeat(30));
|
|
433
|
+
lines.push(`Hub Files: ${s.hubCount}`);
|
|
434
|
+
lines.push(`Isolated Files: ${s.isolatedCount}`);
|
|
435
|
+
lines.push(`Highly Coupled: ${s.highlyCoupledCount}`);
|
|
436
|
+
lines.push('');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Diagram placeholder
|
|
440
|
+
if (result.diagram) {
|
|
441
|
+
lines.push('MERMAID DIAGRAM');
|
|
442
|
+
lines.push('-'.repeat(30));
|
|
443
|
+
lines.push(result.diagram);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return lines.join('\n');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
module.exports = { ArchitectureCommand };
|