@aiready/context-analyzer 0.9.41 → 0.16.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.
Files changed (53) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/.turbo/turbo-test.log +21 -20
  3. package/dist/chunk-4SYIJ7CU.mjs +1538 -0
  4. package/dist/chunk-4XQVYYPC.mjs +1470 -0
  5. package/dist/chunk-5CLU3HYU.mjs +1475 -0
  6. package/dist/chunk-5K73Q3OQ.mjs +1520 -0
  7. package/dist/chunk-6AVS4KTM.mjs +1536 -0
  8. package/dist/chunk-6I4552YB.mjs +1467 -0
  9. package/dist/chunk-6LPITDKG.mjs +1539 -0
  10. package/dist/chunk-AECWO7NQ.mjs +1539 -0
  11. package/dist/chunk-AJC3FR6G.mjs +1509 -0
  12. package/dist/chunk-CVGIDSMN.mjs +1522 -0
  13. package/dist/chunk-DXG5NIYL.mjs +1527 -0
  14. package/dist/chunk-G3CCJCBI.mjs +1521 -0
  15. package/dist/chunk-GFADGYXZ.mjs +1752 -0
  16. package/dist/chunk-GTRIBVS6.mjs +1467 -0
  17. package/dist/chunk-H4HWBQU6.mjs +1530 -0
  18. package/dist/chunk-JH535NPP.mjs +1619 -0
  19. package/dist/chunk-KGFWKSGJ.mjs +1442 -0
  20. package/dist/chunk-N2GQWNFG.mjs +1527 -0
  21. package/dist/chunk-NQA3F2HJ.mjs +1532 -0
  22. package/dist/chunk-NXXQ2U73.mjs +1467 -0
  23. package/dist/chunk-QDGPR3L6.mjs +1518 -0
  24. package/dist/chunk-SAVOSPM3.mjs +1522 -0
  25. package/dist/chunk-SIX4KMF2.mjs +1468 -0
  26. package/dist/chunk-SPAM2YJE.mjs +1537 -0
  27. package/dist/chunk-UG7OPVHB.mjs +1521 -0
  28. package/dist/chunk-VIJTZPBI.mjs +1470 -0
  29. package/dist/chunk-W37E7MW5.mjs +1403 -0
  30. package/dist/chunk-W76FEISE.mjs +1538 -0
  31. package/dist/chunk-WCFQYXQA.mjs +1532 -0
  32. package/dist/chunk-XY77XABG.mjs +1545 -0
  33. package/dist/chunk-YCGDIGOG.mjs +1467 -0
  34. package/dist/cli.js +768 -1160
  35. package/dist/cli.mjs +1 -1
  36. package/dist/index.d.mts +196 -64
  37. package/dist/index.d.ts +196 -64
  38. package/dist/index.js +937 -1209
  39. package/dist/index.mjs +65 -3
  40. package/package.json +2 -2
  41. package/src/__tests__/contract.test.ts +38 -0
  42. package/src/analyzer.ts +143 -2177
  43. package/src/ast-utils.ts +94 -0
  44. package/src/classifier.ts +497 -0
  45. package/src/cluster-detector.ts +100 -0
  46. package/src/defaults.ts +59 -0
  47. package/src/graph-builder.ts +272 -0
  48. package/src/index.ts +30 -519
  49. package/src/metrics.ts +231 -0
  50. package/src/remediation.ts +139 -0
  51. package/src/scoring.ts +12 -34
  52. package/src/semantic-analysis.ts +192 -126
  53. package/src/summary.ts +168 -0
package/dist/cli.js CHANGED
@@ -37,7 +37,7 @@ __export(python_context_exports, {
37
37
  });
38
38
  async function analyzePythonContext(files, rootDir) {
39
39
  const results = [];
40
- const parser = (0, import_core3.getParser)("dummy.py");
40
+ const parser = (0, import_core6.getParser)("dummy.py");
41
41
  if (!parser) {
42
42
  console.warn("Python parser not available");
43
43
  return results;
@@ -101,7 +101,7 @@ async function analyzePythonContext(files, rootDir) {
101
101
  }
102
102
  async function buildPythonDependencyGraph(files, rootDir) {
103
103
  const graph = /* @__PURE__ */ new Map();
104
- const parser = (0, import_core3.getParser)("dummy.py");
104
+ const parser = (0, import_core6.getParser)("dummy.py");
105
105
  if (!parser) return graph;
106
106
  for (const file of files) {
107
107
  try {
@@ -181,7 +181,7 @@ async function calculatePythonImportDepth(file, dependencyGraph, visited, depth
181
181
  }
182
182
  function estimateContextBudget(code, imports, dependencyGraph) {
183
183
  void dependencyGraph;
184
- let budget = (0, import_core3.estimateTokens)(code);
184
+ let budget = (0, import_core6.estimateTokens)(code);
185
185
  const avgTokensPerDep = 500;
186
186
  budget += imports.length * avgTokensPerDep;
187
187
  return budget;
@@ -231,11 +231,11 @@ function detectCircularDependencies2(file, dependencyGraph) {
231
231
  dfs(file, []);
232
232
  return [...new Set(circular)];
233
233
  }
234
- var import_core3, import_path, import_fs;
234
+ var import_core6, import_path, import_fs;
235
235
  var init_python_context = __esm({
236
236
  "src/analyzers/python-context.ts"() {
237
237
  "use strict";
238
- import_core3 = require("@aiready/core");
238
+ import_core6 = require("@aiready/core");
239
239
  import_path = require("path");
240
240
  import_fs = __toESM(require("fs"));
241
241
  }
@@ -245,29 +245,27 @@ var init_python_context = __esm({
245
245
  var import_commander = require("commander");
246
246
 
247
247
  // src/index.ts
248
- var import_core4 = require("@aiready/core");
248
+ var import_core7 = require("@aiready/core");
249
249
 
250
- // src/analyzer.ts
250
+ // src/metrics.ts
251
+ var import_core2 = require("@aiready/core");
252
+
253
+ // src/ast-utils.ts
251
254
  var import_core = require("@aiready/core");
252
255
 
253
256
  // src/semantic-analysis.ts
254
257
  function buildCoUsageMatrix(graph) {
255
258
  const coUsageMatrix = /* @__PURE__ */ new Map();
256
- for (const [sourceFile, node] of graph.nodes) {
257
- void sourceFile;
259
+ for (const [, node] of graph.nodes) {
258
260
  const imports = node.imports;
259
261
  for (let i = 0; i < imports.length; i++) {
260
262
  const fileA = imports[i];
261
- if (!coUsageMatrix.has(fileA)) {
262
- coUsageMatrix.set(fileA, /* @__PURE__ */ new Map());
263
- }
263
+ if (!coUsageMatrix.has(fileA)) coUsageMatrix.set(fileA, /* @__PURE__ */ new Map());
264
264
  for (let j = i + 1; j < imports.length; j++) {
265
265
  const fileB = imports[j];
266
266
  const fileAUsage = coUsageMatrix.get(fileA);
267
267
  fileAUsage.set(fileB, (fileAUsage.get(fileB) || 0) + 1);
268
- if (!coUsageMatrix.has(fileB)) {
269
- coUsageMatrix.set(fileB, /* @__PURE__ */ new Map());
270
- }
268
+ if (!coUsageMatrix.has(fileB)) coUsageMatrix.set(fileB, /* @__PURE__ */ new Map());
271
269
  const fileBUsage = coUsageMatrix.get(fileB);
272
270
  fileBUsage.set(fileA, (fileBUsage.get(fileA) || 0) + 1);
273
271
  }
@@ -281,9 +279,7 @@ function buildTypeGraph(graph) {
281
279
  for (const exp of node.exports) {
282
280
  if (exp.typeReferences) {
283
281
  for (const typeRef of exp.typeReferences) {
284
- if (!typeGraph.has(typeRef)) {
285
- typeGraph.set(typeRef, /* @__PURE__ */ new Set());
286
- }
282
+ if (!typeGraph.has(typeRef)) typeGraph.set(typeRef, /* @__PURE__ */ new Set());
287
283
  typeGraph.get(typeRef).add(file);
288
284
  }
289
285
  }
@@ -291,29 +287,7 @@ function buildTypeGraph(graph) {
291
287
  }
292
288
  return typeGraph;
293
289
  }
294
- function calculateDomainConfidence(signals) {
295
- const weights = {
296
- coUsage: 0.35,
297
- // Strongest signal: actual usage patterns
298
- typeReference: 0.3,
299
- // Strong signal: shared types
300
- exportName: 0.15,
301
- // Medium signal: identifier semantics
302
- importPath: 0.1,
303
- // Weaker signal: path structure
304
- folderStructure: 0.1
305
- // Weakest signal: organization convention
306
- };
307
- let confidence = 0;
308
- if (signals.coUsage) confidence += weights.coUsage;
309
- if (signals.typeReference) confidence += weights.typeReference;
310
- if (signals.exportName) confidence += weights.exportName;
311
- if (signals.importPath) confidence += weights.importPath;
312
- if (signals.folderStructure) confidence += weights.folderStructure;
313
- return confidence;
314
- }
315
290
  function inferDomainFromSemantics(file, exportName, graph, coUsageMatrix, typeGraph, exportTypeRefs) {
316
- const assignments = [];
317
291
  const domainSignals = /* @__PURE__ */ new Map();
318
292
  const coUsages = coUsageMatrix.get(file) || /* @__PURE__ */ new Map();
319
293
  const strongCoUsages = Array.from(coUsages.entries()).filter(([, count]) => count >= 3).map(([coFile]) => coFile);
@@ -342,23 +316,22 @@ function inferDomainFromSemantics(file, exportName, graph, coUsageMatrix, typeGr
342
316
  const filesWithType = typeGraph.get(typeRef);
343
317
  if (filesWithType) {
344
318
  for (const typeFile of filesWithType) {
345
- if (typeFile !== file) {
346
- const typeNode = graph.nodes.get(typeFile);
347
- if (typeNode) {
348
- for (const exp of typeNode.exports) {
349
- if (exp.inferredDomain && exp.inferredDomain !== "unknown") {
350
- const domain = exp.inferredDomain;
351
- if (!domainSignals.has(domain)) {
352
- domainSignals.set(domain, {
353
- coUsage: false,
354
- typeReference: false,
355
- exportName: false,
356
- importPath: false,
357
- folderStructure: false
358
- });
359
- }
360
- domainSignals.get(domain).typeReference = true;
319
+ if (typeFile === file) continue;
320
+ const typeNode = graph.nodes.get(typeFile);
321
+ if (typeNode) {
322
+ for (const exp of typeNode.exports) {
323
+ if (exp.inferredDomain && exp.inferredDomain !== "unknown") {
324
+ const domain = exp.inferredDomain;
325
+ if (!domainSignals.has(domain)) {
326
+ domainSignals.set(domain, {
327
+ coUsage: false,
328
+ typeReference: false,
329
+ exportName: false,
330
+ importPath: false,
331
+ folderStructure: false
332
+ });
361
333
  }
334
+ domainSignals.get(domain).typeReference = true;
362
335
  }
363
336
  }
364
337
  }
@@ -366,17 +339,289 @@ function inferDomainFromSemantics(file, exportName, graph, coUsageMatrix, typeGr
366
339
  }
367
340
  }
368
341
  }
342
+ const assignments = [];
369
343
  for (const [domain, signals] of domainSignals) {
370
344
  const confidence = calculateDomainConfidence(signals);
371
- if (confidence >= 0.3) {
372
- assignments.push({ domain, confidence, signals });
373
- }
345
+ if (confidence >= 0.3) assignments.push({ domain, confidence, signals });
374
346
  }
375
347
  assignments.sort((a, b) => b.confidence - a.confidence);
376
348
  return assignments;
377
349
  }
350
+ function calculateDomainConfidence(signals) {
351
+ const weights = {
352
+ coUsage: 0.35,
353
+ typeReference: 0.3,
354
+ exportName: 0.15,
355
+ importPath: 0.1,
356
+ folderStructure: 0.1
357
+ };
358
+ let confidence = 0;
359
+ if (signals.coUsage) confidence += weights.coUsage;
360
+ if (signals.typeReference) confidence += weights.typeReference;
361
+ if (signals.exportName) confidence += weights.exportName;
362
+ if (signals.importPath) confidence += weights.importPath;
363
+ if (signals.folderStructure) confidence += weights.folderStructure;
364
+ return confidence;
365
+ }
366
+ function extractExports(content, filePath, domainOptions, fileImports) {
367
+ const exports2 = [];
368
+ const patterns = [
369
+ /export\s+function\s+(\w+)/g,
370
+ /export\s+class\s+(\w+)/g,
371
+ /export\s+const\s+(\w+)/g,
372
+ /export\s+type\s+(\w+)/g,
373
+ /export\s+interface\s+(\w+)/g,
374
+ /export\s+default/g
375
+ ];
376
+ const types = [
377
+ "function",
378
+ "class",
379
+ "const",
380
+ "type",
381
+ "interface",
382
+ "default"
383
+ ];
384
+ patterns.forEach((pattern, index) => {
385
+ let match;
386
+ while ((match = pattern.exec(content)) !== null) {
387
+ const name = match[1] || "default";
388
+ const type = types[index];
389
+ const inferredDomain = inferDomain(
390
+ name,
391
+ filePath,
392
+ domainOptions,
393
+ fileImports
394
+ );
395
+ exports2.push({ name, type, inferredDomain });
396
+ }
397
+ });
398
+ return exports2;
399
+ }
400
+ function inferDomain(name, filePath, domainOptions, fileImports) {
401
+ const lower = name.toLowerCase();
402
+ const tokens = Array.from(
403
+ new Set(
404
+ lower.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-z0-9]+/gi, " ").split(" ").filter(Boolean)
405
+ )
406
+ );
407
+ const defaultKeywords = [
408
+ "authentication",
409
+ "authorization",
410
+ "payment",
411
+ "invoice",
412
+ "customer",
413
+ "product",
414
+ "order",
415
+ "cart",
416
+ "user",
417
+ "admin",
418
+ "repository",
419
+ "controller",
420
+ "service",
421
+ "config",
422
+ "model",
423
+ "view",
424
+ "auth"
425
+ ];
426
+ const domainKeywords = domainOptions?.domainKeywords?.length ? [...domainOptions.domainKeywords, ...defaultKeywords] : defaultKeywords;
427
+ for (const keyword of domainKeywords) {
428
+ if (tokens.includes(keyword)) return keyword;
429
+ }
430
+ for (const keyword of domainKeywords) {
431
+ if (lower.includes(keyword)) return keyword;
432
+ }
433
+ if (fileImports) {
434
+ for (const importPath of fileImports) {
435
+ const segments = importPath.split("/");
436
+ for (const segment of segments) {
437
+ const segLower = segment.toLowerCase();
438
+ const singularSegment = singularize(segLower);
439
+ for (const keyword of domainKeywords) {
440
+ if (singularSegment === keyword || segLower === keyword || segLower.includes(keyword))
441
+ return keyword;
442
+ }
443
+ }
444
+ }
445
+ }
446
+ if (filePath) {
447
+ const segments = filePath.split("/");
448
+ for (const segment of segments) {
449
+ const segLower = segment.toLowerCase();
450
+ const singularSegment = singularize(segLower);
451
+ for (const keyword of domainKeywords) {
452
+ if (singularSegment === keyword || segLower === keyword) return keyword;
453
+ }
454
+ }
455
+ }
456
+ return "unknown";
457
+ }
458
+ function singularize(word) {
459
+ const irregulars = {
460
+ people: "person",
461
+ children: "child",
462
+ men: "man",
463
+ women: "woman"
464
+ };
465
+ if (irregulars[word]) return irregulars[word];
466
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
467
+ if (word.endsWith("ses")) return word.slice(0, -2);
468
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
469
+ return word;
470
+ }
378
471
 
379
- // src/analyzer.ts
472
+ // src/ast-utils.ts
473
+ function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
474
+ try {
475
+ const { exports: astExports } = (0, import_core.parseFileExports)(content, filePath);
476
+ return astExports.map((exp) => ({
477
+ name: exp.name,
478
+ type: exp.type,
479
+ inferredDomain: inferDomain(
480
+ exp.name,
481
+ filePath,
482
+ domainOptions,
483
+ fileImports
484
+ ),
485
+ imports: exp.imports,
486
+ dependencies: exp.dependencies,
487
+ typeReferences: exp.typeReferences
488
+ }));
489
+ } catch (error) {
490
+ void error;
491
+ return extractExports(content, filePath, domainOptions, fileImports);
492
+ }
493
+ }
494
+ function isTestFile(filePath) {
495
+ const lower = filePath.toLowerCase();
496
+ return lower.includes(".test.") || lower.includes(".spec.") || lower.includes("/__tests__/") || lower.includes("/tests/") || lower.includes("/test/") || lower.includes("test-") || lower.includes("-test") || lower.includes("/__mocks__/") || lower.includes("/mocks/") || lower.includes("/fixtures/") || lower.includes(".mock.") || lower.includes(".fixture.") || lower.includes("/test-utils/");
497
+ }
498
+
499
+ // src/metrics.ts
500
+ function calculateEnhancedCohesion(exports2, filePath, options) {
501
+ if (exports2.length <= 1) return 1;
502
+ if (filePath && isTestFile(filePath)) return 1;
503
+ const domains = exports2.map((e) => e.inferredDomain || "unknown");
504
+ const domainCounts = /* @__PURE__ */ new Map();
505
+ for (const d of domains) domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
506
+ if (domainCounts.size === 1 && domains[0] !== "unknown") {
507
+ if (!options?.weights) return 1;
508
+ }
509
+ const probs = Array.from(domainCounts.values()).map(
510
+ (c) => c / exports2.length
511
+ );
512
+ let domainEntropy = 0;
513
+ for (const p of probs) {
514
+ if (p > 0) domainEntropy -= p * Math.log2(p);
515
+ }
516
+ const maxEntropy = Math.log2(Math.max(2, domainCounts.size));
517
+ const domainScore = 1 - domainEntropy / maxEntropy;
518
+ let importScoreTotal = 0;
519
+ let pairsWithData = 0;
520
+ let anyImportData = false;
521
+ for (let i = 0; i < exports2.length; i++) {
522
+ for (let j = i + 1; j < exports2.length; j++) {
523
+ const exp1Imports = exports2[i].imports;
524
+ const exp2Imports = exports2[j].imports;
525
+ if (exp1Imports || exp2Imports) {
526
+ anyImportData = true;
527
+ const sim = (0, import_core2.calculateImportSimilarity)(
528
+ { ...exports2[i], imports: exp1Imports || [] },
529
+ { ...exports2[j], imports: exp2Imports || [] }
530
+ );
531
+ importScoreTotal += sim;
532
+ pairsWithData++;
533
+ }
534
+ }
535
+ }
536
+ const avgImportScore = pairsWithData > 0 ? importScoreTotal / pairsWithData : 0;
537
+ let score = 0;
538
+ if (anyImportData) {
539
+ score = domainScore * 0.4 + avgImportScore * 0.6;
540
+ if (score === 0 && domainScore === 0) score = 0.1;
541
+ } else {
542
+ score = domainScore;
543
+ }
544
+ let structuralScore = 0;
545
+ for (const exp of exports2) {
546
+ if (exp.dependencies && exp.dependencies.length > 0) {
547
+ structuralScore += 1;
548
+ }
549
+ }
550
+ if (structuralScore > 0) {
551
+ score = Math.min(1, score + 0.1);
552
+ }
553
+ if (!options?.weights && !anyImportData && domainCounts.size === 1) return 1;
554
+ return score;
555
+ }
556
+ function calculateFragmentation(files, domain, options) {
557
+ if (files.length <= 1) return 0;
558
+ const directories = new Set(
559
+ files.map((f) => f.split("/").slice(0, -1).join("/"))
560
+ );
561
+ const uniqueDirs = directories.size;
562
+ let score = 0;
563
+ if (options?.useLogScale) {
564
+ if (uniqueDirs <= 1) score = 0;
565
+ else {
566
+ const total = files.length;
567
+ const base = options.logBase || Math.E;
568
+ const num = Math.log(uniqueDirs) / Math.log(base);
569
+ const den = Math.log(total) / Math.log(base);
570
+ score = den > 0 ? num / den : 0;
571
+ }
572
+ } else {
573
+ score = (uniqueDirs - 1) / (files.length - 1);
574
+ }
575
+ if (options?.sharedImportRatio && options.sharedImportRatio > 0.5) {
576
+ const discount = (options.sharedImportRatio - 0.5) * 0.4;
577
+ score = score * (1 - discount);
578
+ }
579
+ return score;
580
+ }
581
+ function calculatePathEntropy(files) {
582
+ if (!files || files.length === 0) return 0;
583
+ const dirCounts = /* @__PURE__ */ new Map();
584
+ for (const f of files) {
585
+ const dir = f.split("/").slice(0, -1).join("/") || ".";
586
+ dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
587
+ }
588
+ const counts = Array.from(dirCounts.values());
589
+ if (counts.length <= 1) return 0;
590
+ const total = counts.reduce((s, v) => s + v, 0);
591
+ let entropy = 0;
592
+ for (const count of counts) {
593
+ const prob = count / total;
594
+ entropy -= prob * Math.log2(prob);
595
+ }
596
+ const maxEntropy = Math.log2(counts.length);
597
+ return maxEntropy > 0 ? entropy / maxEntropy : 0;
598
+ }
599
+ function calculateDirectoryDistance(files) {
600
+ if (!files || files.length <= 1) return 0;
601
+ const pathSegments = (p) => p.split("/").filter(Boolean);
602
+ const commonAncestorDepth = (a, b) => {
603
+ const minLen = Math.min(a.length, b.length);
604
+ let i = 0;
605
+ while (i < minLen && a[i] === b[i]) i++;
606
+ return i;
607
+ };
608
+ let totalNormalized = 0;
609
+ let comparisons = 0;
610
+ for (let i = 0; i < files.length; i++) {
611
+ for (let j = i + 1; j < files.length; j++) {
612
+ const segA = pathSegments(files[i]);
613
+ const segB = pathSegments(files[j]);
614
+ const shared = commonAncestorDepth(segA, segB);
615
+ const maxDepth = Math.max(segA.length, segB.length);
616
+ totalNormalized += 1 - (maxDepth > 0 ? shared / maxDepth : 0);
617
+ comparisons++;
618
+ }
619
+ }
620
+ return comparisons > 0 ? totalNormalized / comparisons : 0;
621
+ }
622
+
623
+ // src/graph-builder.ts
624
+ var import_core3 = require("@aiready/core");
380
625
  function extractDomainKeywordsFromPaths(files) {
381
626
  const folderNames = /* @__PURE__ */ new Set();
382
627
  for (const { file } of files) {
@@ -404,39 +649,29 @@ function extractDomainKeywordsFromPaths(files) {
404
649
  for (const segment of segments) {
405
650
  const normalized = segment.toLowerCase();
406
651
  if (normalized && !skipFolders.has(normalized) && !normalized.includes(".")) {
407
- const singular = singularize(normalized);
408
- folderNames.add(singular);
652
+ folderNames.add(singularize2(normalized));
409
653
  }
410
654
  }
411
655
  }
412
656
  return Array.from(folderNames);
413
657
  }
414
- function singularize(word) {
658
+ function singularize2(word) {
415
659
  const irregulars = {
416
660
  people: "person",
417
661
  children: "child",
418
662
  men: "man",
419
663
  women: "woman"
420
664
  };
421
- if (irregulars[word]) {
422
- return irregulars[word];
423
- }
424
- if (word.endsWith("ies")) {
425
- return word.slice(0, -3) + "y";
426
- }
427
- if (word.endsWith("ses")) {
428
- return word.slice(0, -2);
429
- }
430
- if (word.endsWith("s") && word.length > 3) {
431
- return word.slice(0, -1);
432
- }
665
+ if (irregulars[word]) return irregulars[word];
666
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
667
+ if (word.endsWith("ses")) return word.slice(0, -2);
668
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
433
669
  return word;
434
670
  }
435
671
  function buildDependencyGraph(files, options) {
436
672
  const nodes = /* @__PURE__ */ new Map();
437
673
  const edges = /* @__PURE__ */ new Map();
438
674
  const autoDetectedKeywords = options?.domainKeywords ?? extractDomainKeywordsFromPaths(files);
439
- void import_core.calculateImportSimilarity;
440
675
  for (const { file, content } of files) {
441
676
  const imports = extractImportsFromContent(content);
442
677
  const exports2 = extractExportsWithAST(
@@ -445,15 +680,9 @@ function buildDependencyGraph(files, options) {
445
680
  { domainKeywords: autoDetectedKeywords },
446
681
  imports
447
682
  );
448
- const tokenCost = (0, import_core.estimateTokens)(content);
683
+ const tokenCost = (0, import_core3.estimateTokens)(content);
449
684
  const linesOfCode = content.split("\n").length;
450
- nodes.set(file, {
451
- file,
452
- imports,
453
- exports: exports2,
454
- tokenCost,
455
- linesOfCode
456
- });
685
+ nodes.set(file, { file, imports, exports: exports2, tokenCost, linesOfCode });
457
686
  edges.set(file, new Set(imports));
458
687
  }
459
688
  const graph = { nodes, edges };
@@ -483,11 +712,8 @@ function extractImportsFromContent(content) {
483
712
  const imports = [];
484
713
  const patterns = [
485
714
  /import\s+.*?\s+from\s+['"](.+?)['"]/g,
486
- // import ... from '...'
487
715
  /import\s+['"](.+?)['"]/g,
488
- // import '...'
489
716
  /require\(['"](.+?)['"]\)/g
490
- // require('...')
491
717
  ];
492
718
  for (const pattern of patterns) {
493
719
  let match;
@@ -501,31 +727,25 @@ function extractImportsFromContent(content) {
501
727
  return [...new Set(imports)];
502
728
  }
503
729
  function calculateImportDepth(file, graph, visited = /* @__PURE__ */ new Set(), depth = 0) {
504
- if (visited.has(file)) {
505
- return depth;
506
- }
730
+ if (visited.has(file)) return depth;
507
731
  const dependencies = graph.edges.get(file);
508
- if (!dependencies || dependencies.size === 0) {
509
- return depth;
510
- }
732
+ if (!dependencies || dependencies.size === 0) return depth;
511
733
  visited.add(file);
512
734
  let maxDepth = depth;
513
735
  for (const dep of dependencies) {
514
- const depDepth = calculateImportDepth(dep, graph, visited, depth + 1);
515
- maxDepth = Math.max(maxDepth, depDepth);
736
+ maxDepth = Math.max(
737
+ maxDepth,
738
+ calculateImportDepth(dep, graph, visited, depth + 1)
739
+ );
516
740
  }
517
741
  visited.delete(file);
518
742
  return maxDepth;
519
743
  }
520
744
  function getTransitiveDependencies(file, graph, visited = /* @__PURE__ */ new Set()) {
521
- if (visited.has(file)) {
522
- return [];
523
- }
745
+ if (visited.has(file)) return [];
524
746
  visited.add(file);
525
747
  const dependencies = graph.edges.get(file);
526
- if (!dependencies || dependencies.size === 0) {
527
- return [];
528
- }
748
+ if (!dependencies || dependencies.size === 0) return [];
529
749
  const allDeps = [];
530
750
  for (const dep of dependencies) {
531
751
  allDeps.push(dep);
@@ -558,9 +778,7 @@ function detectCircularDependencies(graph) {
558
778
  }
559
779
  return;
560
780
  }
561
- if (visited.has(file)) {
562
- return;
563
- }
781
+ if (visited.has(file)) return;
564
782
  visited.add(file);
565
783
  recursionStack.add(file);
566
784
  path.push(file);
@@ -579,404 +797,23 @@ function detectCircularDependencies(graph) {
579
797
  }
580
798
  return cycles;
581
799
  }
582
- function calculateCohesion(exports2, filePath, options) {
583
- return calculateEnhancedCohesion(exports2, filePath, options);
584
- }
585
- function isTestFile(filePath) {
586
- const lower = filePath.toLowerCase();
587
- return lower.includes("test") || lower.includes("spec") || lower.includes("mock") || lower.includes("fixture") || lower.includes("__tests__") || lower.includes(".test.") || lower.includes(".spec.");
588
- }
589
- function calculateFragmentation(files, domain, options) {
590
- if (files.length <= 1) return 0;
591
- const directories = new Set(
592
- files.map((f) => f.split("/").slice(0, -1).join("/"))
593
- );
594
- const uniqueDirs = directories.size;
595
- if (options?.useLogScale) {
596
- if (uniqueDirs <= 1) return 0;
597
- const total = files.length;
598
- const base = options.logBase || Math.E;
599
- const num = Math.log(uniqueDirs) / Math.log(base);
600
- const den = Math.log(total) / Math.log(base);
601
- return den > 0 ? num / den : 0;
602
- }
603
- return (uniqueDirs - 1) / (files.length - 1);
604
- }
605
- function calculatePathEntropy(files) {
606
- if (!files || files.length === 0) return 0;
607
- const dirCounts = /* @__PURE__ */ new Map();
608
- for (const f of files) {
609
- const dir = f.split("/").slice(0, -1).join("/") || ".";
610
- dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
611
- }
612
- const counts = Array.from(dirCounts.values());
613
- if (counts.length <= 1) return 0;
614
- const total = counts.reduce((s, v) => s + v, 0);
615
- let entropy = 0;
616
- for (const count of counts) {
617
- const prob = count / total;
618
- entropy -= prob * Math.log2(prob);
619
- }
620
- const maxEntropy = Math.log2(counts.length);
621
- return maxEntropy > 0 ? entropy / maxEntropy : 0;
622
- }
623
- function calculateDirectoryDistance(files) {
624
- if (!files || files.length <= 1) return 0;
625
- function pathSegments(p) {
626
- return p.split("/").filter(Boolean);
627
- }
628
- function commonAncestorDepth(a, b) {
629
- const minLen = Math.min(a.length, b.length);
630
- let i = 0;
631
- while (i < minLen && a[i] === b[i]) i++;
632
- return i;
633
- }
634
- let totalNormalized = 0;
635
- let comparisons = 0;
636
- for (let i = 0; i < files.length; i++) {
637
- for (let j = i + 1; j < files.length; j++) {
638
- const segA = pathSegments(files[i]);
639
- const segB = pathSegments(files[j]);
640
- const shared = commonAncestorDepth(segA, segB);
641
- const maxDepth = Math.max(segA.length, segB.length);
642
- const normalizedShared = maxDepth > 0 ? shared / maxDepth : 0;
643
- totalNormalized += 1 - normalizedShared;
644
- comparisons++;
645
- }
646
- }
647
- return comparisons > 0 ? totalNormalized / comparisons : 0;
648
- }
649
- function detectModuleClusters(graph, options) {
650
- const domainMap = /* @__PURE__ */ new Map();
651
- for (const [file, node] of graph.nodes.entries()) {
652
- const domains = node.exports.map((e) => e.inferredDomain || "unknown");
653
- const primaryDomain = domains[0] || "unknown";
654
- if (!domainMap.has(primaryDomain)) {
655
- domainMap.set(primaryDomain, []);
656
- }
657
- domainMap.get(primaryDomain).push(file);
658
- }
659
- const clusters = [];
660
- for (const [domain, files] of domainMap.entries()) {
661
- if (files.length < 2) continue;
662
- const totalTokens = files.reduce((sum, file) => {
663
- const node = graph.nodes.get(file);
664
- return sum + (node?.tokenCost || 0);
665
- }, 0);
666
- const baseFragmentation = calculateFragmentation(files, domain, {
667
- useLogScale: !!options?.useLogScale
668
- });
669
- let importSimilarityTotal = 0;
670
- let importComparisons = 0;
671
- for (let i = 0; i < files.length; i++) {
672
- for (let j = i + 1; j < files.length; j++) {
673
- const f1 = files[i];
674
- const f2 = files[j];
675
- const n1 = graph.nodes.get(f1)?.imports || [];
676
- const n2 = graph.nodes.get(f2)?.imports || [];
677
- const similarity = n1.length === 0 && n2.length === 0 ? 0 : calculateJaccardSimilarity(n1, n2);
678
- importSimilarityTotal += similarity;
679
- importComparisons++;
680
- }
681
- }
682
- const importCohesion = importComparisons > 0 ? importSimilarityTotal / importComparisons : 0;
683
- const couplingDiscountFactor = 1 - 0.2 * importCohesion;
684
- const fragmentationScore = baseFragmentation * couplingDiscountFactor;
685
- const pathEntropy = calculatePathEntropy(files);
686
- const directoryDistance = calculateDirectoryDistance(files);
687
- const avgCohesion = files.reduce((sum, file) => {
688
- const node = graph.nodes.get(file);
689
- return sum + (node ? calculateCohesion(node.exports, file, {
690
- coUsageMatrix: graph.coUsageMatrix
691
- }) : 0);
692
- }, 0) / files.length;
693
- const targetFiles = Math.max(1, Math.ceil(files.length / 3));
694
- const consolidationPlan = generateConsolidationPlan(
695
- domain,
696
- files,
697
- targetFiles
698
- );
699
- clusters.push({
700
- domain,
701
- files,
702
- totalTokens,
703
- fragmentationScore,
704
- pathEntropy,
705
- directoryDistance,
706
- importCohesion,
707
- avgCohesion,
708
- suggestedStructure: {
709
- targetFiles,
710
- consolidationPlan
711
- }
712
- });
713
- }
714
- return clusters.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
715
- }
716
- function extractExports(content, filePath, domainOptions, fileImports) {
717
- const exports2 = [];
718
- const patterns = [
719
- /export\s+function\s+(\w+)/g,
720
- /export\s+class\s+(\w+)/g,
721
- /export\s+const\s+(\w+)/g,
722
- /export\s+type\s+(\w+)/g,
723
- /export\s+interface\s+(\w+)/g,
724
- /export\s+default/g
725
- ];
726
- const types = [
727
- "function",
728
- "class",
729
- "const",
730
- "type",
731
- "interface",
732
- "default"
733
- ];
734
- patterns.forEach((pattern, index) => {
735
- let match;
736
- while ((match = pattern.exec(content)) !== null) {
737
- const name = match[1] || "default";
738
- const type = types[index];
739
- const inferredDomain = inferDomain(
740
- name,
741
- filePath,
742
- domainOptions,
743
- fileImports
744
- );
745
- exports2.push({ name, type, inferredDomain });
746
- }
747
- });
748
- return exports2;
749
- }
750
- function inferDomain(name, filePath, domainOptions, fileImports) {
751
- const lower = name.toLowerCase();
752
- const tokens = Array.from(
753
- new Set(
754
- lower.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-z0-9]+/gi, " ").split(" ").filter(Boolean)
755
- )
756
- );
757
- const defaultKeywords = [
758
- "authentication",
759
- "authorization",
760
- "payment",
761
- "invoice",
762
- "customer",
763
- "product",
764
- "order",
765
- "cart",
766
- "user",
767
- "admin",
768
- "repository",
769
- "controller",
770
- "service",
771
- "config",
772
- "model",
773
- "view",
774
- "auth"
775
- ];
776
- const domainKeywords = domainOptions?.domainKeywords && domainOptions.domainKeywords.length ? [...domainOptions.domainKeywords, ...defaultKeywords] : defaultKeywords;
777
- for (const keyword of domainKeywords) {
778
- if (tokens.includes(keyword)) {
779
- return keyword;
780
- }
781
- }
782
- for (const keyword of domainKeywords) {
783
- if (lower.includes(keyword)) {
784
- return keyword;
785
- }
786
- }
787
- if (fileImports && fileImports.length > 0) {
788
- for (const importPath of fileImports) {
789
- const allSegments = importPath.split("/");
790
- const relevantSegments = allSegments.filter((s) => {
791
- if (!s) return false;
792
- if (s === "." || s === "..") return false;
793
- if (s.startsWith("@") && s.length === 1) return false;
794
- return true;
795
- }).map((s) => s.startsWith("@") ? s.slice(1) : s);
796
- for (const segment of relevantSegments) {
797
- const segLower = segment.toLowerCase();
798
- const singularSegment = singularize(segLower);
799
- for (const keyword of domainKeywords) {
800
- if (singularSegment === keyword || segLower === keyword || segLower.includes(keyword)) {
801
- return keyword;
802
- }
803
- }
804
- }
805
- }
806
- }
807
- if (filePath) {
808
- const pathSegments = filePath.toLowerCase().split("/");
809
- for (const segment of pathSegments) {
810
- const singularSegment = singularize(segment);
811
- for (const keyword of domainKeywords) {
812
- if (singularSegment === keyword || segment === keyword || segment.includes(keyword)) {
813
- return keyword;
814
- }
815
- }
816
- }
817
- }
818
- return "unknown";
819
- }
820
- function generateConsolidationPlan(domain, files, targetFiles) {
821
- const plan = [];
822
- if (files.length <= targetFiles) {
823
- return [`No consolidation needed for ${domain}`];
824
- }
825
- plan.push(
826
- `Consolidate ${files.length} ${domain} files into ${targetFiles} cohesive file(s):`
827
- );
828
- const dirGroups = /* @__PURE__ */ new Map();
829
- for (const file of files) {
830
- const dir = file.split("/").slice(0, -1).join("/");
831
- if (!dirGroups.has(dir)) {
832
- dirGroups.set(dir, []);
833
- }
834
- dirGroups.get(dir).push(file);
835
- }
836
- plan.push(`1. Create unified ${domain} module file`);
837
- plan.push(
838
- `2. Move related functionality from ${files.length} scattered files`
839
- );
840
- plan.push(`3. Update imports in dependent files`);
841
- plan.push(
842
- `4. Remove old files after consolidation (verify with tests first)`
843
- );
844
- return plan;
845
- }
846
- function extractExportsWithAST(content, filePath, domainOptions, fileImports) {
847
- try {
848
- const { exports: astExports } = (0, import_core.parseFileExports)(content, filePath);
849
- return astExports.map((exp) => ({
850
- name: exp.name,
851
- type: exp.type,
852
- inferredDomain: inferDomain(
853
- exp.name,
854
- filePath,
855
- domainOptions,
856
- fileImports
857
- ),
858
- imports: exp.imports,
859
- dependencies: exp.dependencies
860
- }));
861
- } catch (error) {
862
- void error;
863
- return extractExports(content, filePath, domainOptions, fileImports);
864
- }
865
- }
866
- function calculateEnhancedCohesion(exports2, filePath, options) {
867
- if (exports2.length === 0) return 1;
868
- if (exports2.length === 1) return 1;
869
- if (filePath && isTestFile(filePath)) {
870
- return 1;
871
- }
872
- const domainCohesion = calculateDomainCohesion(exports2);
873
- const hasImportData = exports2.some((e) => e.imports && e.imports.length > 0);
874
- const importCohesion = hasImportData ? calculateImportBasedCohesion(exports2) : void 0;
875
- const coUsageMatrix = options?.coUsageMatrix;
876
- const structuralCohesion = filePath && coUsageMatrix ? calculateStructuralCohesionFromCoUsage(filePath, coUsageMatrix) : void 0;
877
- const defaultWeights = {
878
- importBased: 0.5,
879
- structural: 0.3,
880
- domainBased: 0.2
881
- };
882
- const weights = { ...defaultWeights, ...options?.weights || {} };
883
- const signals = [];
884
- if (importCohesion !== void 0)
885
- signals.push({ score: importCohesion, weight: weights.importBased });
886
- if (structuralCohesion !== void 0)
887
- signals.push({ score: structuralCohesion, weight: weights.structural });
888
- signals.push({ score: domainCohesion, weight: weights.domainBased });
889
- const totalWeight = signals.reduce((s, el) => s + el.weight, 0);
890
- if (totalWeight === 0) return domainCohesion;
891
- const combined = signals.reduce(
892
- (sum, el) => sum + el.score * (el.weight / totalWeight),
893
- 0
894
- );
895
- return combined;
896
- }
897
- function calculateStructuralCohesionFromCoUsage(file, coUsageMatrix) {
898
- if (!coUsageMatrix) return 1;
899
- const coUsages = coUsageMatrix.get(file);
900
- if (!coUsages || coUsages.size === 0) return 1;
901
- let total = 0;
902
- for (const count of coUsages.values()) total += count;
903
- if (total === 0) return 1;
904
- const probs = [];
905
- for (const count of coUsages.values()) {
906
- if (count > 0) probs.push(count / total);
907
- }
908
- if (probs.length <= 1) return 1;
909
- let entropy = 0;
910
- for (const prob of probs) {
911
- entropy -= prob * Math.log2(prob);
912
- }
913
- const maxEntropy = Math.log2(probs.length);
914
- return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
915
- }
916
- function calculateImportBasedCohesion(exports2) {
917
- const exportsWithImports = exports2.filter(
918
- (e) => e.imports && e.imports.length > 0
919
- );
920
- if (exportsWithImports.length < 2) {
921
- return 1;
922
- }
923
- let totalSimilarity = 0;
924
- let comparisons = 0;
925
- for (let i = 0; i < exportsWithImports.length; i++) {
926
- for (let j = i + 1; j < exportsWithImports.length; j++) {
927
- const exp1 = exportsWithImports[i];
928
- const exp2 = exportsWithImports[j];
929
- const similarity = calculateJaccardSimilarity(exp1.imports, exp2.imports);
930
- totalSimilarity += similarity;
931
- comparisons++;
932
- }
933
- }
934
- return comparisons > 0 ? totalSimilarity / comparisons : 1;
935
- }
936
- function calculateJaccardSimilarity(arr1, arr2) {
937
- if (arr1.length === 0 && arr2.length === 0) return 1;
938
- if (arr1.length === 0 || arr2.length === 0) return 0;
939
- const set1 = new Set(arr1);
940
- const set2 = new Set(arr2);
941
- const intersection = new Set([...set1].filter((x) => set2.has(x)));
942
- const union = /* @__PURE__ */ new Set([...set1, ...set2]);
943
- return intersection.size / union.size;
944
- }
945
- function calculateDomainCohesion(exports2) {
946
- const domains = exports2.map((e) => e.inferredDomain || "unknown");
947
- const domainCounts = /* @__PURE__ */ new Map();
948
- for (const domain of domains) {
949
- domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1);
950
- }
951
- const total = domains.length;
952
- let entropy = 0;
953
- for (const domainCount of domainCounts.values()) {
954
- const prob = domainCount / total;
955
- if (prob > 0) {
956
- entropy -= prob * Math.log2(prob);
957
- }
958
- }
959
- const maxEntropy = Math.log2(total);
960
- return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
961
- }
962
- function classifyFile(node, cohesionScore, domains) {
963
- const { exports: exports2, imports, linesOfCode, file } = node;
964
- void imports;
965
- void linesOfCode;
800
+
801
+ // src/classifier.ts
802
+ function classifyFile(node, cohesionScore = 1, domains = []) {
966
803
  if (isBarrelExport(node)) {
967
804
  return "barrel-export";
968
805
  }
969
- if (isTypeDefinitionFile(node)) {
806
+ if (isTypeDefinition(node)) {
970
807
  return "type-definition";
971
808
  }
972
- if (isConfigOrSchemaFile(node)) {
973
- return "cohesive-module";
809
+ if (isNextJsPage(node)) {
810
+ return "nextjs-page";
974
811
  }
975
812
  if (isLambdaHandler(node)) {
976
813
  return "lambda-handler";
977
814
  }
978
- if (isDataAccessFile(node)) {
979
- return "cohesive-module";
815
+ if (isServiceFile(node)) {
816
+ return "service-file";
980
817
  }
981
818
  if (isEmailTemplate(node)) {
982
819
  return "email-template";
@@ -984,224 +821,53 @@ function classifyFile(node, cohesionScore, domains) {
984
821
  if (isParserFile(node)) {
985
822
  return "parser-file";
986
823
  }
987
- if (isServiceFile(node)) {
988
- return "service-file";
989
- }
990
824
  if (isSessionFile(node)) {
991
- return "cohesive-module";
992
- }
993
- if (isNextJsPage(node)) {
994
- return "nextjs-page";
995
- }
996
- if (isUtilityFile(node)) {
825
+ if (cohesionScore >= 0.25 && domains.length <= 1) return "cohesive-module";
997
826
  return "utility-module";
998
827
  }
999
- if (file.toLowerCase().includes("/utils/") || file.toLowerCase().includes("/helpers/")) {
828
+ if (isUtilityModule(node)) {
1000
829
  return "utility-module";
1001
830
  }
1002
- const uniqueDomains = domains.filter((d) => d !== "unknown");
1003
- const hasSingleDomain = uniqueDomains.length <= 1;
1004
- if (hasSingleDomain) {
831
+ if (isConfigFile(node)) {
1005
832
  return "cohesive-module";
1006
833
  }
1007
- if (allExportsShareEntityNoun(exports2)) {
834
+ if (domains.length <= 1 && domains[0] !== "unknown") {
1008
835
  return "cohesive-module";
1009
836
  }
1010
- const hasMultipleDomains = uniqueDomains.length > 1;
1011
- const hasLowCohesion = cohesionScore < 0.4;
1012
- if (hasMultipleDomains && hasLowCohesion) {
837
+ if (domains.length > 1 && cohesionScore < 0.4) {
1013
838
  return "mixed-concerns";
1014
839
  }
1015
- if (cohesionScore >= 0.5) {
840
+ if (cohesionScore >= 0.7) {
1016
841
  return "cohesive-module";
1017
842
  }
1018
843
  return "unknown";
1019
844
  }
1020
845
  function isBarrelExport(node) {
1021
- const { file, exports: exports2, imports, linesOfCode } = node;
1022
- const fileName = file.split("/").pop()?.toLowerCase();
1023
- const isIndexFile = fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx";
1024
- const hasReExports = exports2.length > 0 && imports.length > 0;
1025
- const highExportToLinesRatio = exports2.length > 3 && linesOfCode < exports2.length * 5;
1026
- const sparseCode = linesOfCode > 0 && linesOfCode < 50 && exports2.length >= 2;
1027
- if (isIndexFile && hasReExports) {
1028
- return true;
1029
- }
1030
- if (highExportToLinesRatio && imports.length >= exports2.length * 0.5) {
1031
- return true;
1032
- }
1033
- if (sparseCode && imports.length > 0) {
1034
- return true;
1035
- }
1036
- return false;
1037
- }
1038
- function isTypeDefinitionFile(node) {
1039
- const { file, exports: exports2 } = node;
1040
- const fileName = file.split("/").pop()?.toLowerCase();
1041
- const isTypesFile = fileName?.includes("types") || fileName?.includes(".d.ts") || fileName === "types.ts" || fileName === "interfaces.ts";
1042
- const lowerPath = file.toLowerCase();
1043
- const isTypesPath = lowerPath.includes("/types/") || lowerPath.includes("/typings/") || lowerPath.includes("/@types/") || lowerPath.startsWith("types/") || lowerPath.startsWith("typings/");
1044
- const typeExports = exports2.filter(
1045
- (e) => e.type === "type" || e.type === "interface"
1046
- );
1047
- const runtimeExports = exports2.filter(
1048
- (e) => e.type === "function" || e.type === "class" || e.type === "const"
1049
- );
1050
- const mostlyTypes = exports2.length > 0 && typeExports.length > runtimeExports.length && typeExports.length / exports2.length > 0.7;
1051
- const pureTypeFile = exports2.length > 0 && typeExports.length === exports2.length;
1052
- const emptyOrReExportInTypesDir = isTypesPath && exports2.length === 0;
1053
- return isTypesFile || isTypesPath || mostlyTypes || pureTypeFile || emptyOrReExportInTypesDir;
1054
- }
1055
- function isConfigOrSchemaFile(node) {
1056
- const { file, exports: exports2 } = node;
1057
- const fileName = file.split("/").pop()?.toLowerCase();
1058
- const configPatterns = [
1059
- "config",
1060
- "schema",
1061
- "settings",
1062
- "options",
1063
- "constants",
1064
- "env",
1065
- "environment",
1066
- ".config.",
1067
- "-config.",
1068
- "_config."
1069
- ];
1070
- const isConfigName = configPatterns.some(
1071
- (pattern) => fileName?.includes(pattern) || fileName?.startsWith(pattern) || fileName?.endsWith(`${pattern}.ts`)
1072
- );
1073
- const isConfigPath = file.toLowerCase().includes("/config/") || file.toLowerCase().includes("/schemas/") || file.toLowerCase().includes("/settings/");
1074
- const hasSchemaExports = exports2.some(
1075
- (e) => e.name.toLowerCase().includes("table") || e.name.toLowerCase().includes("schema") || e.name.toLowerCase().includes("config") || e.name.toLowerCase().includes("setting")
1076
- );
1077
- return isConfigName || isConfigPath || hasSchemaExports;
1078
- }
1079
- function isUtilityFile(node) {
1080
846
  const { file, exports: exports2 } = node;
1081
847
  const fileName = file.split("/").pop()?.toLowerCase();
1082
- const utilityPatterns = [
1083
- "util",
1084
- "utility",
1085
- "utilities",
1086
- "helper",
1087
- "helpers",
1088
- "common",
1089
- "shared",
1090
- "toolbox",
1091
- "toolkit",
1092
- ".util.",
1093
- "-util.",
1094
- "_util.",
1095
- "-utils.",
1096
- ".utils."
1097
- ];
1098
- const isUtilityName = utilityPatterns.some(
1099
- (pattern) => fileName?.includes(pattern)
848
+ const isIndexFile = fileName === "index.ts" || fileName === "index.js";
849
+ const isSmallAndManyExports = node.tokenCost < 1e3 && (exports2 || []).length > 5;
850
+ const isReexportPattern = (exports2 || []).length >= 5 && (exports2 || []).every(
851
+ (e) => e.type === "const" || e.type === "function" || e.type === "type" || e.type === "interface"
1100
852
  );
1101
- const isUtilityPath = file.toLowerCase().includes("/utils/") || file.toLowerCase().includes("/helpers/") || file.toLowerCase().includes("/common/") || file.toLowerCase().endsWith("-utils.ts") || file.toLowerCase().endsWith("-util.ts") || file.toLowerCase().endsWith("-helper.ts") || file.toLowerCase().endsWith("-helpers.ts");
1102
- const hasManySmallExportsInUtilityContext = exports2.length >= 3 && exports2.every((e) => e.type === "function" || e.type === "const") && (isUtilityName || isUtilityPath);
1103
- return isUtilityName || isUtilityPath || hasManySmallExportsInUtilityContext;
1104
- }
1105
- function splitCamelCase(name) {
1106
- return name.replace(/([A-Z])/g, " $1").trim().toLowerCase().split(/[\s_-]+/).filter(Boolean);
1107
- }
1108
- var SKIP_WORDS = /* @__PURE__ */ new Set([
1109
- "get",
1110
- "set",
1111
- "create",
1112
- "update",
1113
- "delete",
1114
- "fetch",
1115
- "save",
1116
- "load",
1117
- "parse",
1118
- "format",
1119
- "validate",
1120
- "convert",
1121
- "transform",
1122
- "build",
1123
- "generate",
1124
- "render",
1125
- "send",
1126
- "receive",
1127
- "find",
1128
- "list",
1129
- "add",
1130
- "remove",
1131
- "insert",
1132
- "upsert",
1133
- "put",
1134
- "read",
1135
- "write",
1136
- "check",
1137
- "handle",
1138
- "process",
1139
- "compute",
1140
- "calculate",
1141
- "init",
1142
- "reset",
1143
- "clear",
1144
- "pending",
1145
- "active",
1146
- "current",
1147
- "new",
1148
- "old",
1149
- "all",
1150
- "by",
1151
- "with",
1152
- "from",
1153
- "to",
1154
- "and",
1155
- "or",
1156
- "is",
1157
- "has",
1158
- "in",
1159
- "on",
1160
- "of",
1161
- "the"
1162
- ]);
1163
- function simpleSingularize(word) {
1164
- if (word.endsWith("ies") && word.length > 3) return word.slice(0, -3) + "y";
1165
- if (word.endsWith("ses") && word.length > 4) return word.slice(0, -2);
1166
- if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
1167
- return word;
1168
- }
1169
- function extractEntityNouns(name) {
1170
- return splitCamelCase(name).filter((token) => !SKIP_WORDS.has(token) && token.length > 2).map(simpleSingularize);
853
+ return !!isIndexFile || !!isSmallAndManyExports || !!isReexportPattern;
1171
854
  }
1172
- function allExportsShareEntityNoun(exports2) {
1173
- if (exports2.length < 2 || exports2.length > 30) return false;
1174
- const nounSets = exports2.map((e) => new Set(extractEntityNouns(e.name)));
1175
- if (nounSets.some((s) => s.size === 0)) return false;
1176
- const [first, ...rest] = nounSets;
1177
- const commonNouns = Array.from(first).filter(
1178
- (noun) => rest.every((s) => s.has(noun))
1179
- );
1180
- return commonNouns.length > 0;
855
+ function isTypeDefinition(node) {
856
+ const { file } = node;
857
+ if (file.endsWith(".d.ts")) return true;
858
+ const nodeExports = node.exports || [];
859
+ const hasExports = nodeExports.length > 0;
860
+ const areAllTypes = hasExports && nodeExports.every((e) => e.type === "type" || e.type === "interface");
861
+ const allTypes = !!areAllTypes;
862
+ const isTypePath = file.toLowerCase().includes("/types/") || file.toLowerCase().includes("/interfaces/") || file.toLowerCase().includes("/models/");
863
+ return allTypes || isTypePath && hasExports;
1181
864
  }
1182
- function isDataAccessFile(node) {
1183
- const { file, exports: exports2 } = node;
865
+ function isUtilityModule(node) {
866
+ const { file } = node;
867
+ const isUtilPath = file.toLowerCase().includes("/utils/") || file.toLowerCase().includes("/helpers/") || file.toLowerCase().includes("/util/") || file.toLowerCase().includes("/helper/");
1184
868
  const fileName = file.split("/").pop()?.toLowerCase();
1185
- const dalPatterns = [
1186
- "dynamo",
1187
- "database",
1188
- "repository",
1189
- "repo",
1190
- "dao",
1191
- "firestore",
1192
- "postgres",
1193
- "mysql",
1194
- "mongo",
1195
- "redis",
1196
- "sqlite",
1197
- "supabase",
1198
- "prisma"
1199
- ];
1200
- const isDalName = dalPatterns.some((p) => fileName?.includes(p));
1201
- const isDalPath = file.toLowerCase().includes("/repositories/") || file.toLowerCase().includes("/dao/") || file.toLowerCase().includes("/data/");
1202
- const hasDalExportPattern = exports2.length >= 1 && exports2.length <= 10 && allExportsShareEntityNoun(exports2);
1203
- const isUtilityPathLocal = file.toLowerCase().includes("/utils/") || file.toLowerCase().includes("/helpers/");
1204
- return isDalPath || isDalName && hasDalExportPattern && !isUtilityPathLocal;
869
+ const isUtilName = fileName?.includes("utils.") || fileName?.includes("helpers.") || fileName?.includes("util.") || fileName?.includes("helper.");
870
+ return !!isUtilPath || !!isUtilName;
1205
871
  }
1206
872
  function isLambdaHandler(node) {
1207
873
  const { file, exports: exports2 } = node;
@@ -1218,11 +884,10 @@ function isLambdaHandler(node) {
1218
884
  (pattern) => fileName?.includes(pattern)
1219
885
  );
1220
886
  const isHandlerPath = file.toLowerCase().includes("/handlers/") || file.toLowerCase().includes("/lambdas/") || file.toLowerCase().includes("/lambda/") || file.toLowerCase().includes("/functions/");
1221
- const hasHandlerExport = exports2.some(
887
+ const hasHandlerExport = (exports2 || []).some(
1222
888
  (e) => e.name.toLowerCase() === "handler" || e.name.toLowerCase() === "main" || e.name.toLowerCase() === "lambdahandler" || e.name.toLowerCase().endsWith("handler")
1223
889
  );
1224
- const hasSingleEntryInHandlerContext = exports2.length === 1 && (exports2[0].type === "function" || exports2[0].name === "default") && (isHandlerPath || isHandlerName);
1225
- return isHandlerName || isHandlerPath || hasHandlerExport || hasSingleEntryInHandlerContext;
890
+ return !!isHandlerName || !!isHandlerPath || !!hasHandlerExport;
1226
891
  }
1227
892
  function isServiceFile(node) {
1228
893
  const { file, exports: exports2 } = node;
@@ -1232,11 +897,11 @@ function isServiceFile(node) {
1232
897
  (pattern) => fileName?.includes(pattern)
1233
898
  );
1234
899
  const isServicePath = file.toLowerCase().includes("/services/");
1235
- const hasServiceNamedExport = exports2.some(
900
+ const hasServiceNamedExport = (exports2 || []).some(
1236
901
  (e) => e.name.toLowerCase().includes("service") || e.name.toLowerCase().endsWith("service")
1237
902
  );
1238
- const hasClassExport = exports2.some((e) => e.type === "class");
1239
- return isServiceName || isServicePath || hasServiceNamedExport && hasClassExport;
903
+ const hasClassExport = (exports2 || []).some((e) => e.type === "class");
904
+ return !!isServiceName || !!isServicePath || !!hasServiceNamedExport && !!hasClassExport;
1240
905
  }
1241
906
  function isEmailTemplate(node) {
1242
907
  const { file, exports: exports2 } = node;
@@ -1254,15 +919,11 @@ function isEmailTemplate(node) {
1254
919
  const isEmailTemplateName = emailTemplatePatterns.some(
1255
920
  (pattern) => fileName?.includes(pattern)
1256
921
  );
1257
- const isSpecificTemplateName = fileName?.includes("receipt") || fileName?.includes("invoice-email") || fileName?.includes("welcome-email") || fileName?.includes("notification-email") || fileName?.includes("writer") && fileName.includes("receipt");
1258
922
  const isEmailPath = file.toLowerCase().includes("/emails/") || file.toLowerCase().includes("/mail/") || file.toLowerCase().includes("/notifications/");
1259
- const hasTemplateFunction = exports2.some(
923
+ const hasTemplateFunction = (exports2 || []).some(
1260
924
  (e) => e.type === "function" && (e.name.toLowerCase().startsWith("render") || e.name.toLowerCase().startsWith("generate") || e.name.toLowerCase().includes("template") && e.name.toLowerCase().includes("email"))
1261
925
  );
1262
- const hasEmailExport = exports2.some(
1263
- (e) => e.name.toLowerCase().includes("template") && e.type === "function" || e.name.toLowerCase().includes("render") && e.type === "function" || e.name.toLowerCase().includes("email") && e.type !== "class"
1264
- );
1265
- return isEmailPath || isEmailTemplateName || isSpecificTemplateName || hasTemplateFunction && hasEmailExport;
926
+ return !!isEmailPath || !!isEmailTemplateName || !!hasTemplateFunction;
1266
927
  }
1267
928
  function isParserFile(node) {
1268
929
  const { file, exports: exports2 } = node;
@@ -1274,55 +935,51 @@ function isParserFile(node) {
1274
935
  "_parser.",
1275
936
  "transform",
1276
937
  ".transform.",
1277
- "-transform.",
1278
938
  "converter",
1279
- ".converter.",
1280
- "-converter.",
1281
939
  "mapper",
1282
- ".mapper.",
1283
- "-mapper.",
1284
- "serializer",
1285
- ".serializer.",
1286
- "deterministic"
1287
- // For base-parser-deterministic.ts pattern
940
+ "serializer"
1288
941
  ];
1289
942
  const isParserName = parserPatterns.some(
1290
943
  (pattern) => fileName?.includes(pattern)
1291
944
  );
1292
- const isParserPath = file.toLowerCase().includes("/parsers/") || file.toLowerCase().includes("/transformers/") || file.toLowerCase().includes("/converters/") || file.toLowerCase().includes("/mappers/");
1293
- const hasParserExport = exports2.some(
1294
- (e) => e.name.toLowerCase().includes("parse") || e.name.toLowerCase().includes("transform") || e.name.toLowerCase().includes("convert") || e.name.toLowerCase().includes("map") || e.name.toLowerCase().includes("serialize") || e.name.toLowerCase().includes("deserialize")
945
+ const isParserPath = file.toLowerCase().includes("/parsers/") || file.toLowerCase().includes("/transformers/");
946
+ const hasParseFunction = (exports2 || []).some(
947
+ (e) => e.type === "function" && (e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform") || e.name.toLowerCase().startsWith("extract"))
1295
948
  );
1296
- const hasParseFunction = exports2.some(
1297
- (e) => e.type === "function" && (e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform") || e.name.toLowerCase().startsWith("convert") || e.name.toLowerCase().startsWith("map") || e.name.toLowerCase().startsWith("extract"))
1298
- );
1299
- return isParserName || isParserPath || hasParserExport || hasParseFunction;
949
+ return !!isParserName || !!isParserPath || !!hasParseFunction;
1300
950
  }
1301
951
  function isSessionFile(node) {
1302
952
  const { file, exports: exports2 } = node;
1303
953
  const fileName = file.split("/").pop()?.toLowerCase();
1304
- const sessionPatterns = [
1305
- "session",
1306
- ".session.",
1307
- "-session.",
1308
- "state",
1309
- ".state.",
1310
- "-state.",
1311
- "context",
1312
- ".context.",
1313
- "-context.",
1314
- "store",
1315
- ".store.",
1316
- "-store."
1317
- ];
954
+ const sessionPatterns = ["session", "state", "context", "store"];
1318
955
  const isSessionName = sessionPatterns.some(
1319
956
  (pattern) => fileName?.includes(pattern)
1320
957
  );
1321
- const isSessionPath = file.toLowerCase().includes("/sessions/") || file.toLowerCase().includes("/state/") || file.toLowerCase().includes("/context/") || file.toLowerCase().includes("/store/");
1322
- const hasSessionExport = exports2.some(
1323
- (e) => e.name.toLowerCase().includes("session") || e.name.toLowerCase().includes("state") || e.name.toLowerCase().includes("context") || e.name.toLowerCase().includes("manager") || e.name.toLowerCase().includes("store")
958
+ const isSessionPath = file.toLowerCase().includes("/sessions/") || file.toLowerCase().includes("/state/");
959
+ const hasSessionExport = (exports2 || []).some(
960
+ (e) => e.name.toLowerCase().includes("session") || e.name.toLowerCase().includes("state") || e.name.toLowerCase().includes("store")
961
+ );
962
+ return !!isSessionName || !!isSessionPath || !!hasSessionExport;
963
+ }
964
+ function isConfigFile(node) {
965
+ const { file, exports: exports2 } = node;
966
+ const lowerPath = file.toLowerCase();
967
+ const fileName = file.split("/").pop()?.toLowerCase();
968
+ const configPatterns = [
969
+ ".config.",
970
+ "tsconfig",
971
+ "jest.config",
972
+ "package.json",
973
+ "aiready.json",
974
+ "next.config",
975
+ "sst.config"
976
+ ];
977
+ const isConfigName = configPatterns.some((p) => fileName?.includes(p));
978
+ const isConfigPath = lowerPath.includes("/config/") || lowerPath.includes("/settings/") || lowerPath.includes("/schemas/");
979
+ const hasSchemaExports = (exports2 || []).some(
980
+ (e) => e.name.toLowerCase().includes("schema") || e.name.toLowerCase().includes("config") || e.name.toLowerCase().includes("setting")
1324
981
  );
1325
- return isSessionName || isSessionPath || hasSessionExport;
982
+ return !!isConfigName || !!isConfigPath || !!hasSchemaExports;
1326
983
  }
1327
984
  function isNextJsPage(node) {
1328
985
  const { file, exports: exports2 } = node;
@@ -1330,24 +987,19 @@ function isNextJsPage(node) {
1330
987
  const fileName = file.split("/").pop()?.toLowerCase();
1331
988
  const isInAppDir = lowerPath.includes("/app/") || lowerPath.startsWith("app/");
1332
989
  const isPageFile = fileName === "page.tsx" || fileName === "page.ts";
1333
- if (!isInAppDir || !isPageFile) {
1334
- return false;
1335
- }
1336
- const exportNames = exports2.map((e) => e.name.toLowerCase());
1337
- const hasDefaultExport = exports2.some((e) => e.type === "default");
990
+ if (!isInAppDir || !isPageFile) return false;
991
+ const hasDefaultExport = (exports2 || []).some((e) => e.type === "default");
1338
992
  const nextJsExports = [
1339
993
  "metadata",
1340
994
  "generatemetadata",
1341
995
  "faqjsonld",
1342
996
  "jsonld",
1343
- "icon",
1344
- "viewport",
1345
- "dynamic"
997
+ "icon"
1346
998
  ];
1347
- const hasNextJsExports = exportNames.some(
1348
- (name) => nextJsExports.includes(name) || name.includes("jsonld")
999
+ const hasNextJsExports = (exports2 || []).some(
1000
+ (e) => nextJsExports.includes(e.name.toLowerCase())
1349
1001
  );
1350
- return hasDefaultExport || hasNextJsExports;
1002
+ return !!hasDefaultExport || !!hasNextJsExports;
1351
1003
  }
1352
1004
  function adjustCohesionForClassification(baseCohesion, classification, node) {
1353
1005
  switch (classification) {
@@ -1355,55 +1007,24 @@ function adjustCohesionForClassification(baseCohesion, classification, node) {
1355
1007
  return 1;
1356
1008
  case "type-definition":
1357
1009
  return 1;
1010
+ case "nextjs-page":
1011
+ return 1;
1358
1012
  case "utility-module": {
1359
- if (node) {
1360
- const exportNames = node.exports.map((e) => e.name.toLowerCase());
1361
- const hasRelatedNames = hasRelatedExportNames(exportNames);
1362
- if (hasRelatedNames) {
1363
- return Math.max(0.8, Math.min(1, baseCohesion + 0.45));
1364
- }
1365
- }
1366
- return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
1367
- }
1368
- case "service-file": {
1369
- if (node?.exports.some((e) => e.type === "class")) {
1370
- return Math.max(0.78, Math.min(1, baseCohesion + 0.4));
1371
- }
1372
- return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
1373
- }
1374
- case "lambda-handler": {
1375
- if (node) {
1376
- const hasSingleEntry = node.exports.length === 1 || node.exports.some((e) => e.name.toLowerCase() === "handler");
1377
- if (hasSingleEntry) {
1378
- return Math.max(0.8, Math.min(1, baseCohesion + 0.45));
1379
- }
1013
+ if (node && hasRelatedExportNames(
1014
+ (node.exports || []).map((e) => e.name.toLowerCase())
1015
+ )) {
1016
+ return Math.max(0.8, Math.min(1, baseCohesion + 0.45));
1380
1017
  }
1381
1018
  return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
1382
1019
  }
1383
- case "email-template": {
1384
- if (node) {
1385
- const hasTemplateFunc = node.exports.some(
1386
- (e) => e.name.toLowerCase().includes("render") || e.name.toLowerCase().includes("generate") || e.name.toLowerCase().includes("template")
1387
- );
1388
- if (hasTemplateFunc) {
1389
- return Math.max(0.75, Math.min(1, baseCohesion + 0.4));
1390
- }
1391
- }
1020
+ case "service-file":
1392
1021
  return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
1393
- }
1394
- case "parser-file": {
1395
- if (node) {
1396
- const hasParseFunc = node.exports.some(
1397
- (e) => e.name.toLowerCase().startsWith("parse") || e.name.toLowerCase().startsWith("transform") || e.name.toLowerCase().startsWith("convert")
1398
- );
1399
- if (hasParseFunc) {
1400
- return Math.max(0.75, Math.min(1, baseCohesion + 0.4));
1401
- }
1402
- }
1022
+ case "lambda-handler":
1023
+ return Math.max(0.75, Math.min(1, baseCohesion + 0.35));
1024
+ case "email-template":
1025
+ return Math.max(0.72, Math.min(1, baseCohesion + 0.3));
1026
+ case "parser-file":
1403
1027
  return Math.max(0.7, Math.min(1, baseCohesion + 0.3));
1404
- }
1405
- case "nextjs-page":
1406
- return 1;
1407
1028
  case "cohesive-module":
1408
1029
  return Math.max(baseCohesion, 0.7);
1409
1030
  case "mixed-concerns":
@@ -1416,113 +1037,41 @@ function hasRelatedExportNames(exportNames) {
1416
1037
  if (exportNames.length < 2) return true;
1417
1038
  const stems = /* @__PURE__ */ new Set();
1418
1039
  const domains = /* @__PURE__ */ new Set();
1040
+ const verbs = [
1041
+ "get",
1042
+ "set",
1043
+ "create",
1044
+ "update",
1045
+ "delete",
1046
+ "fetch",
1047
+ "save",
1048
+ "load",
1049
+ "parse",
1050
+ "format",
1051
+ "validate"
1052
+ ];
1053
+ const domainPatterns = [
1054
+ "user",
1055
+ "order",
1056
+ "product",
1057
+ "session",
1058
+ "email",
1059
+ "file",
1060
+ "db",
1061
+ "api",
1062
+ "config"
1063
+ ];
1419
1064
  for (const name of exportNames) {
1420
- const verbs = [
1421
- "get",
1422
- "set",
1423
- "create",
1424
- "update",
1425
- "delete",
1426
- "fetch",
1427
- "save",
1428
- "load",
1429
- "parse",
1430
- "format",
1431
- "validate",
1432
- "convert",
1433
- "transform",
1434
- "build",
1435
- "generate",
1436
- "render",
1437
- "send",
1438
- "receive"
1439
- ];
1440
1065
  for (const verb of verbs) {
1441
1066
  if (name.startsWith(verb) && name.length > verb.length) {
1442
1067
  stems.add(name.slice(verb.length).toLowerCase());
1443
1068
  }
1444
1069
  }
1445
- const domainPatterns = [
1446
- "user",
1447
- "order",
1448
- "product",
1449
- "session",
1450
- "email",
1451
- "file",
1452
- "db",
1453
- "s3",
1454
- "dynamo",
1455
- "api",
1456
- "config"
1457
- ];
1458
1070
  for (const domain of domainPatterns) {
1459
- if (name.includes(domain)) {
1460
- domains.add(domain);
1461
- }
1071
+ if (name.includes(domain)) domains.add(domain);
1462
1072
  }
1463
1073
  }
1464
- if (stems.size === 1 && exportNames.length >= 2) return true;
1465
- if (domains.size === 1 && exportNames.length >= 2) return true;
1466
- const prefixes = exportNames.map((name) => {
1467
- const match = name.match(/^([a-z]+)/);
1468
- return match ? match[1] : "";
1469
- }).filter((p) => p.length >= 3);
1470
- if (prefixes.length >= 2) {
1471
- const uniquePrefixes = new Set(prefixes);
1472
- if (uniquePrefixes.size === 1) return true;
1473
- }
1474
- const nounSets = exportNames.map((name) => {
1475
- const tokens = name.replace(/([A-Z])/g, " $1").trim().toLowerCase().split(/[\s_-]+/).filter(Boolean);
1476
- const skip = /* @__PURE__ */ new Set([
1477
- "get",
1478
- "set",
1479
- "create",
1480
- "update",
1481
- "delete",
1482
- "fetch",
1483
- "save",
1484
- "load",
1485
- "parse",
1486
- "format",
1487
- "validate",
1488
- "convert",
1489
- "transform",
1490
- "build",
1491
- "generate",
1492
- "render",
1493
- "send",
1494
- "receive",
1495
- "find",
1496
- "list",
1497
- "add",
1498
- "remove",
1499
- "insert",
1500
- "upsert",
1501
- "put",
1502
- "read",
1503
- "write",
1504
- "check",
1505
- "handle",
1506
- "process",
1507
- "pending",
1508
- "active",
1509
- "current",
1510
- "new",
1511
- "old",
1512
- "all"
1513
- ]);
1514
- const singularize2 = (w) => w.endsWith("s") && w.length > 3 ? w.slice(0, -1) : w;
1515
- return new Set(
1516
- tokens.filter((t) => !skip.has(t) && t.length > 2).map(singularize2)
1517
- );
1518
- });
1519
- if (nounSets.length >= 2 && nounSets.every((s) => s.size > 0)) {
1520
- const [first, ...rest] = nounSets;
1521
- const commonNouns = Array.from(first).filter(
1522
- (n) => rest.every((s) => s.has(n))
1523
- );
1524
- if (commonNouns.length > 0) return true;
1525
- }
1074
+ if (stems.size === 1 || domains.size === 1) return true;
1526
1075
  return false;
1527
1076
  }
1528
1077
  function adjustFragmentationForClassification(baseFragmentation, classification) {
@@ -1546,6 +1095,80 @@ function adjustFragmentationForClassification(baseFragmentation, classification)
1546
1095
  return baseFragmentation * 0.7;
1547
1096
  }
1548
1097
  }
1098
+
1099
+ // src/cluster-detector.ts
1100
+ function detectModuleClusters(graph, options) {
1101
+ const domainMap = /* @__PURE__ */ new Map();
1102
+ for (const [file, node] of graph.nodes.entries()) {
1103
+ const primaryDomain = node.exports[0]?.inferredDomain || "unknown";
1104
+ if (!domainMap.has(primaryDomain)) {
1105
+ domainMap.set(primaryDomain, []);
1106
+ }
1107
+ domainMap.get(primaryDomain).push(file);
1108
+ }
1109
+ const clusters = [];
1110
+ for (const [domain, files] of domainMap.entries()) {
1111
+ if (files.length < 2 || domain === "unknown") continue;
1112
+ const totalTokens = files.reduce((sum, file) => {
1113
+ const node = graph.nodes.get(file);
1114
+ return sum + (node?.tokenCost || 0);
1115
+ }, 0);
1116
+ let sharedImportRatio = 0;
1117
+ if (files.length >= 2) {
1118
+ const allImportSets = files.map(
1119
+ (f) => new Set(graph.nodes.get(f)?.imports || [])
1120
+ );
1121
+ let intersection = new Set(allImportSets[0]);
1122
+ let union = new Set(allImportSets[0]);
1123
+ for (let i = 1; i < allImportSets.length; i++) {
1124
+ const nextSet = allImportSets[i];
1125
+ intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
1126
+ for (const x of nextSet) union.add(x);
1127
+ }
1128
+ sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
1129
+ }
1130
+ const fragmentation = calculateFragmentation(files, domain, {
1131
+ ...options,
1132
+ sharedImportRatio
1133
+ });
1134
+ let totalCohesion = 0;
1135
+ files.forEach((f) => {
1136
+ const node = graph.nodes.get(f);
1137
+ if (node) totalCohesion += calculateEnhancedCohesion(node.exports);
1138
+ });
1139
+ const avgCohesion = totalCohesion / files.length;
1140
+ clusters.push({
1141
+ domain,
1142
+ files,
1143
+ totalTokens,
1144
+ fragmentationScore: fragmentation,
1145
+ avgCohesion,
1146
+ suggestedStructure: generateSuggestedStructure(
1147
+ files,
1148
+ totalTokens,
1149
+ fragmentation
1150
+ )
1151
+ });
1152
+ }
1153
+ return clusters;
1154
+ }
1155
+ function generateSuggestedStructure(files, tokens, fragmentation) {
1156
+ const targetFiles = Math.max(1, Math.ceil(tokens / 1e4));
1157
+ const plan = [];
1158
+ if (fragmentation > 0.5) {
1159
+ plan.push(
1160
+ `Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
1161
+ );
1162
+ }
1163
+ if (tokens > 2e4) {
1164
+ plan.push(
1165
+ `Domain logic is very large (${Math.round(tokens / 1e3)}k tokens). Ensure clear sub-domain boundaries.`
1166
+ );
1167
+ }
1168
+ return { targetFiles, consolidationPlan: plan };
1169
+ }
1170
+
1171
+ // src/remediation.ts
1549
1172
  function getClassificationRecommendations(classification, file, issues) {
1550
1173
  switch (classification) {
1551
1174
  case "barrel-export":
@@ -1604,9 +1227,252 @@ function getClassificationRecommendations(classification, file, issues) {
1604
1227
  }
1605
1228
  }
1606
1229
 
1607
- // src/scoring.ts
1608
- var import_core2 = require("@aiready/core");
1609
-
1230
+ // src/analyzer.ts
1231
+ function calculateCohesion(exports2, filePath, options) {
1232
+ if (exports2.length <= 1) return 1;
1233
+ if (filePath && isTestFile(filePath)) return 1;
1234
+ const domains = exports2.map((e) => e.inferredDomain || "unknown");
1235
+ const uniqueDomains = new Set(domains.filter((d) => d !== "unknown"));
1236
+ const hasImports = exports2.some((e) => !!e.imports);
1237
+ if (!hasImports && !options?.weights) {
1238
+ if (uniqueDomains.size <= 1) return 1;
1239
+ return 0.4;
1240
+ }
1241
+ return calculateEnhancedCohesion(exports2, filePath, options);
1242
+ }
1243
+ function analyzeIssues(params) {
1244
+ const {
1245
+ file,
1246
+ importDepth,
1247
+ contextBudget,
1248
+ cohesionScore,
1249
+ fragmentationScore,
1250
+ maxDepth,
1251
+ maxContextBudget,
1252
+ minCohesion,
1253
+ maxFragmentation,
1254
+ circularDeps
1255
+ } = params;
1256
+ const issues = [];
1257
+ const recommendations = [];
1258
+ let severity = "info";
1259
+ let potentialSavings = 0;
1260
+ if (circularDeps.length > 0) {
1261
+ severity = "critical";
1262
+ issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
1263
+ recommendations.push(
1264
+ "Break circular dependencies by extracting interfaces or using dependency injection"
1265
+ );
1266
+ potentialSavings += contextBudget * 0.2;
1267
+ }
1268
+ if (importDepth > maxDepth * 1.5) {
1269
+ severity = "critical";
1270
+ issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
1271
+ recommendations.push("Flatten dependency tree or use facade pattern");
1272
+ potentialSavings += contextBudget * 0.3;
1273
+ } else if (importDepth > maxDepth) {
1274
+ if (severity !== "critical") severity = "major";
1275
+ issues.push(
1276
+ `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
1277
+ );
1278
+ recommendations.push("Consider reducing dependency depth");
1279
+ potentialSavings += contextBudget * 0.15;
1280
+ }
1281
+ if (contextBudget > maxContextBudget * 1.5) {
1282
+ severity = "critical";
1283
+ issues.push(
1284
+ `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
1285
+ );
1286
+ recommendations.push(
1287
+ "Split into smaller modules or reduce dependency tree"
1288
+ );
1289
+ potentialSavings += contextBudget * 0.4;
1290
+ } else if (contextBudget > maxContextBudget) {
1291
+ if (severity !== "critical") severity = "major";
1292
+ issues.push(
1293
+ `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
1294
+ );
1295
+ recommendations.push("Reduce file size or dependencies");
1296
+ potentialSavings += contextBudget * 0.2;
1297
+ }
1298
+ if (cohesionScore < minCohesion * 0.5) {
1299
+ if (severity !== "critical") severity = "major";
1300
+ issues.push(
1301
+ `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
1302
+ );
1303
+ recommendations.push(
1304
+ "Split file by domain - separate unrelated functionality"
1305
+ );
1306
+ potentialSavings += contextBudget * 0.25;
1307
+ } else if (cohesionScore < minCohesion) {
1308
+ if (severity === "info") severity = "minor";
1309
+ issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
1310
+ recommendations.push("Consider grouping related exports together");
1311
+ potentialSavings += contextBudget * 0.1;
1312
+ }
1313
+ if (fragmentationScore > maxFragmentation) {
1314
+ if (severity === "info" || severity === "minor") severity = "minor";
1315
+ issues.push(
1316
+ `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
1317
+ );
1318
+ recommendations.push("Consolidate with related files in same domain");
1319
+ potentialSavings += contextBudget * 0.3;
1320
+ }
1321
+ if (issues.length === 0) {
1322
+ issues.push("No significant issues detected");
1323
+ recommendations.push("File is well-structured for AI context usage");
1324
+ }
1325
+ if (isBuildArtifact(file)) {
1326
+ issues.push("Detected build artifact (bundled/output file)");
1327
+ recommendations.push("Exclude build outputs from analysis");
1328
+ severity = "info";
1329
+ potentialSavings = 0;
1330
+ }
1331
+ return {
1332
+ severity,
1333
+ issues,
1334
+ recommendations,
1335
+ potentialSavings: Math.floor(potentialSavings)
1336
+ };
1337
+ }
1338
+ function isBuildArtifact(filePath) {
1339
+ const lower = filePath.toLowerCase();
1340
+ return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/.next/");
1341
+ }
1342
+
1343
+ // src/scoring.ts
1344
+ var import_core4 = require("@aiready/core");
1345
+
1346
+ // src/defaults.ts
1347
+ var import_core5 = require("@aiready/core");
1348
+
1349
+ // src/summary.ts
1350
+ function generateSummary(results) {
1351
+ if (results.length === 0) {
1352
+ return {
1353
+ totalFiles: 0,
1354
+ totalTokens: 0,
1355
+ avgContextBudget: 0,
1356
+ maxContextBudget: 0,
1357
+ avgImportDepth: 0,
1358
+ maxImportDepth: 0,
1359
+ deepFiles: [],
1360
+ avgFragmentation: 0,
1361
+ fragmentedModules: [],
1362
+ avgCohesion: 0,
1363
+ lowCohesionFiles: [],
1364
+ criticalIssues: 0,
1365
+ majorIssues: 0,
1366
+ minorIssues: 0,
1367
+ totalPotentialSavings: 0,
1368
+ topExpensiveFiles: []
1369
+ };
1370
+ }
1371
+ const totalFiles = results.length;
1372
+ const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
1373
+ const totalContextBudget = results.reduce(
1374
+ (sum, r) => sum + r.contextBudget,
1375
+ 0
1376
+ );
1377
+ const avgContextBudget = totalContextBudget / totalFiles;
1378
+ const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
1379
+ const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
1380
+ const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
1381
+ const deepFiles = results.filter((r) => r.importDepth >= 5).map((r) => ({ file: r.file, depth: r.importDepth })).sort((a, b) => b.depth - a.depth).slice(0, 10);
1382
+ const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
1383
+ const moduleMap = /* @__PURE__ */ new Map();
1384
+ for (const result of results) {
1385
+ for (const domain of result.domains) {
1386
+ if (!moduleMap.has(domain)) moduleMap.set(domain, []);
1387
+ moduleMap.get(domain).push(result);
1388
+ }
1389
+ }
1390
+ const fragmentedModules = [];
1391
+ for (const [domain, files] of moduleMap.entries()) {
1392
+ let jaccard2 = function(a, b) {
1393
+ const s1 = new Set(a || []);
1394
+ const s2 = new Set(b || []);
1395
+ if (s1.size === 0 && s2.size === 0) return 0;
1396
+ const inter = new Set([...s1].filter((x) => s2.has(x)));
1397
+ const uni = /* @__PURE__ */ new Set([...s1, ...s2]);
1398
+ return uni.size === 0 ? 0 : inter.size / uni.size;
1399
+ };
1400
+ var jaccard = jaccard2;
1401
+ if (files.length < 2) continue;
1402
+ const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
1403
+ if (fragmentationScore < 0.3) continue;
1404
+ const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
1405
+ const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
1406
+ const targetFiles = Math.max(1, Math.ceil(files.length / 3));
1407
+ const filePaths = files.map((f) => f.file);
1408
+ const pathEntropy = calculatePathEntropy(filePaths);
1409
+ const directoryDistance = calculateDirectoryDistance(filePaths);
1410
+ let importSimTotal = 0;
1411
+ let importPairs = 0;
1412
+ for (let i = 0; i < files.length; i++) {
1413
+ for (let j = i + 1; j < files.length; j++) {
1414
+ importSimTotal += jaccard2(
1415
+ files[i].dependencyList || [],
1416
+ files[j].dependencyList || []
1417
+ );
1418
+ importPairs++;
1419
+ }
1420
+ }
1421
+ const importCohesion = importPairs > 0 ? importSimTotal / importPairs : 0;
1422
+ fragmentedModules.push({
1423
+ domain,
1424
+ files: files.map((f) => f.file),
1425
+ totalTokens: totalTokens2,
1426
+ fragmentationScore,
1427
+ avgCohesion: avgCohesion2,
1428
+ importCohesion,
1429
+ pathEntropy,
1430
+ directoryDistance,
1431
+ suggestedStructure: {
1432
+ targetFiles,
1433
+ consolidationPlan: [
1434
+ `Consolidate ${files.length} files across ${new Set(files.map((f) => f.file.split("/").slice(0, -1).join("/"))).size} directories`,
1435
+ `Target ~${targetFiles} core modules to reduce context switching`
1436
+ ]
1437
+ }
1438
+ });
1439
+ }
1440
+ const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
1441
+ const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.4).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
1442
+ const criticalIssues = results.filter(
1443
+ (r) => r.severity === "critical"
1444
+ ).length;
1445
+ const majorIssues = results.filter((r) => r.severity === "major").length;
1446
+ const minorIssues = results.filter((r) => r.severity === "minor").length;
1447
+ const totalPotentialSavings = results.reduce(
1448
+ (sum, r) => sum + r.potentialSavings,
1449
+ 0
1450
+ );
1451
+ const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
1452
+ file: r.file,
1453
+ contextBudget: r.contextBudget,
1454
+ severity: r.severity
1455
+ }));
1456
+ return {
1457
+ totalFiles,
1458
+ totalTokens,
1459
+ avgContextBudget,
1460
+ maxContextBudget,
1461
+ avgImportDepth,
1462
+ maxImportDepth,
1463
+ deepFiles,
1464
+ avgFragmentation,
1465
+ fragmentedModules,
1466
+ avgCohesion,
1467
+ lowCohesionFiles,
1468
+ criticalIssues,
1469
+ majorIssues,
1470
+ minorIssues,
1471
+ totalPotentialSavings,
1472
+ topExpensiveFiles
1473
+ };
1474
+ }
1475
+
1610
1476
  // src/index.ts
1611
1477
  async function analyzeContext(options) {
1612
1478
  const {
@@ -1618,22 +1484,17 @@ async function analyzeContext(options) {
1618
1484
  includeNodeModules = false,
1619
1485
  ...scanOptions
1620
1486
  } = options;
1621
- const files = await (0, import_core4.scanFiles)({
1487
+ const files = await (0, import_core7.scanFiles)({
1622
1488
  ...scanOptions,
1623
- // Only add node_modules to exclude if includeNodeModules is false
1624
- // The DEFAULT_EXCLUDE already includes node_modules, so this is only needed
1625
- // if user overrides the default exclude list
1626
1489
  exclude: includeNodeModules && scanOptions.exclude ? scanOptions.exclude.filter(
1627
1490
  (pattern) => pattern !== "**/node_modules/**"
1628
1491
  ) : scanOptions.exclude
1629
1492
  });
1630
1493
  const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
1631
- const tsJsFiles = files.filter((f) => !f.toLowerCase().endsWith(".py"));
1632
- void tsJsFiles;
1633
1494
  const fileContents = await Promise.all(
1634
1495
  files.map(async (file) => ({
1635
1496
  file,
1636
- content: await (0, import_core4.readFileContent)(file)
1497
+ content: await (0, import_core7.readFileContent)(file)
1637
1498
  }))
1638
1499
  );
1639
1500
  const graph = buildDependencyGraph(
@@ -1653,7 +1514,6 @@ async function analyzeContext(options) {
1653
1514
  contextBudget: metric.contextBudget,
1654
1515
  cohesionScore: metric.cohesion,
1655
1516
  fragmentationScore: 0,
1656
- // Python analyzer doesn't calculate fragmentation yet
1657
1517
  maxDepth,
1658
1518
  maxContextBudget,
1659
1519
  minCohesion,
@@ -1667,7 +1527,6 @@ async function analyzeContext(options) {
1667
1527
  tokenCost: Math.floor(
1668
1528
  metric.contextBudget / (1 + metric.imports.length || 1)
1669
1529
  ),
1670
- // Estimate
1671
1530
  linesOfCode: metric.metrics.linesOfCode,
1672
1531
  importDepth: metric.importDepth,
1673
1532
  dependencyCount: metric.imports.length,
@@ -1679,13 +1538,11 @@ async function analyzeContext(options) {
1679
1538
  ),
1680
1539
  cohesionScore: metric.cohesion,
1681
1540
  domains: ["python"],
1682
- // Generic for now
1683
1541
  exportCount: metric.exports.length,
1684
1542
  contextBudget: metric.contextBudget,
1685
1543
  fragmentationScore: 0,
1686
1544
  relatedFiles: [],
1687
1545
  fileClassification: "unknown",
1688
- // Python files not yet classified
1689
1546
  severity,
1690
1547
  issues,
1691
1548
  recommendations,
@@ -1720,7 +1577,7 @@ async function analyzeContext(options) {
1720
1577
  break;
1721
1578
  }
1722
1579
  }
1723
- const { severity, issues, recommendations, potentialSavings } = analyzeIssues({
1580
+ const { issues } = analyzeIssues({
1724
1581
  file,
1725
1582
  importDepth,
1726
1583
  contextBudget,
@@ -1732,14 +1589,10 @@ async function analyzeContext(options) {
1732
1589
  maxFragmentation,
1733
1590
  circularDeps
1734
1591
  });
1735
- void severity;
1736
- void issues;
1737
- void recommendations;
1738
- void potentialSavings;
1739
1592
  const domains = [
1740
1593
  ...new Set(node.exports.map((e) => e.inferredDomain || "unknown"))
1741
1594
  ];
1742
- const fileClassification = classifyFile(node, cohesionScore, domains);
1595
+ const fileClassification = classifyFile(node);
1743
1596
  const adjustedCohesionScore = adjustCohesionForClassification(
1744
1597
  cohesionScore,
1745
1598
  fileClassification,
@@ -1764,7 +1617,6 @@ async function analyzeContext(options) {
1764
1617
  importDepth,
1765
1618
  contextBudget,
1766
1619
  cohesionScore: adjustedCohesionScore,
1767
- // Use adjusted cohesion
1768
1620
  fragmentationScore: adjustedFragmentationScore,
1769
1621
  maxDepth,
1770
1622
  maxContextBudget,
@@ -1781,7 +1633,6 @@ async function analyzeContext(options) {
1781
1633
  dependencyList,
1782
1634
  circularDeps: circularDeps.filter((cycle) => cycle.includes(file)),
1783
1635
  cohesionScore: adjustedCohesionScore,
1784
- // Report adjusted cohesion
1785
1636
  domains,
1786
1637
  exportCount: node.exports.length,
1787
1638
  contextBudget,
@@ -1798,262 +1649,19 @@ async function analyzeContext(options) {
1798
1649
  });
1799
1650
  }
1800
1651
  const allResults = [...results, ...pythonResults];
1801
- const sorted = allResults.sort((a, b) => {
1652
+ return allResults.sort((a, b) => {
1802
1653
  const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
1803
1654
  const severityDiff = severityOrder[a.severity] - severityOrder[b.severity];
1804
1655
  if (severityDiff !== 0) return severityDiff;
1805
1656
  return b.contextBudget - a.contextBudget;
1806
1657
  });
1807
- return sorted;
1808
- }
1809
- function generateSummary(results) {
1810
- if (results.length === 0) {
1811
- return {
1812
- totalFiles: 0,
1813
- totalTokens: 0,
1814
- avgContextBudget: 0,
1815
- maxContextBudget: 0,
1816
- avgImportDepth: 0,
1817
- maxImportDepth: 0,
1818
- deepFiles: [],
1819
- avgFragmentation: 0,
1820
- fragmentedModules: [],
1821
- avgCohesion: 0,
1822
- lowCohesionFiles: [],
1823
- criticalIssues: 0,
1824
- majorIssues: 0,
1825
- minorIssues: 0,
1826
- totalPotentialSavings: 0,
1827
- topExpensiveFiles: []
1828
- };
1829
- }
1830
- const totalFiles = results.length;
1831
- const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
1832
- const totalContextBudget = results.reduce(
1833
- (sum, r) => sum + r.contextBudget,
1834
- 0
1835
- );
1836
- const avgContextBudget = totalContextBudget / totalFiles;
1837
- const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
1838
- const avgImportDepth = results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
1839
- const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
1840
- const deepFiles = results.filter((r) => r.importDepth >= 5).map((r) => ({ file: r.file, depth: r.importDepth })).sort((a, b) => b.depth - a.depth).slice(0, 10);
1841
- const avgFragmentation = results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
1842
- const moduleMap = /* @__PURE__ */ new Map();
1843
- for (const result of results) {
1844
- for (const domain of result.domains) {
1845
- if (!moduleMap.has(domain)) {
1846
- moduleMap.set(domain, []);
1847
- }
1848
- moduleMap.get(domain).push(result);
1849
- }
1850
- }
1851
- const fragmentedModules = [];
1852
- for (const [domain, files] of moduleMap.entries()) {
1853
- let jaccard2 = function(a, b) {
1854
- const s1 = new Set(a || []);
1855
- const s2 = new Set(b || []);
1856
- if (s1.size === 0 && s2.size === 0) return 0;
1857
- const inter = new Set([...s1].filter((x) => s2.has(x)));
1858
- const uni = /* @__PURE__ */ new Set([...s1, ...s2]);
1859
- return uni.size === 0 ? 0 : inter.size / uni.size;
1860
- };
1861
- var jaccard = jaccard2;
1862
- if (files.length < 2) continue;
1863
- const fragmentationScore = files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
1864
- if (fragmentationScore < 0.3) continue;
1865
- const totalTokens2 = files.reduce((sum, f) => sum + f.tokenCost, 0);
1866
- const avgCohesion2 = files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
1867
- const targetFiles = Math.max(1, Math.ceil(files.length / 3));
1868
- const filePaths = files.map((f) => f.file);
1869
- const pathEntropy = calculatePathEntropy(filePaths);
1870
- const directoryDistance = calculateDirectoryDistance(filePaths);
1871
- let importSimTotal = 0;
1872
- let importPairs = 0;
1873
- for (let i = 0; i < files.length; i++) {
1874
- for (let j = i + 1; j < files.length; j++) {
1875
- importSimTotal += jaccard2(
1876
- files[i].dependencyList || [],
1877
- files[j].dependencyList || []
1878
- );
1879
- importPairs++;
1880
- }
1881
- }
1882
- const importCohesion = importPairs > 0 ? importSimTotal / importPairs : 0;
1883
- fragmentedModules.push({
1884
- domain,
1885
- files: files.map((f) => f.file),
1886
- totalTokens: totalTokens2,
1887
- fragmentationScore,
1888
- pathEntropy,
1889
- directoryDistance,
1890
- importCohesion,
1891
- avgCohesion: avgCohesion2,
1892
- suggestedStructure: {
1893
- targetFiles,
1894
- consolidationPlan: [
1895
- `Consolidate ${files.length} ${domain} files into ${targetFiles} cohesive file(s)`,
1896
- `Current token cost: ${totalTokens2.toLocaleString()}`,
1897
- `Estimated savings: ${Math.floor(totalTokens2 * 0.3).toLocaleString()} tokens (30%)`
1898
- ]
1899
- }
1900
- });
1901
- }
1902
- fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
1903
- const avgCohesion = results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
1904
- const lowCohesionFiles = results.filter((r) => r.cohesionScore < 0.6).map((r) => ({ file: r.file, score: r.cohesionScore })).sort((a, b) => a.score - b.score).slice(0, 10);
1905
- const criticalIssues = results.filter(
1906
- (r) => r.severity === "critical"
1907
- ).length;
1908
- const majorIssues = results.filter((r) => r.severity === "major").length;
1909
- const minorIssues = results.filter((r) => r.severity === "minor").length;
1910
- const totalPotentialSavings = results.reduce(
1911
- (sum, r) => sum + r.potentialSavings,
1912
- 0
1913
- );
1914
- const topExpensiveFiles = results.sort((a, b) => b.contextBudget - a.contextBudget).slice(0, 10).map((r) => ({
1915
- file: r.file,
1916
- contextBudget: r.contextBudget,
1917
- severity: r.severity
1918
- }));
1919
- return {
1920
- totalFiles,
1921
- totalTokens,
1922
- avgContextBudget,
1923
- maxContextBudget,
1924
- avgImportDepth,
1925
- maxImportDepth,
1926
- deepFiles,
1927
- avgFragmentation,
1928
- fragmentedModules: fragmentedModules.slice(0, 10),
1929
- avgCohesion,
1930
- lowCohesionFiles,
1931
- criticalIssues,
1932
- majorIssues,
1933
- minorIssues,
1934
- totalPotentialSavings,
1935
- topExpensiveFiles
1936
- };
1937
- }
1938
- function analyzeIssues(params) {
1939
- const {
1940
- file,
1941
- importDepth,
1942
- contextBudget,
1943
- cohesionScore,
1944
- fragmentationScore,
1945
- maxDepth,
1946
- maxContextBudget,
1947
- minCohesion,
1948
- maxFragmentation,
1949
- circularDeps
1950
- } = params;
1951
- const issues = [];
1952
- const recommendations = [];
1953
- let severity = "info";
1954
- let potentialSavings = 0;
1955
- if (circularDeps.length > 0) {
1956
- severity = "critical";
1957
- issues.push(`Part of ${circularDeps.length} circular dependency chain(s)`);
1958
- recommendations.push(
1959
- "Break circular dependencies by extracting interfaces or using dependency injection"
1960
- );
1961
- potentialSavings += contextBudget * 0.2;
1962
- }
1963
- if (importDepth > maxDepth * 1.5) {
1964
- severity = severity === "critical" ? "critical" : "critical";
1965
- issues.push(`Import depth ${importDepth} exceeds limit by 50%`);
1966
- recommendations.push("Flatten dependency tree or use facade pattern");
1967
- potentialSavings += contextBudget * 0.3;
1968
- } else if (importDepth > maxDepth) {
1969
- severity = severity === "critical" ? "critical" : "major";
1970
- issues.push(
1971
- `Import depth ${importDepth} exceeds recommended maximum ${maxDepth}`
1972
- );
1973
- recommendations.push("Consider reducing dependency depth");
1974
- potentialSavings += contextBudget * 0.15;
1975
- }
1976
- if (contextBudget > maxContextBudget * 1.5) {
1977
- severity = severity === "critical" ? "critical" : "critical";
1978
- issues.push(
1979
- `Context budget ${contextBudget.toLocaleString()} tokens is 50% over limit`
1980
- );
1981
- recommendations.push(
1982
- "Split into smaller modules or reduce dependency tree"
1983
- );
1984
- potentialSavings += contextBudget * 0.4;
1985
- } else if (contextBudget > maxContextBudget) {
1986
- severity = severity === "critical" || severity === "major" ? severity : "major";
1987
- issues.push(
1988
- `Context budget ${contextBudget.toLocaleString()} exceeds ${maxContextBudget.toLocaleString()}`
1989
- );
1990
- recommendations.push("Reduce file size or dependencies");
1991
- potentialSavings += contextBudget * 0.2;
1992
- }
1993
- if (cohesionScore < minCohesion * 0.5) {
1994
- severity = severity === "critical" ? "critical" : "major";
1995
- issues.push(
1996
- `Very low cohesion (${(cohesionScore * 100).toFixed(0)}%) - mixed concerns`
1997
- );
1998
- recommendations.push(
1999
- "Split file by domain - separate unrelated functionality"
2000
- );
2001
- potentialSavings += contextBudget * 0.25;
2002
- } else if (cohesionScore < minCohesion) {
2003
- severity = severity === "critical" || severity === "major" ? severity : "minor";
2004
- issues.push(`Low cohesion (${(cohesionScore * 100).toFixed(0)}%)`);
2005
- recommendations.push("Consider grouping related exports together");
2006
- potentialSavings += contextBudget * 0.1;
2007
- }
2008
- if (fragmentationScore > maxFragmentation) {
2009
- severity = severity === "critical" || severity === "major" ? severity : "minor";
2010
- issues.push(
2011
- `High fragmentation (${(fragmentationScore * 100).toFixed(0)}%) - scattered implementation`
2012
- );
2013
- recommendations.push("Consolidate with related files in same domain");
2014
- potentialSavings += contextBudget * 0.3;
2015
- }
2016
- if (issues.length === 0) {
2017
- issues.push("No significant issues detected");
2018
- recommendations.push("File is well-structured for AI context usage");
2019
- }
2020
- if (isBuildArtifact(file)) {
2021
- issues.push("Detected build artifact (bundled/output file)");
2022
- recommendations.push(
2023
- "Exclude build outputs (e.g., cdk.out, dist, build, .next) from analysis"
2024
- );
2025
- severity = downgradeSeverity(severity);
2026
- potentialSavings = 0;
2027
- }
2028
- return {
2029
- severity,
2030
- issues,
2031
- recommendations,
2032
- potentialSavings: Math.floor(potentialSavings)
2033
- };
2034
- }
2035
- function isBuildArtifact(filePath) {
2036
- const lower = filePath.toLowerCase();
2037
- return lower.includes("/node_modules/") || lower.includes("/dist/") || lower.includes("/build/") || lower.includes("/out/") || lower.includes("/output/") || lower.includes("/cdk.out/") || lower.includes("/.next/") || /\/asset\.[^/]+\//.test(lower);
2038
- }
2039
- function downgradeSeverity(s) {
2040
- switch (s) {
2041
- case "critical":
2042
- return "minor";
2043
- case "major":
2044
- return "minor";
2045
- case "minor":
2046
- return "info";
2047
- default:
2048
- return "info";
2049
- }
2050
1658
  }
2051
1659
 
2052
1660
  // src/cli.ts
2053
1661
  var import_chalk = __toESM(require("chalk"));
2054
1662
  var import_fs2 = require("fs");
2055
1663
  var import_path2 = require("path");
2056
- var import_core5 = require("@aiready/core");
1664
+ var import_core8 = require("@aiready/core");
2057
1665
  var import_prompts = __toESM(require("prompts"));
2058
1666
  var program = new import_commander.Command();
2059
1667
  program.name("aiready-context").description("Analyze AI context window cost and code structure").version("0.1.0").addHelpText(
@@ -2093,7 +1701,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
2093
1701
  exclude: void 0,
2094
1702
  maxResults: 10
2095
1703
  };
2096
- let finalOptions = await (0, import_core5.loadMergedConfig)(directory, defaults, {
1704
+ let finalOptions = await (0, import_core8.loadMergedConfig)(directory, defaults, {
2097
1705
  maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
2098
1706
  maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
2099
1707
  minCohesion: options.minCohesion ? parseFloat(options.minCohesion) : void 0,
@@ -2108,7 +1716,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
2108
1716
  finalOptions = await runInteractiveSetup(directory, finalOptions);
2109
1717
  }
2110
1718
  const results = await analyzeContext(finalOptions);
2111
- const elapsedTime = (0, import_core5.getElapsedTime)(startTime);
1719
+ const elapsedTime = (0, import_core8.getElapsedTime)(startTime);
2112
1720
  const summary = generateSummary(results);
2113
1721
  if (options.output === "json") {
2114
1722
  const jsonOutput = {
@@ -2117,12 +1725,12 @@ program.name("aiready-context").description("Analyze AI context window cost and
2117
1725
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2118
1726
  analysisTime: elapsedTime
2119
1727
  };
2120
- const outputPath = (0, import_core5.resolveOutputPath)(
1728
+ const outputPath = (0, import_core8.resolveOutputPath)(
2121
1729
  options.outputFile,
2122
1730
  `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
2123
1731
  directory
2124
1732
  );
2125
- (0, import_core5.handleJSONOutput)(
1733
+ (0, import_core8.handleJSONOutput)(
2126
1734
  jsonOutput,
2127
1735
  outputPath,
2128
1736
  `
@@ -2132,7 +1740,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
2132
1740
  }
2133
1741
  if (options.output === "html") {
2134
1742
  const html = generateHTMLReport(summary, results);
2135
- const outputPath = (0, import_core5.resolveOutputPath)(
1743
+ const outputPath = (0, import_core8.resolveOutputPath)(
2136
1744
  options.outputFile,
2137
1745
  `context-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
2138
1746
  directory
@@ -2154,7 +1762,7 @@ program.name("aiready-context").description("Analyze AI context window cost and
2154
1762
  );
2155
1763
  displayTuningGuidance(results, finalOptions);
2156
1764
  } catch (error) {
2157
- (0, import_core5.handleCLIError)(error, "Analysis");
1765
+ (0, import_core8.handleCLIError)(error, "Analysis");
2158
1766
  }
2159
1767
  });
2160
1768
  program.parse();