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.
Files changed (80) hide show
  1. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  2. package/dashboard/dist/components/UsagePane.js +51 -0
  3. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  4. package/dashboard/dist/components/UsagePane.test.js +142 -0
  5. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  6. package/dashboard/dist/components/WorkspaceDocsPane.js +146 -0
  7. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  8. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  9. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  10. package/dashboard/dist/components/WorkspacePane.js +17 -0
  11. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  12. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  13. package/package.json +1 -1
  14. package/server/lib/architecture-command.js +450 -0
  15. package/server/lib/architecture-command.test.js +754 -0
  16. package/server/lib/ast-analyzer.js +324 -0
  17. package/server/lib/ast-analyzer.test.js +437 -0
  18. package/server/lib/auth-system.test.js +4 -1
  19. package/server/lib/boundary-detector.js +427 -0
  20. package/server/lib/boundary-detector.test.js +320 -0
  21. package/server/lib/budget-alerts.js +138 -0
  22. package/server/lib/budget-alerts.test.js +235 -0
  23. package/server/lib/candidates-tracker.js +210 -0
  24. package/server/lib/candidates-tracker.test.js +300 -0
  25. package/server/lib/checkpoint-manager.js +251 -0
  26. package/server/lib/checkpoint-manager.test.js +474 -0
  27. package/server/lib/circular-detector.js +337 -0
  28. package/server/lib/circular-detector.test.js +353 -0
  29. package/server/lib/cohesion-analyzer.js +310 -0
  30. package/server/lib/cohesion-analyzer.test.js +447 -0
  31. package/server/lib/contract-testing.js +625 -0
  32. package/server/lib/contract-testing.test.js +342 -0
  33. package/server/lib/conversion-planner.js +469 -0
  34. package/server/lib/conversion-planner.test.js +361 -0
  35. package/server/lib/convert-command.js +351 -0
  36. package/server/lib/convert-command.test.js +608 -0
  37. package/server/lib/coupling-calculator.js +189 -0
  38. package/server/lib/coupling-calculator.test.js +509 -0
  39. package/server/lib/dependency-graph.js +367 -0
  40. package/server/lib/dependency-graph.test.js +516 -0
  41. package/server/lib/duplication-detector.js +349 -0
  42. package/server/lib/duplication-detector.test.js +401 -0
  43. package/server/lib/example-service.js +616 -0
  44. package/server/lib/example-service.test.js +397 -0
  45. package/server/lib/impact-scorer.js +184 -0
  46. package/server/lib/impact-scorer.test.js +211 -0
  47. package/server/lib/mermaid-generator.js +358 -0
  48. package/server/lib/mermaid-generator.test.js +301 -0
  49. package/server/lib/messaging-patterns.js +750 -0
  50. package/server/lib/messaging-patterns.test.js +213 -0
  51. package/server/lib/microservice-template.js +386 -0
  52. package/server/lib/microservice-template.test.js +325 -0
  53. package/server/lib/new-project-microservice.js +450 -0
  54. package/server/lib/new-project-microservice.test.js +600 -0
  55. package/server/lib/refactor-command.js +326 -0
  56. package/server/lib/refactor-command.test.js +528 -0
  57. package/server/lib/refactor-executor.js +254 -0
  58. package/server/lib/refactor-executor.test.js +305 -0
  59. package/server/lib/refactor-observer.js +292 -0
  60. package/server/lib/refactor-observer.test.js +422 -0
  61. package/server/lib/refactor-progress.js +193 -0
  62. package/server/lib/refactor-progress.test.js +251 -0
  63. package/server/lib/refactor-reporter.js +237 -0
  64. package/server/lib/refactor-reporter.test.js +247 -0
  65. package/server/lib/semantic-analyzer.js +198 -0
  66. package/server/lib/semantic-analyzer.test.js +474 -0
  67. package/server/lib/service-scaffold.js +486 -0
  68. package/server/lib/service-scaffold.test.js +373 -0
  69. package/server/lib/shared-kernel.js +578 -0
  70. package/server/lib/shared-kernel.test.js +255 -0
  71. package/server/lib/traefik-config.js +282 -0
  72. package/server/lib/traefik-config.test.js +312 -0
  73. package/server/lib/usage-command.js +218 -0
  74. package/server/lib/usage-command.test.js +391 -0
  75. package/server/lib/usage-formatter.js +192 -0
  76. package/server/lib/usage-formatter.test.js +267 -0
  77. package/server/lib/usage-history.js +122 -0
  78. package/server/lib/usage-history.test.js +206 -0
  79. package/server/package-lock.json +14 -0
  80. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "1.2.29",
3
+ "version": "1.3.0",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc": "./bin/tlc.js",
@@ -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 };