@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.
@@ -1,32 +1,39 @@
1
- import type { DependencyGraph, CoUsageData, TypeDependency, DomainAssignment, DomainSignals } from './types';
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(graph: DependencyGraph): Map<string, Map<string, number>> {
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(graph: DependencyGraph): Map<string, Set<string>> {
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, // 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
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) { // Minimum confidence threshold
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<{ files: string[]; reason: string; strength: number }> = [];
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(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
-
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 = (coUsageCount / 10) + (sharedTypes.length / 5);
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' // 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
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
-