@aiready/core 0.9.32 → 0.9.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  getToolWeight,
19
19
  normalizeToolName,
20
20
  parseWeightString
21
- } from "./chunk-CWRCDSKZ.mjs";
21
+ } from "./chunk-HFLFBA6F.mjs";
22
22
 
23
23
  // src/utils/file-scanner.ts
24
24
  import { glob } from "glob";
@@ -88,7 +88,9 @@ async function scanFiles(options) {
88
88
  ignoreFromFile = [];
89
89
  }
90
90
  }
91
- const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...DEFAULT_EXCLUDE])];
91
+ const finalExclude = [
92
+ .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...DEFAULT_EXCLUDE])
93
+ ];
92
94
  const files = await glob(include, {
93
95
  cwd: rootDir,
94
96
  ignore: finalExclude,
@@ -336,7 +338,9 @@ async function loadConfig(rootDir) {
336
338
  return config;
337
339
  } catch (error) {
338
340
  const errorMessage = error instanceof Error ? error.message : String(error);
339
- throw new Error(`Failed to load config from ${configPath}: ${errorMessage}`);
341
+ throw new Error(
342
+ `Failed to load config from ${configPath}: ${errorMessage}`
343
+ );
340
344
  }
341
345
  }
342
346
  }
@@ -476,7 +480,7 @@ var MODEL_PRICING_PRESETS = {
476
480
  contextTier: "frontier",
477
481
  typicalQueriesPerDevPerDay: 150
478
482
  },
479
- "copilot": {
483
+ copilot: {
480
484
  name: "GitHub Copilot (subscription)",
481
485
  // Amortized per-request cost for a $19/month plan at 80 queries/day
482
486
  pricePer1KInputTokens: 1e-4,
@@ -542,9 +546,18 @@ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
542
546
  hourlyRate,
543
547
  totalCost: Math.round(totalCost),
544
548
  bySeverity: {
545
- critical: { hours: Math.round(hours.critical * 10) / 10, cost: Math.round(hours.critical * hourlyRate) },
546
- major: { hours: Math.round(hours.major * 10) / 10, cost: Math.round(hours.major * hourlyRate) },
547
- minor: { hours: Math.round(hours.minor * 10) / 10, cost: Math.round(hours.minor * hourlyRate) }
549
+ critical: {
550
+ hours: Math.round(hours.critical * 10) / 10,
551
+ cost: Math.round(hours.critical * hourlyRate)
552
+ },
553
+ major: {
554
+ hours: Math.round(hours.major * 10) / 10,
555
+ cost: Math.round(hours.major * hourlyRate)
556
+ },
557
+ minor: {
558
+ hours: Math.round(hours.minor * 10) / 10,
559
+ cost: Math.round(hours.minor * hourlyRate)
560
+ }
548
561
  }
549
562
  };
550
563
  }
@@ -602,12 +615,18 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
602
615
  const criticalBudget = tierThresholds.criticalTokens;
603
616
  const idealDepth = tierThresholds.idealDepth;
604
617
  const budgetRange = criticalBudget - idealBudget;
605
- const budgetFactor = Math.min(100, Math.max(
606
- 0,
607
- (contextBudget - idealBudget) / budgetRange * 100
608
- ));
609
- const depthFactor = Math.min(100, Math.max(0, (importDepth - idealDepth) * 10));
610
- const fragmentationFactor = Math.min(100, Math.max(0, (fragmentation - 0.3) * 250));
618
+ const budgetFactor = Math.min(
619
+ 100,
620
+ Math.max(0, (contextBudget - idealBudget) / budgetRange * 100)
621
+ );
622
+ const depthFactor = Math.min(
623
+ 100,
624
+ Math.max(0, (importDepth - idealDepth) * 10)
625
+ );
626
+ const fragmentationFactor = Math.min(
627
+ 100,
628
+ Math.max(0, (fragmentation - 0.3) * 250)
629
+ );
611
630
  const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
612
631
  const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
613
632
  const score = Math.round(
@@ -687,8 +706,12 @@ function calculateScoreTrend(history) {
687
706
  const now = /* @__PURE__ */ new Date();
688
707
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
689
708
  const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
690
- const last30Days = history.filter((e) => new Date(e.timestamp) >= thirtyDaysAgo);
691
- const last90Days = history.filter((e) => new Date(e.timestamp) >= ninetyDaysAgo);
709
+ const last30Days = history.filter(
710
+ (e) => new Date(e.timestamp) >= thirtyDaysAgo
711
+ );
712
+ const last90Days = history.filter(
713
+ (e) => new Date(e.timestamp) >= ninetyDaysAgo
714
+ );
692
715
  const currentScore = history[history.length - 1].overallScore;
693
716
  const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
694
717
  const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
@@ -701,7 +724,10 @@ function calculateScoreTrend(history) {
701
724
  if (change30Days > 3) direction = "improving";
702
725
  else if (change30Days < -3) direction = "degrading";
703
726
  else direction = "stable";
704
- const projectedScore = Math.max(0, Math.min(100, currentScore + velocity * 4));
727
+ const projectedScore = Math.max(
728
+ 0,
729
+ Math.min(100, currentScore + velocity * 4)
730
+ );
705
731
  return {
706
732
  direction,
707
733
  change30Days,
@@ -762,9 +788,13 @@ function calculateKnowledgeConcentration(files, authorData) {
762
788
  recommendations: ["No files to analyze"]
763
789
  };
764
790
  }
765
- const orphanFiles = files.filter((f) => f.exports < 2 && f.imports < 2).length;
791
+ const orphanFiles = files.filter(
792
+ (f) => f.exports < 2 && f.imports < 2
793
+ ).length;
766
794
  const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
767
- const uniqueConceptFiles = files.filter((f) => f.exports > avgExports * 2).length;
795
+ const uniqueConceptFiles = files.filter(
796
+ (f) => f.exports > avgExports * 2
797
+ ).length;
768
798
  const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
769
799
  const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
770
800
  let singleAuthorFiles = 0;
@@ -776,7 +806,10 @@ function calculateKnowledgeConcentration(files, authorData) {
776
806
  const orphanRisk = orphanFiles / files.length * 30;
777
807
  const uniqueRisk = concentrationRatio * 40;
778
808
  const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
779
- const score = Math.min(100, Math.round(orphanRisk + uniqueRisk + singleAuthorRisk));
809
+ const score = Math.min(
810
+ 100,
811
+ Math.round(orphanRisk + uniqueRisk + singleAuthorRisk)
812
+ );
780
813
  let rating;
781
814
  if (score < 20) rating = "low";
782
815
  else if (score < 40) rating = "moderate";
@@ -784,13 +817,19 @@ function calculateKnowledgeConcentration(files, authorData) {
784
817
  else rating = "critical";
785
818
  const recommendations = [];
786
819
  if (orphanFiles > files.length * 0.2) {
787
- recommendations.push(`Reduce ${orphanFiles} orphan files by connecting them to main modules`);
820
+ recommendations.push(
821
+ `Reduce ${orphanFiles} orphan files by connecting them to main modules`
822
+ );
788
823
  }
789
824
  if (uniqueConceptFiles > files.length * 0.1) {
790
- recommendations.push("Distribute high-export files into more focused modules");
825
+ recommendations.push(
826
+ "Distribute high-export files into more focused modules"
827
+ );
791
828
  }
792
829
  if (authorData && singleAuthorFiles > files.length * 0.3) {
793
- recommendations.push("Increase knowledge sharing to reduce single-author dependencies");
830
+ recommendations.push(
831
+ "Increase knowledge sharing to reduce single-author dependencies"
832
+ );
794
833
  }
795
834
  return {
796
835
  score,
@@ -944,7 +983,10 @@ var TypeScriptParser = class {
944
983
  specifiers,
945
984
  isTypeOnly,
946
985
  loc: node.loc ? {
947
- start: { line: node.loc.start.line, column: node.loc.start.column },
986
+ start: {
987
+ line: node.loc.start.line,
988
+ column: node.loc.start.column
989
+ },
948
990
  end: { line: node.loc.end.line, column: node.loc.end.column }
949
991
  } : void 0
950
992
  });
@@ -955,11 +997,16 @@ var TypeScriptParser = class {
955
997
  extractExports(ast, imports) {
956
998
  const exports = [];
957
999
  const importedNames = new Set(
958
- imports.flatMap((imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default"))
1000
+ imports.flatMap(
1001
+ (imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
1002
+ )
959
1003
  );
960
1004
  for (const node of ast.body) {
961
1005
  if (node.type === "ExportNamedDeclaration" && node.declaration) {
962
- const extracted = this.extractFromDeclaration(node.declaration, importedNames);
1006
+ const extracted = this.extractFromDeclaration(
1007
+ node.declaration,
1008
+ importedNames
1009
+ );
963
1010
  exports.push(...extracted);
964
1011
  } else if (node.type === "ExportDefaultDeclaration") {
965
1012
  let name = "default";
@@ -975,7 +1022,10 @@ var TypeScriptParser = class {
975
1022
  name,
976
1023
  type,
977
1024
  loc: node.loc ? {
978
- start: { line: node.loc.start.line, column: node.loc.start.column },
1025
+ start: {
1026
+ line: node.loc.start.line,
1027
+ column: node.loc.start.column
1028
+ },
979
1029
  end: { line: node.loc.end.line, column: node.loc.end.column }
980
1030
  } : void 0
981
1031
  });
@@ -997,7 +1047,10 @@ var TypeScriptParser = class {
997
1047
  line: declaration.loc.start.line,
998
1048
  column: declaration.loc.start.column
999
1049
  },
1000
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1050
+ end: {
1051
+ line: declaration.loc.end.line,
1052
+ column: declaration.loc.end.column
1053
+ }
1001
1054
  } : void 0
1002
1055
  });
1003
1056
  } else if (declaration.type === "ClassDeclaration" && declaration.id) {
@@ -1009,7 +1062,10 @@ var TypeScriptParser = class {
1009
1062
  line: declaration.loc.start.line,
1010
1063
  column: declaration.loc.start.column
1011
1064
  },
1012
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1065
+ end: {
1066
+ line: declaration.loc.end.line,
1067
+ column: declaration.loc.end.column
1068
+ }
1013
1069
  } : void 0
1014
1070
  });
1015
1071
  } else if (declaration.type === "VariableDeclaration") {
@@ -1040,7 +1096,10 @@ var TypeScriptParser = class {
1040
1096
  line: declaration.loc.start.line,
1041
1097
  column: declaration.loc.start.column
1042
1098
  },
1043
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1099
+ end: {
1100
+ line: declaration.loc.end.line,
1101
+ column: declaration.loc.end.column
1102
+ }
1044
1103
  } : void 0
1045
1104
  });
1046
1105
  } else if (declaration.type === "TSInterfaceDeclaration") {
@@ -1052,7 +1111,10 @@ var TypeScriptParser = class {
1052
1111
  line: declaration.loc.start.line,
1053
1112
  column: declaration.loc.start.column
1054
1113
  },
1055
- end: { line: declaration.loc.end.line, column: declaration.loc.end.column }
1114
+ end: {
1115
+ line: declaration.loc.end.line,
1116
+ column: declaration.loc.end.column
1117
+ }
1056
1118
  } : void 0
1057
1119
  });
1058
1120
  }
@@ -1077,7 +1139,9 @@ var PythonParser = class {
1077
1139
  try {
1078
1140
  this.initialized = true;
1079
1141
  } catch (error) {
1080
- throw new Error(`Failed to initialize Python parser: ${error.message}`);
1142
+ throw new Error(
1143
+ `Failed to initialize Python parser: ${error.message}`
1144
+ );
1081
1145
  }
1082
1146
  }
1083
1147
  parse(code, filePath) {
@@ -1088,7 +1152,9 @@ var PythonParser = class {
1088
1152
  exports,
1089
1153
  imports,
1090
1154
  language: "python" /* Python */,
1091
- warnings: ["Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."]
1155
+ warnings: [
1156
+ "Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."
1157
+ ]
1092
1158
  };
1093
1159
  } catch (error) {
1094
1160
  throw new ParseError(
@@ -1183,7 +1249,7 @@ var PythonParser = class {
1183
1249
  }
1184
1250
  /**
1185
1251
  * Regex-based export extraction (temporary implementation)
1186
- *
1252
+ *
1187
1253
  * Python doesn't have explicit exports like JavaScript.
1188
1254
  * We extract:
1189
1255
  * - Functions defined at module level (def)
@@ -1351,7 +1417,13 @@ function getSupportedLanguages() {
1351
1417
 
1352
1418
  // src/future-proof-metrics.ts
1353
1419
  function calculateCognitiveLoad(params) {
1354
- const { linesOfCode, exportCount, importCount, uniqueConcepts, cyclomaticComplexity = 1 } = params;
1420
+ const {
1421
+ linesOfCode,
1422
+ exportCount,
1423
+ importCount,
1424
+ uniqueConcepts,
1425
+ cyclomaticComplexity = 1
1426
+ } = params;
1355
1427
  const sizeFactor = {
1356
1428
  name: "Size Complexity",
1357
1429
  score: Math.min(100, Math.max(0, (linesOfCode - 50) / 10)),
@@ -1377,7 +1449,12 @@ function calculateCognitiveLoad(params) {
1377
1449
  weight: 0.2,
1378
1450
  description: `${uniqueConcepts} unique concepts`
1379
1451
  };
1380
- const factors = [sizeFactor, interfaceFactor, dependencyFactor, conceptFactor];
1452
+ const factors = [
1453
+ sizeFactor,
1454
+ interfaceFactor,
1455
+ dependencyFactor,
1456
+ conceptFactor
1457
+ ];
1381
1458
  const score = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
1382
1459
  let rating;
1383
1460
  if (score < 20) rating = "trivial";
@@ -1400,7 +1477,10 @@ function calculateCognitiveLoad(params) {
1400
1477
  function calculateSemanticDistance(params) {
1401
1478
  const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
1402
1479
  const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
1403
- const importOverlap = sharedDependencies.length / Math.max(1, Math.min(params.file1Imports.length, params.file2Imports.length));
1480
+ const importOverlap = sharedDependencies.length / Math.max(
1481
+ 1,
1482
+ Math.min(params.file1Imports.length, params.file2Imports.length)
1483
+ );
1404
1484
  const importDistance = 1 - importOverlap;
1405
1485
  const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
1406
1486
  let relationship;
@@ -1408,7 +1488,9 @@ function calculateSemanticDistance(params) {
1408
1488
  else if (file1Domain === file2Domain) relationship = "same-domain";
1409
1489
  else if (sharedDependencies.length > 0) relationship = "cross-domain";
1410
1490
  else relationship = "unrelated";
1411
- const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter((s) => typeof s === "string" && s.length > 0);
1491
+ const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter(
1492
+ (s) => typeof s === "string" && s.length > 0
1493
+ );
1412
1494
  return {
1413
1495
  between: [file1, file2],
1414
1496
  distance: Math.round(distance * 100) / 100,
@@ -1423,7 +1505,11 @@ function calculatePatternEntropy(files) {
1423
1505
  domain: "unknown",
1424
1506
  entropy: 0,
1425
1507
  rating: "crystalline",
1426
- distribution: { locationCount: 0, dominantLocation: "", giniCoefficient: 0 },
1508
+ distribution: {
1509
+ locationCount: 0,
1510
+ dominantLocation: "",
1511
+ giniCoefficient: 0
1512
+ },
1427
1513
  recommendations: ["No files to analyze"]
1428
1514
  };
1429
1515
  }
@@ -1463,10 +1549,14 @@ function calculatePatternEntropy(files) {
1463
1549
  else rating = "chaotic";
1464
1550
  const recommendations = [];
1465
1551
  if (normalizedEntropy > 0.5) {
1466
- recommendations.push(`Consolidate ${files.length} files into fewer directories by domain`);
1552
+ recommendations.push(
1553
+ `Consolidate ${files.length} files into fewer directories by domain`
1554
+ );
1467
1555
  }
1468
1556
  if (dirGroups.size > 5) {
1469
- recommendations.push("Consider barrel exports to reduce directory navigation");
1557
+ recommendations.push(
1558
+ "Consider barrel exports to reduce directory navigation"
1559
+ );
1470
1560
  }
1471
1561
  if (gini > 0.5) {
1472
1562
  recommendations.push("Redistribute files more evenly across directories");
@@ -1491,7 +1581,11 @@ function calculateConceptCohesion(params) {
1491
1581
  return {
1492
1582
  score: 1,
1493
1583
  rating: "excellent",
1494
- analysis: { uniqueDomains: 0, domainConcentration: 0, exportPurposeClarity: 1 }
1584
+ analysis: {
1585
+ uniqueDomains: 0,
1586
+ domainConcentration: 0,
1587
+ exportPurposeClarity: 1
1588
+ }
1495
1589
  };
1496
1590
  }
1497
1591
  const allDomains = [];
@@ -1597,7 +1691,10 @@ function calculateAiSignalClarity(params) {
1597
1691
  recommendations: []
1598
1692
  };
1599
1693
  }
1600
- const overloadRatio = Math.min(1, overloadedSymbols / Math.max(1, totalSymbols));
1694
+ const overloadRatio = Math.min(
1695
+ 1,
1696
+ overloadedSymbols / Math.max(1, totalSymbols)
1697
+ );
1601
1698
  const overloadSignal = {
1602
1699
  name: "Symbol Overloading",
1603
1700
  count: overloadedSymbols,
@@ -1621,7 +1718,10 @@ function calculateAiSignalClarity(params) {
1621
1718
  // 20% weight
1622
1719
  description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
1623
1720
  };
1624
- const sideEffectRatio = Math.min(1, implicitSideEffects / Math.max(1, totalExports));
1721
+ const sideEffectRatio = Math.min(
1722
+ 1,
1723
+ implicitSideEffects / Math.max(1, totalExports)
1724
+ );
1625
1725
  const sideEffectSignal = {
1626
1726
  name: "Implicit Side Effects",
1627
1727
  count: implicitSideEffects,
@@ -1629,7 +1729,10 @@ function calculateAiSignalClarity(params) {
1629
1729
  // 15% weight
1630
1730
  description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
1631
1731
  };
1632
- const callbackRatio = Math.min(1, deepCallbacks / Math.max(1, totalSymbols * 0.1));
1732
+ const callbackRatio = Math.min(
1733
+ 1,
1734
+ deepCallbacks / Math.max(1, totalSymbols * 0.1)
1735
+ );
1633
1736
  const callbackSignal = {
1634
1737
  name: "Callback Nesting",
1635
1738
  count: deepCallbacks,
@@ -1637,7 +1740,10 @@ function calculateAiSignalClarity(params) {
1637
1740
  // 10% weight
1638
1741
  description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
1639
1742
  };
1640
- const ambiguousRatio = Math.min(1, ambiguousNames / Math.max(1, totalSymbols));
1743
+ const ambiguousRatio = Math.min(
1744
+ 1,
1745
+ ambiguousNames / Math.max(1, totalSymbols)
1746
+ );
1641
1747
  const ambiguousSignal = {
1642
1748
  name: "Ambiguous Names",
1643
1749
  count: ambiguousNames,
@@ -1645,7 +1751,10 @@ function calculateAiSignalClarity(params) {
1645
1751
  // 10% weight
1646
1752
  description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
1647
1753
  };
1648
- const undocRatio = Math.min(1, undocumentedExports / Math.max(1, totalExports));
1754
+ const undocRatio = Math.min(
1755
+ 1,
1756
+ undocumentedExports / Math.max(1, totalExports)
1757
+ );
1649
1758
  const undocSignal = {
1650
1759
  name: "Undocumented Exports",
1651
1760
  count: undocumentedExports,
@@ -1662,30 +1771,45 @@ function calculateAiSignalClarity(params) {
1662
1771
  ambiguousSignal,
1663
1772
  undocSignal
1664
1773
  ];
1665
- const score = Math.min(100, signals.reduce((sum, s) => sum + s.riskContribution, 0));
1774
+ const score = Math.min(
1775
+ 100,
1776
+ signals.reduce((sum, s) => sum + s.riskContribution, 0)
1777
+ );
1666
1778
  let rating;
1667
1779
  if (score < 10) rating = "minimal";
1668
1780
  else if (score < 25) rating = "low";
1669
1781
  else if (score < 50) rating = "moderate";
1670
1782
  else if (score < 75) rating = "high";
1671
1783
  else rating = "severe";
1672
- const topSignal = signals.reduce((a, b) => a.riskContribution > b.riskContribution ? a : b);
1784
+ const topSignal = signals.reduce(
1785
+ (a, b) => a.riskContribution > b.riskContribution ? a : b
1786
+ );
1673
1787
  const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant AI signal claritys detected";
1674
1788
  const recommendations = [];
1675
1789
  if (overloadSignal.riskContribution > 5) {
1676
- recommendations.push(`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`);
1790
+ recommendations.push(
1791
+ `Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
1792
+ );
1677
1793
  }
1678
1794
  if (magicSignal.riskContribution > 5) {
1679
- recommendations.push(`Extract ${magicLiterals} magic literals into named constants`);
1795
+ recommendations.push(
1796
+ `Extract ${magicLiterals} magic literals into named constants`
1797
+ );
1680
1798
  }
1681
1799
  if (trapSignal.riskContribution > 5) {
1682
- recommendations.push(`Replace ${booleanTraps} boolean traps with named options objects`);
1800
+ recommendations.push(
1801
+ `Replace ${booleanTraps} boolean traps with named options objects`
1802
+ );
1683
1803
  }
1684
1804
  if (undocSignal.riskContribution > 5) {
1685
- recommendations.push(`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`);
1805
+ recommendations.push(
1806
+ `Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
1807
+ );
1686
1808
  }
1687
1809
  if (sideEffectSignal.riskContribution > 5) {
1688
- recommendations.push("Mark functions with side effects explicitly in their names or docs");
1810
+ recommendations.push(
1811
+ "Mark functions with side effects explicitly in their names or docs"
1812
+ );
1689
1813
  }
1690
1814
  return {
1691
1815
  score: Math.round(score),
@@ -1710,7 +1834,10 @@ function calculateAgentGrounding(params) {
1710
1834
  domainVocabularySize
1711
1835
  } = params;
1712
1836
  const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
1713
- const structureClarityScore = Math.max(0, Math.round(100 - deepDirRatio * 80));
1837
+ const structureClarityScore = Math.max(
1838
+ 0,
1839
+ Math.round(100 - deepDirRatio * 80)
1840
+ );
1714
1841
  const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
1715
1842
  const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
1716
1843
  let entryPointScore = 60;
@@ -1722,7 +1849,10 @@ function calculateAgentGrounding(params) {
1722
1849
  const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
1723
1850
  const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
1724
1851
  const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
1725
- const domainConsistencyScore = Math.max(0, Math.round(100 - inconsistencyRatio * 80));
1852
+ const domainConsistencyScore = Math.max(
1853
+ 0,
1854
+ Math.round(100 - inconsistencyRatio * 80)
1855
+ );
1726
1856
  const score = Math.round(
1727
1857
  structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
1728
1858
  );
@@ -1734,21 +1864,33 @@ function calculateAgentGrounding(params) {
1734
1864
  else rating = "disorienting";
1735
1865
  const recommendations = [];
1736
1866
  if (structureClarityScore < 70) {
1737
- recommendations.push(`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`);
1867
+ recommendations.push(
1868
+ `Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
1869
+ );
1738
1870
  }
1739
1871
  if (selfDocumentationScore < 70) {
1740
- recommendations.push(`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`);
1872
+ recommendations.push(
1873
+ `Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
1874
+ );
1741
1875
  }
1742
1876
  if (!hasRootReadme) {
1743
- recommendations.push("Add a root README.md so agents understand the project context immediately");
1877
+ recommendations.push(
1878
+ "Add a root README.md so agents understand the project context immediately"
1879
+ );
1744
1880
  } else if (!readmeIsFresh) {
1745
- recommendations.push("Update README.md \u2014 stale entry-point documentation disorients agents");
1881
+ recommendations.push(
1882
+ "Update README.md \u2014 stale entry-point documentation disorients agents"
1883
+ );
1746
1884
  }
1747
1885
  if (apiClarityScore < 70) {
1748
- recommendations.push(`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`);
1886
+ recommendations.push(
1887
+ `Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
1888
+ );
1749
1889
  }
1750
1890
  if (domainConsistencyScore < 70) {
1751
- recommendations.push(`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`);
1891
+ recommendations.push(
1892
+ `Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
1893
+ );
1752
1894
  }
1753
1895
  return {
1754
1896
  score,
@@ -1781,7 +1923,9 @@ function calculateTestabilityIndex(params) {
1781
1923
  const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
1782
1924
  const purityScore = Math.round(purityRatio * 100);
1783
1925
  const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
1784
- const dependencyInjectionScore = Math.round(Math.min(100, injectionRatio * 100));
1926
+ const dependencyInjectionScore = Math.round(
1927
+ Math.min(100, injectionRatio * 100)
1928
+ );
1785
1929
  const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
1786
1930
  const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
1787
1931
  const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
@@ -1797,25 +1941,36 @@ function calculateTestabilityIndex(params) {
1797
1941
  else rating = "unverifiable";
1798
1942
  let aiChangeSafetyRating;
1799
1943
  if (rawCoverageRatio >= 0.5 && score >= 70) aiChangeSafetyRating = "safe";
1800
- else if (rawCoverageRatio >= 0.2 && score >= 50) aiChangeSafetyRating = "moderate-risk";
1944
+ else if (rawCoverageRatio >= 0.2 && score >= 50)
1945
+ aiChangeSafetyRating = "moderate-risk";
1801
1946
  else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
1802
1947
  else aiChangeSafetyRating = "blind-risk";
1803
1948
  const recommendations = [];
1804
1949
  if (!hasTestFramework) {
1805
- recommendations.push("Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests");
1950
+ recommendations.push(
1951
+ "Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
1952
+ );
1806
1953
  }
1807
1954
  if (rawCoverageRatio < 0.3) {
1808
1955
  const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
1809
- recommendations.push(`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`);
1956
+ recommendations.push(
1957
+ `Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
1958
+ );
1810
1959
  }
1811
1960
  if (purityScore < 50) {
1812
- recommendations.push("Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable");
1961
+ recommendations.push(
1962
+ "Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
1963
+ );
1813
1964
  }
1814
1965
  if (dependencyInjectionScore < 50 && totalClasses > 0) {
1815
- recommendations.push("Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable");
1966
+ recommendations.push(
1967
+ "Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
1968
+ );
1816
1969
  }
1817
1970
  if (externalStateMutations > totalFunctions * 0.3) {
1818
- recommendations.push("Reduce direct state mutations \u2014 return values instead to improve observability");
1971
+ recommendations.push(
1972
+ "Reduce direct state mutations \u2014 return values instead to improve observability"
1973
+ );
1819
1974
  }
1820
1975
  return {
1821
1976
  score,
@@ -1832,7 +1987,12 @@ function calculateTestabilityIndex(params) {
1832
1987
  };
1833
1988
  }
1834
1989
  function calculateDocDrift(params) {
1835
- const { uncommentedExports, totalExports, outdatedComments, undocumentedComplexity } = params;
1990
+ const {
1991
+ uncommentedExports,
1992
+ totalExports,
1993
+ outdatedComments,
1994
+ undocumentedComplexity
1995
+ } = params;
1836
1996
  const uncommentedRatio = totalExports > 0 ? uncommentedExports / totalExports : 0;
1837
1997
  const outdatedScore = Math.min(100, outdatedComments * 15);
1838
1998
  const uncommentedScore = Math.min(100, uncommentedRatio * 100);
@@ -1849,13 +2009,19 @@ function calculateDocDrift(params) {
1849
2009
  else rating = "severe";
1850
2010
  const recommendations = [];
1851
2011
  if (outdatedComments > 0) {
1852
- recommendations.push(`Update or remove ${outdatedComments} outdated comments that contradict the code.`);
2012
+ recommendations.push(
2013
+ `Update or remove ${outdatedComments} outdated comments that contradict the code.`
2014
+ );
1853
2015
  }
1854
2016
  if (uncommentedRatio > 0.3) {
1855
- recommendations.push(`Add JSDoc to ${uncommentedExports} uncommented exports.`);
2017
+ recommendations.push(
2018
+ `Add JSDoc to ${uncommentedExports} uncommented exports.`
2019
+ );
1856
2020
  }
1857
2021
  if (undocumentedComplexity > 0) {
1858
- recommendations.push(`Explain the business logic for ${undocumentedComplexity} highly complex functions.`);
2022
+ recommendations.push(
2023
+ `Explain the business logic for ${undocumentedComplexity} highly complex functions.`
2024
+ );
1859
2025
  }
1860
2026
  return {
1861
2027
  score: finalScore,
@@ -1869,7 +2035,12 @@ function calculateDocDrift(params) {
1869
2035
  };
1870
2036
  }
1871
2037
  function calculateDependencyHealth(params) {
1872
- const { totalPackages, outdatedPackages, deprecatedPackages, trainingCutoffSkew } = params;
2038
+ const {
2039
+ totalPackages,
2040
+ outdatedPackages,
2041
+ deprecatedPackages,
2042
+ trainingCutoffSkew
2043
+ } = params;
1873
2044
  const outdatedRatio = totalPackages > 0 ? outdatedPackages / totalPackages : 0;
1874
2045
  const deprecatedRatio = totalPackages > 0 ? deprecatedPackages / totalPackages : 0;
1875
2046
  const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
@@ -1884,19 +2055,27 @@ function calculateDependencyHealth(params) {
1884
2055
  else if (score >= 30) rating = "poor";
1885
2056
  else rating = "hazardous";
1886
2057
  let aiKnowledgeConfidence;
1887
- if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0) aiKnowledgeConfidence = "high";
1888
- else if (trainingCutoffSkew < 0.5 && deprecatedPackages <= 2) aiKnowledgeConfidence = "moderate";
2058
+ if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0)
2059
+ aiKnowledgeConfidence = "high";
2060
+ else if (trainingCutoffSkew < 0.5 && deprecatedPackages <= 2)
2061
+ aiKnowledgeConfidence = "moderate";
1889
2062
  else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
1890
2063
  else aiKnowledgeConfidence = "blind";
1891
2064
  const recommendations = [];
1892
2065
  if (deprecatedPackages > 0) {
1893
- recommendations.push(`Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`);
2066
+ recommendations.push(
2067
+ `Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`
2068
+ );
1894
2069
  }
1895
2070
  if (outdatedRatio > 0.2) {
1896
- recommendations.push(`Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`);
2071
+ recommendations.push(
2072
+ `Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`
2073
+ );
1897
2074
  }
1898
2075
  if (trainingCutoffSkew > 0.5) {
1899
- recommendations.push("High training cutoff skew detected. AI may hallucinate APIs that were introduced recently.");
2076
+ recommendations.push(
2077
+ "High training cutoff skew detected. AI may hallucinate APIs that were introduced recently."
2078
+ );
1900
2079
  }
1901
2080
  return {
1902
2081
  score,
@@ -1937,10 +2116,14 @@ function calculateChangeAmplification(params) {
1937
2116
  else if (score < 90) rating = "contained";
1938
2117
  const recommendations = [];
1939
2118
  if (score < 70 && hotspots.length > 0) {
1940
- recommendations.push(`Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`);
2119
+ recommendations.push(
2120
+ `Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`
2121
+ );
1941
2122
  }
1942
2123
  if (maxAmplification > 30) {
1943
- recommendations.push(`Break down key bottlenecks with amplification factor > 30.`);
2124
+ recommendations.push(
2125
+ `Break down key bottlenecks with amplification factor > 30.`
2126
+ );
1944
2127
  }
1945
2128
  return {
1946
2129
  score: Math.round(score),
@@ -2022,7 +2205,11 @@ function calculateExtendedFutureProofScore(params) {
2022
2205
  recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
2023
2206
  }
2024
2207
  for (const rec of params.agentGrounding.recommendations) {
2025
- recommendations.push({ action: rec, estimatedImpact: 6, priority: "medium" });
2208
+ recommendations.push({
2209
+ action: rec,
2210
+ estimatedImpact: 6,
2211
+ priority: "medium"
2212
+ });
2026
2213
  }
2027
2214
  for (const rec of params.testability.recommendations) {
2028
2215
  const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
@@ -2033,12 +2220,20 @@ function calculateExtendedFutureProofScore(params) {
2033
2220
  }
2034
2221
  if (params.docDrift) {
2035
2222
  for (const rec of params.docDrift.recommendations) {
2036
- recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
2223
+ recommendations.push({
2224
+ action: rec,
2225
+ estimatedImpact: 8,
2226
+ priority: "high"
2227
+ });
2037
2228
  }
2038
2229
  }
2039
2230
  if (params.dependencyHealth) {
2040
2231
  for (const rec of params.dependencyHealth.recommendations) {
2041
- recommendations.push({ action: rec, estimatedImpact: 7, priority: "medium" });
2232
+ recommendations.push({
2233
+ action: rec,
2234
+ estimatedImpact: 7,
2235
+ priority: "medium"
2236
+ });
2042
2237
  }
2043
2238
  }
2044
2239
  const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;