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