@aiready/context-analyzer 0.5.3 → 0.7.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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-test.log +12 -28
- package/README.md +5 -3
- package/SEMANTIC-VALIDATION.md +235 -0
- package/dist/chunk-AEK3MZC5.mjs +709 -0
- package/dist/chunk-DD7UVNE3.mjs +678 -0
- package/dist/chunk-DMRZMS2U.mjs +964 -0
- package/dist/chunk-HQNHM2X7.mjs +997 -0
- package/dist/chunk-I54HL4FZ.mjs +781 -0
- package/dist/chunk-IRWCPDWD.mjs +779 -0
- package/dist/chunk-PVVCCE6W.mjs +755 -0
- package/dist/chunk-RYIB5CWD.mjs +781 -0
- package/dist/cli.js +304 -33
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +90 -1
- package/dist/index.d.ts +90 -1
- package/dist/index.js +381 -35
- package/dist/index.mjs +17 -3
- package/package.json +2 -2
- package/src/__tests__/auto-detection.test.ts +156 -0
- package/src/__tests__/enhanced-cohesion.test.ts +126 -0
- package/src/analyzer.ts +313 -47
- package/src/index.ts +34 -2
- package/src/semantic-analysis.ts +287 -0
- package/src/types.ts +36 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { DependencyGraph, CoUsageData, TypeDependency, DomainAssignment, DomainSignals } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build co-usage matrix: track which files are imported together
|
|
5
|
+
*
|
|
6
|
+
* Files frequently imported together likely belong to the same semantic domain
|
|
7
|
+
*/
|
|
8
|
+
export function buildCoUsageMatrix(graph: DependencyGraph): Map<string, Map<string, number>> {
|
|
9
|
+
const coUsageMatrix = new Map<string, Map<string, number>>();
|
|
10
|
+
|
|
11
|
+
// For each file, track which other files are imported alongside it
|
|
12
|
+
for (const [sourceFile, node] of graph.nodes) {
|
|
13
|
+
const imports = node.imports;
|
|
14
|
+
|
|
15
|
+
// For each pair of imports in this file, increment their co-usage count
|
|
16
|
+
for (let i = 0; i < imports.length; i++) {
|
|
17
|
+
const fileA = imports[i];
|
|
18
|
+
|
|
19
|
+
if (!coUsageMatrix.has(fileA)) {
|
|
20
|
+
coUsageMatrix.set(fileA, new Map());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (let j = i + 1; j < imports.length; j++) {
|
|
24
|
+
const fileB = imports[j];
|
|
25
|
+
|
|
26
|
+
// Increment bidirectional co-usage count
|
|
27
|
+
const fileAUsage = coUsageMatrix.get(fileA)!;
|
|
28
|
+
fileAUsage.set(fileB, (fileAUsage.get(fileB) || 0) + 1);
|
|
29
|
+
|
|
30
|
+
if (!coUsageMatrix.has(fileB)) {
|
|
31
|
+
coUsageMatrix.set(fileB, new Map());
|
|
32
|
+
}
|
|
33
|
+
const fileBUsage = coUsageMatrix.get(fileB)!;
|
|
34
|
+
fileBUsage.set(fileA, (fileBUsage.get(fileA) || 0) + 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return coUsageMatrix;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract type dependencies from AST exports
|
|
44
|
+
*
|
|
45
|
+
* Files that share types are semantically related
|
|
46
|
+
*/
|
|
47
|
+
export function buildTypeGraph(graph: DependencyGraph): Map<string, Set<string>> {
|
|
48
|
+
const typeGraph = new Map<string, Set<string>>();
|
|
49
|
+
|
|
50
|
+
for (const [file, node] of graph.nodes) {
|
|
51
|
+
for (const exp of node.exports) {
|
|
52
|
+
if (exp.typeReferences) {
|
|
53
|
+
for (const typeRef of exp.typeReferences) {
|
|
54
|
+
if (!typeGraph.has(typeRef)) {
|
|
55
|
+
typeGraph.set(typeRef, new Set());
|
|
56
|
+
}
|
|
57
|
+
typeGraph.get(typeRef)!.add(file);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return typeGraph;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find semantic clusters using co-usage patterns
|
|
68
|
+
*
|
|
69
|
+
* Files with high co-usage counts belong in the same cluster
|
|
70
|
+
*/
|
|
71
|
+
export function findSemanticClusters(
|
|
72
|
+
coUsageMatrix: Map<string, Map<string, number>>,
|
|
73
|
+
minCoUsage: number = 3
|
|
74
|
+
): Map<string, string[]> {
|
|
75
|
+
const clusters = new Map<string, string[]>();
|
|
76
|
+
const visited = new Set<string>();
|
|
77
|
+
|
|
78
|
+
// Simple clustering: group files with high co-usage
|
|
79
|
+
for (const [file, coUsages] of coUsageMatrix) {
|
|
80
|
+
if (visited.has(file)) continue;
|
|
81
|
+
|
|
82
|
+
const cluster: string[] = [file];
|
|
83
|
+
visited.add(file);
|
|
84
|
+
|
|
85
|
+
// Find strongly related files (co-imported >= minCoUsage times)
|
|
86
|
+
for (const [relatedFile, count] of coUsages) {
|
|
87
|
+
if (count >= minCoUsage && !visited.has(relatedFile)) {
|
|
88
|
+
cluster.push(relatedFile);
|
|
89
|
+
visited.add(relatedFile);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (cluster.length > 1) {
|
|
94
|
+
// Use first file as cluster ID
|
|
95
|
+
clusters.set(file, cluster);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return clusters;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Calculate confidence score for domain assignment based on multiple signals
|
|
104
|
+
*/
|
|
105
|
+
export function calculateDomainConfidence(signals: DomainSignals): number {
|
|
106
|
+
const weights = {
|
|
107
|
+
coUsage: 0.35, // Strongest signal: actual usage patterns
|
|
108
|
+
typeReference: 0.30, // Strong signal: shared types
|
|
109
|
+
exportName: 0.15, // Medium signal: identifier semantics
|
|
110
|
+
importPath: 0.10, // Weaker signal: path structure
|
|
111
|
+
folderStructure: 0.10 // Weakest signal: organization convention
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
let confidence = 0;
|
|
115
|
+
if (signals.coUsage) confidence += weights.coUsage;
|
|
116
|
+
if (signals.typeReference) confidence += weights.typeReference;
|
|
117
|
+
if (signals.exportName) confidence += weights.exportName;
|
|
118
|
+
if (signals.importPath) confidence += weights.importPath;
|
|
119
|
+
if (signals.folderStructure) confidence += weights.folderStructure;
|
|
120
|
+
|
|
121
|
+
return confidence;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Infer domain from semantic analysis (co-usage + types)
|
|
126
|
+
*
|
|
127
|
+
* This replaces the folder-based heuristic with actual code relationships
|
|
128
|
+
*/
|
|
129
|
+
export function inferDomainFromSemantics(
|
|
130
|
+
file: string,
|
|
131
|
+
exportName: string,
|
|
132
|
+
graph: DependencyGraph,
|
|
133
|
+
coUsageMatrix: Map<string, Map<string, number>>,
|
|
134
|
+
typeGraph: Map<string, Set<string>>,
|
|
135
|
+
exportTypeRefs?: string[]
|
|
136
|
+
): DomainAssignment[] {
|
|
137
|
+
const assignments: DomainAssignment[] = [];
|
|
138
|
+
const domainSignals = new Map<string, DomainSignals>();
|
|
139
|
+
|
|
140
|
+
// 1. Check co-usage patterns
|
|
141
|
+
const coUsages = coUsageMatrix.get(file) || new Map();
|
|
142
|
+
const strongCoUsages = Array.from(coUsages.entries())
|
|
143
|
+
.filter(([_, count]) => count >= 3)
|
|
144
|
+
.map(([coFile]) => coFile);
|
|
145
|
+
|
|
146
|
+
// Extract domains from frequently co-imported files
|
|
147
|
+
for (const coFile of strongCoUsages) {
|
|
148
|
+
const coNode = graph.nodes.get(coFile);
|
|
149
|
+
if (coNode) {
|
|
150
|
+
for (const exp of coNode.exports) {
|
|
151
|
+
if (exp.inferredDomain && exp.inferredDomain !== 'unknown') {
|
|
152
|
+
const domain = exp.inferredDomain;
|
|
153
|
+
if (!domainSignals.has(domain)) {
|
|
154
|
+
domainSignals.set(domain, {
|
|
155
|
+
coUsage: false,
|
|
156
|
+
typeReference: false,
|
|
157
|
+
exportName: false,
|
|
158
|
+
importPath: false,
|
|
159
|
+
folderStructure: false
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
domainSignals.get(domain)!.coUsage = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 2. Check type references
|
|
169
|
+
if (exportTypeRefs) {
|
|
170
|
+
for (const typeRef of exportTypeRefs) {
|
|
171
|
+
const filesWithType = typeGraph.get(typeRef);
|
|
172
|
+
if (filesWithType) {
|
|
173
|
+
for (const typeFile of filesWithType) {
|
|
174
|
+
if (typeFile !== file) {
|
|
175
|
+
const typeNode = graph.nodes.get(typeFile);
|
|
176
|
+
if (typeNode) {
|
|
177
|
+
for (const exp of typeNode.exports) {
|
|
178
|
+
if (exp.inferredDomain && exp.inferredDomain !== 'unknown') {
|
|
179
|
+
const domain = exp.inferredDomain;
|
|
180
|
+
if (!domainSignals.has(domain)) {
|
|
181
|
+
domainSignals.set(domain, {
|
|
182
|
+
coUsage: false,
|
|
183
|
+
typeReference: false,
|
|
184
|
+
exportName: false,
|
|
185
|
+
importPath: false,
|
|
186
|
+
folderStructure: false
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
domainSignals.get(domain)!.typeReference = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 3. Build domain assignments with confidence scores
|
|
200
|
+
for (const [domain, signals] of domainSignals) {
|
|
201
|
+
const confidence = calculateDomainConfidence(signals);
|
|
202
|
+
if (confidence >= 0.3) { // Minimum confidence threshold
|
|
203
|
+
assignments.push({ domain, confidence, signals });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Sort by confidence (highest first)
|
|
208
|
+
assignments.sort((a, b) => b.confidence - a.confidence);
|
|
209
|
+
|
|
210
|
+
return assignments;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get co-usage data for a specific file
|
|
215
|
+
*/
|
|
216
|
+
export function getCoUsageData(
|
|
217
|
+
file: string,
|
|
218
|
+
coUsageMatrix: Map<string, Map<string, number>>
|
|
219
|
+
): CoUsageData {
|
|
220
|
+
const coImportedWith = coUsageMatrix.get(file) || new Map();
|
|
221
|
+
|
|
222
|
+
// Find files that import both this file and others
|
|
223
|
+
const sharedImporters: string[] = [];
|
|
224
|
+
// This would require inverse mapping from imports, simplified for now
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
file,
|
|
228
|
+
coImportedWith,
|
|
229
|
+
sharedImporters
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Find files that should be consolidated based on semantic similarity
|
|
235
|
+
*
|
|
236
|
+
* High co-usage + shared types = strong consolidation candidate
|
|
237
|
+
*/
|
|
238
|
+
export function findConsolidationCandidates(
|
|
239
|
+
graph: DependencyGraph,
|
|
240
|
+
coUsageMatrix: Map<string, Map<string, number>>,
|
|
241
|
+
typeGraph: Map<string, Set<string>>,
|
|
242
|
+
minCoUsage: number = 5,
|
|
243
|
+
minSharedTypes: number = 2
|
|
244
|
+
): Array<{ files: string[]; reason: string; strength: number }> {
|
|
245
|
+
const candidates: Array<{ files: string[]; reason: string; strength: number }> = [];
|
|
246
|
+
|
|
247
|
+
// Find file pairs with both high co-usage AND shared types
|
|
248
|
+
for (const [fileA, coUsages] of coUsageMatrix) {
|
|
249
|
+
const nodeA = graph.nodes.get(fileA);
|
|
250
|
+
if (!nodeA) continue;
|
|
251
|
+
|
|
252
|
+
for (const [fileB, coUsageCount] of coUsages) {
|
|
253
|
+
if (fileB <= fileA) continue; // Avoid duplicates
|
|
254
|
+
if (coUsageCount < minCoUsage) continue;
|
|
255
|
+
|
|
256
|
+
const nodeB = graph.nodes.get(fileB);
|
|
257
|
+
if (!nodeB) continue;
|
|
258
|
+
|
|
259
|
+
// Count shared types
|
|
260
|
+
const typesA = new Set(nodeA.exports.flatMap(e => e.typeReferences || []));
|
|
261
|
+
const typesB = new Set(nodeB.exports.flatMap(e => e.typeReferences || []));
|
|
262
|
+
const sharedTypes = Array.from(typesA).filter(t => typesB.has(t));
|
|
263
|
+
|
|
264
|
+
if (sharedTypes.length >= minSharedTypes) {
|
|
265
|
+
const strength = (coUsageCount / 10) + (sharedTypes.length / 5);
|
|
266
|
+
candidates.push({
|
|
267
|
+
files: [fileA, fileB],
|
|
268
|
+
reason: `High co-usage (${coUsageCount}x) and ${sharedTypes.length} shared types`,
|
|
269
|
+
strength
|
|
270
|
+
});
|
|
271
|
+
} else if (coUsageCount >= minCoUsage * 2) {
|
|
272
|
+
// Very high co-usage alone is enough
|
|
273
|
+
const strength = coUsageCount / 10;
|
|
274
|
+
candidates.push({
|
|
275
|
+
files: [fileA, fileB],
|
|
276
|
+
reason: `Very high co-usage (${coUsageCount}x)`,
|
|
277
|
+
strength
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Sort by strength (highest first)
|
|
284
|
+
candidates.sort((a, b) => b.strength - a.strength);
|
|
285
|
+
|
|
286
|
+
return candidates;
|
|
287
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -87,6 +87,8 @@ export interface ContextSummary {
|
|
|
87
87
|
export interface DependencyGraph {
|
|
88
88
|
nodes: Map<string, DependencyNode>;
|
|
89
89
|
edges: Map<string, Set<string>>; // file -> dependencies
|
|
90
|
+
coUsageMatrix?: Map<string, Map<string, number>>; // file -> file -> co-usage count
|
|
91
|
+
typeGraph?: Map<string, Set<string>>; // type -> files that reference it
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export interface DependencyNode {
|
|
@@ -95,10 +97,43 @@ export interface DependencyNode {
|
|
|
95
97
|
exports: ExportInfo[];
|
|
96
98
|
tokenCost: number;
|
|
97
99
|
linesOfCode: number;
|
|
100
|
+
exportedBy?: string[]; // Files that import exports from this file
|
|
101
|
+
sharedTypes?: string[]; // Types shared with other files
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
export interface ExportInfo {
|
|
101
105
|
name: string;
|
|
102
106
|
type: 'function' | 'class' | 'const' | 'type' | 'interface' | 'default';
|
|
103
|
-
inferredDomain?: string; // Inferred from name/usage
|
|
107
|
+
inferredDomain?: string; // Inferred from name/usage (legacy single domain)
|
|
108
|
+
domains?: DomainAssignment[]; // Multi-domain support with confidence scores
|
|
109
|
+
imports?: string[]; // Imports used by this export (for import-based cohesion)
|
|
110
|
+
dependencies?: string[]; // Other exports from same file this depends on
|
|
111
|
+
typeReferences?: string[]; // TypeScript types referenced by this export
|
|
104
112
|
}
|
|
113
|
+
|
|
114
|
+
export interface DomainAssignment {
|
|
115
|
+
domain: string;
|
|
116
|
+
confidence: number; // 0-1, how confident are we in this assignment
|
|
117
|
+
signals: DomainSignals; // Which signals contributed to this assignment
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface DomainSignals {
|
|
121
|
+
folderStructure: boolean; // Matched from folder name
|
|
122
|
+
importPath: boolean; // Matched from import paths
|
|
123
|
+
typeReference: boolean; // Matched from TypeScript type usage
|
|
124
|
+
coUsage: boolean; // Matched from co-usage patterns
|
|
125
|
+
exportName: boolean; // Matched from export identifier name
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface CoUsageData {
|
|
129
|
+
file: string;
|
|
130
|
+
coImportedWith: Map<string, number>; // file -> count of times imported together
|
|
131
|
+
sharedImporters: string[]; // files that import both this and another file
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface TypeDependency {
|
|
135
|
+
typeName: string;
|
|
136
|
+
definedIn: string; // file where type is defined
|
|
137
|
+
usedBy: string[]; // files that reference this type
|
|
138
|
+
}
|
|
139
|
+
|