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.
- package/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- 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 +130 -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/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- 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/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -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/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -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/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -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/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -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/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -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/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -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/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -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/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -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/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohesion Analyzer Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
|
|
7
|
+
// Mock DependencyGraph for testing
|
|
8
|
+
function createMockGraph(nodes, edges) {
|
|
9
|
+
const graphData = {
|
|
10
|
+
nodes: nodes.map(n => ({
|
|
11
|
+
id: n.id,
|
|
12
|
+
name: n.name,
|
|
13
|
+
imports: 0,
|
|
14
|
+
importedBy: 0,
|
|
15
|
+
})),
|
|
16
|
+
edges: edges.map(e => ({
|
|
17
|
+
from: e.from,
|
|
18
|
+
to: e.to,
|
|
19
|
+
fromName: nodes.find(n => n.id === e.from)?.name || e.from,
|
|
20
|
+
toName: nodes.find(n => n.id === e.to)?.name || e.to,
|
|
21
|
+
})),
|
|
22
|
+
external: [],
|
|
23
|
+
stats: {
|
|
24
|
+
totalFiles: nodes.length,
|
|
25
|
+
totalEdges: edges.length,
|
|
26
|
+
externalDeps: 0,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Build imports/importedBy maps
|
|
31
|
+
const importsMap = {};
|
|
32
|
+
const importersMap = {};
|
|
33
|
+
|
|
34
|
+
for (const node of nodes) {
|
|
35
|
+
importsMap[node.id] = [];
|
|
36
|
+
importersMap[node.id] = [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const edge of edges) {
|
|
40
|
+
if (importsMap[edge.from]) {
|
|
41
|
+
importsMap[edge.from].push(edge.to);
|
|
42
|
+
}
|
|
43
|
+
if (importersMap[edge.to]) {
|
|
44
|
+
importersMap[edge.to].push(edge.from);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
getGraph: () => graphData,
|
|
50
|
+
getImports: (file) => importsMap[file] || [],
|
|
51
|
+
getImporters: (file) => importersMap[file] || [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('CohesionAnalyzer', () => {
|
|
56
|
+
describe('groupByDirectory', () => {
|
|
57
|
+
it('groups files by directory correctly', async () => {
|
|
58
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
59
|
+
|
|
60
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
61
|
+
const nodes = [
|
|
62
|
+
{ id: '/project/src/utils/helpers.js', name: 'src/utils/helpers.js' },
|
|
63
|
+
{ id: '/project/src/utils/format.js', name: 'src/utils/format.js' },
|
|
64
|
+
{ id: '/project/src/components/Button.js', name: 'src/components/Button.js' },
|
|
65
|
+
{ id: '/project/src/index.js', name: 'src/index.js' },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const modules = analyzer.groupByDirectory(nodes);
|
|
69
|
+
|
|
70
|
+
expect(Object.keys(modules)).toHaveLength(3);
|
|
71
|
+
expect(modules['src/utils']).toHaveLength(2);
|
|
72
|
+
expect(modules['src/components']).toHaveLength(1);
|
|
73
|
+
expect(modules['src']).toHaveLength(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('puts root files in (root) module', async () => {
|
|
77
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
78
|
+
|
|
79
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
80
|
+
const nodes = [
|
|
81
|
+
{ id: '/project/index.js', name: 'index.js' },
|
|
82
|
+
{ id: '/project/config.js', name: 'config.js' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const modules = analyzer.groupByDirectory(nodes);
|
|
86
|
+
|
|
87
|
+
expect(modules['(root)']).toHaveLength(2);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('high cohesion', () => {
|
|
92
|
+
it('scores high when files only import each other', async () => {
|
|
93
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
94
|
+
|
|
95
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
96
|
+
|
|
97
|
+
// Module where files only import each other
|
|
98
|
+
const nodes = [
|
|
99
|
+
{ id: '/project/src/utils/a.js', name: 'src/utils/a.js' },
|
|
100
|
+
{ id: '/project/src/utils/b.js', name: 'src/utils/b.js' },
|
|
101
|
+
{ id: '/project/src/utils/c.js', name: 'src/utils/c.js' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const edges = [
|
|
105
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/b.js' },
|
|
106
|
+
{ from: '/project/src/utils/b.js', to: '/project/src/utils/c.js' },
|
|
107
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/c.js' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
111
|
+
const result = analyzer.analyze(mockGraph);
|
|
112
|
+
|
|
113
|
+
expect(result.modules['src/utils'].cohesion).toBe(1);
|
|
114
|
+
expect(result.lowCohesion).toHaveLength(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('reports high average cohesion for well-structured codebase', async () => {
|
|
118
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
119
|
+
|
|
120
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
121
|
+
|
|
122
|
+
const nodes = [
|
|
123
|
+
{ id: '/project/src/utils/a.js', name: 'src/utils/a.js' },
|
|
124
|
+
{ id: '/project/src/utils/b.js', name: 'src/utils/b.js' },
|
|
125
|
+
{ id: '/project/src/lib/x.js', name: 'src/lib/x.js' },
|
|
126
|
+
{ id: '/project/src/lib/y.js', name: 'src/lib/y.js' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const edges = [
|
|
130
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/b.js' },
|
|
131
|
+
{ from: '/project/src/lib/x.js', to: '/project/src/lib/y.js' },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
135
|
+
const result = analyzer.analyze(mockGraph);
|
|
136
|
+
|
|
137
|
+
expect(result.summary.averageCohesion).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('low cohesion', () => {
|
|
142
|
+
it('scores low when files mostly import external', async () => {
|
|
143
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
144
|
+
|
|
145
|
+
const analyzer = new CohesionAnalyzer({
|
|
146
|
+
basePath: '/project',
|
|
147
|
+
lowCohesionThreshold: 0.3,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Module where files import mostly from other modules
|
|
151
|
+
const nodes = [
|
|
152
|
+
{ id: '/project/src/utils/a.js', name: 'src/utils/a.js' },
|
|
153
|
+
{ id: '/project/src/utils/b.js', name: 'src/utils/b.js' },
|
|
154
|
+
{ id: '/project/src/lib/x.js', name: 'src/lib/x.js' },
|
|
155
|
+
{ id: '/project/src/lib/y.js', name: 'src/lib/y.js' },
|
|
156
|
+
{ id: '/project/src/lib/z.js', name: 'src/lib/z.js' },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// a and b import from lib instead of each other
|
|
160
|
+
const edges = [
|
|
161
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/lib/x.js' },
|
|
162
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/lib/y.js' },
|
|
163
|
+
{ from: '/project/src/utils/b.js', to: '/project/src/lib/x.js' },
|
|
164
|
+
{ from: '/project/src/utils/b.js', to: '/project/src/lib/z.js' },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
168
|
+
const result = analyzer.analyze(mockGraph);
|
|
169
|
+
|
|
170
|
+
expect(result.modules['src/utils'].cohesion).toBe(0);
|
|
171
|
+
expect(result.lowCohesion.length).toBeGreaterThan(0);
|
|
172
|
+
expect(result.lowCohesion[0].module).toBe('src/utils');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('identifies all low cohesion modules', async () => {
|
|
176
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
177
|
+
|
|
178
|
+
const analyzer = new CohesionAnalyzer({
|
|
179
|
+
basePath: '/project',
|
|
180
|
+
lowCohesionThreshold: 0.5,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const nodes = [
|
|
184
|
+
{ id: '/project/src/a/1.js', name: 'src/a/1.js' },
|
|
185
|
+
{ id: '/project/src/a/2.js', name: 'src/a/2.js' },
|
|
186
|
+
{ id: '/project/src/b/1.js', name: 'src/b/1.js' },
|
|
187
|
+
{ id: '/project/src/b/2.js', name: 'src/b/2.js' },
|
|
188
|
+
{ id: '/project/src/shared.js', name: 'src/shared.js' },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
// Both modules depend heavily on shared
|
|
192
|
+
const edges = [
|
|
193
|
+
{ from: '/project/src/a/1.js', to: '/project/src/shared.js' },
|
|
194
|
+
{ from: '/project/src/a/2.js', to: '/project/src/shared.js' },
|
|
195
|
+
{ from: '/project/src/b/1.js', to: '/project/src/shared.js' },
|
|
196
|
+
{ from: '/project/src/b/2.js', to: '/project/src/shared.js' },
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
200
|
+
const result = analyzer.analyze(mockGraph);
|
|
201
|
+
|
|
202
|
+
// Both src/a and src/b should be low cohesion
|
|
203
|
+
const lowModules = result.lowCohesion.map(l => l.module);
|
|
204
|
+
expect(lowModules).toContain('src/a');
|
|
205
|
+
expect(lowModules).toContain('src/b');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('suggestions', () => {
|
|
210
|
+
it('suggests moving outlier files for better cohesion', async () => {
|
|
211
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
212
|
+
|
|
213
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
214
|
+
|
|
215
|
+
// File in utils that mostly depends on lib
|
|
216
|
+
const nodes = [
|
|
217
|
+
{ id: '/project/src/utils/a.js', name: 'src/utils/a.js' },
|
|
218
|
+
{ id: '/project/src/utils/b.js', name: 'src/utils/b.js' },
|
|
219
|
+
{ id: '/project/src/utils/outlier.js', name: 'src/utils/outlier.js' },
|
|
220
|
+
{ id: '/project/src/lib/x.js', name: 'src/lib/x.js' },
|
|
221
|
+
{ id: '/project/src/lib/y.js', name: 'src/lib/y.js' },
|
|
222
|
+
{ id: '/project/src/lib/z.js', name: 'src/lib/z.js' },
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
// outlier imports from lib, not from utils
|
|
226
|
+
const edges = [
|
|
227
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/b.js' },
|
|
228
|
+
{ from: '/project/src/utils/outlier.js', to: '/project/src/lib/x.js' },
|
|
229
|
+
{ from: '/project/src/utils/outlier.js', to: '/project/src/lib/y.js' },
|
|
230
|
+
{ from: '/project/src/utils/outlier.js', to: '/project/src/lib/z.js' },
|
|
231
|
+
{ from: '/project/src/lib/x.js', to: '/project/src/lib/y.js' },
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
235
|
+
const result = analyzer.analyze(mockGraph);
|
|
236
|
+
|
|
237
|
+
const moveSuggestions = result.suggestions.filter(s => s.type === 'move');
|
|
238
|
+
const outlierSuggestion = moveSuggestions.find(s =>
|
|
239
|
+
s.file.includes('outlier')
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(outlierSuggestion).toBeDefined();
|
|
243
|
+
expect(outlierSuggestion.from).toBe('src/utils');
|
|
244
|
+
expect(outlierSuggestion.to).toBe('src/lib');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('does not suggest moves for well-placed files', async () => {
|
|
248
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
249
|
+
|
|
250
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
251
|
+
|
|
252
|
+
// All files are well-placed
|
|
253
|
+
const nodes = [
|
|
254
|
+
{ id: '/project/src/utils/a.js', name: 'src/utils/a.js' },
|
|
255
|
+
{ id: '/project/src/utils/b.js', name: 'src/utils/b.js' },
|
|
256
|
+
{ id: '/project/src/utils/c.js', name: 'src/utils/c.js' },
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const edges = [
|
|
260
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/b.js' },
|
|
261
|
+
{ from: '/project/src/utils/b.js', to: '/project/src/utils/c.js' },
|
|
262
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/c.js' },
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
266
|
+
const result = analyzer.analyze(mockGraph);
|
|
267
|
+
|
|
268
|
+
expect(result.suggestions).toHaveLength(0);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('single-file modules', () => {
|
|
273
|
+
it('handles single-file modules correctly', async () => {
|
|
274
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
275
|
+
|
|
276
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
277
|
+
|
|
278
|
+
const nodes = [
|
|
279
|
+
{ id: '/project/src/utils/single.js', name: 'src/utils/single.js' },
|
|
280
|
+
{ id: '/project/src/lib/a.js', name: 'src/lib/a.js' },
|
|
281
|
+
{ id: '/project/src/lib/b.js', name: 'src/lib/b.js' },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
const edges = [
|
|
285
|
+
{ from: '/project/src/utils/single.js', to: '/project/src/lib/a.js' },
|
|
286
|
+
{ from: '/project/src/lib/a.js', to: '/project/src/lib/b.js' },
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
290
|
+
const result = analyzer.analyze(mockGraph);
|
|
291
|
+
|
|
292
|
+
// Single file module should have cohesion of 1
|
|
293
|
+
expect(result.modules['src/utils'].cohesion).toBe(1);
|
|
294
|
+
expect(result.modules['src/utils'].singleFile).toBe(true);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('does not mark single-file modules as low cohesion', async () => {
|
|
298
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
299
|
+
|
|
300
|
+
const analyzer = new CohesionAnalyzer({
|
|
301
|
+
basePath: '/project',
|
|
302
|
+
lowCohesionThreshold: 0.5,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const nodes = [
|
|
306
|
+
{ id: '/project/src/single/only.js', name: 'src/single/only.js' },
|
|
307
|
+
{ id: '/project/src/other/a.js', name: 'src/other/a.js' },
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
const edges = [
|
|
311
|
+
{ from: '/project/src/single/only.js', to: '/project/src/other/a.js' },
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
315
|
+
const result = analyzer.analyze(mockGraph);
|
|
316
|
+
|
|
317
|
+
// Single file should not appear in low cohesion list
|
|
318
|
+
const singleInLow = result.lowCohesion.find(
|
|
319
|
+
l => l.module === 'src/single'
|
|
320
|
+
);
|
|
321
|
+
expect(singleInLow).toBeUndefined();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('empty modules', () => {
|
|
326
|
+
it('handles empty graph', async () => {
|
|
327
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
328
|
+
|
|
329
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
330
|
+
const mockGraph = createMockGraph([], []);
|
|
331
|
+
|
|
332
|
+
const result = analyzer.analyze(mockGraph);
|
|
333
|
+
|
|
334
|
+
expect(result.modules).toEqual({});
|
|
335
|
+
expect(result.lowCohesion).toHaveLength(0);
|
|
336
|
+
expect(result.suggestions).toHaveLength(0);
|
|
337
|
+
expect(result.summary.totalModules).toBe(0);
|
|
338
|
+
expect(result.summary.averageCohesion).toBe(1);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('summary statistics', () => {
|
|
343
|
+
it('calculates average cohesion correctly', async () => {
|
|
344
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
345
|
+
|
|
346
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
347
|
+
|
|
348
|
+
const nodes = [
|
|
349
|
+
{ id: '/project/src/a/1.js', name: 'src/a/1.js' },
|
|
350
|
+
{ id: '/project/src/a/2.js', name: 'src/a/2.js' },
|
|
351
|
+
{ id: '/project/src/b/1.js', name: 'src/b/1.js' },
|
|
352
|
+
{ id: '/project/src/b/2.js', name: 'src/b/2.js' },
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
// Module a has 100% cohesion, module b has 0%
|
|
356
|
+
const edges = [
|
|
357
|
+
{ from: '/project/src/a/1.js', to: '/project/src/a/2.js' },
|
|
358
|
+
{ from: '/project/src/b/1.js', to: '/project/src/a/1.js' },
|
|
359
|
+
{ from: '/project/src/b/2.js', to: '/project/src/a/2.js' },
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
363
|
+
const result = analyzer.analyze(mockGraph);
|
|
364
|
+
|
|
365
|
+
// Average should be between 0 and 1
|
|
366
|
+
expect(result.summary.averageCohesion).toBeGreaterThanOrEqual(0);
|
|
367
|
+
expect(result.summary.averageCohesion).toBeLessThanOrEqual(1);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('counts total modules correctly', async () => {
|
|
371
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
372
|
+
|
|
373
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
374
|
+
|
|
375
|
+
const nodes = [
|
|
376
|
+
{ id: '/project/src/a/1.js', name: 'src/a/1.js' },
|
|
377
|
+
{ id: '/project/src/b/1.js', name: 'src/b/1.js' },
|
|
378
|
+
{ id: '/project/src/c/1.js', name: 'src/c/1.js' },
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
const mockGraph = createMockGraph(nodes, []);
|
|
382
|
+
const result = analyzer.analyze(mockGraph);
|
|
383
|
+
|
|
384
|
+
expect(result.summary.totalModules).toBe(3);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('counts low cohesion modules correctly', async () => {
|
|
388
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
389
|
+
|
|
390
|
+
const analyzer = new CohesionAnalyzer({
|
|
391
|
+
basePath: '/project',
|
|
392
|
+
lowCohesionThreshold: 0.5,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const nodes = [
|
|
396
|
+
{ id: '/project/src/a/1.js', name: 'src/a/1.js' },
|
|
397
|
+
{ id: '/project/src/a/2.js', name: 'src/a/2.js' },
|
|
398
|
+
{ id: '/project/src/b/1.js', name: 'src/b/1.js' },
|
|
399
|
+
{ id: '/project/src/b/2.js', name: 'src/b/2.js' },
|
|
400
|
+
{ id: '/project/src/shared.js', name: 'src/shared.js' },
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
// Both a and b have low cohesion
|
|
404
|
+
const edges = [
|
|
405
|
+
{ from: '/project/src/a/1.js', to: '/project/src/shared.js' },
|
|
406
|
+
{ from: '/project/src/a/2.js', to: '/project/src/shared.js' },
|
|
407
|
+
{ from: '/project/src/b/1.js', to: '/project/src/shared.js' },
|
|
408
|
+
{ from: '/project/src/b/2.js', to: '/project/src/shared.js' },
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
412
|
+
const result = analyzer.analyze(mockGraph);
|
|
413
|
+
|
|
414
|
+
expect(result.summary.lowCohesionCount).toBe(2);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('mixed cohesion scenarios', () => {
|
|
419
|
+
it('handles modules with mixed internal/external deps', async () => {
|
|
420
|
+
const { CohesionAnalyzer } = await import('./cohesion-analyzer.js');
|
|
421
|
+
|
|
422
|
+
const analyzer = new CohesionAnalyzer({ basePath: '/project' });
|
|
423
|
+
|
|
424
|
+
const nodes = [
|
|
425
|
+
{ id: '/project/src/utils/a.js', name: 'src/utils/a.js' },
|
|
426
|
+
{ id: '/project/src/utils/b.js', name: 'src/utils/b.js' },
|
|
427
|
+
{ id: '/project/src/utils/c.js', name: 'src/utils/c.js' },
|
|
428
|
+
{ id: '/project/src/lib/x.js', name: 'src/lib/x.js' },
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
// Some internal deps, some external
|
|
432
|
+
const edges = [
|
|
433
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/utils/b.js' },
|
|
434
|
+
{ from: '/project/src/utils/b.js', to: '/project/src/utils/c.js' },
|
|
435
|
+
{ from: '/project/src/utils/a.js', to: '/project/src/lib/x.js' },
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
const mockGraph = createMockGraph(nodes, edges);
|
|
439
|
+
const result = analyzer.analyze(mockGraph);
|
|
440
|
+
|
|
441
|
+
// Cohesion should be between 0 and 1
|
|
442
|
+
const utilsCohesion = result.modules['src/utils'].cohesion;
|
|
443
|
+
expect(utilsCohesion).toBeGreaterThan(0);
|
|
444
|
+
expect(utilsCohesion).toBeLessThan(1);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
});
|