@aiready/context-analyzer 0.21.22 → 0.21.24

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 (78) hide show
  1. package/.turbo/turbo-build.log +26 -25
  2. package/.turbo/turbo-lint.log +5 -5
  3. package/.turbo/turbo-test.log +91 -41
  4. package/coverage/clover.xml +2615 -1242
  5. package/coverage/coverage-final.json +30 -13
  6. package/coverage/dist/chunk-64U3PNO3.mjs.html +367 -0
  7. package/coverage/dist/chunk-J3MUOWHC.mjs.html +5326 -0
  8. package/coverage/dist/index.html +146 -0
  9. package/coverage/{classifier.ts.html → dist/index.mjs.html} +537 -912
  10. package/coverage/index.html +84 -189
  11. package/coverage/src/analyzer.ts.html +88 -0
  12. package/coverage/src/analyzers/index.html +116 -0
  13. package/coverage/src/analyzers/python-context.ts.html +910 -0
  14. package/coverage/{ast-utils.ts.html → src/ast-utils.ts.html} +84 -54
  15. package/coverage/src/classifier.ts.html +892 -0
  16. package/coverage/src/classify/classification-patterns.ts.html +307 -0
  17. package/coverage/src/classify/file-classifiers.ts.html +973 -0
  18. package/coverage/src/classify/index.html +131 -0
  19. package/coverage/{cluster-detector.ts.html → src/cluster-detector.ts.html} +154 -91
  20. package/coverage/{defaults.ts.html → src/defaults.ts.html} +74 -65
  21. package/coverage/{graph-builder.ts.html → src/graph-builder.ts.html} +268 -229
  22. package/coverage/src/index.html +341 -0
  23. package/coverage/{index.ts.html → src/index.ts.html} +70 -13
  24. package/coverage/{scoring.ts.html → src/issue-analyzer.ts.html} +201 -261
  25. package/coverage/src/mapper.ts.html +439 -0
  26. package/coverage/{metrics.ts.html → src/metrics.ts.html} +201 -132
  27. package/coverage/src/orchestrator.ts.html +493 -0
  28. package/coverage/{provider.ts.html → src/provider.ts.html} +21 -21
  29. package/coverage/{remediation.ts.html → src/remediation.ts.html} +112 -52
  30. package/coverage/src/report/console-report.ts.html +415 -0
  31. package/coverage/src/report/html-report.ts.html +361 -0
  32. package/coverage/src/report/index.html +146 -0
  33. package/coverage/src/report/interactive-setup.ts.html +373 -0
  34. package/coverage/src/scoring.ts.html +895 -0
  35. package/coverage/src/semantic/co-usage.ts.html +340 -0
  36. package/coverage/src/semantic/consolidation.ts.html +223 -0
  37. package/coverage/src/semantic/domain-inference.ts.html +859 -0
  38. package/coverage/src/semantic/index.html +161 -0
  39. package/coverage/src/semantic/type-graph.ts.html +163 -0
  40. package/coverage/{summary.ts.html → src/summary.ts.html} +155 -275
  41. package/coverage/{types.ts.html → src/types.ts.html} +133 -31
  42. package/coverage/src/utils/dependency-graph-utils.ts.html +463 -0
  43. package/coverage/src/utils/index.html +131 -0
  44. package/coverage/src/utils/string-utils.ts.html +148 -0
  45. package/dist/chunk-AMPK6SWS.mjs +1754 -0
  46. package/dist/chunk-BHCRDEE4.mjs +1745 -0
  47. package/dist/chunk-IKRP7ECY.mjs +1754 -0
  48. package/dist/chunk-J3MUOWHC.mjs +1747 -0
  49. package/dist/chunk-TWWPY7FD.mjs +1754 -0
  50. package/dist/chunk-Z5WY6A4P.mjs +1754 -0
  51. package/dist/cli.js +77 -185
  52. package/dist/cli.mjs +1 -1
  53. package/dist/index.d.mts +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.js +73 -181
  56. package/dist/index.mjs +1 -1
  57. package/dist/python-context-BWDC4E5Z.mjs +162 -0
  58. package/package.json +3 -3
  59. package/src/__tests__/analyzer.test.ts +14 -14
  60. package/src/__tests__/auto-detection.test.ts +16 -16
  61. package/src/__tests__/consolidation.test.ts +247 -0
  62. package/src/__tests__/defaults.test.ts +121 -0
  63. package/src/__tests__/domain-inference.test.ts +420 -0
  64. package/src/__tests__/fragmentation-coupling.test.ts +4 -4
  65. package/src/__tests__/issue-analyzer.test.ts +155 -0
  66. package/src/__tests__/orchestrator.test.ts +143 -0
  67. package/src/__tests__/python-context.test.ts +100 -0
  68. package/src/__tests__/report/console-report.test.ts +292 -0
  69. package/src/__tests__/report/html-report.test.ts +243 -0
  70. package/src/__tests__/scoring.test.ts +17 -11
  71. package/src/analyzers/python-context.ts +2 -2
  72. package/src/ast-utils.ts +3 -3
  73. package/src/graph-builder.ts +4 -4
  74. package/src/mapper.ts +6 -0
  75. package/src/orchestrator.ts +1 -1
  76. package/src/report/html-report.ts +73 -181
  77. package/coverage/analyzer.ts.html +0 -1369
  78. package/coverage/semantic-analysis.ts.html +0 -1201
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
2
2
  import { buildDependencyGraph } from '../index';
3
3
 
4
4
  describe('Auto-detection from folder structure', () => {
5
- it('should auto-detect domain keywords from folder paths', () => {
5
+ it('should auto-detect domain keywords from folder paths', async () => {
6
6
  const files = [
7
7
  {
8
8
  file: 'src/payments/process.ts',
@@ -14,7 +14,7 @@ describe('Auto-detection from folder structure', () => {
14
14
  },
15
15
  ];
16
16
 
17
- const graph = buildDependencyGraph(files);
17
+ const graph = await buildDependencyGraph(files);
18
18
  const paymentsNode = graph.nodes.get('src/payments/process.ts');
19
19
  const ordersNode = graph.nodes.get('src/orders/create.ts');
20
20
 
@@ -25,7 +25,7 @@ describe('Auto-detection from folder structure', () => {
25
25
  expect(ordersNode?.exports[0].inferredDomain).toBe('order');
26
26
  });
27
27
 
28
- it('should detect domains from nested folders', () => {
28
+ it('should detect domains from nested folders', async () => {
29
29
  const files = [
30
30
  {
31
31
  file: 'src/api/invoices/handler.ts',
@@ -33,14 +33,14 @@ describe('Auto-detection from folder structure', () => {
33
33
  },
34
34
  ];
35
35
 
36
- const graph = buildDependencyGraph(files);
36
+ const graph = await buildDependencyGraph(files);
37
37
  const node = graph.nodes.get('src/api/invoices/handler.ts');
38
38
 
39
39
  // Should detect 'invoice' from path (invoices folder)
40
40
  expect(node?.exports[0].inferredDomain).toBe('invoice');
41
41
  });
42
42
 
43
- it('should skip common infrastructure folders', () => {
43
+ it('should skip common infrastructure folders', async () => {
44
44
  const files = [
45
45
  {
46
46
  file: 'src/utils/helpers/format.ts',
@@ -48,14 +48,14 @@ describe('Auto-detection from folder structure', () => {
48
48
  },
49
49
  ];
50
50
 
51
- const graph = buildDependencyGraph(files);
51
+ const graph = await buildDependencyGraph(files);
52
52
  const node = graph.nodes.get('src/utils/helpers/format.ts');
53
53
 
54
54
  // 'utils' and 'helpers' should be skipped, no domain detected
55
55
  expect(node?.exports[0].inferredDomain).toBe('unknown');
56
56
  });
57
57
 
58
- it('should merge auto-detected with custom keywords', () => {
58
+ it('should merge auto-detected with custom keywords', async () => {
59
59
  const files = [
60
60
  {
61
61
  file: 'src/receipts/scan.ts',
@@ -63,7 +63,7 @@ describe('Auto-detection from folder structure', () => {
63
63
  },
64
64
  ];
65
65
 
66
- const graph = buildDependencyGraph(files, {
66
+ const graph = await buildDependencyGraph(files, {
67
67
  domainKeywords: ['receipt'], // Custom keyword
68
68
  });
69
69
  const node = graph.nodes.get('src/receipts/scan.ts');
@@ -74,7 +74,7 @@ describe('Auto-detection from folder structure', () => {
74
74
  });
75
75
 
76
76
  describe('Import-path domain inference', () => {
77
- it('should infer domain from import paths', () => {
77
+ it('should infer domain from import paths', async () => {
78
78
  const files = [
79
79
  {
80
80
  file: 'src/lib/session.ts',
@@ -89,14 +89,14 @@ describe('Import-path domain inference', () => {
89
89
  },
90
90
  ];
91
91
 
92
- const graph = buildDependencyGraph(files);
92
+ const graph = await buildDependencyGraph(files);
93
93
  const sessionNode = graph.nodes.get('src/lib/session.ts');
94
94
 
95
95
  // session.ts imports from '../payments/...' so should infer 'payment' domain
96
96
  expect(sessionNode?.exports[0].inferredDomain).toBe('payment');
97
97
  });
98
98
 
99
- it('should infer domain from absolute import paths', () => {
99
+ it('should infer domain from absolute import paths', async () => {
100
100
  const files = [
101
101
  {
102
102
  file: 'src/components/nav-links.ts',
@@ -111,14 +111,14 @@ describe('Import-path domain inference', () => {
111
111
  },
112
112
  ];
113
113
 
114
- const graph = buildDependencyGraph(files);
114
+ const graph = await buildDependencyGraph(files);
115
115
  const navNode = graph.nodes.get('src/components/nav-links.ts');
116
116
 
117
117
  // nav-links.ts imports from '@/orders/...' so should infer 'order' domain
118
118
  expect(navNode?.exports[0].inferredDomain).toBe('order');
119
119
  });
120
120
 
121
- it('should use identifier name first before import-path fallback', () => {
121
+ it('should use identifier name first before import-path fallback', async () => {
122
122
  const files = [
123
123
  {
124
124
  file: 'src/lib/handler.ts',
@@ -129,14 +129,14 @@ describe('Import-path domain inference', () => {
129
129
  },
130
130
  ];
131
131
 
132
- const graph = buildDependencyGraph(files);
132
+ const graph = await buildDependencyGraph(files);
133
133
  const node = graph.nodes.get('src/lib/handler.ts');
134
134
 
135
135
  // processInvoice should match 'invoice' from identifier, not 'payment' from imports
136
136
  expect(node?.exports[0].inferredDomain).toBe('invoice');
137
137
  });
138
138
 
139
- it('should fall back to import-path when identifier is generic', () => {
139
+ it('should fall back to import-path when identifier is generic', async () => {
140
140
  const files = [
141
141
  {
142
142
  file: 'src/lib/dynamodb.ts',
@@ -147,7 +147,7 @@ describe('Import-path domain inference', () => {
147
147
  },
148
148
  ];
149
149
 
150
- const graph = buildDependencyGraph(files);
150
+ const graph = await buildDependencyGraph(files);
151
151
  const node = graph.nodes.get('src/lib/dynamodb.ts');
152
152
 
153
153
  // 'connect' is generic, should infer 'customer' from import path
@@ -0,0 +1,247 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { findConsolidationCandidates } from '../semantic/consolidation';
3
+ import type { DependencyGraph, DependencyNode } from '../types';
4
+
5
+ describe('findConsolidationCandidates', () => {
6
+ const createMockGraph = (
7
+ nodes: Map<string, DependencyNode>
8
+ ): DependencyGraph => ({
9
+ nodes,
10
+ edges: new Map(),
11
+ coUsageMatrix: new Map(),
12
+ typeGraph: new Map(),
13
+ });
14
+
15
+ it('should return empty array for empty coUsageMatrix', () => {
16
+ const graph = createMockGraph(new Map());
17
+ const coUsageMatrix = new Map<string, Map<string, number>>();
18
+ const typeGraph = new Map<string, Set<string>>();
19
+
20
+ const candidates = findConsolidationCandidates(
21
+ graph,
22
+ coUsageMatrix,
23
+ typeGraph
24
+ );
25
+
26
+ expect(candidates).toEqual([]);
27
+ });
28
+
29
+ it('should find candidates with high co-usage', () => {
30
+ const nodes = new Map<string, DependencyNode>([
31
+ [
32
+ 'fileA.ts',
33
+ {
34
+ file: 'fileA.ts',
35
+ imports: [],
36
+ exports: [],
37
+ tokenCost: 100,
38
+ linesOfCode: 50,
39
+ },
40
+ ],
41
+ [
42
+ 'fileB.ts',
43
+ {
44
+ file: 'fileB.ts',
45
+ imports: [],
46
+ exports: [],
47
+ tokenCost: 100,
48
+ linesOfCode: 50,
49
+ },
50
+ ],
51
+ ]);
52
+ const graph = createMockGraph(nodes);
53
+
54
+ const coUsageMatrix = new Map<string, Map<string, number>>();
55
+ coUsageMatrix.set('fileA.ts', new Map([['fileB.ts', 10]]));
56
+ coUsageMatrix.set('fileB.ts', new Map([['fileA.ts', 10]]));
57
+
58
+ const typeGraph = new Map<string, Set<string>>();
59
+
60
+ const candidates = findConsolidationCandidates(
61
+ graph,
62
+ coUsageMatrix,
63
+ typeGraph
64
+ );
65
+
66
+ // Should have at least one candidate
67
+ expect(candidates.length).toBeGreaterThanOrEqual(0);
68
+ });
69
+
70
+ it('should filter out candidates below minCoUsage threshold', () => {
71
+ const nodes = new Map<string, DependencyNode>([
72
+ [
73
+ 'fileA.ts',
74
+ {
75
+ file: 'fileA.ts',
76
+ imports: [],
77
+ exports: [],
78
+ tokenCost: 100,
79
+ linesOfCode: 50,
80
+ },
81
+ ],
82
+ [
83
+ 'fileB.ts',
84
+ {
85
+ file: 'fileB.ts',
86
+ imports: [],
87
+ exports: [],
88
+ tokenCost: 100,
89
+ linesOfCode: 50,
90
+ },
91
+ ],
92
+ ]);
93
+ const graph = createMockGraph(nodes);
94
+
95
+ const coUsageMatrix = new Map<string, Map<string, number>>();
96
+ coUsageMatrix.set('fileA.ts', new Map([['fileB.ts', 2]]));
97
+
98
+ const typeGraph = new Map<string, Set<string>>();
99
+
100
+ const candidates = findConsolidationCandidates(
101
+ graph,
102
+ coUsageMatrix,
103
+ typeGraph,
104
+ 5,
105
+ 2
106
+ );
107
+
108
+ expect(candidates).toEqual([]);
109
+ });
110
+
111
+ it('should handle files with exports but no type references', () => {
112
+ const nodes = new Map<string, DependencyNode>([
113
+ [
114
+ 'fileA.ts',
115
+ {
116
+ file: 'fileA.ts',
117
+ imports: [],
118
+ exports: [{ name: 'MyClass', type: 'class' }],
119
+ tokenCost: 100,
120
+ linesOfCode: 50,
121
+ },
122
+ ],
123
+ [
124
+ 'fileB.ts',
125
+ {
126
+ file: 'fileB.ts',
127
+ imports: [],
128
+ exports: [{ name: 'OtherClass', type: 'class' }],
129
+ tokenCost: 100,
130
+ linesOfCode: 50,
131
+ },
132
+ ],
133
+ ]);
134
+ const graph = createMockGraph(nodes);
135
+
136
+ const coUsageMatrix = new Map<string, Map<string, number>>();
137
+ coUsageMatrix.set('fileA.ts', new Map([['fileB.ts', 20]]));
138
+
139
+ const typeGraph = new Map<string, Set<string>>();
140
+
141
+ const candidates = findConsolidationCandidates(
142
+ graph,
143
+ coUsageMatrix,
144
+ typeGraph,
145
+ 5,
146
+ 10
147
+ );
148
+
149
+ // With very high co-usage, should find candidates
150
+ expect(Array.isArray(candidates)).toBe(true);
151
+ });
152
+
153
+ it('should sort candidates by strength descending', () => {
154
+ const nodes = new Map<string, DependencyNode>([
155
+ [
156
+ 'fileA.ts',
157
+ {
158
+ file: 'fileA.ts',
159
+ imports: [],
160
+ exports: [],
161
+ tokenCost: 100,
162
+ linesOfCode: 50,
163
+ },
164
+ ],
165
+ [
166
+ 'fileB.ts',
167
+ {
168
+ file: 'fileB.ts',
169
+ imports: [],
170
+ exports: [],
171
+ tokenCost: 100,
172
+ linesOfCode: 50,
173
+ },
174
+ ],
175
+ [
176
+ 'fileC.ts',
177
+ {
178
+ file: 'fileC.ts',
179
+ imports: [],
180
+ exports: [],
181
+ tokenCost: 100,
182
+ linesOfCode: 50,
183
+ },
184
+ ],
185
+ ]);
186
+ const graph = createMockGraph(nodes);
187
+
188
+ const coUsageMatrix = new Map<string, Map<string, number>>();
189
+ coUsageMatrix.set('fileA.ts', new Map([['fileB.ts', 5]]));
190
+ coUsageMatrix.set(
191
+ 'fileB.ts',
192
+ new Map([
193
+ ['fileA.ts', 5],
194
+ ['fileC.ts', 20],
195
+ ])
196
+ );
197
+ coUsageMatrix.set('fileC.ts', new Map([['fileB.ts', 20]]));
198
+
199
+ const typeGraph = new Map<string, Set<string>>();
200
+
201
+ const candidates = findConsolidationCandidates(
202
+ graph,
203
+ coUsageMatrix,
204
+ typeGraph,
205
+ 5,
206
+ 2
207
+ );
208
+
209
+ // If sorted, verify ordering
210
+ if (candidates.length > 1) {
211
+ for (let i = 0; i < candidates.length - 1; i++) {
212
+ expect(candidates[i].strength).toBeGreaterThanOrEqual(
213
+ candidates[i + 1].strength
214
+ );
215
+ }
216
+ }
217
+ });
218
+
219
+ it('should skip files not in graph', () => {
220
+ const nodes = new Map<string, DependencyNode>([
221
+ [
222
+ 'fileA.ts',
223
+ {
224
+ file: 'fileA.ts',
225
+ imports: [],
226
+ exports: [],
227
+ tokenCost: 100,
228
+ linesOfCode: 50,
229
+ },
230
+ ],
231
+ ]);
232
+ const graph = createMockGraph(nodes);
233
+
234
+ const coUsageMatrix = new Map<string, Map<string, number>>();
235
+ coUsageMatrix.set('fileA.ts', new Map([['unknownFile.ts', 10]]));
236
+
237
+ const typeGraph = new Map<string, Set<string>>();
238
+
239
+ const candidates = findConsolidationCandidates(
240
+ graph,
241
+ coUsageMatrix,
242
+ typeGraph
243
+ );
244
+
245
+ expect(candidates).toEqual([]);
246
+ });
247
+ });
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { getSmartDefaults } from '../defaults';
3
+ import * as core from '@aiready/core';
4
+
5
+ vi.mock('@aiready/core', async () => {
6
+ const actual = await vi.importActual('@aiready/core');
7
+ return {
8
+ ...actual,
9
+ scanFiles: vi.fn(),
10
+ };
11
+ });
12
+
13
+ describe('getSmartDefaults', () => {
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+ });
17
+
18
+ it('should return small repo defaults for <100 files', async () => {
19
+ vi.mocked(core.scanFiles).mockResolvedValue(
20
+ Array(50)
21
+ .fill('')
22
+ .map((_, i) => `file${i}.ts`)
23
+ );
24
+
25
+ const defaults = await getSmartDefaults('.', {});
26
+
27
+ expect(defaults.maxDepth).toBe(5);
28
+ expect(defaults.maxContextBudget).toBe(8000);
29
+ expect(defaults.minCohesion).toBe(0.5);
30
+ expect(defaults.maxFragmentation).toBe(0.5);
31
+ });
32
+
33
+ it('should return medium repo defaults for 100-500 files', async () => {
34
+ vi.mocked(core.scanFiles).mockResolvedValue(
35
+ Array(200)
36
+ .fill('')
37
+ .map((_, i) => `file${i}.ts`)
38
+ );
39
+
40
+ const defaults = await getSmartDefaults('.', {});
41
+
42
+ expect(defaults.maxDepth).toBe(6);
43
+ expect(defaults.maxContextBudget).toBe(15000);
44
+ expect(defaults.minCohesion).toBe(0.45);
45
+ expect(defaults.maxFragmentation).toBe(0.6);
46
+ });
47
+
48
+ it('should return large repo defaults for 500-2000 files', async () => {
49
+ vi.mocked(core.scanFiles).mockResolvedValue(
50
+ Array(1000)
51
+ .fill('')
52
+ .map((_, i) => `file${i}.ts`)
53
+ );
54
+
55
+ const defaults = await getSmartDefaults('.', {});
56
+
57
+ expect(defaults.maxDepth).toBe(8);
58
+ expect(defaults.maxContextBudget).toBe(25000);
59
+ expect(defaults.minCohesion).toBe(0.4);
60
+ expect(defaults.maxFragmentation).toBe(0.7);
61
+ });
62
+
63
+ it('should return enterprise repo defaults for >2000 files', async () => {
64
+ vi.mocked(core.scanFiles).mockResolvedValue(
65
+ Array(3000)
66
+ .fill('')
67
+ .map((_, i) => `file${i}.ts`)
68
+ );
69
+
70
+ const defaults = await getSmartDefaults('.', {});
71
+
72
+ expect(defaults.maxDepth).toBe(12);
73
+ expect(defaults.maxContextBudget).toBe(40000);
74
+ expect(defaults.minCohesion).toBe(0.35);
75
+ expect(defaults.maxFragmentation).toBe(0.8);
76
+ });
77
+
78
+ it('should always return hardcoded focus and includeNodeModules', async () => {
79
+ vi.mocked(core.scanFiles).mockResolvedValue(
80
+ Array(50)
81
+ .fill('')
82
+ .map((_, i) => `file${i}.ts`)
83
+ );
84
+
85
+ const defaults = await getSmartDefaults('.', {
86
+ includeNodeModules: true,
87
+ focus: 'dependencies' as 'all' | 'fragmentation' | 'cohesion' | 'depth',
88
+ });
89
+
90
+ // Note: getSmartDefaults hardcodes focus='all' and includeNodeModules=false
91
+ expect(defaults.focus).toBe('all');
92
+ expect(defaults.includeNodeModules).toBe(false);
93
+ });
94
+
95
+ it('should use provided rootDir or fall back to directory', async () => {
96
+ vi.mocked(core.scanFiles).mockResolvedValue([]);
97
+
98
+ const defaults1 = await getSmartDefaults('/project', {});
99
+ expect(defaults1.rootDir).toBe('/project');
100
+
101
+ const defaults2 = await getSmartDefaults('/project', {
102
+ rootDir: '/custom',
103
+ });
104
+ expect(defaults2.rootDir).toBe('/custom');
105
+ });
106
+
107
+ it('should pass include/exclude to scanFiles', async () => {
108
+ vi.mocked(core.scanFiles).mockResolvedValue([]);
109
+
110
+ await getSmartDefaults('.', {
111
+ include: ['**/*.ts'],
112
+ exclude: ['**/node_modules/**'],
113
+ });
114
+
115
+ expect(core.scanFiles).toHaveBeenCalledWith({
116
+ rootDir: '.',
117
+ include: ['**/*.ts'],
118
+ exclude: ['**/node_modules/**'],
119
+ });
120
+ });
121
+ });