@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.
@@ -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
+