@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.
@@ -1,32 +1,40 @@
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) {
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(graph: DependencyGraph): Map<string, Set<string>> {
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, // 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
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(([_, count]) => count >= 3)
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) { // Minimum confidence threshold
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<{ files: string[]; reason: string; strength: number }> = [];
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(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
-
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 = (coUsageCount / 10) + (sharedTypes.length / 5);
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' // 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
-