@aiready/core 0.9.38 → 0.17.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.
package/dist/index.mjs CHANGED
@@ -20,6 +20,54 @@ import {
20
20
  parseWeightString
21
21
  } from "./chunk-UQGI67WR.mjs";
22
22
 
23
+ // src/types/contract.ts
24
+ function validateSpokeOutput(toolName, output) {
25
+ const errors = [];
26
+ if (!output) {
27
+ return { valid: false, errors: ["Output is null or undefined"] };
28
+ }
29
+ if (!Array.isArray(output.results)) {
30
+ errors.push(`${toolName}: 'results' must be an array`);
31
+ } else {
32
+ output.results.forEach((res, idx) => {
33
+ const fileName = res.fileName || res.file || res.filePath;
34
+ if (!fileName)
35
+ errors.push(
36
+ `${toolName}: results[${idx}] missing 'fileName', 'file' or 'filePath'`
37
+ );
38
+ const issues = res.issues;
39
+ if (!Array.isArray(issues)) {
40
+ errors.push(`${toolName}: results[${idx}] 'issues' must be an array`);
41
+ } else if (issues.length > 0) {
42
+ issues.forEach((issue, iidx) => {
43
+ if (typeof issue === "string") return;
44
+ if (!issue.type && !res.file)
45
+ errors.push(
46
+ `${toolName}: results[${idx}].issues[${iidx}] missing 'type'`
47
+ );
48
+ if (!issue.severity && !res.severity)
49
+ errors.push(
50
+ `${toolName}: results[${idx}].issues[${iidx}] missing 'severity'`
51
+ );
52
+ const severity = issue.severity || res.severity;
53
+ if (severity && !["critical", "major", "minor", "info"].includes(severity)) {
54
+ errors.push(
55
+ `${toolName}: results[${idx}].issues[${iidx}] has invalid severity: ${severity}`
56
+ );
57
+ }
58
+ });
59
+ }
60
+ });
61
+ }
62
+ if (!output.summary) {
63
+ errors.push(`${toolName}: missing 'summary'`);
64
+ }
65
+ return {
66
+ valid: errors.length === 0,
67
+ errors
68
+ };
69
+ }
70
+
23
71
  // src/utils/file-scanner.ts
24
72
  import { glob } from "glob";
25
73
  import { readFile } from "fs/promises";
@@ -513,8 +561,43 @@ function handleCLIError(error, commandName) {
513
561
  function getElapsedTime(startTime) {
514
562
  return ((Date.now() - startTime) / 1e3).toFixed(2);
515
563
  }
564
+ function getScoreBar(val) {
565
+ return "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
566
+ }
567
+ function getSafetyIcon(rating) {
568
+ switch (rating) {
569
+ case "safe":
570
+ return "\u2705";
571
+ case "moderate-risk":
572
+ return "\u26A0\uFE0F ";
573
+ case "high-risk":
574
+ return "\u{1F534}";
575
+ case "blind-risk":
576
+ return "\u{1F480}";
577
+ default:
578
+ return "\u2753";
579
+ }
580
+ }
581
+ function getSeverityColor(severity, chalk) {
582
+ switch (severity.toLowerCase()) {
583
+ case "critical":
584
+ case "high-risk":
585
+ case "blind-risk":
586
+ return chalk.red;
587
+ case "major":
588
+ case "moderate-risk":
589
+ return chalk.yellow;
590
+ case "minor":
591
+ case "safe":
592
+ return chalk.green;
593
+ case "info":
594
+ return chalk.blue;
595
+ default:
596
+ return chalk.white;
597
+ }
598
+ }
516
599
 
517
- // src/business-metrics.ts
600
+ // src/business/pricing-models.ts
518
601
  var MODEL_PRICING_PRESETS = {
519
602
  "gpt-5.3": {
520
603
  name: "GPT-5.3",
@@ -576,30 +659,17 @@ var MODEL_PRICING_PRESETS = {
576
659
  function getModelPreset(modelId) {
577
660
  return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
578
661
  }
662
+
663
+ // src/business/cost-metrics.ts
579
664
  var DEFAULT_COST_CONFIG = {
580
665
  pricePer1KTokens: 5e-3,
581
- // GPT-4o input price (updated from GPT-4 era 0.01)
582
666
  queriesPerDevPerDay: 60,
583
- // Average AI queries per developer (updated: 40→60 as of 2026)
584
667
  developerCount: 5,
585
- // Default team size
586
668
  daysPerMonth: 30
587
669
  };
588
- var SEVERITY_TIME_ESTIMATES = {
589
- critical: 4,
590
- // Complex architectural issues (industry avg: 6.8h)
591
- major: 2,
592
- // Significant refactoring needed (avg: 3.4h)
593
- minor: 0.5,
594
- // Simple naming/style fixes (avg: 0.8h)
595
- info: 0.25
596
- // Documentation improvements
597
- };
598
- var DEFAULT_HOURLY_RATE = 75;
599
670
  function calculateMonthlyCost(tokenWaste, config = {}) {
600
671
  const budget = calculateTokenBudget({
601
672
  totalContextTokens: tokenWaste * 2.5,
602
- // Heuristic: context is larger than waste
603
673
  wastedTokens: {
604
674
  duplication: tokenWaste * 0.7,
605
675
  fragmentation: tokenWaste * 0.3,
@@ -633,7 +703,6 @@ function calculateTokenBudget(params) {
633
703
  },
634
704
  efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
635
705
  potentialRetrievableTokens: Math.round(totalWaste * 0.8)
636
- // Heuristic: 80% is fixable
637
706
  };
638
707
  }
639
708
  function estimateCostFromBudget(budget, model, config = {}) {
@@ -657,6 +726,15 @@ function estimateCostFromBudget(budget, model, config = {}) {
657
726
  confidence
658
727
  };
659
728
  }
729
+
730
+ // src/business/productivity-metrics.ts
731
+ var SEVERITY_TIME_ESTIMATES = {
732
+ critical: 4,
733
+ major: 2,
734
+ minor: 0.5,
735
+ info: 0.25
736
+ };
737
+ var DEFAULT_HOURLY_RATE = 75;
660
738
  function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
661
739
  const counts = {
662
740
  critical: issues.filter((i) => i.severity === "critical").length,
@@ -697,108 +775,158 @@ function predictAcceptanceRate(toolOutputs) {
697
775
  const baseRate = 0.3;
698
776
  const patterns = toolOutputs.get("pattern-detect");
699
777
  if (patterns) {
700
- const patternImpact = (patterns.score - 50) * 3e-3;
701
778
  factors.push({
702
779
  name: "Semantic Duplication",
703
- impact: Math.round(patternImpact * 100)
780
+ impact: Math.round((patterns.score - 50) * 3e-3 * 100)
704
781
  });
705
782
  }
706
783
  const context = toolOutputs.get("context-analyzer");
707
784
  if (context) {
708
- const contextImpact = (context.score - 50) * 4e-3;
709
785
  factors.push({
710
786
  name: "Context Efficiency",
711
- impact: Math.round(contextImpact * 100)
787
+ impact: Math.round((context.score - 50) * 4e-3 * 100)
712
788
  });
713
789
  }
714
790
  const consistency = toolOutputs.get("consistency");
715
791
  if (consistency) {
716
- const consistencyImpact = (consistency.score - 50) * 2e-3;
717
792
  factors.push({
718
793
  name: "Code Consistency",
719
- impact: Math.round(consistencyImpact * 100)
794
+ impact: Math.round((consistency.score - 50) * 2e-3 * 100)
720
795
  });
721
796
  }
722
797
  const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
723
798
  if (aiSignalClarity) {
724
- const hrImpact = (50 - aiSignalClarity.score) * 2e-3;
725
799
  factors.push({
726
800
  name: "AI Signal Clarity",
727
- impact: Math.round(hrImpact * 100)
801
+ impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
728
802
  });
729
803
  }
730
804
  const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
731
805
  const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
732
- let confidence;
806
+ let confidence = 0.35;
733
807
  if (toolOutputs.size >= 4) confidence = 0.75;
734
808
  else if (toolOutputs.size >= 3) confidence = 0.65;
735
809
  else if (toolOutputs.size >= 2) confidence = 0.5;
736
- else confidence = 0.35;
737
- return {
738
- rate: Math.round(rate * 100) / 100,
739
- confidence,
740
- factors
741
- };
810
+ return { rate: Math.round(rate * 100) / 100, confidence, factors };
742
811
  }
743
- function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles, modelTier = "standard") {
744
- const tierThresholds = CONTEXT_TIER_THRESHOLDS[modelTier];
745
- const idealBudget = tierThresholds.idealTokens;
746
- const criticalBudget = tierThresholds.criticalTokens;
747
- const idealDepth = tierThresholds.idealDepth;
748
- const budgetRange = criticalBudget - idealBudget;
749
- const budgetFactor = Math.min(
750
- 100,
751
- Math.max(0, (contextBudget - idealBudget) / budgetRange * 100)
752
- );
753
- const depthFactor = Math.min(
754
- 100,
755
- Math.max(0, (importDepth - idealDepth) * 10)
756
- );
757
- const fragmentationFactor = Math.min(
758
- 100,
759
- Math.max(0, (fragmentation - 0.3) * 250)
760
- );
761
- const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
762
- const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
812
+
813
+ // src/business/risk-metrics.ts
814
+ function calculateKnowledgeConcentration(params) {
815
+ const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
816
+ const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
763
817
  const score = Math.round(
764
- budgetFactor * 0.35 + depthFactor * 0.2 + fragmentationFactor * 0.2 + consistencyFactor * 0.15 + fileFactor * 0.1
818
+ Math.min(
819
+ 100,
820
+ concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
821
+ )
765
822
  );
766
823
  let rating;
767
- if (score < 20) rating = "trivial";
768
- else if (score < 40) rating = "easy";
769
- else if (score < 60) rating = "moderate";
770
- else if (score < 80) rating = "difficult";
771
- else rating = "expert";
824
+ if (score < 30) rating = "low";
825
+ else if (score < 50) rating = "moderate";
826
+ else if (score < 75) rating = "high";
827
+ else rating = "critical";
828
+ const recommendations = [];
829
+ if (singleAuthorFiles > 0)
830
+ recommendations.push(
831
+ `Distribute knowledge for ${singleAuthorFiles} single-author files.`
832
+ );
833
+ if (orphanFiles > 0)
834
+ recommendations.push(
835
+ `Link ${orphanFiles} orphan files to the rest of the codebase.`
836
+ );
772
837
  return {
773
838
  score,
774
839
  rating,
775
- factors: [
776
- {
777
- name: "Context Budget",
778
- contribution: Math.round(budgetFactor * 0.35),
779
- description: `${Math.round(contextBudget)} tokens required (${modelTier} model tier: ideal <${idealBudget.toLocaleString()})`
780
- },
781
- {
782
- name: "Import Depth",
783
- contribution: Math.round(depthFactor * 0.2),
784
- description: `${importDepth.toFixed(1)} average levels (ideal <${idealDepth} for ${modelTier})`
785
- },
786
- {
787
- name: "Code Fragmentation",
788
- contribution: Math.round(fragmentationFactor * 0.2),
789
- description: `${(fragmentation * 100).toFixed(0)}% fragmentation`
790
- },
791
- {
792
- name: "Consistency",
793
- contribution: Math.round(consistencyFactor * 0.15),
794
- description: `${consistencyScore}/100 consistency score`
795
- },
796
- {
797
- name: "Project Scale",
798
- contribution: Math.round(fileFactor * 0.1),
799
- description: `${totalFiles} files analyzed`
800
- }
801
- ]
840
+ recommendations,
841
+ analysis: {
842
+ uniqueConceptFiles,
843
+ totalFiles,
844
+ concentrationRatio,
845
+ singleAuthorFiles,
846
+ orphanFiles
847
+ }
848
+ };
849
+ }
850
+ function calculateDebtInterest(principal, monthlyGrowthRate) {
851
+ const monthlyRate = monthlyGrowthRate;
852
+ const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
853
+ const monthlyCost = principal * monthlyRate;
854
+ return {
855
+ monthlyRate,
856
+ annualRate,
857
+ principal,
858
+ monthlyCost,
859
+ projections: {
860
+ months6: principal * Math.pow(1 + monthlyRate, 6),
861
+ months12: principal * Math.pow(1 + monthlyRate, 12),
862
+ months24: principal * Math.pow(1 + monthlyRate, 24)
863
+ }
864
+ };
865
+ }
866
+
867
+ // src/business/comprehension-metrics.ts
868
+ function calculateTechnicalValueChain(params) {
869
+ const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
870
+ const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
871
+ return {
872
+ score: Math.round(Math.max(0, Math.min(100, score))),
873
+ density: businessLogicDensity,
874
+ complexity: dataAccessComplexity,
875
+ surface: apiSurfaceArea
876
+ };
877
+ }
878
+ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
879
+ const tierMap = {
880
+ compact: "compact",
881
+ standard: "standard",
882
+ extended: "extended",
883
+ frontier: "frontier",
884
+ easy: "frontier",
885
+ // Map legacy 'easy' to 'frontier'
886
+ moderate: "standard",
887
+ difficult: "compact"
888
+ };
889
+ const tier = tierMap[modelTier] || "frontier";
890
+ const threshold = CONTEXT_TIER_THRESHOLDS[tier];
891
+ const budgetRatio = contextBudget / threshold.idealTokens;
892
+ const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
893
+ const finalScore = Math.round(Math.max(0, Math.min(100, score)));
894
+ let rating;
895
+ if (finalScore < 20) rating = "trivial";
896
+ else if (finalScore < 40) rating = "easy";
897
+ else if (finalScore < 60) rating = "moderate";
898
+ else if (finalScore < 85) rating = "difficult";
899
+ else rating = "expert";
900
+ return {
901
+ score: finalScore,
902
+ rating,
903
+ factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
904
+ };
905
+ }
906
+
907
+ // src/business-metrics.ts
908
+ function calculateBusinessROI(params) {
909
+ const model = getModelPreset(params.modelId || "claude-4.6");
910
+ const devCount = params.developerCount || 5;
911
+ const budget = calculateTokenBudget({
912
+ totalContextTokens: params.tokenWaste * 2.5,
913
+ wastedTokens: {
914
+ duplication: params.tokenWaste * 0.7,
915
+ fragmentation: params.tokenWaste * 0.3,
916
+ chattiness: 0
917
+ }
918
+ });
919
+ const cost = estimateCostFromBudget(budget, model, {
920
+ developerCount: devCount
921
+ });
922
+ const productivity = calculateProductivityImpact(params.issues);
923
+ const monthlySavings = cost.total;
924
+ const productivityGainHours = productivity.totalHours;
925
+ const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
926
+ return {
927
+ monthlySavings: Math.round(monthlySavings),
928
+ productivityGainHours: Math.round(productivityGainHours),
929
+ annualValue: Math.round(annualValue)
802
930
  };
803
931
  }
804
932
  function formatCost(cost) {
@@ -824,220 +952,6 @@ function formatHours(hours) {
824
952
  function formatAcceptanceRate(rate) {
825
953
  return `${Math.round(rate * 100)}%`;
826
954
  }
827
- function calculateScoreTrend(history) {
828
- if (history.length < 2) {
829
- return {
830
- direction: "stable",
831
- change30Days: 0,
832
- change90Days: 0,
833
- velocity: 0,
834
- projectedScore: history[0]?.overallScore || 100
835
- };
836
- }
837
- const now = /* @__PURE__ */ new Date();
838
- const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
839
- const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
840
- const last30Days = history.filter(
841
- (e) => new Date(e.timestamp) >= thirtyDaysAgo
842
- );
843
- const last90Days = history.filter(
844
- (e) => new Date(e.timestamp) >= ninetyDaysAgo
845
- );
846
- const currentScore = history[history.length - 1].overallScore;
847
- const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
848
- const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
849
- const change30Days = currentScore - thirtyDaysAgoScore;
850
- const change90Days = currentScore - ninetyDaysAgoScore;
851
- const weeksOfData = Math.max(1, history.length / 7);
852
- const totalChange = currentScore - history[0].overallScore;
853
- const velocity = totalChange / weeksOfData;
854
- let direction;
855
- if (change30Days > 3) direction = "improving";
856
- else if (change30Days < -3) direction = "degrading";
857
- else direction = "stable";
858
- const projectedScore = Math.max(
859
- 0,
860
- Math.min(100, currentScore + velocity * 4)
861
- );
862
- return {
863
- direction,
864
- change30Days,
865
- change90Days,
866
- velocity: Math.round(velocity * 10) / 10,
867
- projectedScore: Math.round(projectedScore)
868
- };
869
- }
870
- function calculateRemediationVelocity(history, currentIssues) {
871
- if (history.length < 2) {
872
- return {
873
- issuesFixedThisWeek: 0,
874
- avgIssuesPerWeek: 0,
875
- trend: "stable",
876
- estimatedCompletionWeeks: currentIssues > 0 ? Infinity : 0
877
- };
878
- }
879
- const now = /* @__PURE__ */ new Date();
880
- const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
881
- const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1e3);
882
- const thisWeek = history.filter((e) => new Date(e.timestamp) >= oneWeekAgo);
883
- const lastWeek = history.filter(
884
- (e) => new Date(e.timestamp) >= twoWeeksAgo && new Date(e.timestamp) < oneWeekAgo
885
- );
886
- const issuesFixedThisWeek = thisWeek.length > 1 ? thisWeek[0].totalIssues - thisWeek[thisWeek.length - 1].totalIssues : 0;
887
- const totalIssuesFixed = history[0].totalIssues - history[history.length - 1].totalIssues;
888
- const weeksOfData = Math.max(1, history.length / 7);
889
- const avgIssuesPerWeek = totalIssuesFixed / weeksOfData;
890
- let trend;
891
- if (lastWeek.length > 1) {
892
- const lastWeekFixed = lastWeek[0].totalIssues - lastWeek[lastWeek.length - 1].totalIssues;
893
- if (issuesFixedThisWeek > lastWeekFixed * 1.2) trend = "accelerating";
894
- else if (issuesFixedThisWeek < lastWeekFixed * 0.8) trend = "decelerating";
895
- else trend = "stable";
896
- } else {
897
- trend = "stable";
898
- }
899
- const estimatedCompletionWeeks = avgIssuesPerWeek > 0 ? Math.ceil(currentIssues / avgIssuesPerWeek) : Infinity;
900
- return {
901
- issuesFixedThisWeek: Math.max(0, issuesFixedThisWeek),
902
- avgIssuesPerWeek: Math.round(avgIssuesPerWeek * 10) / 10,
903
- trend,
904
- estimatedCompletionWeeks
905
- };
906
- }
907
- function calculateKnowledgeConcentration(files, authorData) {
908
- if (files.length === 0) {
909
- return {
910
- score: 0,
911
- rating: "low",
912
- analysis: {
913
- uniqueConceptFiles: 0,
914
- totalFiles: 0,
915
- concentrationRatio: 0,
916
- singleAuthorFiles: 0,
917
- orphanFiles: 0
918
- },
919
- recommendations: ["No files to analyze"]
920
- };
921
- }
922
- const orphanFiles = files.filter(
923
- (f) => f.exports < 2 && f.imports < 2
924
- ).length;
925
- const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
926
- const uniqueConceptFiles = files.filter(
927
- (f) => f.exports > avgExports * 2
928
- ).length;
929
- const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
930
- const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
931
- let singleAuthorFiles = 0;
932
- if (authorData) {
933
- for (const files2 of authorData.values()) {
934
- if (files2.length === 1) singleAuthorFiles++;
935
- }
936
- }
937
- const orphanRisk = orphanFiles / files.length * 30;
938
- const uniqueRisk = concentrationRatio * 40;
939
- const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
940
- const score = Math.min(
941
- 100,
942
- Math.round(orphanRisk + uniqueRisk + singleAuthorRisk)
943
- );
944
- let rating;
945
- if (score < 20) rating = "low";
946
- else if (score < 40) rating = "moderate";
947
- else if (score < 70) rating = "high";
948
- else rating = "critical";
949
- const recommendations = [];
950
- if (orphanFiles > files.length * 0.2) {
951
- recommendations.push(
952
- `Reduce ${orphanFiles} orphan files by connecting them to main modules`
953
- );
954
- }
955
- if (uniqueConceptFiles > files.length * 0.1) {
956
- recommendations.push(
957
- "Distribute high-export files into more focused modules"
958
- );
959
- }
960
- if (authorData && singleAuthorFiles > files.length * 0.3) {
961
- recommendations.push(
962
- "Increase knowledge sharing to reduce single-author dependencies"
963
- );
964
- }
965
- return {
966
- score,
967
- rating,
968
- analysis: {
969
- uniqueConceptFiles,
970
- totalFiles: files.length,
971
- concentrationRatio: Math.round(concentrationRatio * 100) / 100,
972
- singleAuthorFiles,
973
- orphanFiles
974
- },
975
- recommendations
976
- };
977
- }
978
- function calculateTechnicalDebtInterest(params) {
979
- const { currentMonthlyCost, issues, monthsOpen } = params;
980
- const criticalCount = issues.filter((i) => i.severity === "critical").length;
981
- const majorCount = issues.filter((i) => i.severity === "major").length;
982
- const minorCount = issues.filter((i) => i.severity === "minor").length;
983
- const severityWeight = (criticalCount * 3 + majorCount * 2 + minorCount * 1) / Math.max(1, issues.length);
984
- const baseRate = 0.02 + severityWeight * 0.01;
985
- const timeMultiplier = Math.max(1, 1 + monthsOpen * 0.1);
986
- const monthlyRate = baseRate * timeMultiplier;
987
- const projectDebt = (principal2, months) => {
988
- let debt = principal2;
989
- for (let i = 0; i < months; i++) {
990
- debt = debt * (1 + monthlyRate);
991
- }
992
- return Math.round(debt);
993
- };
994
- const principal = currentMonthlyCost * 12;
995
- const projections = {
996
- months6: projectDebt(principal, 6),
997
- months12: projectDebt(principal, 12),
998
- months24: projectDebt(principal, 24)
999
- };
1000
- return {
1001
- monthlyRate: Math.round(monthlyRate * 1e4) / 100,
1002
- annualRate: Math.round((Math.pow(1 + monthlyRate, 12) - 1) * 1e4) / 100,
1003
- principal,
1004
- projections,
1005
- monthlyCost: Math.round(currentMonthlyCost * (1 + monthlyRate) * 100) / 100
1006
- };
1007
- }
1008
- function getDebtBreakdown(patternCost, contextCost, consistencyCost) {
1009
- const breakdowns = [
1010
- {
1011
- category: "Semantic Duplication",
1012
- currentCost: patternCost,
1013
- monthlyGrowthRate: 5,
1014
- // Grows as devs copy-paste
1015
- priority: patternCost > 1e3 ? "high" : "medium",
1016
- fixCost: patternCost * 3
1017
- // Fixing costs 3x current waste
1018
- },
1019
- {
1020
- category: "Context Fragmentation",
1021
- currentCost: contextCost,
1022
- monthlyGrowthRate: 3,
1023
- // Grows with new features
1024
- priority: contextCost > 500 ? "high" : "medium",
1025
- fixCost: contextCost * 2.5
1026
- },
1027
- {
1028
- category: "Consistency Issues",
1029
- currentCost: consistencyCost,
1030
- monthlyGrowthRate: 2,
1031
- // Grows with new devs
1032
- priority: consistencyCost > 200 ? "medium" : "low",
1033
- fixCost: consistencyCost * 1.5
1034
- }
1035
- ];
1036
- return breakdowns.sort((a, b) => {
1037
- const priorityOrder = { high: 0, medium: 1, low: 2 };
1038
- return priorityOrder[a.priority] - priorityOrder[b.priority];
1039
- });
1040
- }
1041
955
  function generateValueChain(params) {
1042
956
  const { issueType, count, severity } = params;
1043
957
  const impacts = {
@@ -1077,9 +991,7 @@ function generateValueChain(params) {
1077
991
  },
1078
992
  businessOutcome: {
1079
993
  directCost: count * 12,
1080
- // Placeholder linear cost
1081
994
  opportunityCost: productivityLoss * 15e3,
1082
- // Monthly avg dev cost $15k
1083
995
  riskLevel: impact.risk
1084
996
  }
1085
997
  };
@@ -1594,7 +1506,55 @@ function getSupportedLanguages() {
1594
1506
  return ParserFactory.getInstance().getSupportedLanguages();
1595
1507
  }
1596
1508
 
1597
- // src/future-proof-metrics.ts
1509
+ // src/metrics/remediation-utils.ts
1510
+ function collectFutureProofRecommendations(params) {
1511
+ const recommendations = [];
1512
+ for (const rec of params.aiSignalClarity.recommendations) {
1513
+ recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
1514
+ }
1515
+ for (const rec of params.agentGrounding.recommendations) {
1516
+ recommendations.push({
1517
+ action: rec,
1518
+ estimatedImpact: 6,
1519
+ priority: "medium"
1520
+ });
1521
+ }
1522
+ for (const rec of params.testability.recommendations) {
1523
+ const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
1524
+ recommendations.push({ action: rec, estimatedImpact: 10, priority });
1525
+ }
1526
+ for (const rec of params.patternEntropy.recommendations) {
1527
+ recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
1528
+ }
1529
+ if (params.conceptCohesion.rating === "poor") {
1530
+ recommendations.push({
1531
+ action: "Improve concept cohesion by grouping related exports",
1532
+ estimatedImpact: 8,
1533
+ priority: "high"
1534
+ });
1535
+ }
1536
+ if (params.docDrift) {
1537
+ for (const rec of params.docDrift.recommendations) {
1538
+ recommendations.push({
1539
+ action: rec,
1540
+ estimatedImpact: 8,
1541
+ priority: "high"
1542
+ });
1543
+ }
1544
+ }
1545
+ if (params.dependencyHealth) {
1546
+ for (const rec of params.dependencyHealth.recommendations) {
1547
+ recommendations.push({
1548
+ action: rec,
1549
+ estimatedImpact: 7,
1550
+ priority: "medium"
1551
+ });
1552
+ }
1553
+ }
1554
+ return recommendations;
1555
+ }
1556
+
1557
+ // src/metrics/cognitive-load.ts
1598
1558
  function calculateCognitiveLoad(params) {
1599
1559
  const {
1600
1560
  linesOfCode,
@@ -1653,13 +1613,20 @@ function calculateCognitiveLoad(params) {
1653
1613
  }
1654
1614
  };
1655
1615
  }
1616
+
1617
+ // src/metrics/semantic-distance.ts
1656
1618
  function calculateSemanticDistance(params) {
1657
- const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
1619
+ const {
1620
+ file1,
1621
+ file2,
1622
+ file1Domain,
1623
+ file2Domain,
1624
+ sharedDependencies,
1625
+ file1Imports,
1626
+ file2Imports
1627
+ } = params;
1658
1628
  const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
1659
- const importOverlap = sharedDependencies.length / Math.max(
1660
- 1,
1661
- Math.min(params.file1Imports.length, params.file2Imports.length)
1662
- );
1629
+ const importOverlap = sharedDependencies.length / Math.max(1, Math.min(file1Imports.length, file2Imports.length));
1663
1630
  const importDistance = 1 - importOverlap;
1664
1631
  const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
1665
1632
  let relationship;
@@ -1678,6 +1645,8 @@ function calculateSemanticDistance(params) {
1678
1645
  reason: relationship === "same-domain" ? `Both in "${file1Domain}" domain` : relationship === "cross-domain" ? `Share ${sharedDependencies.length} dependency(ies)` : "No strong semantic relationship detected"
1679
1646
  };
1680
1647
  }
1648
+
1649
+ // src/metrics/structural-metrics.ts
1681
1650
  function calculatePatternEntropy(files) {
1682
1651
  if (files.length === 0) {
1683
1652
  return {
@@ -1727,23 +1696,18 @@ function calculatePatternEntropy(files) {
1727
1696
  else if (normalizedEntropy < 0.8) rating = "fragmented";
1728
1697
  else rating = "chaotic";
1729
1698
  const recommendations = [];
1730
- if (normalizedEntropy > 0.5) {
1699
+ if (normalizedEntropy > 0.5)
1731
1700
  recommendations.push(
1732
1701
  `Consolidate ${files.length} files into fewer directories by domain`
1733
1702
  );
1734
- }
1735
- if (dirGroups.size > 5) {
1703
+ if (dirGroups.size > 5)
1736
1704
  recommendations.push(
1737
1705
  "Consider barrel exports to reduce directory navigation"
1738
1706
  );
1739
- }
1740
- if (gini > 0.5) {
1707
+ if (gini > 0.5)
1741
1708
  recommendations.push("Redistribute files more evenly across directories");
1742
- }
1743
- const firstFile = files.length > 0 ? files[0] : null;
1744
- const domainValue = firstFile ? firstFile.domain : "mixed";
1745
1709
  return {
1746
- domain: domainValue,
1710
+ domain: files[0]?.domain || "mixed",
1747
1711
  entropy: Math.round(normalizedEntropy * 100) / 100,
1748
1712
  rating,
1749
1713
  distribution: {
@@ -1774,9 +1738,8 @@ function calculateConceptCohesion(params) {
1774
1738
  }
1775
1739
  const uniqueDomains = new Set(allDomains);
1776
1740
  const domainCounts = /* @__PURE__ */ new Map();
1777
- for (const d of allDomains) {
1741
+ for (const d of allDomains)
1778
1742
  domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
1779
- }
1780
1743
  const maxCount = Math.max(...Array.from(domainCounts.values()), 1);
1781
1744
  const domainConcentration = maxCount / allDomains.length;
1782
1745
  const exportPurposeClarity = 1 - (uniqueDomains.size - 1) / Math.max(1, exports.length);
@@ -1796,59 +1759,8 @@ function calculateConceptCohesion(params) {
1796
1759
  }
1797
1760
  };
1798
1761
  }
1799
- function calculateFutureProofScore(params) {
1800
- const loadScore = 100 - params.cognitiveLoad.score;
1801
- const entropyScore = 100 - params.patternEntropy.entropy * 100;
1802
- const cohesionScore = params.conceptCohesion.score * 100;
1803
- const overall = Math.round(
1804
- loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
1805
- );
1806
- const factors = [
1807
- {
1808
- name: "Cognitive Load",
1809
- impact: Math.round(loadScore - 50),
1810
- description: params.cognitiveLoad.rating
1811
- },
1812
- {
1813
- name: "Pattern Entropy",
1814
- impact: Math.round(entropyScore - 50),
1815
- description: params.patternEntropy.rating
1816
- },
1817
- {
1818
- name: "Concept Cohesion",
1819
- impact: Math.round(cohesionScore - 50),
1820
- description: params.conceptCohesion.rating
1821
- }
1822
- ];
1823
- const recommendations = [];
1824
- for (const rec of params.patternEntropy.recommendations) {
1825
- recommendations.push({
1826
- action: rec,
1827
- estimatedImpact: 5,
1828
- priority: "medium"
1829
- });
1830
- }
1831
- if (params.conceptCohesion.rating === "poor") {
1832
- recommendations.push({
1833
- action: "Improve concept cohesion by grouping related exports",
1834
- estimatedImpact: 8,
1835
- priority: "high"
1836
- });
1837
- }
1838
- const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
1839
- return {
1840
- toolName: "future-proof",
1841
- score: overall,
1842
- rawMetrics: {
1843
- cognitiveLoadScore: params.cognitiveLoad.score,
1844
- entropyScore: params.patternEntropy.entropy,
1845
- cohesionScore: params.conceptCohesion.score,
1846
- semanticDistanceAvg
1847
- },
1848
- factors,
1849
- recommendations
1850
- };
1851
- }
1762
+
1763
+ // src/metrics/ai-signal-clarity.ts
1852
1764
  function calculateAiSignalClarity(params) {
1853
1765
  const {
1854
1766
  overloadedSymbols,
@@ -1870,75 +1782,53 @@ function calculateAiSignalClarity(params) {
1870
1782
  recommendations: []
1871
1783
  };
1872
1784
  }
1873
- const overloadRatio = Math.min(
1874
- 1,
1875
- overloadedSymbols / Math.max(1, totalSymbols)
1876
- );
1785
+ const overloadRatio = overloadedSymbols / Math.max(1, totalSymbols);
1877
1786
  const overloadSignal = {
1878
1787
  name: "Symbol Overloading",
1879
1788
  count: overloadedSymbols,
1880
- riskContribution: Math.round(overloadRatio * 100 * 0.25),
1881
- // 25% weight
1789
+ riskContribution: Math.round(Math.min(1, overloadRatio) * 100 * 0.25),
1882
1790
  description: `${overloadedSymbols} overloaded symbols \u2014 AI picks wrong signature`
1883
1791
  };
1884
- const magicRatio = Math.min(1, magicLiterals / Math.max(1, totalSymbols * 2));
1792
+ const magicRatio = magicLiterals / Math.max(1, totalSymbols * 2);
1885
1793
  const magicSignal = {
1886
1794
  name: "Magic Literals",
1887
1795
  count: magicLiterals,
1888
- riskContribution: Math.round(magicRatio * 100 * 0.2),
1889
- // 20% weight
1796
+ riskContribution: Math.round(Math.min(1, magicRatio) * 100 * 0.2),
1890
1797
  description: `${magicLiterals} unnamed constants \u2014 AI invents wrong values`
1891
1798
  };
1892
- const trapRatio = Math.min(1, booleanTraps / Math.max(1, totalSymbols));
1799
+ const trapRatio = booleanTraps / Math.max(1, totalSymbols);
1893
1800
  const trapSignal = {
1894
1801
  name: "Boolean Traps",
1895
1802
  count: booleanTraps,
1896
- riskContribution: Math.round(trapRatio * 100 * 0.2),
1897
- // 20% weight
1803
+ riskContribution: Math.round(Math.min(1, trapRatio) * 100 * 0.2),
1898
1804
  description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
1899
1805
  };
1900
- const sideEffectRatio = Math.min(
1901
- 1,
1902
- implicitSideEffects / Math.max(1, totalExports)
1903
- );
1806
+ const sideEffectRatio = implicitSideEffects / Math.max(1, totalExports);
1904
1807
  const sideEffectSignal = {
1905
1808
  name: "Implicit Side Effects",
1906
1809
  count: implicitSideEffects,
1907
- riskContribution: Math.round(sideEffectRatio * 100 * 0.15),
1908
- // 15% weight
1810
+ riskContribution: Math.round(Math.min(1, sideEffectRatio) * 100 * 0.15),
1909
1811
  description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
1910
1812
  };
1911
- const callbackRatio = Math.min(
1912
- 1,
1913
- deepCallbacks / Math.max(1, totalSymbols * 0.1)
1914
- );
1813
+ const callbackRatio = deepCallbacks / Math.max(1, totalSymbols * 0.1);
1915
1814
  const callbackSignal = {
1916
1815
  name: "Callback Nesting",
1917
1816
  count: deepCallbacks,
1918
- riskContribution: Math.round(callbackRatio * 100 * 0.1),
1919
- // 10% weight
1817
+ riskContribution: Math.round(Math.min(1, callbackRatio) * 100 * 0.1),
1920
1818
  description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
1921
1819
  };
1922
- const ambiguousRatio = Math.min(
1923
- 1,
1924
- ambiguousNames / Math.max(1, totalSymbols)
1925
- );
1820
+ const ambiguousRatio = ambiguousNames / Math.max(1, totalSymbols);
1926
1821
  const ambiguousSignal = {
1927
1822
  name: "Ambiguous Names",
1928
1823
  count: ambiguousNames,
1929
- riskContribution: Math.round(ambiguousRatio * 100 * 0.1),
1930
- // 10% weight
1824
+ riskContribution: Math.round(Math.min(1, ambiguousRatio) * 100 * 0.1),
1931
1825
  description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
1932
1826
  };
1933
- const undocRatio = Math.min(
1934
- 1,
1935
- undocumentedExports / Math.max(1, totalExports)
1936
- );
1827
+ const undocRatio = undocumentedExports / Math.max(1, totalExports);
1937
1828
  const undocSignal = {
1938
1829
  name: "Undocumented Exports",
1939
1830
  count: undocumentedExports,
1940
- riskContribution: Math.round(undocRatio * 100 * 0.1),
1941
- // 10% weight
1831
+ riskContribution: Math.round(Math.min(1, undocRatio) * 100 * 0.1),
1942
1832
  description: `${undocumentedExports} public functions without docs \u2014 AI fabricates behavior`
1943
1833
  };
1944
1834
  const signals = [
@@ -1963,33 +1853,28 @@ function calculateAiSignalClarity(params) {
1963
1853
  const topSignal = signals.reduce(
1964
1854
  (a, b) => a.riskContribution > b.riskContribution ? a : b
1965
1855
  );
1966
- const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant AI signal claritys detected";
1856
+ const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant issues detected";
1967
1857
  const recommendations = [];
1968
- if (overloadSignal.riskContribution > 5) {
1858
+ if (overloadSignal.riskContribution > 5)
1969
1859
  recommendations.push(
1970
1860
  `Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
1971
1861
  );
1972
- }
1973
- if (magicSignal.riskContribution > 5) {
1862
+ if (magicSignal.riskContribution > 5)
1974
1863
  recommendations.push(
1975
1864
  `Extract ${magicLiterals} magic literals into named constants`
1976
1865
  );
1977
- }
1978
- if (trapSignal.riskContribution > 5) {
1866
+ if (trapSignal.riskContribution > 5)
1979
1867
  recommendations.push(
1980
1868
  `Replace ${booleanTraps} boolean traps with named options objects`
1981
1869
  );
1982
- }
1983
- if (undocSignal.riskContribution > 5) {
1870
+ if (undocSignal.riskContribution > 5)
1984
1871
  recommendations.push(
1985
1872
  `Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
1986
1873
  );
1987
- }
1988
- if (sideEffectSignal.riskContribution > 5) {
1874
+ if (sideEffectSignal.riskContribution > 5)
1989
1875
  recommendations.push(
1990
1876
  "Mark functions with side effects explicitly in their names or docs"
1991
1877
  );
1992
- }
1993
1878
  return {
1994
1879
  score: Math.round(score),
1995
1880
  rating,
@@ -1998,6 +1883,8 @@ function calculateAiSignalClarity(params) {
1998
1883
  recommendations
1999
1884
  };
2000
1885
  }
1886
+
1887
+ // src/metrics/agent-grounding.ts
2001
1888
  function calculateAgentGrounding(params) {
2002
1889
  const {
2003
1890
  deepDirectories,
@@ -2012,25 +1899,33 @@ function calculateAgentGrounding(params) {
2012
1899
  inconsistentDomainTerms,
2013
1900
  domainVocabularySize
2014
1901
  } = params;
2015
- const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
2016
1902
  const structureClarityScore = Math.max(
2017
1903
  0,
2018
- Math.round(100 - deepDirRatio * 80)
1904
+ Math.round(
1905
+ 100 - (totalDirectories > 0 ? deepDirectories / totalDirectories * 80 : 0)
1906
+ )
1907
+ );
1908
+ const selfDocumentationScore = Math.max(
1909
+ 0,
1910
+ Math.round(100 - (totalFiles > 0 ? vagueFileNames / totalFiles * 90 : 0))
2019
1911
  );
2020
- const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
2021
- const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
2022
1912
  let entryPointScore = 60;
2023
1913
  if (hasRootReadme) entryPointScore += 25;
2024
1914
  if (readmeIsFresh) entryPointScore += 10;
2025
1915
  const barrelRatio = totalFiles > 0 ? barrelExports / (totalFiles * 0.1) : 0;
2026
1916
  entryPointScore += Math.round(Math.min(5, barrelRatio * 5));
2027
1917
  entryPointScore = Math.min(100, entryPointScore);
2028
- const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
2029
- const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
2030
- const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
1918
+ const apiClarityScore = Math.max(
1919
+ 0,
1920
+ Math.round(
1921
+ 100 - (totalExports > 0 ? untypedExports / totalExports * 70 : 0)
1922
+ )
1923
+ );
2031
1924
  const domainConsistencyScore = Math.max(
2032
1925
  0,
2033
- Math.round(100 - inconsistencyRatio * 80)
1926
+ Math.round(
1927
+ 100 - (domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize * 80 : 0)
1928
+ )
2034
1929
  );
2035
1930
  const score = Math.round(
2036
1931
  structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
@@ -2042,35 +1937,30 @@ function calculateAgentGrounding(params) {
2042
1937
  else if (score >= 30) rating = "poor";
2043
1938
  else rating = "disorienting";
2044
1939
  const recommendations = [];
2045
- if (structureClarityScore < 70) {
1940
+ if (structureClarityScore < 70)
2046
1941
  recommendations.push(
2047
1942
  `Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
2048
1943
  );
2049
- }
2050
- if (selfDocumentationScore < 70) {
1944
+ if (selfDocumentationScore < 70)
2051
1945
  recommendations.push(
2052
1946
  `Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
2053
1947
  );
2054
- }
2055
- if (!hasRootReadme) {
1948
+ if (!hasRootReadme)
2056
1949
  recommendations.push(
2057
1950
  "Add a root README.md so agents understand the project context immediately"
2058
1951
  );
2059
- } else if (!readmeIsFresh) {
1952
+ else if (!readmeIsFresh)
2060
1953
  recommendations.push(
2061
1954
  "Update README.md \u2014 stale entry-point documentation disorients agents"
2062
1955
  );
2063
- }
2064
- if (apiClarityScore < 70) {
1956
+ if (apiClarityScore < 70)
2065
1957
  recommendations.push(
2066
1958
  `Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
2067
1959
  );
2068
- }
2069
- if (domainConsistencyScore < 70) {
1960
+ if (domainConsistencyScore < 70)
2070
1961
  recommendations.push(
2071
1962
  `Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
2072
1963
  );
2073
- }
2074
1964
  return {
2075
1965
  score,
2076
1966
  rating,
@@ -2084,6 +1974,8 @@ function calculateAgentGrounding(params) {
2084
1974
  recommendations
2085
1975
  };
2086
1976
  }
1977
+
1978
+ // src/metrics/testability-index.ts
2087
1979
  function calculateTestabilityIndex(params) {
2088
1980
  const {
2089
1981
  testFiles,
@@ -2099,16 +1991,27 @@ function calculateTestabilityIndex(params) {
2099
1991
  } = params;
2100
1992
  const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
2101
1993
  const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
2102
- const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
2103
- const purityScore = Math.round(purityRatio * 100);
2104
- const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
1994
+ const purityScore = Math.round(
1995
+ (totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
1996
+ );
2105
1997
  const dependencyInjectionScore = Math.round(
2106
- Math.min(100, injectionRatio * 100)
1998
+ Math.min(
1999
+ 100,
2000
+ (totalClasses > 0 ? injectionPatterns / totalClasses : 0.5) * 100
2001
+ )
2002
+ );
2003
+ const interfaceFocusScore = Math.max(
2004
+ 0,
2005
+ Math.round(
2006
+ 100 - (totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces * 80 : 0)
2007
+ )
2008
+ );
2009
+ const observabilityScore = Math.max(
2010
+ 0,
2011
+ Math.round(
2012
+ 100 - (totalFunctions > 0 ? externalStateMutations / totalFunctions * 100 : 0)
2013
+ )
2107
2014
  );
2108
- const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
2109
- const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
2110
- const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
2111
- const observabilityScore = Math.max(0, Math.round(100 - mutationRatio * 100));
2112
2015
  const frameworkWeight = hasTestFramework ? 1 : 0.8;
2113
2016
  const rawScore = (testCoverageRatio * 0.3 + purityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
2114
2017
  const score = Math.max(0, Math.min(100, Math.round(rawScore)));
@@ -2125,32 +2028,28 @@ function calculateTestabilityIndex(params) {
2125
2028
  else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
2126
2029
  else aiChangeSafetyRating = "blind-risk";
2127
2030
  const recommendations = [];
2128
- if (!hasTestFramework) {
2031
+ if (!hasTestFramework)
2129
2032
  recommendations.push(
2130
2033
  "Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
2131
2034
  );
2132
- }
2133
2035
  if (rawCoverageRatio < 0.3) {
2134
2036
  const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
2135
2037
  recommendations.push(
2136
2038
  `Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
2137
2039
  );
2138
2040
  }
2139
- if (purityScore < 50) {
2041
+ if (purityScore < 50)
2140
2042
  recommendations.push(
2141
2043
  "Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
2142
2044
  );
2143
- }
2144
- if (dependencyInjectionScore < 50 && totalClasses > 0) {
2045
+ if (dependencyInjectionScore < 50 && totalClasses > 0)
2145
2046
  recommendations.push(
2146
2047
  "Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
2147
2048
  );
2148
- }
2149
- if (externalStateMutations > totalFunctions * 0.3) {
2049
+ if (externalStateMutations > totalFunctions * 0.3)
2150
2050
  recommendations.push(
2151
2051
  "Reduce direct state mutations \u2014 return values instead to improve observability"
2152
2052
  );
2153
- }
2154
2053
  return {
2155
2054
  score,
2156
2055
  rating,
@@ -2165,6 +2064,8 @@ function calculateTestabilityIndex(params) {
2165
2064
  recommendations
2166
2065
  };
2167
2066
  }
2067
+
2068
+ // src/metrics/doc-drift.ts
2168
2069
  function calculateDocDrift(params) {
2169
2070
  const {
2170
2071
  uncommentedExports,
@@ -2187,21 +2088,18 @@ function calculateDocDrift(params) {
2187
2088
  else if (finalScore < 85) rating = "high";
2188
2089
  else rating = "severe";
2189
2090
  const recommendations = [];
2190
- if (outdatedComments > 0) {
2091
+ if (outdatedComments > 0)
2191
2092
  recommendations.push(
2192
2093
  `Update or remove ${outdatedComments} outdated comments that contradict the code.`
2193
2094
  );
2194
- }
2195
- if (uncommentedRatio > 0.3) {
2095
+ if (uncommentedRatio > 0.3)
2196
2096
  recommendations.push(
2197
2097
  `Add JSDoc to ${uncommentedExports} uncommented exports.`
2198
2098
  );
2199
- }
2200
- if (undocumentedComplexity > 0) {
2099
+ if (undocumentedComplexity > 0)
2201
2100
  recommendations.push(
2202
2101
  `Explain the business logic for ${undocumentedComplexity} highly complex functions.`
2203
2102
  );
2204
- }
2205
2103
  return {
2206
2104
  score: finalScore,
2207
2105
  rating,
@@ -2213,6 +2111,8 @@ function calculateDocDrift(params) {
2213
2111
  recommendations
2214
2112
  };
2215
2113
  }
2114
+
2115
+ // src/metrics/dependency-health.ts
2216
2116
  function calculateDependencyHealth(params) {
2217
2117
  const {
2218
2118
  totalPackages,
@@ -2225,8 +2125,12 @@ function calculateDependencyHealth(params) {
2225
2125
  const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
2226
2126
  const deprecatedScore = Math.max(0, 100 - deprecatedRatio * 500);
2227
2127
  const skewScore = Math.max(0, 100 - trainingCutoffSkew * 100);
2228
- const rawScore = outdatedScore * 0.3 + deprecatedScore * 0.4 + skewScore * 0.3;
2229
- const score = Math.round(Math.min(100, Math.max(0, rawScore)));
2128
+ const score = Math.round(
2129
+ Math.min(
2130
+ 100,
2131
+ Math.max(0, outdatedScore * 0.3 + deprecatedScore * 0.4 + skewScore * 0.3)
2132
+ )
2133
+ );
2230
2134
  let rating;
2231
2135
  if (score >= 85) rating = "excellent";
2232
2136
  else if (score >= 70) rating = "good";
@@ -2241,33 +2145,22 @@ function calculateDependencyHealth(params) {
2241
2145
  else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
2242
2146
  else aiKnowledgeConfidence = "blind";
2243
2147
  const recommendations = [];
2244
- if (deprecatedPackages > 0) {
2245
- recommendations.push(
2246
- `Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`
2247
- );
2248
- }
2249
- if (outdatedRatio > 0.2) {
2250
- recommendations.push(
2251
- `Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`
2252
- );
2253
- }
2254
- if (trainingCutoffSkew > 0.5) {
2255
- recommendations.push(
2256
- "High training cutoff skew detected. AI may hallucinate APIs that were introduced recently."
2257
- );
2258
- }
2148
+ if (deprecatedPackages > 0)
2149
+ recommendations.push(`Replace ${deprecatedPackages} deprecated packages.`);
2150
+ if (outdatedRatio > 0.2)
2151
+ recommendations.push(`Update ${outdatedPackages} outdated packages.`);
2152
+ if (trainingCutoffSkew > 0.5)
2153
+ recommendations.push("High training cutoff skew detected.");
2259
2154
  return {
2260
2155
  score,
2261
2156
  rating,
2262
- dimensions: {
2263
- outdatedPackages,
2264
- deprecatedPackages,
2265
- trainingCutoffSkew
2266
- },
2157
+ dimensions: { outdatedPackages, deprecatedPackages, trainingCutoffSkew },
2267
2158
  aiKnowledgeConfidence,
2268
2159
  recommendations
2269
2160
  };
2270
2161
  }
2162
+
2163
+ // src/metrics/change-amplification.ts
2271
2164
  function calculateChangeAmplification(params) {
2272
2165
  const { files } = params;
2273
2166
  if (files.length === 0) {
@@ -2280,15 +2173,16 @@ function calculateChangeAmplification(params) {
2280
2173
  recommendations: []
2281
2174
  };
2282
2175
  }
2283
- const hotspots = files.map((f) => {
2284
- const amplificationFactor = f.fanOut + f.fanIn * 0.5;
2285
- return { ...f, amplificationFactor };
2286
- }).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
2176
+ const hotspots = files.map((f) => ({ ...f, amplificationFactor: f.fanOut + f.fanIn * 0.5 })).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
2287
2177
  const maxAmplification = hotspots[0].amplificationFactor;
2288
2178
  const avgAmplification = hotspots.reduce((sum, h) => sum + h.amplificationFactor, 0) / hotspots.length;
2289
- let score = 100 - avgAmplification * 5;
2290
- if (maxAmplification > 20) score -= maxAmplification - 20;
2291
- score = Math.max(0, Math.min(100, score));
2179
+ let score = Math.max(
2180
+ 0,
2181
+ Math.min(
2182
+ 100,
2183
+ 100 - avgAmplification * 5 - (maxAmplification > 20 ? maxAmplification - 20 : 0)
2184
+ )
2185
+ );
2292
2186
  let rating = "isolated";
2293
2187
  if (score < 40) rating = "explosive";
2294
2188
  else if (score < 70) rating = "amplified";
@@ -2296,12 +2190,12 @@ function calculateChangeAmplification(params) {
2296
2190
  const recommendations = [];
2297
2191
  if (score < 70 && hotspots.length > 0) {
2298
2192
  recommendations.push(
2299
- `Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`
2193
+ `Refactor top hotspot '${hotspots[0].file}' to reduce coupling.`
2300
2194
  );
2301
2195
  }
2302
2196
  if (maxAmplification > 30) {
2303
2197
  recommendations.push(
2304
- `Break down key bottlenecks with amplification factor > 30.`
2198
+ "Break down key bottlenecks with amplification factor > 30."
2305
2199
  );
2306
2200
  }
2307
2201
  return {
@@ -2313,6 +2207,61 @@ function calculateChangeAmplification(params) {
2313
2207
  recommendations
2314
2208
  };
2315
2209
  }
2210
+
2211
+ // src/future-proof-metrics.ts
2212
+ function calculateFutureProofScore(params) {
2213
+ const loadScore = 100 - params.cognitiveLoad.score;
2214
+ const entropyScore = 100 - params.patternEntropy.entropy * 100;
2215
+ const cohesionScore = params.conceptCohesion.score * 100;
2216
+ const overall = Math.round(
2217
+ loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
2218
+ );
2219
+ const factors = [
2220
+ {
2221
+ name: "Cognitive Load",
2222
+ impact: Math.round(loadScore - 50),
2223
+ description: params.cognitiveLoad.rating
2224
+ },
2225
+ {
2226
+ name: "Pattern Entropy",
2227
+ impact: Math.round(entropyScore - 50),
2228
+ description: params.patternEntropy.rating
2229
+ },
2230
+ {
2231
+ name: "Concept Cohesion",
2232
+ impact: Math.round(cohesionScore - 50),
2233
+ description: params.conceptCohesion.rating
2234
+ }
2235
+ ];
2236
+ const recommendations = [];
2237
+ for (const rec of params.patternEntropy.recommendations) {
2238
+ recommendations.push({
2239
+ action: rec,
2240
+ estimatedImpact: 5,
2241
+ priority: "medium"
2242
+ });
2243
+ }
2244
+ if (params.conceptCohesion.rating === "poor") {
2245
+ recommendations.push({
2246
+ action: "Improve concept cohesion by grouping related exports",
2247
+ estimatedImpact: 8,
2248
+ priority: "high"
2249
+ });
2250
+ }
2251
+ const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
2252
+ return {
2253
+ toolName: "future-proof",
2254
+ score: overall,
2255
+ rawMetrics: {
2256
+ cognitiveLoadScore: params.cognitiveLoad.score,
2257
+ entropyScore: params.patternEntropy.entropy,
2258
+ cohesionScore: params.conceptCohesion.score,
2259
+ semanticDistanceAvg
2260
+ },
2261
+ factors,
2262
+ recommendations
2263
+ };
2264
+ }
2316
2265
  function calculateExtendedFutureProofScore(params) {
2317
2266
  const loadScore = 100 - params.cognitiveLoad.score;
2318
2267
  const entropyScore = 100 - params.patternEntropy.entropy * 100;
@@ -2321,7 +2270,7 @@ function calculateExtendedFutureProofScore(params) {
2321
2270
  const groundingScore = params.agentGrounding.score;
2322
2271
  const testabilityScore = params.testability.score;
2323
2272
  const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
2324
- const depsHealthScore = params.dependencyHealth ? params.dependencyHealth.score : 100;
2273
+ const depsHealthScore = params.dependencyHealth?.score ?? 100;
2325
2274
  let totalWeight = 0.8;
2326
2275
  let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
2327
2276
  if (params.docDrift) {
@@ -2352,7 +2301,7 @@ function calculateExtendedFutureProofScore(params) {
2352
2301
  {
2353
2302
  name: "AI Signal Clarity",
2354
2303
  impact: Math.round(aiSignalClarityScore - 50),
2355
- description: `${params.aiSignalClarity.rating} risk (${params.aiSignalClarity.score}/100 raw)`
2304
+ description: `${params.aiSignalClarity.rating} risk`
2356
2305
  },
2357
2306
  {
2358
2307
  name: "Agent Grounding",
@@ -2362,60 +2311,25 @@ function calculateExtendedFutureProofScore(params) {
2362
2311
  {
2363
2312
  name: "Testability",
2364
2313
  impact: Math.round(testabilityScore - 50),
2365
- description: `${params.testability.rating} \u2014 AI changes are ${params.testability.aiChangeSafetyRating}`
2314
+ description: params.testability.rating
2366
2315
  }
2367
2316
  ];
2368
2317
  if (params.docDrift) {
2369
2318
  factors.push({
2370
2319
  name: "Documentation Drift",
2371
2320
  impact: Math.round(docDriftScore - 50),
2372
- description: `${params.docDrift.rating} risk of AI signal clarity from drift`
2321
+ description: params.docDrift.rating
2373
2322
  });
2374
2323
  }
2375
2324
  if (params.dependencyHealth) {
2376
2325
  factors.push({
2377
2326
  name: "Dependency Health",
2378
2327
  impact: Math.round(depsHealthScore - 50),
2379
- description: `${params.dependencyHealth.rating} health \u2014 AI knowledge is ${params.dependencyHealth.aiKnowledgeConfidence}`
2328
+ description: params.dependencyHealth.rating
2380
2329
  });
2381
2330
  }
2382
- const recommendations = [];
2383
- for (const rec of params.aiSignalClarity.recommendations) {
2384
- recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
2385
- }
2386
- for (const rec of params.agentGrounding.recommendations) {
2387
- recommendations.push({
2388
- action: rec,
2389
- estimatedImpact: 6,
2390
- priority: "medium"
2391
- });
2392
- }
2393
- for (const rec of params.testability.recommendations) {
2394
- const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
2395
- recommendations.push({ action: rec, estimatedImpact: 10, priority });
2396
- }
2397
- for (const rec of params.patternEntropy.recommendations) {
2398
- recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
2399
- }
2400
- if (params.docDrift) {
2401
- for (const rec of params.docDrift.recommendations) {
2402
- recommendations.push({
2403
- action: rec,
2404
- estimatedImpact: 8,
2405
- priority: "high"
2406
- });
2407
- }
2408
- }
2409
- if (params.dependencyHealth) {
2410
- for (const rec of params.dependencyHealth.recommendations) {
2411
- recommendations.push({
2412
- action: rec,
2413
- estimatedImpact: 7,
2414
- priority: "medium"
2415
- });
2416
- }
2417
- }
2418
- const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
2331
+ const recommendations = collectFutureProofRecommendations(params);
2332
+ const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
2419
2333
  return {
2420
2334
  toolName: "future-proof",
2421
2335
  score: overall,
@@ -2591,16 +2505,19 @@ export {
2591
2505
  ParseError,
2592
2506
  ParserFactory,
2593
2507
  PythonParser,
2508
+ SEVERITY_TIME_ESTIMATES,
2594
2509
  SIZE_ADJUSTED_THRESHOLDS,
2595
2510
  TOOL_NAME_MAP,
2596
2511
  TypeScriptParser,
2597
2512
  VAGUE_FILE_NAMES,
2598
2513
  calculateAgentGrounding,
2599
2514
  calculateAiSignalClarity,
2515
+ calculateBusinessROI,
2600
2516
  calculateChangeAmplification,
2601
2517
  calculateCognitiveLoad,
2602
2518
  calculateComprehensionDifficulty,
2603
2519
  calculateConceptCohesion,
2520
+ calculateDebtInterest,
2604
2521
  calculateDependencyHealth,
2605
2522
  calculateDocDrift,
2606
2523
  calculateExtendedFutureProofScore,
@@ -2611,10 +2528,8 @@ export {
2611
2528
  calculateOverallScore,
2612
2529
  calculatePatternEntropy,
2613
2530
  calculateProductivityImpact,
2614
- calculateRemediationVelocity,
2615
- calculateScoreTrend,
2616
2531
  calculateSemanticDistance,
2617
- calculateTechnicalDebtInterest,
2532
+ calculateTechnicalValueChain,
2618
2533
  calculateTestabilityIndex,
2619
2534
  calculateTokenBudget,
2620
2535
  clearHistory,
@@ -2630,7 +2545,6 @@ export {
2630
2545
  formatToolScore,
2631
2546
  generateHTML,
2632
2547
  generateValueChain,
2633
- getDebtBreakdown,
2634
2548
  getElapsedTime,
2635
2549
  getFileCommitTimestamps,
2636
2550
  getFileExtension,
@@ -2644,6 +2558,9 @@ export {
2644
2558
  getRatingWithContext,
2645
2559
  getRecommendedThreshold,
2646
2560
  getRepoMetadata,
2561
+ getSafetyIcon,
2562
+ getScoreBar,
2563
+ getSeverityColor,
2647
2564
  getSupportedLanguages,
2648
2565
  getToolWeight,
2649
2566
  handleCLIError,
@@ -2663,5 +2580,6 @@ export {
2663
2580
  resolveOutputPath,
2664
2581
  saveScoreEntry,
2665
2582
  scanEntries,
2666
- scanFiles
2583
+ scanFiles,
2584
+ validateSpokeOutput
2667
2585
  };