@aiready/context-analyzer 0.9.1 → 0.9.5
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-test.log +22 -36
- package/README.md +8 -0
- package/dist/__tests__/analyzer.test.d.ts +2 -0
- package/dist/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer.test.js +157 -0
- package/dist/__tests__/analyzer.test.js.map +1 -0
- package/dist/__tests__/auto-detection.test.d.ts +2 -0
- package/dist/__tests__/auto-detection.test.d.ts.map +1 -0
- package/dist/__tests__/auto-detection.test.js +132 -0
- package/dist/__tests__/auto-detection.test.js.map +1 -0
- package/dist/__tests__/enhanced-cohesion.test.d.ts +2 -0
- package/dist/__tests__/enhanced-cohesion.test.d.ts.map +1 -0
- package/dist/__tests__/enhanced-cohesion.test.js +109 -0
- package/dist/__tests__/enhanced-cohesion.test.js.map +1 -0
- package/dist/__tests__/fragmentation-advanced.test.d.ts +2 -0
- package/dist/__tests__/fragmentation-advanced.test.d.ts.map +1 -0
- package/dist/__tests__/fragmentation-advanced.test.js +50 -0
- package/dist/__tests__/fragmentation-advanced.test.js.map +1 -0
- package/dist/__tests__/fragmentation-coupling.test.d.ts +2 -0
- package/dist/__tests__/fragmentation-coupling.test.d.ts.map +1 -0
- package/dist/__tests__/fragmentation-coupling.test.js +52 -0
- package/dist/__tests__/fragmentation-coupling.test.js.map +1 -0
- package/dist/__tests__/fragmentation-log.test.d.ts +2 -0
- package/dist/__tests__/fragmentation-log.test.d.ts.map +1 -0
- package/dist/__tests__/fragmentation-log.test.js +33 -0
- package/dist/__tests__/fragmentation-log.test.js.map +1 -0
- package/dist/__tests__/scoring.test.d.ts +2 -0
- package/dist/__tests__/scoring.test.d.ts.map +1 -0
- package/dist/__tests__/scoring.test.js +118 -0
- package/dist/__tests__/scoring.test.js.map +1 -0
- package/dist/__tests__/structural-cohesion.test.d.ts +2 -0
- package/dist/__tests__/structural-cohesion.test.d.ts.map +1 -0
- package/dist/__tests__/structural-cohesion.test.js +29 -0
- package/dist/__tests__/structural-cohesion.test.js.map +1 -0
- package/dist/analyzer.d.ts +100 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +701 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/analyzers/python-context.d.ts +38 -0
- package/dist/analyzers/python-context.d.ts.map +1 -0
- package/dist/analyzers/python-context.js +232 -0
- package/dist/analyzers/python-context.js.map +1 -0
- package/dist/chunk-BD4NWUVG.mjs +1242 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +139 -13
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +139 -13
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/scoring.d.ts +13 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +133 -0
- package/dist/scoring.js.map +1 -0
- package/dist/semantic-analysis.d.ts +44 -0
- package/dist/semantic-analysis.d.ts.map +1 -0
- package/dist/semantic-analysis.js +241 -0
- package/dist/semantic-analysis.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/fragmentation-advanced.test.ts +60 -0
- package/src/__tests__/fragmentation-coupling.test.ts +62 -0
- package/src/__tests__/fragmentation-log.test.ts +38 -0
- package/src/__tests__/structural-cohesion.test.ts +32 -0
- package/src/analyzer.ts +193 -18
- package/src/index.ts +34 -2
- package/src/types.ts +3 -0
- package/tsconfig.tsbuildinfo +1 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/context-analyzer@0.9.
|
|
3
|
+
> @aiready/context-analyzer@0.9.5 build /Users/pengcao/projects/aiready/packages/context-analyzer
|
|
4
4
|
> tsup src/index.ts src/cli.ts --format cjs,esm --dts
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mCJS[39m [1mdist/cli.js [22m[
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in
|
|
12
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m62.78 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m51.03 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 69ms
|
|
15
15
|
[32mESM[39m [1mdist/index.mjs [22m[32m585.00 B[39m
|
|
16
|
+
[32mESM[39m [1mdist/cli.mjs [22m[32m18.53 KB[39m
|
|
16
17
|
[32mESM[39m [1mdist/chunk-Y6FXYEAI.mjs [22m[32m390.00 B[39m
|
|
18
|
+
[32mESM[39m [1mdist/chunk-BD4NWUVG.mjs [22m[32m42.75 KB[39m
|
|
17
19
|
[32mESM[39m [1mdist/python-context-UOPTQH44.mjs [22m[32m5.64 KB[39m
|
|
18
|
-
[32mESM[39m
|
|
19
|
-
[32mESM[39m [1mdist/cli.mjs [22m[32m18.53 KB[39m
|
|
20
|
-
[32mESM[39m ⚡️ Build success in 26ms
|
|
20
|
+
[32mESM[39m ⚡️ Build success in 91ms
|
|
21
21
|
DTS Build start
|
|
22
|
-
DTS ⚡️ Build success in
|
|
22
|
+
DTS ⚡️ Build success in 1944ms
|
|
23
23
|
DTS dist/cli.d.ts 20.00 B
|
|
24
|
-
DTS dist/index.d.ts
|
|
24
|
+
DTS dist/index.d.ts 6.03 KB
|
|
25
25
|
DTS dist/cli.d.mts 20.00 B
|
|
26
|
-
DTS dist/index.d.mts
|
|
26
|
+
DTS dist/index.d.mts 6.03 KB
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,45 +1,31 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/context-analyzer@0.9.
|
|
3
|
+
> @aiready/context-analyzer@0.9.5 test /Users/pengcao/projects/aiready/packages/context-analyzer
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
[?25l
|
|
7
7
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/context-analyzer[39m
|
|
8
8
|
|
|
9
|
-
[
|
|
10
|
-
[
|
|
11
|
-
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[2m
|
|
15
|
-
[2m
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
[
|
|
20
|
-
[
|
|
21
|
-
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
[2m
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
|
|
28
|
-
[
|
|
29
|
-
[
|
|
30
|
-
[1m[33m ❯ [39m[22msrc/__tests__/enhanced-cohesion.test.ts[2m 0/6[22m
|
|
31
|
-
|
|
32
|
-
[2m Test Files [22m[1m[32m1 passed[39m[22m[90m (4)[39m
|
|
33
|
-
[2m Tests [22m[1m[32m7 passed[39m[22m[90m (13)[39m
|
|
34
|
-
[2m Start at [22m20:59:54
|
|
35
|
-
[2m Duration [22m512ms
|
|
36
|
-
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m src/__tests__/enhanced-cohesion.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
37
|
-
[32m✓[39m src/__tests__/auto-detection.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
38
|
-
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
39
|
-
|
|
40
|
-
[2m Test Files [22m [1m[32m4 passed[39m[22m[90m (4)[39m
|
|
41
|
-
[2m Tests [22m [1m[32m35 passed[39m[22m[90m (35)[39m
|
|
42
|
-
[2m Start at [22m 20:59:54
|
|
43
|
-
[2m Duration [22m 608ms[2m (transform 350ms, setup 0ms, import 1.16s, tests 36ms, environment 0ms)[22m
|
|
9
|
+
[32m✓[39m src/__tests__/scoring.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
10
|
+
[32m✓[39m dist/__tests__/scoring.test.js [2m([22m[2m7 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/__tests__/enhanced-cohesion.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
12
|
+
[32m✓[39m src/__tests__/fragmentation-coupling.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 19[2mms[22m[39m
|
|
13
|
+
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 35[2mms[22m[39m
|
|
14
|
+
[32m✓[39m src/__tests__/auto-detection.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 28[2mms[22m[39m
|
|
15
|
+
[32m✓[39m src/__tests__/fragmentation-advanced.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
16
|
+
[32m✓[39m dist/__tests__/fragmentation-advanced.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
17
|
+
[32m✓[39m dist/__tests__/enhanced-cohesion.test.js [2m([22m[2m6 tests[22m[2m)[22m[32m 31[2mms[22m[39m
|
|
18
|
+
[32m✓[39m dist/__tests__/fragmentation-coupling.test.js [2m([22m[2m2 tests[22m[2m)[22m[32m 29[2mms[22m[39m
|
|
19
|
+
[32m✓[39m dist/__tests__/analyzer.test.js [2m([22m[2m14 tests[22m[2m)[22m[32m 101[2mms[22m[39m
|
|
20
|
+
[32m✓[39m dist/__tests__/auto-detection.test.js [2m([22m[2m8 tests[22m[2m)[22m[32m 111[2mms[22m[39m
|
|
21
|
+
[32m✓[39m dist/__tests__/fragmentation-log.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
22
|
+
[32m✓[39m src/__tests__/fragmentation-log.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 1[2mms[22m[39m
|
|
23
|
+
[32m✓[39m dist/__tests__/structural-cohesion.test.js [2m([22m[2m4 tests[22m[2m)[22m[32m 1[2mms[22m[39m
|
|
24
|
+
[32m✓[39m src/__tests__/structural-cohesion.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 1[2mms[22m[39m
|
|
25
|
+
|
|
26
|
+
[2m Test Files [22m [1m[32m16 passed[39m[22m[90m (16)[39m
|
|
27
|
+
[2m Tests [22m [1m[32m94 passed[39m[22m[90m (94)[39m
|
|
28
|
+
[2m Start at [22m 22:40:01
|
|
29
|
+
[2m Duration [22m 1.72s[2m (transform 2.67s, setup 0ms, import 12.51s, tests 378ms, environment 1ms)[22m
|
|
44
30
|
|
|
45
31
|
[?25h
|
package/README.md
CHANGED
|
@@ -250,6 +250,14 @@ src/user/types.ts // 600 tokens
|
|
|
250
250
|
|
|
251
251
|
**🎯 Recommendation:** Domains with high fragmentation should be **consolidated** into cohesive modules.
|
|
252
252
|
|
|
253
|
+
### 🔎 Additional Fragmentation Signals (new)
|
|
254
|
+
|
|
255
|
+
- **Path Entropy:** A normalized Shannon-entropy over directory distribution. Distinguishes "9 files in one folder + 1 outlier" from "5 and 5" — higher entropy means more evenly spread (worse).
|
|
256
|
+
- **Directory Distance:** Measures how deep the common ancestor is across file pairs. Files sharing a deep common ancestor are considered "closer" (lower distance) than files only sharing the repo root.
|
|
257
|
+
- **Import Cohesion (coupling discount):** If fragmented files actively import each other, the analyzer reduces the fragmentation penalty (up to 20%). This rewards intentional modularization where cross-folder imports are a natural design instead of accidental scattering.
|
|
258
|
+
|
|
259
|
+
These signals are included in the JSON summary (`fragmentedModules` entries) so you can prioritize consolidation efforts with more context: `fragmentationScore`, `pathEntropy`, `directoryDistance`, and `importCohesion`.
|
|
260
|
+
|
|
253
261
|
---
|
|
254
262
|
|
|
255
263
|
### ⚖️ The Tradeoff: Splitting vs. Consolidating
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildDependencyGraph, calculateImportDepth, getTransitiveDependencies, calculateContextBudget, detectCircularDependencies, calculateCohesion, calculateFragmentation, } from '../analyzer';
|
|
3
|
+
describe('buildDependencyGraph', () => {
|
|
4
|
+
it('should build a basic dependency graph', () => {
|
|
5
|
+
const files = [
|
|
6
|
+
{
|
|
7
|
+
file: 'a.ts',
|
|
8
|
+
content: `
|
|
9
|
+
export const a = 1;
|
|
10
|
+
`,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
file: 'b.ts',
|
|
14
|
+
content: `
|
|
15
|
+
import { a } from './a';
|
|
16
|
+
export const b = a + 1;
|
|
17
|
+
`,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
const graph = buildDependencyGraph(files);
|
|
21
|
+
expect(graph.nodes.size).toBe(2);
|
|
22
|
+
expect(graph.edges.get('b.ts')?.has('./a')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('calculateImportDepth', () => {
|
|
26
|
+
it('should calculate import depth correctly', () => {
|
|
27
|
+
const files = [
|
|
28
|
+
{ file: 'a.ts', content: 'export const a = 1;' },
|
|
29
|
+
{ file: 'b.ts', content: 'import { a } from "a.ts";\nexport const b = a;' },
|
|
30
|
+
{ file: 'c.ts', content: 'import { b } from "b.ts";\nexport const c = b;' },
|
|
31
|
+
];
|
|
32
|
+
const graph = buildDependencyGraph(files);
|
|
33
|
+
expect(calculateImportDepth('a.ts', graph)).toBe(0);
|
|
34
|
+
expect(calculateImportDepth('b.ts', graph)).toBe(1);
|
|
35
|
+
expect(calculateImportDepth('c.ts', graph)).toBe(2);
|
|
36
|
+
});
|
|
37
|
+
it('should handle circular dependencies gracefully', () => {
|
|
38
|
+
const files = [
|
|
39
|
+
{ file: 'a.ts', content: 'import { b } from "./b";\nexport const a = 1;' },
|
|
40
|
+
{ file: 'b.ts', content: 'import { a } from "./a";\nexport const b = 2;' },
|
|
41
|
+
];
|
|
42
|
+
const graph = buildDependencyGraph(files);
|
|
43
|
+
// Should not infinite loop
|
|
44
|
+
const depth = calculateImportDepth('a.ts', graph);
|
|
45
|
+
expect(depth).toBeGreaterThanOrEqual(0);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('getTransitiveDependencies', () => {
|
|
49
|
+
it('should get all transitive dependencies', () => {
|
|
50
|
+
const files = [
|
|
51
|
+
{ file: 'a.ts', content: 'export const a = 1;' },
|
|
52
|
+
{ file: 'b.ts', content: 'import { a } from "a.ts";\nexport const b = a;' },
|
|
53
|
+
{ file: 'c.ts', content: 'import { b } from "b.ts";\nexport const c = b;' },
|
|
54
|
+
];
|
|
55
|
+
const graph = buildDependencyGraph(files);
|
|
56
|
+
const deps = getTransitiveDependencies('c.ts', graph);
|
|
57
|
+
expect(deps).toContain('b.ts');
|
|
58
|
+
expect(deps).toContain('a.ts');
|
|
59
|
+
expect(deps.length).toBe(2);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('calculateContextBudget', () => {
|
|
63
|
+
it('should calculate total token cost including dependencies', () => {
|
|
64
|
+
const files = [
|
|
65
|
+
{ file: 'a.ts', content: 'export const a = 1;'.repeat(10) }, // ~40 tokens
|
|
66
|
+
{ file: 'b.ts', content: 'import { a } from "./a";\nexport const b = a;'.repeat(10) }, // ~60 tokens
|
|
67
|
+
];
|
|
68
|
+
const graph = buildDependencyGraph(files);
|
|
69
|
+
const budget = calculateContextBudget('b.ts', graph);
|
|
70
|
+
// Should include both files' tokens
|
|
71
|
+
expect(budget).toBeGreaterThan(80);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('detectCircularDependencies', () => {
|
|
75
|
+
it('should detect circular dependencies', () => {
|
|
76
|
+
const files = [
|
|
77
|
+
{ file: 'a.ts', content: 'import { b } from "b.ts";\nexport const a = 1;' },
|
|
78
|
+
{ file: 'b.ts', content: 'import { a } from "a.ts";\nexport const b = 2;' },
|
|
79
|
+
];
|
|
80
|
+
const graph = buildDependencyGraph(files);
|
|
81
|
+
const cycles = detectCircularDependencies(graph);
|
|
82
|
+
expect(cycles.length).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
it('should return empty for no circular dependencies', () => {
|
|
85
|
+
const files = [
|
|
86
|
+
{ file: 'a.ts', content: 'export const a = 1;' },
|
|
87
|
+
{ file: 'b.ts', content: 'import { a } from "a.ts";\nexport const b = a;' },
|
|
88
|
+
];
|
|
89
|
+
const graph = buildDependencyGraph(files);
|
|
90
|
+
const cycles = detectCircularDependencies(graph);
|
|
91
|
+
expect(cycles.length).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('calculateCohesion', () => {
|
|
95
|
+
it('should return 1 for single export', () => {
|
|
96
|
+
const exports = [{ name: 'foo', type: 'function', inferredDomain: 'user' }];
|
|
97
|
+
expect(calculateCohesion(exports)).toBe(1);
|
|
98
|
+
});
|
|
99
|
+
it('should return high cohesion for related exports', () => {
|
|
100
|
+
const exports = [
|
|
101
|
+
{ name: 'getUser', type: 'function', inferredDomain: 'user' },
|
|
102
|
+
{ name: 'updateUser', type: 'function', inferredDomain: 'user' },
|
|
103
|
+
{ name: 'deleteUser', type: 'function', inferredDomain: 'user' },
|
|
104
|
+
];
|
|
105
|
+
const cohesion = calculateCohesion(exports);
|
|
106
|
+
expect(cohesion).toBeGreaterThan(0.9);
|
|
107
|
+
});
|
|
108
|
+
it('should return low cohesion for mixed exports', () => {
|
|
109
|
+
const exports = [
|
|
110
|
+
{ name: 'getUser', type: 'function', inferredDomain: 'user' },
|
|
111
|
+
{ name: 'getOrder', type: 'function', inferredDomain: 'order' },
|
|
112
|
+
{ name: 'parseConfig', type: 'function', inferredDomain: 'config' },
|
|
113
|
+
];
|
|
114
|
+
const cohesion = calculateCohesion(exports);
|
|
115
|
+
expect(cohesion).toBeLessThan(0.5);
|
|
116
|
+
});
|
|
117
|
+
it('should return 1 for test files even with mixed domains', () => {
|
|
118
|
+
const exports = [
|
|
119
|
+
{ name: 'mockUser', type: 'function', inferredDomain: 'user' },
|
|
120
|
+
{ name: 'mockOrder', type: 'function', inferredDomain: 'order' },
|
|
121
|
+
{ name: 'setupTestDb', type: 'function', inferredDomain: 'helper' },
|
|
122
|
+
];
|
|
123
|
+
// Test file - should return 1 despite mixed domains
|
|
124
|
+
const cohesionTestFile = calculateCohesion(exports, 'src/__tests__/helpers.test.ts');
|
|
125
|
+
expect(cohesionTestFile).toBe(1);
|
|
126
|
+
// Mock file - should return 1 despite mixed domains
|
|
127
|
+
const cohesionMockFile = calculateCohesion(exports, 'src/test-utils/mocks.ts');
|
|
128
|
+
expect(cohesionMockFile).toBe(1);
|
|
129
|
+
// Fixture file - should return 1 despite mixed domains
|
|
130
|
+
const cohesionFixtureFile = calculateCohesion(exports, 'src/fixtures/data.ts');
|
|
131
|
+
expect(cohesionFixtureFile).toBe(1);
|
|
132
|
+
// Regular file - should have low cohesion
|
|
133
|
+
const cohesionRegularFile = calculateCohesion(exports, 'src/utils/helpers.ts');
|
|
134
|
+
expect(cohesionRegularFile).toBeLessThan(0.5);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('calculateFragmentation', () => {
|
|
138
|
+
it('should return 0 for single file', () => {
|
|
139
|
+
const files = ['src/user/user.ts'];
|
|
140
|
+
expect(calculateFragmentation(files, 'user')).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
it('should return 0 for files in same directory', () => {
|
|
143
|
+
const files = ['src/user/get.ts', 'src/user/update.ts'];
|
|
144
|
+
expect(calculateFragmentation(files, 'user')).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
it('should return high fragmentation for scattered files', () => {
|
|
147
|
+
const files = [
|
|
148
|
+
'src/user/get.ts',
|
|
149
|
+
'src/api/user.ts',
|
|
150
|
+
'src/services/user.ts',
|
|
151
|
+
'src/helpers/user.ts',
|
|
152
|
+
];
|
|
153
|
+
const fragmentation = calculateFragmentation(files, 'user');
|
|
154
|
+
expect(fragmentation).toBeGreaterThan(0.8);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=analyzer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.test.js","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-detection.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auto-detection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildDependencyGraph } from '../analyzer';
|
|
3
|
+
describe('Auto-detection from folder structure', () => {
|
|
4
|
+
it('should auto-detect domain keywords from folder paths', () => {
|
|
5
|
+
const files = [
|
|
6
|
+
{
|
|
7
|
+
file: 'src/payments/process.ts',
|
|
8
|
+
content: 'export function processPayment() { return 1; }',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
file: 'src/orders/create.ts',
|
|
12
|
+
content: 'export function createOrder() { return 2; }',
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
const graph = buildDependencyGraph(files);
|
|
16
|
+
const paymentsNode = graph.nodes.get('src/payments/process.ts');
|
|
17
|
+
const ordersNode = graph.nodes.get('src/orders/create.ts');
|
|
18
|
+
// Should detect 'payment' from processPayment (now part of auto-detected keywords)
|
|
19
|
+
expect(paymentsNode?.exports[0].inferredDomain).toBe('payment');
|
|
20
|
+
// Should detect 'order' from createOrder
|
|
21
|
+
expect(ordersNode?.exports[0].inferredDomain).toBe('order');
|
|
22
|
+
});
|
|
23
|
+
it('should detect domains from nested folders', () => {
|
|
24
|
+
const files = [
|
|
25
|
+
{
|
|
26
|
+
file: 'src/api/invoices/handler.ts',
|
|
27
|
+
content: 'export function handleRequest() { return 1; }',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
const graph = buildDependencyGraph(files);
|
|
31
|
+
const node = graph.nodes.get('src/api/invoices/handler.ts');
|
|
32
|
+
// Should detect 'invoice' from path (invoices folder)
|
|
33
|
+
expect(node?.exports[0].inferredDomain).toBe('invoice');
|
|
34
|
+
});
|
|
35
|
+
it('should skip common infrastructure folders', () => {
|
|
36
|
+
const files = [
|
|
37
|
+
{
|
|
38
|
+
file: 'src/utils/helpers/format.ts',
|
|
39
|
+
content: 'export function formatData() { return 1; }',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
const graph = buildDependencyGraph(files);
|
|
43
|
+
const node = graph.nodes.get('src/utils/helpers/format.ts');
|
|
44
|
+
// 'utils' and 'helpers' should be skipped, no domain detected
|
|
45
|
+
expect(node?.exports[0].inferredDomain).toBe('unknown');
|
|
46
|
+
});
|
|
47
|
+
it('should merge auto-detected with custom keywords', () => {
|
|
48
|
+
const files = [
|
|
49
|
+
{
|
|
50
|
+
file: 'src/receipts/scan.ts',
|
|
51
|
+
content: 'export function scanReceipt() { return 1; }',
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
const graph = buildDependencyGraph(files, {
|
|
55
|
+
domainKeywords: ['receipt'], // Custom keyword
|
|
56
|
+
});
|
|
57
|
+
const node = graph.nodes.get('src/receipts/scan.ts');
|
|
58
|
+
// Should detect 'receipt' from both auto-detection AND custom keywords
|
|
59
|
+
expect(node?.exports[0].inferredDomain).toBe('receipt');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('Import-path domain inference', () => {
|
|
63
|
+
it('should infer domain from import paths', () => {
|
|
64
|
+
const files = [
|
|
65
|
+
{
|
|
66
|
+
file: 'src/lib/session.ts',
|
|
67
|
+
content: `
|
|
68
|
+
import { processPayment } from '../payments/processor';
|
|
69
|
+
export function createSession() { return 1; }
|
|
70
|
+
`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
file: 'src/payments/processor.ts',
|
|
74
|
+
content: 'export function processPayment() { return 2; }',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const graph = buildDependencyGraph(files);
|
|
78
|
+
const sessionNode = graph.nodes.get('src/lib/session.ts');
|
|
79
|
+
// session.ts imports from '../payments/...' so should infer 'payment' domain
|
|
80
|
+
expect(sessionNode?.exports[0].inferredDomain).toBe('payment');
|
|
81
|
+
});
|
|
82
|
+
it('should infer domain from absolute import paths', () => {
|
|
83
|
+
const files = [
|
|
84
|
+
{
|
|
85
|
+
file: 'src/components/nav-links.ts',
|
|
86
|
+
content: `
|
|
87
|
+
import { getOrders } from '@/orders/service';
|
|
88
|
+
export function NavLinks() { return 'nav'; }
|
|
89
|
+
`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
file: 'src/orders/service.ts',
|
|
93
|
+
content: 'export function getOrders() { return []; }',
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
const graph = buildDependencyGraph(files);
|
|
97
|
+
const navNode = graph.nodes.get('src/components/nav-links.ts');
|
|
98
|
+
// nav-links.ts imports from '@/orders/...' so should infer 'order' domain
|
|
99
|
+
expect(navNode?.exports[0].inferredDomain).toBe('order');
|
|
100
|
+
});
|
|
101
|
+
it('should use identifier name first before import-path fallback', () => {
|
|
102
|
+
const files = [
|
|
103
|
+
{
|
|
104
|
+
file: 'src/lib/handler.ts',
|
|
105
|
+
content: `
|
|
106
|
+
import { processPayment } from '../payments/processor';
|
|
107
|
+
export function processInvoice() { return 1; }
|
|
108
|
+
`,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
const graph = buildDependencyGraph(files);
|
|
112
|
+
const node = graph.nodes.get('src/lib/handler.ts');
|
|
113
|
+
// processInvoice should match 'invoice' from identifier, not 'payment' from imports
|
|
114
|
+
expect(node?.exports[0].inferredDomain).toBe('invoice');
|
|
115
|
+
});
|
|
116
|
+
it('should fall back to import-path when identifier is generic', () => {
|
|
117
|
+
const files = [
|
|
118
|
+
{
|
|
119
|
+
file: 'src/lib/dynamodb.ts',
|
|
120
|
+
content: `
|
|
121
|
+
import { Customer } from '../customers/model';
|
|
122
|
+
export function connect() { return 1; }
|
|
123
|
+
`,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
const graph = buildDependencyGraph(files);
|
|
127
|
+
const node = graph.nodes.get('src/lib/dynamodb.ts');
|
|
128
|
+
// 'connect' is generic, should infer 'customer' from import path
|
|
129
|
+
expect(node?.exports[0].inferredDomain).toBe('customer');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=auto-detection.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-detection.test.js","sourceRoot":"","sources":["../../src/__tests__/auto-detection.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enhanced-cohesion.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/enhanced-cohesion.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { calculateCohesion } from '../analyzer';
|
|
3
|
+
describe('Enhanced Cohesion Calculation', () => {
|
|
4
|
+
it('should use domain-based cohesion when no import data available', () => {
|
|
5
|
+
const exports = [
|
|
6
|
+
{ name: 'getUserData', type: 'function', inferredDomain: 'user' },
|
|
7
|
+
{ name: 'getProductData', type: 'function', inferredDomain: 'product' },
|
|
8
|
+
];
|
|
9
|
+
const cohesion = calculateCohesion(exports);
|
|
10
|
+
// With mixed domains (user, product) and no import data, should use domain-based calculation
|
|
11
|
+
// Domain entropy for 2 different domains = low cohesion
|
|
12
|
+
expect(cohesion).toBeLessThan(0.5);
|
|
13
|
+
});
|
|
14
|
+
it('should use import-based cohesion when import data available', () => {
|
|
15
|
+
const exports = [
|
|
16
|
+
{
|
|
17
|
+
name: 'getUserData',
|
|
18
|
+
type: 'function',
|
|
19
|
+
inferredDomain: 'user',
|
|
20
|
+
imports: ['react', 'axios', 'lodash'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'getProductData',
|
|
24
|
+
type: 'function',
|
|
25
|
+
inferredDomain: 'product',
|
|
26
|
+
imports: ['react', 'axios', 'lodash'], // Same imports!
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const cohesion = calculateCohesion(exports);
|
|
30
|
+
// Even though domains differ, imports are identical (Jaccard = 1.0)
|
|
31
|
+
// Enhanced cohesion = 0.6 * 1.0 + 0.4 * 0.0 (different domains) = 0.6
|
|
32
|
+
// Should be >= 0.6 (import-based weight)
|
|
33
|
+
expect(cohesion).toBeGreaterThanOrEqual(0.6);
|
|
34
|
+
});
|
|
35
|
+
it('should weight import-based similarity higher than domain-based', () => {
|
|
36
|
+
const exportsWithSharedImports = [
|
|
37
|
+
{
|
|
38
|
+
name: 'getUserData',
|
|
39
|
+
type: 'function',
|
|
40
|
+
inferredDomain: 'user',
|
|
41
|
+
imports: ['react', 'axios'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'getProductData',
|
|
45
|
+
type: 'function',
|
|
46
|
+
inferredDomain: 'product',
|
|
47
|
+
imports: ['react', 'axios'],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
const exportsWithoutSharedImports = [
|
|
51
|
+
{
|
|
52
|
+
name: 'getUserData',
|
|
53
|
+
type: 'function',
|
|
54
|
+
inferredDomain: 'user',
|
|
55
|
+
imports: ['react', 'axios'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'getProductData',
|
|
59
|
+
type: 'function',
|
|
60
|
+
inferredDomain: 'product',
|
|
61
|
+
imports: ['lodash', 'moment'],
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
const cohesionWithShared = calculateCohesion(exportsWithSharedImports);
|
|
65
|
+
const cohesionWithoutShared = calculateCohesion(exportsWithoutSharedImports);
|
|
66
|
+
// Shared imports should result in higher cohesion
|
|
67
|
+
expect(cohesionWithShared).toBeGreaterThan(cohesionWithoutShared);
|
|
68
|
+
});
|
|
69
|
+
it('should handle mixed case: some exports with imports, some without', () => {
|
|
70
|
+
const exports = [
|
|
71
|
+
{
|
|
72
|
+
name: 'getUserData',
|
|
73
|
+
type: 'function',
|
|
74
|
+
inferredDomain: 'user',
|
|
75
|
+
imports: ['react', 'axios'],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'getProductData',
|
|
79
|
+
type: 'function',
|
|
80
|
+
inferredDomain: 'product',
|
|
81
|
+
// No imports field
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const cohesion = calculateCohesion(exports);
|
|
85
|
+
// Should fall back to domain-based when not all exports have import data
|
|
86
|
+
expect(cohesion).toBeGreaterThan(0);
|
|
87
|
+
expect(cohesion).toBeLessThan(1);
|
|
88
|
+
});
|
|
89
|
+
it('should return 1 for single export', () => {
|
|
90
|
+
const exports = [
|
|
91
|
+
{
|
|
92
|
+
name: 'getUserData',
|
|
93
|
+
type: 'function',
|
|
94
|
+
inferredDomain: 'user',
|
|
95
|
+
imports: ['react'],
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
expect(calculateCohesion(exports)).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
it('should return 1 for test files regardless of domains or imports', () => {
|
|
101
|
+
const exports = [
|
|
102
|
+
{ name: 'testUserLogin', type: 'function', inferredDomain: 'user', imports: ['react'] },
|
|
103
|
+
{ name: 'testProductView', type: 'function', inferredDomain: 'product', imports: [] },
|
|
104
|
+
];
|
|
105
|
+
const cohesion = calculateCohesion(exports, 'src/utils/test-helpers.ts');
|
|
106
|
+
expect(cohesion).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=enhanced-cohesion.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enhanced-cohesion.test.js","sourceRoot":"","sources":["../../src/__tests__/enhanced-cohesion.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,OAAO,GAAiB;YAC5B,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE;YACjE,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE;SACxE,CAAC;QAEF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAE5C,6FAA6F;QAC7F,wDAAwD;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,OAAO,GAAiB;YAC5B;gBACE,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,MAAM;gBACtB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC;aACtC;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,SAAS;gBACzB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,gBAAgB;aACxD;SACF,CAAC;QAEF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAE5C,oEAAoE;QACpE,sEAAsE;QACtE,yCAAyC;QACzC,MAAM,CAAC,QAAQ,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,wBAAwB,GAAiB;YAC7C;gBACE,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,MAAM;gBACtB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;aAC5B;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,SAAS;gBACzB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;aAC5B;SACF,CAAC;QAEF,MAAM,2BAA2B,GAAiB;YAChD;gBACE,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,MAAM;gBACtB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;aAC5B;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,SAAS;gBACzB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;aAC9B;SACF,CAAC;QAEF,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;QACvE,MAAM,qBAAqB,GAAG,iBAAiB,CAAC,2BAA2B,CAAC,CAAC;QAE7E,kDAAkD;QAClD,MAAM,CAAC,kBAAkB,CAAC,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,OAAO,GAAiB;YAC5B;gBACE,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,MAAM;gBACtB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;aAC5B;YACD;gBACE,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,SAAS;gBACzB,mBAAmB;aACpB;SACF,CAAC;QAEF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAE5C,yEAAyE;QACzE,MAAM,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,OAAO,GAAiB;YAC5B;gBACE,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,MAAM;gBACtB,OAAO,EAAE,CAAC,OAAO,CAAC;aACnB;SACF,CAAC;QAEF,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,OAAO,GAAiB;YAC5B,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE;YACvF,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE;SACtF,CAAC;QAEF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;QACzE,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragmentation-advanced.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fragmentation-advanced.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { calculatePathEntropy, calculateDirectoryDistance, } from '../analyzer';
|
|
3
|
+
describe('advanced fragmentation heuristics', () => {
|
|
4
|
+
it('path entropy is low when most files are in one directory', () => {
|
|
5
|
+
const files = [
|
|
6
|
+
'src/billing/a.ts',
|
|
7
|
+
'src/billing/b.ts',
|
|
8
|
+
'src/billing/c.ts',
|
|
9
|
+
'src/billing/d.ts',
|
|
10
|
+
'src/billing/e.ts',
|
|
11
|
+
'src/billing/f.ts',
|
|
12
|
+
'src/billing/g.ts',
|
|
13
|
+
'src/billing/h.ts',
|
|
14
|
+
'src/billing/i.ts',
|
|
15
|
+
'src/other/x.ts',
|
|
16
|
+
];
|
|
17
|
+
const entropy = calculatePathEntropy(files);
|
|
18
|
+
// 9 in one folder and 1 in another should yield low entropy (< 0.5)
|
|
19
|
+
expect(entropy).toBeLessThan(0.5);
|
|
20
|
+
});
|
|
21
|
+
it('path entropy is high when files are evenly distributed', () => {
|
|
22
|
+
const files = [
|
|
23
|
+
'a/f1.ts',
|
|
24
|
+
'b/f2.ts',
|
|
25
|
+
'c/f3.ts',
|
|
26
|
+
'd/f4.ts',
|
|
27
|
+
'e/f5.ts',
|
|
28
|
+
];
|
|
29
|
+
const entropy = calculatePathEntropy(files);
|
|
30
|
+
expect(entropy).toBeGreaterThan(0.8);
|
|
31
|
+
});
|
|
32
|
+
it('directory distance is low for sibling files and high for scattered files', () => {
|
|
33
|
+
const siblings = [
|
|
34
|
+
'src/auth/ui/button.ts',
|
|
35
|
+
'src/auth/ui/input.ts',
|
|
36
|
+
'src/auth/ui/modal.ts',
|
|
37
|
+
];
|
|
38
|
+
const scattered = [
|
|
39
|
+
'src/auth/ui/button.ts',
|
|
40
|
+
'src/payments/api/charge.ts',
|
|
41
|
+
'lib/analytics/track.ts',
|
|
42
|
+
];
|
|
43
|
+
const distSiblings = calculateDirectoryDistance(siblings);
|
|
44
|
+
const distScattered = calculateDirectoryDistance(scattered);
|
|
45
|
+
expect(distSiblings).toBeLessThan(distScattered);
|
|
46
|
+
expect(distSiblings).toBeLessThan(0.5);
|
|
47
|
+
expect(distScattered).toBeGreaterThan(0.6);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=fragmentation-advanced.test.js.map
|