@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/README.md +3 -0
- package/dist/client.d.mts +14 -6
- package/dist/client.d.ts +14 -6
- package/dist/index.d.mts +221 -281
- package/dist/index.d.ts +221 -281
- package/dist/index.js +464 -542
- package/dist/index.mjs +455 -537
- package/package.json +1 -1
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-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const
|
|
747
|
-
const
|
|
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
|
-
|
|
818
|
+
Math.min(
|
|
819
|
+
100,
|
|
820
|
+
concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
|
|
821
|
+
)
|
|
765
822
|
);
|
|
766
823
|
let rating;
|
|
767
|
-
if (score <
|
|
768
|
-
else if (score <
|
|
769
|
-
else if (score <
|
|
770
|
-
else
|
|
771
|
-
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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/
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
1800
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
2029
|
-
|
|
2030
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
2103
|
-
|
|
2104
|
-
|
|
1994
|
+
const purityScore = Math.round(
|
|
1995
|
+
(totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
|
|
1996
|
+
);
|
|
2105
1997
|
const dependencyInjectionScore = Math.round(
|
|
2106
|
-
Math.min(
|
|
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
|
|
2229
|
-
|
|
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
|
-
|
|
2247
|
-
);
|
|
2248
|
-
|
|
2249
|
-
|
|
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 =
|
|
2290
|
-
|
|
2291
|
-
|
|
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
|
|
2193
|
+
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling.`
|
|
2300
2194
|
);
|
|
2301
2195
|
}
|
|
2302
2196
|
if (maxAmplification > 30) {
|
|
2303
2197
|
recommendations.push(
|
|
2304
|
-
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
2328
|
+
description: params.dependencyHealth.rating
|
|
2380
2329
|
});
|
|
2381
2330
|
}
|
|
2382
|
-
const recommendations =
|
|
2383
|
-
|
|
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
|
-
|
|
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
|
};
|