@aiready/context-analyzer 0.9.34 → 0.9.36
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/.github/FUNDING.yml +2 -2
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +19 -19
- package/CONTRIBUTING.md +10 -2
- package/dist/chunk-7LUSCLGR.mjs +2058 -0
- package/dist/cli.js +346 -83
- package/dist/cli.mjs +137 -33
- package/dist/index.js +231 -56
- package/dist/index.mjs +1 -1
- package/dist/python-context-GOH747QU.mjs +202 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +69 -17
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +19 -7
- package/src/__tests__/file-classification.test.ts +188 -53
- package/src/__tests__/fragmentation-advanced.test.ts +2 -11
- package/src/__tests__/fragmentation-coupling.test.ts +8 -2
- package/src/__tests__/fragmentation-log.test.ts +9 -9
- package/src/__tests__/scoring.test.ts +19 -7
- package/src/__tests__/structural-cohesion.test.ts +33 -21
- package/src/analyzer.ts +724 -376
- package/src/analyzers/python-context.ts +33 -10
- package/src/cli.ts +223 -59
- package/src/index.ts +112 -55
- package/src/scoring.ts +53 -43
- package/src/semantic-analysis.ts +73 -55
- package/src/types.ts +12 -13
package/src/semantic-analysis.ts
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DependencyGraph,
|
|
3
|
+
CoUsageData,
|
|
4
|
+
DomainAssignment,
|
|
5
|
+
DomainSignals,
|
|
6
|
+
} from './types';
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Build co-usage matrix: track which files are imported together
|
|
5
|
-
*
|
|
10
|
+
*
|
|
6
11
|
* Files frequently imported together likely belong to the same semantic domain
|
|
7
12
|
*/
|
|
8
|
-
export function buildCoUsageMatrix(
|
|
13
|
+
export function buildCoUsageMatrix(
|
|
14
|
+
graph: DependencyGraph
|
|
15
|
+
): Map<string, Map<string, number>> {
|
|
9
16
|
const coUsageMatrix = new Map<string, Map<string, number>>();
|
|
10
|
-
|
|
17
|
+
|
|
11
18
|
// For each file, track which other files are imported alongside it
|
|
12
19
|
for (const [sourceFile, node] of graph.nodes) {
|
|
13
20
|
const imports = node.imports;
|
|
14
|
-
|
|
21
|
+
|
|
15
22
|
// For each pair of imports in this file, increment their co-usage count
|
|
16
23
|
for (let i = 0; i < imports.length; i++) {
|
|
17
24
|
const fileA = imports[i];
|
|
18
|
-
|
|
25
|
+
|
|
19
26
|
if (!coUsageMatrix.has(fileA)) {
|
|
20
27
|
coUsageMatrix.set(fileA, new Map());
|
|
21
28
|
}
|
|
22
|
-
|
|
29
|
+
|
|
23
30
|
for (let j = i + 1; j < imports.length; j++) {
|
|
24
31
|
const fileB = imports[j];
|
|
25
|
-
|
|
32
|
+
|
|
26
33
|
// Increment bidirectional co-usage count
|
|
27
34
|
const fileAUsage = coUsageMatrix.get(fileA)!;
|
|
28
35
|
fileAUsage.set(fileB, (fileAUsage.get(fileB) || 0) + 1);
|
|
29
|
-
|
|
36
|
+
|
|
30
37
|
if (!coUsageMatrix.has(fileB)) {
|
|
31
38
|
coUsageMatrix.set(fileB, new Map());
|
|
32
39
|
}
|
|
@@ -35,18 +42,20 @@ export function buildCoUsageMatrix(graph: DependencyGraph): Map<string, Map<stri
|
|
|
35
42
|
}
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
|
-
|
|
45
|
+
|
|
39
46
|
return coUsageMatrix;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* Extract type dependencies from AST exports
|
|
44
|
-
*
|
|
51
|
+
*
|
|
45
52
|
* Files that share types are semantically related
|
|
46
53
|
*/
|
|
47
|
-
export function buildTypeGraph(
|
|
54
|
+
export function buildTypeGraph(
|
|
55
|
+
graph: DependencyGraph
|
|
56
|
+
): Map<string, Set<string>> {
|
|
48
57
|
const typeGraph = new Map<string, Set<string>>();
|
|
49
|
-
|
|
58
|
+
|
|
50
59
|
for (const [file, node] of graph.nodes) {
|
|
51
60
|
for (const exp of node.exports) {
|
|
52
61
|
if (exp.typeReferences) {
|
|
@@ -59,13 +68,13 @@ export function buildTypeGraph(graph: DependencyGraph): Map<string, Set<string>>
|
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
|
-
|
|
71
|
+
|
|
63
72
|
return typeGraph;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
/**
|
|
67
76
|
* Find semantic clusters using co-usage patterns
|
|
68
|
-
*
|
|
77
|
+
*
|
|
69
78
|
* Files with high co-usage counts belong in the same cluster
|
|
70
79
|
*/
|
|
71
80
|
export function findSemanticClusters(
|
|
@@ -74,14 +83,14 @@ export function findSemanticClusters(
|
|
|
74
83
|
): Map<string, string[]> {
|
|
75
84
|
const clusters = new Map<string, string[]>();
|
|
76
85
|
const visited = new Set<string>();
|
|
77
|
-
|
|
86
|
+
|
|
78
87
|
// Simple clustering: group files with high co-usage
|
|
79
88
|
for (const [file, coUsages] of coUsageMatrix) {
|
|
80
89
|
if (visited.has(file)) continue;
|
|
81
|
-
|
|
90
|
+
|
|
82
91
|
const cluster: string[] = [file];
|
|
83
92
|
visited.add(file);
|
|
84
|
-
|
|
93
|
+
|
|
85
94
|
// Find strongly related files (co-imported >= minCoUsage times)
|
|
86
95
|
for (const [relatedFile, count] of coUsages) {
|
|
87
96
|
if (count >= minCoUsage && !visited.has(relatedFile)) {
|
|
@@ -89,13 +98,13 @@ export function findSemanticClusters(
|
|
|
89
98
|
visited.add(relatedFile);
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
|
-
|
|
101
|
+
|
|
93
102
|
if (cluster.length > 1) {
|
|
94
103
|
// Use first file as cluster ID
|
|
95
104
|
clusters.set(file, cluster);
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
|
-
|
|
107
|
+
|
|
99
108
|
return clusters;
|
|
100
109
|
}
|
|
101
110
|
|
|
@@ -104,26 +113,26 @@ export function findSemanticClusters(
|
|
|
104
113
|
*/
|
|
105
114
|
export function calculateDomainConfidence(signals: DomainSignals): number {
|
|
106
115
|
const weights = {
|
|
107
|
-
coUsage: 0.35,
|
|
108
|
-
typeReference: 0.
|
|
109
|
-
exportName: 0.15,
|
|
110
|
-
importPath: 0.
|
|
111
|
-
folderStructure: 0.
|
|
116
|
+
coUsage: 0.35, // Strongest signal: actual usage patterns
|
|
117
|
+
typeReference: 0.3, // Strong signal: shared types
|
|
118
|
+
exportName: 0.15, // Medium signal: identifier semantics
|
|
119
|
+
importPath: 0.1, // Weaker signal: path structure
|
|
120
|
+
folderStructure: 0.1, // Weakest signal: organization convention
|
|
112
121
|
};
|
|
113
|
-
|
|
122
|
+
|
|
114
123
|
let confidence = 0;
|
|
115
124
|
if (signals.coUsage) confidence += weights.coUsage;
|
|
116
125
|
if (signals.typeReference) confidence += weights.typeReference;
|
|
117
126
|
if (signals.exportName) confidence += weights.exportName;
|
|
118
127
|
if (signals.importPath) confidence += weights.importPath;
|
|
119
128
|
if (signals.folderStructure) confidence += weights.folderStructure;
|
|
120
|
-
|
|
129
|
+
|
|
121
130
|
return confidence;
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
/**
|
|
125
134
|
* Infer domain from semantic analysis (co-usage + types)
|
|
126
|
-
*
|
|
135
|
+
*
|
|
127
136
|
* This replaces the folder-based heuristic with actual code relationships
|
|
128
137
|
*/
|
|
129
138
|
export function inferDomainFromSemantics(
|
|
@@ -136,13 +145,13 @@ export function inferDomainFromSemantics(
|
|
|
136
145
|
): DomainAssignment[] {
|
|
137
146
|
const assignments: DomainAssignment[] = [];
|
|
138
147
|
const domainSignals = new Map<string, DomainSignals>();
|
|
139
|
-
|
|
148
|
+
|
|
140
149
|
// 1. Check co-usage patterns
|
|
141
150
|
const coUsages = coUsageMatrix.get(file) || new Map();
|
|
142
151
|
const strongCoUsages = Array.from(coUsages.entries())
|
|
143
152
|
.filter(([_, count]) => count >= 3)
|
|
144
153
|
.map(([coFile]) => coFile);
|
|
145
|
-
|
|
154
|
+
|
|
146
155
|
// Extract domains from frequently co-imported files
|
|
147
156
|
for (const coFile of strongCoUsages) {
|
|
148
157
|
const coNode = graph.nodes.get(coFile);
|
|
@@ -156,7 +165,7 @@ export function inferDomainFromSemantics(
|
|
|
156
165
|
typeReference: false,
|
|
157
166
|
exportName: false,
|
|
158
167
|
importPath: false,
|
|
159
|
-
folderStructure: false
|
|
168
|
+
folderStructure: false,
|
|
160
169
|
});
|
|
161
170
|
}
|
|
162
171
|
domainSignals.get(domain)!.coUsage = true;
|
|
@@ -164,7 +173,7 @@ export function inferDomainFromSemantics(
|
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
}
|
|
167
|
-
|
|
176
|
+
|
|
168
177
|
// 2. Check type references
|
|
169
178
|
if (exportTypeRefs) {
|
|
170
179
|
for (const typeRef of exportTypeRefs) {
|
|
@@ -183,7 +192,7 @@ export function inferDomainFromSemantics(
|
|
|
183
192
|
typeReference: false,
|
|
184
193
|
exportName: false,
|
|
185
194
|
importPath: false,
|
|
186
|
-
folderStructure: false
|
|
195
|
+
folderStructure: false,
|
|
187
196
|
});
|
|
188
197
|
}
|
|
189
198
|
domainSignals.get(domain)!.typeReference = true;
|
|
@@ -195,18 +204,19 @@ export function inferDomainFromSemantics(
|
|
|
195
204
|
}
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
|
-
|
|
207
|
+
|
|
199
208
|
// 3. Build domain assignments with confidence scores
|
|
200
209
|
for (const [domain, signals] of domainSignals) {
|
|
201
210
|
const confidence = calculateDomainConfidence(signals);
|
|
202
|
-
if (confidence >= 0.3) {
|
|
211
|
+
if (confidence >= 0.3) {
|
|
212
|
+
// Minimum confidence threshold
|
|
203
213
|
assignments.push({ domain, confidence, signals });
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
|
-
|
|
216
|
+
|
|
207
217
|
// Sort by confidence (highest first)
|
|
208
218
|
assignments.sort((a, b) => b.confidence - a.confidence);
|
|
209
|
-
|
|
219
|
+
|
|
210
220
|
return assignments;
|
|
211
221
|
}
|
|
212
222
|
|
|
@@ -218,21 +228,21 @@ export function getCoUsageData(
|
|
|
218
228
|
coUsageMatrix: Map<string, Map<string, number>>
|
|
219
229
|
): CoUsageData {
|
|
220
230
|
const coImportedWith = coUsageMatrix.get(file) || new Map();
|
|
221
|
-
|
|
231
|
+
|
|
222
232
|
// Find files that import both this file and others
|
|
223
233
|
const sharedImporters: string[] = [];
|
|
224
234
|
// This would require inverse mapping from imports, simplified for now
|
|
225
|
-
|
|
235
|
+
|
|
226
236
|
return {
|
|
227
237
|
file,
|
|
228
238
|
coImportedWith,
|
|
229
|
-
sharedImporters
|
|
239
|
+
sharedImporters,
|
|
230
240
|
};
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
/**
|
|
234
244
|
* Find files that should be consolidated based on semantic similarity
|
|
235
|
-
*
|
|
245
|
+
*
|
|
236
246
|
* High co-usage + shared types = strong consolidation candidate
|
|
237
247
|
*/
|
|
238
248
|
export function findConsolidationCandidates(
|
|
@@ -242,31 +252,39 @@ export function findConsolidationCandidates(
|
|
|
242
252
|
minCoUsage: number = 5,
|
|
243
253
|
minSharedTypes: number = 2
|
|
244
254
|
): Array<{ files: string[]; reason: string; strength: number }> {
|
|
245
|
-
const candidates: Array<{
|
|
246
|
-
|
|
255
|
+
const candidates: Array<{
|
|
256
|
+
files: string[];
|
|
257
|
+
reason: string;
|
|
258
|
+
strength: number;
|
|
259
|
+
}> = [];
|
|
260
|
+
|
|
247
261
|
// Find file pairs with both high co-usage AND shared types
|
|
248
262
|
for (const [fileA, coUsages] of coUsageMatrix) {
|
|
249
263
|
const nodeA = graph.nodes.get(fileA);
|
|
250
264
|
if (!nodeA) continue;
|
|
251
|
-
|
|
265
|
+
|
|
252
266
|
for (const [fileB, coUsageCount] of coUsages) {
|
|
253
267
|
if (fileB <= fileA) continue; // Avoid duplicates
|
|
254
268
|
if (coUsageCount < minCoUsage) continue;
|
|
255
|
-
|
|
269
|
+
|
|
256
270
|
const nodeB = graph.nodes.get(fileB);
|
|
257
271
|
if (!nodeB) continue;
|
|
258
|
-
|
|
272
|
+
|
|
259
273
|
// Count shared types
|
|
260
|
-
const typesA = new Set(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
274
|
+
const typesA = new Set(
|
|
275
|
+
nodeA.exports.flatMap((e) => e.typeReferences || [])
|
|
276
|
+
);
|
|
277
|
+
const typesB = new Set(
|
|
278
|
+
nodeB.exports.flatMap((e) => e.typeReferences || [])
|
|
279
|
+
);
|
|
280
|
+
const sharedTypes = Array.from(typesA).filter((t) => typesB.has(t));
|
|
281
|
+
|
|
264
282
|
if (sharedTypes.length >= minSharedTypes) {
|
|
265
|
-
const strength =
|
|
283
|
+
const strength = coUsageCount / 10 + sharedTypes.length / 5;
|
|
266
284
|
candidates.push({
|
|
267
285
|
files: [fileA, fileB],
|
|
268
286
|
reason: `High co-usage (${coUsageCount}x) and ${sharedTypes.length} shared types`,
|
|
269
|
-
strength
|
|
287
|
+
strength,
|
|
270
288
|
});
|
|
271
289
|
} else if (coUsageCount >= minCoUsage * 2) {
|
|
272
290
|
// Very high co-usage alone is enough
|
|
@@ -274,14 +292,14 @@ export function findConsolidationCandidates(
|
|
|
274
292
|
candidates.push({
|
|
275
293
|
files: [fileA, fileB],
|
|
276
294
|
reason: `Very high co-usage (${coUsageCount}x)`,
|
|
277
|
-
strength
|
|
295
|
+
strength,
|
|
278
296
|
});
|
|
279
297
|
}
|
|
280
298
|
}
|
|
281
299
|
}
|
|
282
|
-
|
|
300
|
+
|
|
283
301
|
// Sort by strength (highest first)
|
|
284
302
|
candidates.sort((a, b) => b.strength - a.strength);
|
|
285
|
-
|
|
303
|
+
|
|
286
304
|
return candidates;
|
|
287
305
|
}
|
package/src/types.ts
CHANGED
|
@@ -46,18 +46,18 @@ export interface ContextAnalysisResult {
|
|
|
46
46
|
* Classification of file type for analysis context
|
|
47
47
|
* Helps distinguish real issues from false positives
|
|
48
48
|
*/
|
|
49
|
-
export type FileClassification =
|
|
50
|
-
| 'barrel-export'
|
|
51
|
-
| 'type-definition'
|
|
52
|
-
| 'cohesive-module'
|
|
53
|
-
| 'utility-module'
|
|
54
|
-
| 'service-file'
|
|
55
|
-
| 'lambda-handler'
|
|
56
|
-
| 'email-template'
|
|
57
|
-
| 'parser-file'
|
|
58
|
-
| 'nextjs-page'
|
|
59
|
-
| 'mixed-concerns'
|
|
60
|
-
| 'unknown';
|
|
49
|
+
export type FileClassification =
|
|
50
|
+
| 'barrel-export' // Re-exports from other modules (index.ts files)
|
|
51
|
+
| 'type-definition' // Primarily type/interface definitions
|
|
52
|
+
| 'cohesive-module' // Single domain, high cohesion (acceptable large files)
|
|
53
|
+
| 'utility-module' // Utility/helper files with cohesive purpose despite multi-domain
|
|
54
|
+
| 'service-file' // Service files orchestrating multiple dependencies
|
|
55
|
+
| 'lambda-handler' // Lambda/API handlers with single business purpose
|
|
56
|
+
| 'email-template' // Email templates/layouts with structural cohesion
|
|
57
|
+
| 'parser-file' // Parser/transformer files with single transformation purpose
|
|
58
|
+
| 'nextjs-page' // Next.js App Router page with SEO/structured data exports
|
|
59
|
+
| 'mixed-concerns' // Multiple domains, potential refactoring candidate
|
|
60
|
+
| 'unknown'; // Unable to classify
|
|
61
61
|
|
|
62
62
|
export interface ModuleCluster {
|
|
63
63
|
domain: string; // e.g., "user-management", "auth"
|
|
@@ -159,4 +159,3 @@ export interface TypeDependency {
|
|
|
159
159
|
definedIn: string; // file where type is defined
|
|
160
160
|
usedBy: string[]; // files that reference this type
|
|
161
161
|
}
|
|
162
|
-
|