@aiready/context-analyzer 0.6.0 → 0.7.1

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/dist/cli.js CHANGED
@@ -31,12 +31,176 @@ var import_core2 = require("@aiready/core");
31
31
 
32
32
  // src/analyzer.ts
33
33
  var import_core = require("@aiready/core");
34
+
35
+ // src/semantic-analysis.ts
36
+ function buildCoUsageMatrix(graph) {
37
+ const coUsageMatrix = /* @__PURE__ */ new Map();
38
+ for (const [sourceFile, node] of graph.nodes) {
39
+ const imports = node.imports;
40
+ for (let i = 0; i < imports.length; i++) {
41
+ const fileA = imports[i];
42
+ if (!coUsageMatrix.has(fileA)) {
43
+ coUsageMatrix.set(fileA, /* @__PURE__ */ new Map());
44
+ }
45
+ for (let j = i + 1; j < imports.length; j++) {
46
+ const fileB = imports[j];
47
+ const fileAUsage = coUsageMatrix.get(fileA);
48
+ fileAUsage.set(fileB, (fileAUsage.get(fileB) || 0) + 1);
49
+ if (!coUsageMatrix.has(fileB)) {
50
+ coUsageMatrix.set(fileB, /* @__PURE__ */ new Map());
51
+ }
52
+ const fileBUsage = coUsageMatrix.get(fileB);
53
+ fileBUsage.set(fileA, (fileBUsage.get(fileA) || 0) + 1);
54
+ }
55
+ }
56
+ }
57
+ return coUsageMatrix;
58
+ }
59
+ function buildTypeGraph(graph) {
60
+ const typeGraph = /* @__PURE__ */ new Map();
61
+ for (const [file, node] of graph.nodes) {
62
+ for (const exp of node.exports) {
63
+ if (exp.typeReferences) {
64
+ for (const typeRef of exp.typeReferences) {
65
+ if (!typeGraph.has(typeRef)) {
66
+ typeGraph.set(typeRef, /* @__PURE__ */ new Set());
67
+ }
68
+ typeGraph.get(typeRef).add(file);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return typeGraph;
74
+ }
75
+ function calculateDomainConfidence(signals) {
76
+ const weights = {
77
+ coUsage: 0.35,
78
+ // Strongest signal: actual usage patterns
79
+ typeReference: 0.3,
80
+ // Strong signal: shared types
81
+ exportName: 0.15,
82
+ // Medium signal: identifier semantics
83
+ importPath: 0.1,
84
+ // Weaker signal: path structure
85
+ folderStructure: 0.1
86
+ // Weakest signal: organization convention
87
+ };
88
+ let confidence = 0;
89
+ if (signals.coUsage) confidence += weights.coUsage;
90
+ if (signals.typeReference) confidence += weights.typeReference;
91
+ if (signals.exportName) confidence += weights.exportName;
92
+ if (signals.importPath) confidence += weights.importPath;
93
+ if (signals.folderStructure) confidence += weights.folderStructure;
94
+ return confidence;
95
+ }
96
+ function inferDomainFromSemantics(file, exportName, graph, coUsageMatrix, typeGraph, exportTypeRefs) {
97
+ const assignments = [];
98
+ const domainSignals = /* @__PURE__ */ new Map();
99
+ const coUsages = coUsageMatrix.get(file) || /* @__PURE__ */ new Map();
100
+ const strongCoUsages = Array.from(coUsages.entries()).filter(([_, count]) => count >= 3).map(([coFile]) => coFile);
101
+ for (const coFile of strongCoUsages) {
102
+ const coNode = graph.nodes.get(coFile);
103
+ if (coNode) {
104
+ for (const exp of coNode.exports) {
105
+ if (exp.inferredDomain && exp.inferredDomain !== "unknown") {
106
+ const domain = exp.inferredDomain;
107
+ if (!domainSignals.has(domain)) {
108
+ domainSignals.set(domain, {
109
+ coUsage: false,
110
+ typeReference: false,
111
+ exportName: false,
112
+ importPath: false,
113
+ folderStructure: false
114
+ });
115
+ }
116
+ domainSignals.get(domain).coUsage = true;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ if (exportTypeRefs) {
122
+ for (const typeRef of exportTypeRefs) {
123
+ const filesWithType = typeGraph.get(typeRef);
124
+ if (filesWithType) {
125
+ for (const typeFile of filesWithType) {
126
+ if (typeFile !== file) {
127
+ const typeNode = graph.nodes.get(typeFile);
128
+ if (typeNode) {
129
+ for (const exp of typeNode.exports) {
130
+ if (exp.inferredDomain && exp.inferredDomain !== "unknown") {
131
+ const domain = exp.inferredDomain;
132
+ if (!domainSignals.has(domain)) {
133
+ domainSignals.set(domain, {
134
+ coUsage: false,
135
+ typeReference: false,
136
+ exportName: false,
137
+ importPath: false,
138
+ folderStructure: false
139
+ });
140
+ }
141
+ domainSignals.get(domain).typeReference = true;
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ for (const [domain, signals] of domainSignals) {
151
+ const confidence = calculateDomainConfidence(signals);
152
+ if (confidence >= 0.3) {
153
+ assignments.push({ domain, confidence, signals });
154
+ }
155
+ }
156
+ assignments.sort((a, b) => b.confidence - a.confidence);
157
+ return assignments;
158
+ }
159
+
160
+ // src/analyzer.ts
161
+ function extractDomainKeywordsFromPaths(files) {
162
+ const folderNames = /* @__PURE__ */ new Set();
163
+ for (const { file } of files) {
164
+ const segments = file.split("/");
165
+ const skipFolders = /* @__PURE__ */ new Set(["src", "lib", "dist", "build", "node_modules", "test", "tests", "__tests__", "spec", "e2e", "scripts", "components", "utils", "helpers", "util", "helper", "api", "apis"]);
166
+ for (const segment of segments) {
167
+ const normalized = segment.toLowerCase();
168
+ if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
169
+ const singular = singularize(normalized);
170
+ folderNames.add(singular);
171
+ }
172
+ }
173
+ }
174
+ return Array.from(folderNames);
175
+ }
176
+ function singularize(word) {
177
+ const irregulars = {
178
+ people: "person",
179
+ children: "child",
180
+ men: "man",
181
+ women: "woman"
182
+ };
183
+ if (irregulars[word]) {
184
+ return irregulars[word];
185
+ }
186
+ if (word.endsWith("ies")) {
187
+ return word.slice(0, -3) + "y";
188
+ }
189
+ if (word.endsWith("ses")) {
190
+ return word.slice(0, -2);
191
+ }
192
+ if (word.endsWith("s") && word.length > 3) {
193
+ return word.slice(0, -1);
194
+ }
195
+ return word;
196
+ }
34
197
  function buildDependencyGraph(files) {
35
198
  const nodes = /* @__PURE__ */ new Map();
36
199
  const edges = /* @__PURE__ */ new Map();
200
+ const autoDetectedKeywords = extractDomainKeywordsFromPaths(files);
37
201
  for (const { file, content } of files) {
38
202
  const imports = extractImportsFromContent(content);
39
- const exports2 = extractExportsWithAST(content, file);
203
+ const exports2 = extractExportsWithAST(content, file, { domainKeywords: autoDetectedKeywords }, imports);
40
204
  const tokenCost = (0, import_core.estimateTokens)(content);
41
205
  const linesOfCode = content.split("\n").length;
42
206
  nodes.set(file, {
@@ -48,7 +212,28 @@ function buildDependencyGraph(files) {
48
212
  });
49
213
  edges.set(file, new Set(imports));
50
214
  }
51
- return { nodes, edges };
215
+ const graph = { nodes, edges };
216
+ const coUsageMatrix = buildCoUsageMatrix(graph);
217
+ const typeGraph = buildTypeGraph(graph);
218
+ graph.coUsageMatrix = coUsageMatrix;
219
+ graph.typeGraph = typeGraph;
220
+ for (const [file, node] of nodes) {
221
+ for (const exp of node.exports) {
222
+ const semanticAssignments = inferDomainFromSemantics(
223
+ file,
224
+ exp.name,
225
+ graph,
226
+ coUsageMatrix,
227
+ typeGraph,
228
+ exp.typeReferences
229
+ );
230
+ exp.domains = semanticAssignments;
231
+ if (semanticAssignments.length > 0) {
232
+ exp.inferredDomain = semanticAssignments[0].domain;
233
+ }
234
+ }
235
+ }
236
+ return graph;
52
237
  }
53
238
  function extractImportsFromContent(content) {
54
239
  const imports = [];
@@ -64,7 +249,7 @@ function extractImportsFromContent(content) {
64
249
  let match;
65
250
  while ((match = pattern.exec(content)) !== null) {
66
251
  const importPath = match[1];
67
- if (importPath && !importPath.startsWith("@") && !importPath.startsWith("node:")) {
252
+ if (importPath && !importPath.startsWith("node:")) {
68
253
  imports.push(importPath);
69
254
  }
70
255
  }
@@ -204,7 +389,7 @@ function detectModuleClusters(graph) {
204
389
  }
205
390
  return clusters.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
206
391
  }
207
- function extractExports(content) {
392
+ function extractExports(content, filePath, domainOptions, fileImports) {
208
393
  const exports2 = [];
209
394
  const patterns = [
210
395
  /export\s+function\s+(\w+)/g,
@@ -227,15 +412,20 @@ function extractExports(content) {
227
412
  while ((match = pattern.exec(content)) !== null) {
228
413
  const name = match[1] || "default";
229
414
  const type = types[index];
230
- const inferredDomain = inferDomain(name);
415
+ const inferredDomain = inferDomain(name, filePath, domainOptions, fileImports);
231
416
  exports2.push({ name, type, inferredDomain });
232
417
  }
233
418
  });
234
419
  return exports2;
235
420
  }
236
- function inferDomain(name) {
421
+ function inferDomain(name, filePath, domainOptions, fileImports) {
237
422
  const lower = name.toLowerCase();
238
- const domainKeywords = [
423
+ const tokens = Array.from(
424
+ new Set(
425
+ lower.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-z0-9]+/gi, " ").split(" ").filter(Boolean)
426
+ )
427
+ );
428
+ const defaultKeywords = [
239
429
  "authentication",
240
430
  "authorization",
241
431
  "payment",
@@ -252,14 +442,11 @@ function inferDomain(name) {
252
442
  "config",
253
443
  "model",
254
444
  "view",
255
- "auth",
256
- "api",
257
- "helper",
258
- "util"
445
+ "auth"
259
446
  ];
447
+ const domainKeywords = domainOptions?.domainKeywords && domainOptions.domainKeywords.length ? [...domainOptions.domainKeywords, ...defaultKeywords] : defaultKeywords;
260
448
  for (const keyword of domainKeywords) {
261
- const wordBoundaryPattern = new RegExp(`\\b${keyword}\\b`, "i");
262
- if (wordBoundaryPattern.test(name)) {
449
+ if (tokens.includes(keyword)) {
263
450
  return keyword;
264
451
  }
265
452
  }
@@ -268,6 +455,37 @@ function inferDomain(name) {
268
455
  return keyword;
269
456
  }
270
457
  }
458
+ if (fileImports && fileImports.length > 0) {
459
+ for (const importPath of fileImports) {
460
+ const allSegments = importPath.split("/");
461
+ const relevantSegments = allSegments.filter((s) => {
462
+ if (!s) return false;
463
+ if (s === "." || s === "..") return false;
464
+ if (s.startsWith("@") && s.length === 1) return false;
465
+ return true;
466
+ }).map((s) => s.startsWith("@") ? s.slice(1) : s);
467
+ for (const segment of relevantSegments) {
468
+ const segLower = segment.toLowerCase();
469
+ const singularSegment = singularize(segLower);
470
+ for (const keyword of domainKeywords) {
471
+ if (singularSegment === keyword || segLower === keyword || segLower.includes(keyword)) {
472
+ return keyword;
473
+ }
474
+ }
475
+ }
476
+ }
477
+ }
478
+ if (filePath) {
479
+ const pathSegments = filePath.toLowerCase().split("/");
480
+ for (const segment of pathSegments) {
481
+ const singularSegment = singularize(segment);
482
+ for (const keyword of domainKeywords) {
483
+ if (singularSegment === keyword || segment === keyword || segment.includes(keyword)) {
484
+ return keyword;
485
+ }
486
+ }
487
+ }
488
+ }
271
489
  return "unknown";
272
490
  }
273
491
  function generateConsolidationPlan(domain, files, targetFiles) {
@@ -296,18 +514,18 @@ function generateConsolidationPlan(domain, files, targetFiles) {
296
514
  );
297
515
  return plan;
298
516
  }
299
- function extractExportsWithAST(content, filePath) {
517
+ function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
300
518
  try {
301
519
  const { exports: astExports } = (0, import_core.parseFileExports)(content, filePath);
302
520
  return astExports.map((exp) => ({
303
521
  name: exp.name,
304
522
  type: exp.type,
305
- inferredDomain: inferDomain(exp.name),
523
+ inferredDomain: inferDomain(exp.name, filePath, domainOptions, fileImports),
306
524
  imports: exp.imports,
307
525
  dependencies: exp.dependencies
308
526
  }));
309
527
  } catch (error) {
310
- return extractExports(content);
528
+ return extractExports(content, filePath, domainOptions, fileImports);
311
529
  }
312
530
  }
313
531
  function calculateEnhancedCohesion(exports2, filePath) {
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeContext,
4
4
  generateSummary
5
- } from "./chunk-DD7UVNE3.mjs";
5
+ } from "./chunk-DMRZMS2U.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.d.mts CHANGED
@@ -66,6 +66,95 @@ interface ContextSummary {
66
66
  severity: string;
67
67
  }>;
68
68
  }
69
+ interface DependencyGraph {
70
+ nodes: Map<string, DependencyNode>;
71
+ edges: Map<string, Set<string>>;
72
+ coUsageMatrix?: Map<string, Map<string, number>>;
73
+ typeGraph?: Map<string, Set<string>>;
74
+ }
75
+ interface DependencyNode {
76
+ file: string;
77
+ imports: string[];
78
+ exports: ExportInfo[];
79
+ tokenCost: number;
80
+ linesOfCode: number;
81
+ exportedBy?: string[];
82
+ sharedTypes?: string[];
83
+ }
84
+ interface ExportInfo {
85
+ name: string;
86
+ type: 'function' | 'class' | 'const' | 'type' | 'interface' | 'default';
87
+ inferredDomain?: string;
88
+ domains?: DomainAssignment[];
89
+ imports?: string[];
90
+ dependencies?: string[];
91
+ typeReferences?: string[];
92
+ }
93
+ interface DomainAssignment {
94
+ domain: string;
95
+ confidence: number;
96
+ signals: DomainSignals;
97
+ }
98
+ interface DomainSignals {
99
+ folderStructure: boolean;
100
+ importPath: boolean;
101
+ typeReference: boolean;
102
+ coUsage: boolean;
103
+ exportName: boolean;
104
+ }
105
+ interface CoUsageData {
106
+ file: string;
107
+ coImportedWith: Map<string, number>;
108
+ sharedImporters: string[];
109
+ }
110
+ interface TypeDependency {
111
+ typeName: string;
112
+ definedIn: string;
113
+ usedBy: string[];
114
+ }
115
+
116
+ /**
117
+ * Build co-usage matrix: track which files are imported together
118
+ *
119
+ * Files frequently imported together likely belong to the same semantic domain
120
+ */
121
+ declare function buildCoUsageMatrix(graph: DependencyGraph): Map<string, Map<string, number>>;
122
+ /**
123
+ * Extract type dependencies from AST exports
124
+ *
125
+ * Files that share types are semantically related
126
+ */
127
+ declare function buildTypeGraph(graph: DependencyGraph): Map<string, Set<string>>;
128
+ /**
129
+ * Find semantic clusters using co-usage patterns
130
+ *
131
+ * Files with high co-usage counts belong in the same cluster
132
+ */
133
+ declare function findSemanticClusters(coUsageMatrix: Map<string, Map<string, number>>, minCoUsage?: number): Map<string, string[]>;
134
+ /**
135
+ * Calculate confidence score for domain assignment based on multiple signals
136
+ */
137
+ declare function calculateDomainConfidence(signals: DomainSignals): number;
138
+ /**
139
+ * Infer domain from semantic analysis (co-usage + types)
140
+ *
141
+ * This replaces the folder-based heuristic with actual code relationships
142
+ */
143
+ declare function inferDomainFromSemantics(file: string, exportName: string, graph: DependencyGraph, coUsageMatrix: Map<string, Map<string, number>>, typeGraph: Map<string, Set<string>>, exportTypeRefs?: string[]): DomainAssignment[];
144
+ /**
145
+ * Get co-usage data for a specific file
146
+ */
147
+ declare function getCoUsageData(file: string, coUsageMatrix: Map<string, Map<string, number>>): CoUsageData;
148
+ /**
149
+ * Find files that should be consolidated based on semantic similarity
150
+ *
151
+ * High co-usage + shared types = strong consolidation candidate
152
+ */
153
+ declare function findConsolidationCandidates(graph: DependencyGraph, coUsageMatrix: Map<string, Map<string, number>>, typeGraph: Map<string, Set<string>>, minCoUsage?: number, minSharedTypes?: number): Array<{
154
+ files: string[];
155
+ reason: string;
156
+ strength: number;
157
+ }>;
69
158
 
70
159
  /**
71
160
  * Generate smart defaults for context analysis based on repository size
@@ -81,4 +170,4 @@ declare function analyzeContext(options: ContextAnalyzerOptions): Promise<Contex
81
170
  */
82
171
  declare function generateSummary(results: ContextAnalysisResult[]): ContextSummary;
83
172
 
84
- export { type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type ModuleCluster, analyzeContext, generateSummary, getSmartDefaults };
173
+ export { type CoUsageData, type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type DomainAssignment, type DomainSignals, type ModuleCluster, type TypeDependency, analyzeContext, buildCoUsageMatrix, buildTypeGraph, calculateDomainConfidence, findConsolidationCandidates, findSemanticClusters, generateSummary, getCoUsageData, getSmartDefaults, inferDomainFromSemantics };
package/dist/index.d.ts CHANGED
@@ -66,6 +66,95 @@ interface ContextSummary {
66
66
  severity: string;
67
67
  }>;
68
68
  }
69
+ interface DependencyGraph {
70
+ nodes: Map<string, DependencyNode>;
71
+ edges: Map<string, Set<string>>;
72
+ coUsageMatrix?: Map<string, Map<string, number>>;
73
+ typeGraph?: Map<string, Set<string>>;
74
+ }
75
+ interface DependencyNode {
76
+ file: string;
77
+ imports: string[];
78
+ exports: ExportInfo[];
79
+ tokenCost: number;
80
+ linesOfCode: number;
81
+ exportedBy?: string[];
82
+ sharedTypes?: string[];
83
+ }
84
+ interface ExportInfo {
85
+ name: string;
86
+ type: 'function' | 'class' | 'const' | 'type' | 'interface' | 'default';
87
+ inferredDomain?: string;
88
+ domains?: DomainAssignment[];
89
+ imports?: string[];
90
+ dependencies?: string[];
91
+ typeReferences?: string[];
92
+ }
93
+ interface DomainAssignment {
94
+ domain: string;
95
+ confidence: number;
96
+ signals: DomainSignals;
97
+ }
98
+ interface DomainSignals {
99
+ folderStructure: boolean;
100
+ importPath: boolean;
101
+ typeReference: boolean;
102
+ coUsage: boolean;
103
+ exportName: boolean;
104
+ }
105
+ interface CoUsageData {
106
+ file: string;
107
+ coImportedWith: Map<string, number>;
108
+ sharedImporters: string[];
109
+ }
110
+ interface TypeDependency {
111
+ typeName: string;
112
+ definedIn: string;
113
+ usedBy: string[];
114
+ }
115
+
116
+ /**
117
+ * Build co-usage matrix: track which files are imported together
118
+ *
119
+ * Files frequently imported together likely belong to the same semantic domain
120
+ */
121
+ declare function buildCoUsageMatrix(graph: DependencyGraph): Map<string, Map<string, number>>;
122
+ /**
123
+ * Extract type dependencies from AST exports
124
+ *
125
+ * Files that share types are semantically related
126
+ */
127
+ declare function buildTypeGraph(graph: DependencyGraph): Map<string, Set<string>>;
128
+ /**
129
+ * Find semantic clusters using co-usage patterns
130
+ *
131
+ * Files with high co-usage counts belong in the same cluster
132
+ */
133
+ declare function findSemanticClusters(coUsageMatrix: Map<string, Map<string, number>>, minCoUsage?: number): Map<string, string[]>;
134
+ /**
135
+ * Calculate confidence score for domain assignment based on multiple signals
136
+ */
137
+ declare function calculateDomainConfidence(signals: DomainSignals): number;
138
+ /**
139
+ * Infer domain from semantic analysis (co-usage + types)
140
+ *
141
+ * This replaces the folder-based heuristic with actual code relationships
142
+ */
143
+ declare function inferDomainFromSemantics(file: string, exportName: string, graph: DependencyGraph, coUsageMatrix: Map<string, Map<string, number>>, typeGraph: Map<string, Set<string>>, exportTypeRefs?: string[]): DomainAssignment[];
144
+ /**
145
+ * Get co-usage data for a specific file
146
+ */
147
+ declare function getCoUsageData(file: string, coUsageMatrix: Map<string, Map<string, number>>): CoUsageData;
148
+ /**
149
+ * Find files that should be consolidated based on semantic similarity
150
+ *
151
+ * High co-usage + shared types = strong consolidation candidate
152
+ */
153
+ declare function findConsolidationCandidates(graph: DependencyGraph, coUsageMatrix: Map<string, Map<string, number>>, typeGraph: Map<string, Set<string>>, minCoUsage?: number, minSharedTypes?: number): Array<{
154
+ files: string[];
155
+ reason: string;
156
+ strength: number;
157
+ }>;
69
158
 
70
159
  /**
71
160
  * Generate smart defaults for context analysis based on repository size
@@ -81,4 +170,4 @@ declare function analyzeContext(options: ContextAnalyzerOptions): Promise<Contex
81
170
  */
82
171
  declare function generateSummary(results: ContextAnalysisResult[]): ContextSummary;
83
172
 
84
- export { type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type ModuleCluster, analyzeContext, generateSummary, getSmartDefaults };
173
+ export { type CoUsageData, type ContextAnalysisResult, type ContextAnalyzerOptions, type ContextSummary, type DomainAssignment, type DomainSignals, type ModuleCluster, type TypeDependency, analyzeContext, buildCoUsageMatrix, buildTypeGraph, calculateDomainConfidence, findConsolidationCandidates, findSemanticClusters, generateSummary, getCoUsageData, getSmartDefaults, inferDomainFromSemantics };