@aiready/core 0.9.25 → 0.9.27
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/chunk-JJ5JL5FX.mjs +408 -0
- package/dist/client.js +58 -2
- package/dist/client.mjs +11 -1
- package/dist/index.d.mts +263 -1
- package/dist/index.d.ts +263 -1
- package/dist/index.js +1038 -20
- package/dist/index.mjs +970 -19
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -30,22 +30,40 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
CONTEXT_TIER_THRESHOLDS: () => CONTEXT_TIER_THRESHOLDS,
|
|
33
34
|
DEFAULT_COST_CONFIG: () => DEFAULT_COST_CONFIG,
|
|
34
35
|
DEFAULT_EXCLUDE: () => DEFAULT_EXCLUDE,
|
|
35
36
|
DEFAULT_TOOL_WEIGHTS: () => DEFAULT_TOOL_WEIGHTS,
|
|
36
37
|
LANGUAGE_EXTENSIONS: () => LANGUAGE_EXTENSIONS,
|
|
37
38
|
Language: () => Language,
|
|
39
|
+
MODEL_PRICING_PRESETS: () => MODEL_PRICING_PRESETS,
|
|
38
40
|
ParseError: () => ParseError,
|
|
39
41
|
ParserFactory: () => ParserFactory,
|
|
40
42
|
PythonParser: () => PythonParser,
|
|
43
|
+
SIZE_ADJUSTED_THRESHOLDS: () => SIZE_ADJUSTED_THRESHOLDS,
|
|
41
44
|
TOOL_NAME_MAP: () => TOOL_NAME_MAP,
|
|
42
45
|
TypeScriptParser: () => TypeScriptParser,
|
|
46
|
+
calculateAgentGrounding: () => calculateAgentGrounding,
|
|
47
|
+
calculateCognitiveLoad: () => calculateCognitiveLoad,
|
|
43
48
|
calculateComprehensionDifficulty: () => calculateComprehensionDifficulty,
|
|
49
|
+
calculateConceptCohesion: () => calculateConceptCohesion,
|
|
50
|
+
calculateExtendedFutureProofScore: () => calculateExtendedFutureProofScore,
|
|
51
|
+
calculateFutureProofScore: () => calculateFutureProofScore,
|
|
52
|
+
calculateHallucinationRisk: () => calculateHallucinationRisk,
|
|
44
53
|
calculateImportSimilarity: () => calculateImportSimilarity,
|
|
54
|
+
calculateKnowledgeConcentration: () => calculateKnowledgeConcentration,
|
|
45
55
|
calculateMonthlyCost: () => calculateMonthlyCost,
|
|
46
56
|
calculateOverallScore: () => calculateOverallScore,
|
|
57
|
+
calculatePatternEntropy: () => calculatePatternEntropy,
|
|
47
58
|
calculateProductivityImpact: () => calculateProductivityImpact,
|
|
59
|
+
calculateRemediationVelocity: () => calculateRemediationVelocity,
|
|
60
|
+
calculateScoreTrend: () => calculateScoreTrend,
|
|
61
|
+
calculateSemanticDistance: () => calculateSemanticDistance,
|
|
62
|
+
calculateTechnicalDebtInterest: () => calculateTechnicalDebtInterest,
|
|
63
|
+
calculateTestabilityIndex: () => calculateTestabilityIndex,
|
|
64
|
+
clearHistory: () => clearHistory,
|
|
48
65
|
estimateTokens: () => estimateTokens,
|
|
66
|
+
exportHistory: () => exportHistory,
|
|
49
67
|
extractFunctions: () => extractFunctions,
|
|
50
68
|
extractImports: () => extractImports,
|
|
51
69
|
formatAcceptanceRate: () => formatAcceptanceRate,
|
|
@@ -54,11 +72,17 @@ __export(index_exports, {
|
|
|
54
72
|
formatScore: () => formatScore,
|
|
55
73
|
formatToolScore: () => formatToolScore,
|
|
56
74
|
generateHTML: () => generateHTML,
|
|
75
|
+
getDebtBreakdown: () => getDebtBreakdown,
|
|
57
76
|
getElapsedTime: () => getElapsedTime,
|
|
58
77
|
getFileExtension: () => getFileExtension,
|
|
78
|
+
getHistorySummary: () => getHistorySummary,
|
|
79
|
+
getModelPreset: () => getModelPreset,
|
|
59
80
|
getParser: () => getParser,
|
|
81
|
+
getProjectSizeTier: () => getProjectSizeTier,
|
|
60
82
|
getRating: () => getRating,
|
|
61
83
|
getRatingDisplay: () => getRatingDisplay,
|
|
84
|
+
getRatingWithContext: () => getRatingWithContext,
|
|
85
|
+
getRecommendedThreshold: () => getRecommendedThreshold,
|
|
62
86
|
getSupportedLanguages: () => getSupportedLanguages,
|
|
63
87
|
getToolWeight: () => getToolWeight,
|
|
64
88
|
handleCLIError: () => handleCLIError,
|
|
@@ -67,6 +91,7 @@ __export(index_exports, {
|
|
|
67
91
|
isSourceFile: () => isSourceFile,
|
|
68
92
|
loadConfig: () => loadConfig,
|
|
69
93
|
loadMergedConfig: () => loadMergedConfig,
|
|
94
|
+
loadScoreHistory: () => loadScoreHistory,
|
|
70
95
|
mergeConfigWithDefaults: () => mergeConfigWithDefaults,
|
|
71
96
|
normalizeToolName: () => normalizeToolName,
|
|
72
97
|
parseCode: () => parseCode,
|
|
@@ -75,6 +100,7 @@ __export(index_exports, {
|
|
|
75
100
|
predictAcceptanceRate: () => predictAcceptanceRate,
|
|
76
101
|
readFileContent: () => readFileContent,
|
|
77
102
|
resolveOutputPath: () => resolveOutputPath,
|
|
103
|
+
saveScoreEntry: () => saveScoreEntry,
|
|
78
104
|
scanFiles: () => scanFiles
|
|
79
105
|
});
|
|
80
106
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -679,16 +705,57 @@ var DEFAULT_TOOL_WEIGHTS = {
|
|
|
679
705
|
"pattern-detect": 40,
|
|
680
706
|
"context-analyzer": 35,
|
|
681
707
|
"consistency": 25,
|
|
682
|
-
"
|
|
683
|
-
"
|
|
708
|
+
"hallucination-risk": 20,
|
|
709
|
+
"agent-grounding": 18,
|
|
710
|
+
"testability": 18,
|
|
711
|
+
"doc-drift": 15,
|
|
712
|
+
"deps": 12
|
|
684
713
|
};
|
|
685
714
|
var TOOL_NAME_MAP = {
|
|
686
715
|
"patterns": "pattern-detect",
|
|
687
716
|
"context": "context-analyzer",
|
|
688
717
|
"consistency": "consistency",
|
|
718
|
+
"hallucination": "hallucination-risk",
|
|
719
|
+
"hallucination-risk": "hallucination-risk",
|
|
720
|
+
"grounding": "agent-grounding",
|
|
721
|
+
"agent-grounding": "agent-grounding",
|
|
722
|
+
"testability": "testability",
|
|
723
|
+
"tests": "testability",
|
|
689
724
|
"doc-drift": "doc-drift",
|
|
725
|
+
"docs": "doc-drift",
|
|
690
726
|
"deps": "deps"
|
|
691
727
|
};
|
|
728
|
+
var CONTEXT_TIER_THRESHOLDS = {
|
|
729
|
+
compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
|
|
730
|
+
standard: { idealTokens: 5e3, criticalTokens: 15e3, idealDepth: 5 },
|
|
731
|
+
extended: { idealTokens: 15e3, criticalTokens: 5e4, idealDepth: 7 },
|
|
732
|
+
frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
|
|
733
|
+
};
|
|
734
|
+
var SIZE_ADJUSTED_THRESHOLDS = {
|
|
735
|
+
"xs": 80,
|
|
736
|
+
// < 50 files
|
|
737
|
+
"small": 75,
|
|
738
|
+
// 50-200 files
|
|
739
|
+
"medium": 70,
|
|
740
|
+
// 200-500 files
|
|
741
|
+
"large": 65,
|
|
742
|
+
// 500-2000 files
|
|
743
|
+
"enterprise": 58
|
|
744
|
+
// 2000+ files
|
|
745
|
+
};
|
|
746
|
+
function getProjectSizeTier(fileCount) {
|
|
747
|
+
if (fileCount < 50) return "xs";
|
|
748
|
+
if (fileCount < 200) return "small";
|
|
749
|
+
if (fileCount < 500) return "medium";
|
|
750
|
+
if (fileCount < 2e3) return "large";
|
|
751
|
+
return "enterprise";
|
|
752
|
+
}
|
|
753
|
+
function getRecommendedThreshold(fileCount, modelTier = "standard") {
|
|
754
|
+
const sizeTier = getProjectSizeTier(fileCount);
|
|
755
|
+
const base = SIZE_ADJUSTED_THRESHOLDS[sizeTier];
|
|
756
|
+
const modelBonus = modelTier === "frontier" ? -3 : modelTier === "extended" ? -2 : 0;
|
|
757
|
+
return base + modelBonus;
|
|
758
|
+
}
|
|
692
759
|
function normalizeToolName(shortName) {
|
|
693
760
|
return TOOL_NAME_MAP[shortName] || shortName;
|
|
694
761
|
}
|
|
@@ -771,6 +838,11 @@ function getRating(score) {
|
|
|
771
838
|
if (score >= 40) return "Needs Work";
|
|
772
839
|
return "Critical";
|
|
773
840
|
}
|
|
841
|
+
function getRatingWithContext(score, fileCount, modelTier = "standard") {
|
|
842
|
+
const threshold = getRecommendedThreshold(fileCount, modelTier);
|
|
843
|
+
const normalized = score - threshold + 70;
|
|
844
|
+
return getRating(normalized);
|
|
845
|
+
}
|
|
774
846
|
function getRatingDisplay(rating) {
|
|
775
847
|
switch (rating) {
|
|
776
848
|
case "Excellent":
|
|
@@ -819,11 +891,87 @@ function formatToolScore(output) {
|
|
|
819
891
|
}
|
|
820
892
|
|
|
821
893
|
// src/business-metrics.ts
|
|
894
|
+
var MODEL_PRICING_PRESETS = {
|
|
895
|
+
"gpt-4": {
|
|
896
|
+
name: "GPT-4",
|
|
897
|
+
pricePer1KInputTokens: 0.03,
|
|
898
|
+
pricePer1KOutputTokens: 0.06,
|
|
899
|
+
contextTier: "standard",
|
|
900
|
+
typicalQueriesPerDevPerDay: 40
|
|
901
|
+
},
|
|
902
|
+
"gpt-4o": {
|
|
903
|
+
name: "GPT-4o",
|
|
904
|
+
pricePer1KInputTokens: 5e-3,
|
|
905
|
+
pricePer1KOutputTokens: 0.015,
|
|
906
|
+
contextTier: "extended",
|
|
907
|
+
typicalQueriesPerDevPerDay: 60
|
|
908
|
+
},
|
|
909
|
+
"gpt-4o-mini": {
|
|
910
|
+
name: "GPT-4o mini",
|
|
911
|
+
pricePer1KInputTokens: 15e-5,
|
|
912
|
+
pricePer1KOutputTokens: 6e-4,
|
|
913
|
+
contextTier: "extended",
|
|
914
|
+
typicalQueriesPerDevPerDay: 120
|
|
915
|
+
},
|
|
916
|
+
"claude-3-5-sonnet": {
|
|
917
|
+
name: "Claude 3.5 Sonnet",
|
|
918
|
+
pricePer1KInputTokens: 3e-3,
|
|
919
|
+
pricePer1KOutputTokens: 0.015,
|
|
920
|
+
contextTier: "extended",
|
|
921
|
+
typicalQueriesPerDevPerDay: 80
|
|
922
|
+
},
|
|
923
|
+
"claude-3-7-sonnet": {
|
|
924
|
+
name: "Claude 3.7 Sonnet",
|
|
925
|
+
pricePer1KInputTokens: 3e-3,
|
|
926
|
+
pricePer1KOutputTokens: 0.015,
|
|
927
|
+
contextTier: "frontier",
|
|
928
|
+
typicalQueriesPerDevPerDay: 80
|
|
929
|
+
},
|
|
930
|
+
"claude-sonnet-4": {
|
|
931
|
+
name: "Claude Sonnet 4",
|
|
932
|
+
pricePer1KInputTokens: 3e-3,
|
|
933
|
+
pricePer1KOutputTokens: 0.015,
|
|
934
|
+
contextTier: "frontier",
|
|
935
|
+
typicalQueriesPerDevPerDay: 80
|
|
936
|
+
},
|
|
937
|
+
"gemini-1-5-pro": {
|
|
938
|
+
name: "Gemini 1.5 Pro",
|
|
939
|
+
pricePer1KInputTokens: 125e-5,
|
|
940
|
+
pricePer1KOutputTokens: 5e-3,
|
|
941
|
+
contextTier: "frontier",
|
|
942
|
+
typicalQueriesPerDevPerDay: 80
|
|
943
|
+
},
|
|
944
|
+
"gemini-2-0-flash": {
|
|
945
|
+
name: "Gemini 2.0 Flash",
|
|
946
|
+
pricePer1KInputTokens: 1e-4,
|
|
947
|
+
pricePer1KOutputTokens: 4e-4,
|
|
948
|
+
contextTier: "frontier",
|
|
949
|
+
typicalQueriesPerDevPerDay: 150
|
|
950
|
+
},
|
|
951
|
+
"copilot": {
|
|
952
|
+
name: "GitHub Copilot (subscription)",
|
|
953
|
+
// Amortized per-request cost for a $19/month plan at 80 queries/day
|
|
954
|
+
pricePer1KInputTokens: 1e-4,
|
|
955
|
+
pricePer1KOutputTokens: 1e-4,
|
|
956
|
+
contextTier: "extended",
|
|
957
|
+
typicalQueriesPerDevPerDay: 80
|
|
958
|
+
},
|
|
959
|
+
"cursor-pro": {
|
|
960
|
+
name: "Cursor Pro (subscription)",
|
|
961
|
+
pricePer1KInputTokens: 1e-4,
|
|
962
|
+
pricePer1KOutputTokens: 1e-4,
|
|
963
|
+
contextTier: "frontier",
|
|
964
|
+
typicalQueriesPerDevPerDay: 100
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
function getModelPreset(modelId) {
|
|
968
|
+
return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["gpt-4o"];
|
|
969
|
+
}
|
|
822
970
|
var DEFAULT_COST_CONFIG = {
|
|
823
|
-
pricePer1KTokens:
|
|
824
|
-
//
|
|
825
|
-
queriesPerDevPerDay:
|
|
826
|
-
// Average AI queries per developer
|
|
971
|
+
pricePer1KTokens: 5e-3,
|
|
972
|
+
// GPT-4o input price (updated from GPT-4 era 0.01)
|
|
973
|
+
queriesPerDevPerDay: 60,
|
|
974
|
+
// Average AI queries per developer (updated: 40→60 as of 2026)
|
|
827
975
|
developerCount: 5,
|
|
828
976
|
// Default team size
|
|
829
977
|
daysPerMonth: 30
|
|
@@ -874,43 +1022,63 @@ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
|
874
1022
|
}
|
|
875
1023
|
function predictAcceptanceRate(toolOutputs) {
|
|
876
1024
|
const factors = [];
|
|
1025
|
+
const baseRate = 0.3;
|
|
877
1026
|
const patterns = toolOutputs.get("pattern-detect");
|
|
878
1027
|
if (patterns) {
|
|
879
|
-
const patternImpact = (patterns.score - 50) *
|
|
1028
|
+
const patternImpact = (patterns.score - 50) * 3e-3;
|
|
880
1029
|
factors.push({
|
|
881
1030
|
name: "Semantic Duplication",
|
|
882
|
-
impact: Math.round(patternImpact)
|
|
1031
|
+
impact: Math.round(patternImpact * 100)
|
|
883
1032
|
});
|
|
884
1033
|
}
|
|
885
1034
|
const context = toolOutputs.get("context-analyzer");
|
|
886
1035
|
if (context) {
|
|
887
|
-
const contextImpact = (context.score - 50) *
|
|
1036
|
+
const contextImpact = (context.score - 50) * 4e-3;
|
|
888
1037
|
factors.push({
|
|
889
1038
|
name: "Context Efficiency",
|
|
890
|
-
impact: Math.round(contextImpact)
|
|
1039
|
+
impact: Math.round(contextImpact * 100)
|
|
891
1040
|
});
|
|
892
1041
|
}
|
|
893
1042
|
const consistency = toolOutputs.get("consistency");
|
|
894
1043
|
if (consistency) {
|
|
895
|
-
const consistencyImpact = (consistency.score - 50) *
|
|
1044
|
+
const consistencyImpact = (consistency.score - 50) * 2e-3;
|
|
896
1045
|
factors.push({
|
|
897
1046
|
name: "Code Consistency",
|
|
898
|
-
impact: Math.round(consistencyImpact)
|
|
1047
|
+
impact: Math.round(consistencyImpact * 100)
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
const hallucinationRisk = toolOutputs.get("hallucination-risk");
|
|
1051
|
+
if (hallucinationRisk) {
|
|
1052
|
+
const hrImpact = (50 - hallucinationRisk.score) * 2e-3;
|
|
1053
|
+
factors.push({
|
|
1054
|
+
name: "Hallucination Risk",
|
|
1055
|
+
impact: Math.round(hrImpact * 100)
|
|
899
1056
|
});
|
|
900
1057
|
}
|
|
901
|
-
const baseRate = 0.65;
|
|
902
1058
|
const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
|
|
903
|
-
const rate = Math.max(0.
|
|
904
|
-
|
|
1059
|
+
const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
|
|
1060
|
+
let confidence;
|
|
1061
|
+
if (toolOutputs.size >= 4) confidence = 0.75;
|
|
1062
|
+
else if (toolOutputs.size >= 3) confidence = 0.65;
|
|
1063
|
+
else if (toolOutputs.size >= 2) confidence = 0.5;
|
|
1064
|
+
else confidence = 0.35;
|
|
905
1065
|
return {
|
|
906
1066
|
rate: Math.round(rate * 100) / 100,
|
|
907
1067
|
confidence,
|
|
908
1068
|
factors
|
|
909
1069
|
};
|
|
910
1070
|
}
|
|
911
|
-
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles) {
|
|
912
|
-
const
|
|
913
|
-
const
|
|
1071
|
+
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles, modelTier = "standard") {
|
|
1072
|
+
const tierThresholds = CONTEXT_TIER_THRESHOLDS[modelTier];
|
|
1073
|
+
const idealBudget = tierThresholds.idealTokens;
|
|
1074
|
+
const criticalBudget = tierThresholds.criticalTokens;
|
|
1075
|
+
const idealDepth = tierThresholds.idealDepth;
|
|
1076
|
+
const budgetRange = criticalBudget - idealBudget;
|
|
1077
|
+
const budgetFactor = Math.min(100, Math.max(
|
|
1078
|
+
0,
|
|
1079
|
+
(contextBudget - idealBudget) / budgetRange * 100
|
|
1080
|
+
));
|
|
1081
|
+
const depthFactor = Math.min(100, Math.max(0, (importDepth - idealDepth) * 10));
|
|
914
1082
|
const fragmentationFactor = Math.min(100, Math.max(0, (fragmentation - 0.3) * 250));
|
|
915
1083
|
const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
|
|
916
1084
|
const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
|
|
@@ -930,12 +1098,12 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
|
|
|
930
1098
|
{
|
|
931
1099
|
name: "Context Budget",
|
|
932
1100
|
contribution: Math.round(budgetFactor * 0.35),
|
|
933
|
-
description: `${Math.round(contextBudget)} tokens required`
|
|
1101
|
+
description: `${Math.round(contextBudget)} tokens required (${modelTier} model tier: ideal <${idealBudget.toLocaleString()})`
|
|
934
1102
|
},
|
|
935
1103
|
{
|
|
936
1104
|
name: "Import Depth",
|
|
937
1105
|
contribution: Math.round(depthFactor * 0.2),
|
|
938
|
-
description: `${importDepth.toFixed(1)} average levels`
|
|
1106
|
+
description: `${importDepth.toFixed(1)} average levels (ideal <${idealDepth} for ${modelTier})`
|
|
939
1107
|
},
|
|
940
1108
|
{
|
|
941
1109
|
name: "Code Fragmentation",
|
|
@@ -978,6 +1146,200 @@ function formatHours(hours) {
|
|
|
978
1146
|
function formatAcceptanceRate(rate) {
|
|
979
1147
|
return `${Math.round(rate * 100)}%`;
|
|
980
1148
|
}
|
|
1149
|
+
function calculateScoreTrend(history) {
|
|
1150
|
+
if (history.length < 2) {
|
|
1151
|
+
return {
|
|
1152
|
+
direction: "stable",
|
|
1153
|
+
change30Days: 0,
|
|
1154
|
+
change90Days: 0,
|
|
1155
|
+
velocity: 0,
|
|
1156
|
+
projectedScore: history[0]?.overallScore || 100
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const now = /* @__PURE__ */ new Date();
|
|
1160
|
+
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
1161
|
+
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
|
|
1162
|
+
const last30Days = history.filter((e) => new Date(e.timestamp) >= thirtyDaysAgo);
|
|
1163
|
+
const last90Days = history.filter((e) => new Date(e.timestamp) >= ninetyDaysAgo);
|
|
1164
|
+
const currentScore = history[history.length - 1].overallScore;
|
|
1165
|
+
const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
|
|
1166
|
+
const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
|
|
1167
|
+
const change30Days = currentScore - thirtyDaysAgoScore;
|
|
1168
|
+
const change90Days = currentScore - ninetyDaysAgoScore;
|
|
1169
|
+
const weeksOfData = Math.max(1, history.length / 7);
|
|
1170
|
+
const totalChange = currentScore - history[0].overallScore;
|
|
1171
|
+
const velocity = totalChange / weeksOfData;
|
|
1172
|
+
let direction;
|
|
1173
|
+
if (change30Days > 3) direction = "improving";
|
|
1174
|
+
else if (change30Days < -3) direction = "degrading";
|
|
1175
|
+
else direction = "stable";
|
|
1176
|
+
const projectedScore = Math.max(0, Math.min(100, currentScore + velocity * 4));
|
|
1177
|
+
return {
|
|
1178
|
+
direction,
|
|
1179
|
+
change30Days,
|
|
1180
|
+
change90Days,
|
|
1181
|
+
velocity: Math.round(velocity * 10) / 10,
|
|
1182
|
+
projectedScore: Math.round(projectedScore)
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function calculateRemediationVelocity(history, currentIssues) {
|
|
1186
|
+
if (history.length < 2) {
|
|
1187
|
+
return {
|
|
1188
|
+
issuesFixedThisWeek: 0,
|
|
1189
|
+
avgIssuesPerWeek: 0,
|
|
1190
|
+
trend: "stable",
|
|
1191
|
+
estimatedCompletionWeeks: currentIssues > 0 ? Infinity : 0
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
const now = /* @__PURE__ */ new Date();
|
|
1195
|
+
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
|
|
1196
|
+
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1e3);
|
|
1197
|
+
const thisWeek = history.filter((e) => new Date(e.timestamp) >= oneWeekAgo);
|
|
1198
|
+
const lastWeek = history.filter(
|
|
1199
|
+
(e) => new Date(e.timestamp) >= twoWeeksAgo && new Date(e.timestamp) < oneWeekAgo
|
|
1200
|
+
);
|
|
1201
|
+
const issuesFixedThisWeek = thisWeek.length > 1 ? thisWeek[0].totalIssues - thisWeek[thisWeek.length - 1].totalIssues : 0;
|
|
1202
|
+
const totalIssuesFixed = history[0].totalIssues - history[history.length - 1].totalIssues;
|
|
1203
|
+
const weeksOfData = Math.max(1, history.length / 7);
|
|
1204
|
+
const avgIssuesPerWeek = totalIssuesFixed / weeksOfData;
|
|
1205
|
+
let trend;
|
|
1206
|
+
if (lastWeek.length > 1) {
|
|
1207
|
+
const lastWeekFixed = lastWeek[0].totalIssues - lastWeek[lastWeek.length - 1].totalIssues;
|
|
1208
|
+
if (issuesFixedThisWeek > lastWeekFixed * 1.2) trend = "accelerating";
|
|
1209
|
+
else if (issuesFixedThisWeek < lastWeekFixed * 0.8) trend = "decelerating";
|
|
1210
|
+
else trend = "stable";
|
|
1211
|
+
} else {
|
|
1212
|
+
trend = "stable";
|
|
1213
|
+
}
|
|
1214
|
+
const estimatedCompletionWeeks = avgIssuesPerWeek > 0 ? Math.ceil(currentIssues / avgIssuesPerWeek) : Infinity;
|
|
1215
|
+
return {
|
|
1216
|
+
issuesFixedThisWeek: Math.max(0, issuesFixedThisWeek),
|
|
1217
|
+
avgIssuesPerWeek: Math.round(avgIssuesPerWeek * 10) / 10,
|
|
1218
|
+
trend,
|
|
1219
|
+
estimatedCompletionWeeks
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
function calculateKnowledgeConcentration(files, authorData) {
|
|
1223
|
+
if (files.length === 0) {
|
|
1224
|
+
return {
|
|
1225
|
+
score: 0,
|
|
1226
|
+
rating: "low",
|
|
1227
|
+
analysis: {
|
|
1228
|
+
uniqueConceptFiles: 0,
|
|
1229
|
+
totalFiles: 0,
|
|
1230
|
+
concentrationRatio: 0,
|
|
1231
|
+
singleAuthorFiles: 0,
|
|
1232
|
+
orphanFiles: 0
|
|
1233
|
+
},
|
|
1234
|
+
recommendations: ["No files to analyze"]
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
const orphanFiles = files.filter((f) => f.exports < 2 && f.imports < 2).length;
|
|
1238
|
+
const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
|
|
1239
|
+
const uniqueConceptFiles = files.filter((f) => f.exports > avgExports * 2).length;
|
|
1240
|
+
const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
|
|
1241
|
+
const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
|
|
1242
|
+
let singleAuthorFiles = 0;
|
|
1243
|
+
if (authorData) {
|
|
1244
|
+
for (const files2 of authorData.values()) {
|
|
1245
|
+
if (files2.length === 1) singleAuthorFiles++;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const orphanRisk = orphanFiles / files.length * 30;
|
|
1249
|
+
const uniqueRisk = concentrationRatio * 40;
|
|
1250
|
+
const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
|
|
1251
|
+
const score = Math.min(100, Math.round(orphanRisk + uniqueRisk + singleAuthorRisk));
|
|
1252
|
+
let rating;
|
|
1253
|
+
if (score < 20) rating = "low";
|
|
1254
|
+
else if (score < 40) rating = "moderate";
|
|
1255
|
+
else if (score < 70) rating = "high";
|
|
1256
|
+
else rating = "critical";
|
|
1257
|
+
const recommendations = [];
|
|
1258
|
+
if (orphanFiles > files.length * 0.2) {
|
|
1259
|
+
recommendations.push(`Reduce ${orphanFiles} orphan files by connecting them to main modules`);
|
|
1260
|
+
}
|
|
1261
|
+
if (uniqueConceptFiles > files.length * 0.1) {
|
|
1262
|
+
recommendations.push("Distribute high-export files into more focused modules");
|
|
1263
|
+
}
|
|
1264
|
+
if (authorData && singleAuthorFiles > files.length * 0.3) {
|
|
1265
|
+
recommendations.push("Increase knowledge sharing to reduce single-author dependencies");
|
|
1266
|
+
}
|
|
1267
|
+
return {
|
|
1268
|
+
score,
|
|
1269
|
+
rating,
|
|
1270
|
+
analysis: {
|
|
1271
|
+
uniqueConceptFiles,
|
|
1272
|
+
totalFiles: files.length,
|
|
1273
|
+
concentrationRatio: Math.round(concentrationRatio * 100) / 100,
|
|
1274
|
+
singleAuthorFiles,
|
|
1275
|
+
orphanFiles
|
|
1276
|
+
},
|
|
1277
|
+
recommendations
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function calculateTechnicalDebtInterest(params) {
|
|
1281
|
+
const { currentMonthlyCost, issues, monthsOpen } = params;
|
|
1282
|
+
const criticalCount = issues.filter((i) => i.severity === "critical").length;
|
|
1283
|
+
const majorCount = issues.filter((i) => i.severity === "major").length;
|
|
1284
|
+
const minorCount = issues.filter((i) => i.severity === "minor").length;
|
|
1285
|
+
const severityWeight = (criticalCount * 3 + majorCount * 2 + minorCount * 1) / Math.max(1, issues.length);
|
|
1286
|
+
const baseRate = 0.02 + severityWeight * 0.01;
|
|
1287
|
+
const timeMultiplier = Math.max(1, 1 + monthsOpen * 0.1);
|
|
1288
|
+
const monthlyRate = baseRate * timeMultiplier;
|
|
1289
|
+
const projectDebt = (principal2, months) => {
|
|
1290
|
+
let debt = principal2;
|
|
1291
|
+
for (let i = 0; i < months; i++) {
|
|
1292
|
+
debt = debt * (1 + monthlyRate);
|
|
1293
|
+
}
|
|
1294
|
+
return Math.round(debt);
|
|
1295
|
+
};
|
|
1296
|
+
const principal = currentMonthlyCost * 12;
|
|
1297
|
+
const projections = {
|
|
1298
|
+
months6: projectDebt(principal, 6),
|
|
1299
|
+
months12: projectDebt(principal, 12),
|
|
1300
|
+
months24: projectDebt(principal, 24)
|
|
1301
|
+
};
|
|
1302
|
+
return {
|
|
1303
|
+
monthlyRate: Math.round(monthlyRate * 1e4) / 100,
|
|
1304
|
+
annualRate: Math.round((Math.pow(1 + monthlyRate, 12) - 1) * 1e4) / 100,
|
|
1305
|
+
principal,
|
|
1306
|
+
projections,
|
|
1307
|
+
monthlyCost: Math.round(currentMonthlyCost * (1 + monthlyRate) * 100) / 100
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function getDebtBreakdown(patternCost, contextCost, consistencyCost) {
|
|
1311
|
+
const breakdowns = [
|
|
1312
|
+
{
|
|
1313
|
+
category: "Semantic Duplication",
|
|
1314
|
+
currentCost: patternCost,
|
|
1315
|
+
monthlyGrowthRate: 5,
|
|
1316
|
+
// Grows as devs copy-paste
|
|
1317
|
+
priority: patternCost > 1e3 ? "high" : "medium",
|
|
1318
|
+
fixCost: patternCost * 3
|
|
1319
|
+
// Fixing costs 3x current waste
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
category: "Context Fragmentation",
|
|
1323
|
+
currentCost: contextCost,
|
|
1324
|
+
monthlyGrowthRate: 3,
|
|
1325
|
+
// Grows with new features
|
|
1326
|
+
priority: contextCost > 500 ? "high" : "medium",
|
|
1327
|
+
fixCost: contextCost * 2.5
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
category: "Consistency Issues",
|
|
1331
|
+
currentCost: consistencyCost,
|
|
1332
|
+
monthlyGrowthRate: 2,
|
|
1333
|
+
// Grows with new devs
|
|
1334
|
+
priority: consistencyCost > 200 ? "medium" : "low",
|
|
1335
|
+
fixCost: consistencyCost * 1.5
|
|
1336
|
+
}
|
|
1337
|
+
];
|
|
1338
|
+
return breakdowns.sort((a, b) => {
|
|
1339
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
1340
|
+
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
981
1343
|
|
|
982
1344
|
// src/parsers/typescript-parser.ts
|
|
983
1345
|
var import_typescript_estree2 = require("@typescript-eslint/typescript-estree");
|
|
@@ -1458,24 +1820,672 @@ function isFileSupported(filePath) {
|
|
|
1458
1820
|
function getSupportedLanguages() {
|
|
1459
1821
|
return ParserFactory.getInstance().getSupportedLanguages();
|
|
1460
1822
|
}
|
|
1823
|
+
|
|
1824
|
+
// src/future-proof-metrics.ts
|
|
1825
|
+
function calculateCognitiveLoad(params) {
|
|
1826
|
+
const { linesOfCode, exportCount, importCount, uniqueConcepts, cyclomaticComplexity = 1 } = params;
|
|
1827
|
+
const sizeFactor = {
|
|
1828
|
+
name: "Size Complexity",
|
|
1829
|
+
score: Math.min(100, Math.max(0, (linesOfCode - 50) / 10)),
|
|
1830
|
+
weight: 0.3,
|
|
1831
|
+
description: `${linesOfCode} lines of code`
|
|
1832
|
+
};
|
|
1833
|
+
const interfaceFactor = {
|
|
1834
|
+
name: "Interface Complexity",
|
|
1835
|
+
score: Math.min(100, exportCount * 5),
|
|
1836
|
+
weight: 0.25,
|
|
1837
|
+
description: `${exportCount} exported concepts`
|
|
1838
|
+
};
|
|
1839
|
+
const dependencyFactor = {
|
|
1840
|
+
name: "Dependency Complexity",
|
|
1841
|
+
score: Math.min(100, importCount * 8),
|
|
1842
|
+
weight: 0.25,
|
|
1843
|
+
description: `${importCount} dependencies`
|
|
1844
|
+
};
|
|
1845
|
+
const conceptDensity = linesOfCode > 0 ? uniqueConcepts / linesOfCode : 0;
|
|
1846
|
+
const conceptFactor = {
|
|
1847
|
+
name: "Conceptual Density",
|
|
1848
|
+
score: Math.min(100, conceptDensity * 500),
|
|
1849
|
+
weight: 0.2,
|
|
1850
|
+
description: `${uniqueConcepts} unique concepts`
|
|
1851
|
+
};
|
|
1852
|
+
const factors = [sizeFactor, interfaceFactor, dependencyFactor, conceptFactor];
|
|
1853
|
+
const score = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
|
|
1854
|
+
let rating;
|
|
1855
|
+
if (score < 20) rating = "trivial";
|
|
1856
|
+
else if (score < 40) rating = "easy";
|
|
1857
|
+
else if (score < 60) rating = "moderate";
|
|
1858
|
+
else if (score < 80) rating = "difficult";
|
|
1859
|
+
else rating = "expert";
|
|
1860
|
+
return {
|
|
1861
|
+
score: Math.round(score),
|
|
1862
|
+
rating,
|
|
1863
|
+
factors,
|
|
1864
|
+
rawValues: {
|
|
1865
|
+
size: linesOfCode,
|
|
1866
|
+
complexity: cyclomaticComplexity,
|
|
1867
|
+
dependencyCount: importCount,
|
|
1868
|
+
conceptCount: uniqueConcepts
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function calculateSemanticDistance(params) {
|
|
1873
|
+
const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
|
|
1874
|
+
const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
|
|
1875
|
+
const importOverlap = sharedDependencies.length / Math.max(1, Math.min(params.file1Imports.length, params.file2Imports.length));
|
|
1876
|
+
const importDistance = 1 - importOverlap;
|
|
1877
|
+
const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
|
|
1878
|
+
let relationship;
|
|
1879
|
+
if (file1 === file2) relationship = "same-file";
|
|
1880
|
+
else if (file1Domain === file2Domain) relationship = "same-domain";
|
|
1881
|
+
else if (sharedDependencies.length > 0) relationship = "cross-domain";
|
|
1882
|
+
else relationship = "unrelated";
|
|
1883
|
+
const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter((s) => typeof s === "string" && s.length > 0);
|
|
1884
|
+
return {
|
|
1885
|
+
between: [file1, file2],
|
|
1886
|
+
distance: Math.round(distance * 100) / 100,
|
|
1887
|
+
relationship,
|
|
1888
|
+
path: pathItems,
|
|
1889
|
+
reason: relationship === "same-domain" ? `Both in "${file1Domain}" domain` : relationship === "cross-domain" ? `Share ${sharedDependencies.length} dependency(ies)` : "No strong semantic relationship detected"
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
function calculatePatternEntropy(files) {
|
|
1893
|
+
if (files.length === 0) {
|
|
1894
|
+
return {
|
|
1895
|
+
domain: "unknown",
|
|
1896
|
+
entropy: 0,
|
|
1897
|
+
rating: "crystalline",
|
|
1898
|
+
distribution: { locationCount: 0, dominantLocation: "", giniCoefficient: 0 },
|
|
1899
|
+
recommendations: ["No files to analyze"]
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
const dirGroups = /* @__PURE__ */ new Map();
|
|
1903
|
+
for (const file of files) {
|
|
1904
|
+
const parts = file.path.split("/").slice(0, 4).join("/") || "root";
|
|
1905
|
+
dirGroups.set(parts, (dirGroups.get(parts) || 0) + 1);
|
|
1906
|
+
}
|
|
1907
|
+
const counts = Array.from(dirGroups.values());
|
|
1908
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
1909
|
+
let entropy = 0;
|
|
1910
|
+
for (const count of counts) {
|
|
1911
|
+
const p = count / total;
|
|
1912
|
+
if (p > 0) entropy -= p * Math.log2(p);
|
|
1913
|
+
}
|
|
1914
|
+
const maxEntropy = Math.log2(dirGroups.size || 1);
|
|
1915
|
+
const normalizedEntropy = maxEntropy > 0 ? entropy / maxEntropy : 0;
|
|
1916
|
+
const sortedCounts = counts.sort((a, b) => a - b);
|
|
1917
|
+
let gini = 0;
|
|
1918
|
+
for (let i = 0; i < sortedCounts.length; i++) {
|
|
1919
|
+
gini += (2 * (i + 1) - sortedCounts.length - 1) * sortedCounts[i];
|
|
1920
|
+
}
|
|
1921
|
+
gini /= total * sortedCounts.length;
|
|
1922
|
+
let dominantLocation = "";
|
|
1923
|
+
let maxCount = 0;
|
|
1924
|
+
for (const [loc, count] of dirGroups.entries()) {
|
|
1925
|
+
if (count > maxCount) {
|
|
1926
|
+
maxCount = count;
|
|
1927
|
+
dominantLocation = loc;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
let rating;
|
|
1931
|
+
if (normalizedEntropy < 0.2) rating = "crystalline";
|
|
1932
|
+
else if (normalizedEntropy < 0.4) rating = "well-structured";
|
|
1933
|
+
else if (normalizedEntropy < 0.6) rating = "moderate";
|
|
1934
|
+
else if (normalizedEntropy < 0.8) rating = "fragmented";
|
|
1935
|
+
else rating = "chaotic";
|
|
1936
|
+
const recommendations = [];
|
|
1937
|
+
if (normalizedEntropy > 0.5) {
|
|
1938
|
+
recommendations.push(`Consolidate ${files.length} files into fewer directories by domain`);
|
|
1939
|
+
}
|
|
1940
|
+
if (dirGroups.size > 5) {
|
|
1941
|
+
recommendations.push("Consider barrel exports to reduce directory navigation");
|
|
1942
|
+
}
|
|
1943
|
+
if (gini > 0.5) {
|
|
1944
|
+
recommendations.push("Redistribute files more evenly across directories");
|
|
1945
|
+
}
|
|
1946
|
+
const firstFile = files.length > 0 ? files[0] : null;
|
|
1947
|
+
const domainValue = firstFile ? firstFile.domain : "mixed";
|
|
1948
|
+
return {
|
|
1949
|
+
domain: domainValue,
|
|
1950
|
+
entropy: Math.round(normalizedEntropy * 100) / 100,
|
|
1951
|
+
rating,
|
|
1952
|
+
distribution: {
|
|
1953
|
+
locationCount: dirGroups.size,
|
|
1954
|
+
dominantLocation,
|
|
1955
|
+
giniCoefficient: Math.round(gini * 100) / 100
|
|
1956
|
+
},
|
|
1957
|
+
recommendations
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function calculateConceptCohesion(params) {
|
|
1961
|
+
const { exports: exports2 } = params;
|
|
1962
|
+
if (exports2.length === 0) {
|
|
1963
|
+
return {
|
|
1964
|
+
score: 1,
|
|
1965
|
+
rating: "excellent",
|
|
1966
|
+
analysis: { uniqueDomains: 0, domainConcentration: 0, exportPurposeClarity: 1 }
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
const allDomains = [];
|
|
1970
|
+
for (const exp of exports2) {
|
|
1971
|
+
if (exp.inferredDomain) allDomains.push(exp.inferredDomain);
|
|
1972
|
+
if (exp.domains) allDomains.push(...exp.domains);
|
|
1973
|
+
}
|
|
1974
|
+
const uniqueDomains = new Set(allDomains);
|
|
1975
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
1976
|
+
for (const d of allDomains) {
|
|
1977
|
+
domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
|
|
1978
|
+
}
|
|
1979
|
+
const maxCount = Math.max(...Array.from(domainCounts.values()), 1);
|
|
1980
|
+
const domainConcentration = maxCount / allDomains.length;
|
|
1981
|
+
const exportPurposeClarity = 1 - (uniqueDomains.size - 1) / Math.max(1, exports2.length);
|
|
1982
|
+
const score = domainConcentration * 0.5 + exportPurposeClarity * 0.5;
|
|
1983
|
+
let rating;
|
|
1984
|
+
if (score > 0.8) rating = "excellent";
|
|
1985
|
+
else if (score > 0.6) rating = "good";
|
|
1986
|
+
else if (score > 0.4) rating = "moderate";
|
|
1987
|
+
else rating = "poor";
|
|
1988
|
+
return {
|
|
1989
|
+
score: Math.round(score * 100) / 100,
|
|
1990
|
+
rating,
|
|
1991
|
+
analysis: {
|
|
1992
|
+
uniqueDomains: uniqueDomains.size,
|
|
1993
|
+
domainConcentration: Math.round(domainConcentration * 100) / 100,
|
|
1994
|
+
exportPurposeClarity: Math.round(exportPurposeClarity * 100) / 100
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
function calculateFutureProofScore(params) {
|
|
1999
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2000
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2001
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2002
|
+
const overall = Math.round(
|
|
2003
|
+
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
2004
|
+
);
|
|
2005
|
+
const factors = [
|
|
2006
|
+
{
|
|
2007
|
+
name: "Cognitive Load",
|
|
2008
|
+
impact: Math.round(loadScore - 50),
|
|
2009
|
+
description: params.cognitiveLoad.rating
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
name: "Pattern Entropy",
|
|
2013
|
+
impact: Math.round(entropyScore - 50),
|
|
2014
|
+
description: params.patternEntropy.rating
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
name: "Concept Cohesion",
|
|
2018
|
+
impact: Math.round(cohesionScore - 50),
|
|
2019
|
+
description: params.conceptCohesion.rating
|
|
2020
|
+
}
|
|
2021
|
+
];
|
|
2022
|
+
const recommendations = [];
|
|
2023
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
2024
|
+
recommendations.push({
|
|
2025
|
+
action: rec,
|
|
2026
|
+
estimatedImpact: 5,
|
|
2027
|
+
priority: "medium"
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
2031
|
+
recommendations.push({
|
|
2032
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
2033
|
+
estimatedImpact: 8,
|
|
2034
|
+
priority: "high"
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2038
|
+
return {
|
|
2039
|
+
toolName: "future-proof",
|
|
2040
|
+
score: overall,
|
|
2041
|
+
rawMetrics: {
|
|
2042
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2043
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2044
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2045
|
+
semanticDistanceAvg
|
|
2046
|
+
},
|
|
2047
|
+
factors,
|
|
2048
|
+
recommendations
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
function calculateHallucinationRisk(params) {
|
|
2052
|
+
const {
|
|
2053
|
+
overloadedSymbols,
|
|
2054
|
+
magicLiterals,
|
|
2055
|
+
booleanTraps,
|
|
2056
|
+
implicitSideEffects,
|
|
2057
|
+
deepCallbacks,
|
|
2058
|
+
ambiguousNames,
|
|
2059
|
+
undocumentedExports,
|
|
2060
|
+
totalSymbols,
|
|
2061
|
+
totalExports
|
|
2062
|
+
} = params;
|
|
2063
|
+
if (totalSymbols === 0) {
|
|
2064
|
+
return {
|
|
2065
|
+
score: 0,
|
|
2066
|
+
rating: "minimal",
|
|
2067
|
+
signals: [],
|
|
2068
|
+
topRisk: "No symbols to analyze",
|
|
2069
|
+
recommendations: []
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
const overloadRatio = Math.min(1, overloadedSymbols / Math.max(1, totalSymbols));
|
|
2073
|
+
const overloadSignal = {
|
|
2074
|
+
name: "Symbol Overloading",
|
|
2075
|
+
count: overloadedSymbols,
|
|
2076
|
+
riskContribution: Math.round(overloadRatio * 100 * 0.25),
|
|
2077
|
+
// 25% weight
|
|
2078
|
+
description: `${overloadedSymbols} overloaded symbols \u2014 AI picks wrong signature`
|
|
2079
|
+
};
|
|
2080
|
+
const magicRatio = Math.min(1, magicLiterals / Math.max(1, totalSymbols * 2));
|
|
2081
|
+
const magicSignal = {
|
|
2082
|
+
name: "Magic Literals",
|
|
2083
|
+
count: magicLiterals,
|
|
2084
|
+
riskContribution: Math.round(magicRatio * 100 * 0.2),
|
|
2085
|
+
// 20% weight
|
|
2086
|
+
description: `${magicLiterals} unnamed constants \u2014 AI invents wrong values`
|
|
2087
|
+
};
|
|
2088
|
+
const trapRatio = Math.min(1, booleanTraps / Math.max(1, totalSymbols));
|
|
2089
|
+
const trapSignal = {
|
|
2090
|
+
name: "Boolean Traps",
|
|
2091
|
+
count: booleanTraps,
|
|
2092
|
+
riskContribution: Math.round(trapRatio * 100 * 0.2),
|
|
2093
|
+
// 20% weight
|
|
2094
|
+
description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
|
|
2095
|
+
};
|
|
2096
|
+
const sideEffectRatio = Math.min(1, implicitSideEffects / Math.max(1, totalExports));
|
|
2097
|
+
const sideEffectSignal = {
|
|
2098
|
+
name: "Implicit Side Effects",
|
|
2099
|
+
count: implicitSideEffects,
|
|
2100
|
+
riskContribution: Math.round(sideEffectRatio * 100 * 0.15),
|
|
2101
|
+
// 15% weight
|
|
2102
|
+
description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
|
|
2103
|
+
};
|
|
2104
|
+
const callbackRatio = Math.min(1, deepCallbacks / Math.max(1, totalSymbols * 0.1));
|
|
2105
|
+
const callbackSignal = {
|
|
2106
|
+
name: "Callback Nesting",
|
|
2107
|
+
count: deepCallbacks,
|
|
2108
|
+
riskContribution: Math.round(callbackRatio * 100 * 0.1),
|
|
2109
|
+
// 10% weight
|
|
2110
|
+
description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
|
|
2111
|
+
};
|
|
2112
|
+
const ambiguousRatio = Math.min(1, ambiguousNames / Math.max(1, totalSymbols));
|
|
2113
|
+
const ambiguousSignal = {
|
|
2114
|
+
name: "Ambiguous Names",
|
|
2115
|
+
count: ambiguousNames,
|
|
2116
|
+
riskContribution: Math.round(ambiguousRatio * 100 * 0.1),
|
|
2117
|
+
// 10% weight
|
|
2118
|
+
description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
|
|
2119
|
+
};
|
|
2120
|
+
const undocRatio = Math.min(1, undocumentedExports / Math.max(1, totalExports));
|
|
2121
|
+
const undocSignal = {
|
|
2122
|
+
name: "Undocumented Exports",
|
|
2123
|
+
count: undocumentedExports,
|
|
2124
|
+
riskContribution: Math.round(undocRatio * 100 * 0.1),
|
|
2125
|
+
// 10% weight
|
|
2126
|
+
description: `${undocumentedExports} public functions without docs \u2014 AI fabricates behavior`
|
|
2127
|
+
};
|
|
2128
|
+
const signals = [
|
|
2129
|
+
overloadSignal,
|
|
2130
|
+
magicSignal,
|
|
2131
|
+
trapSignal,
|
|
2132
|
+
sideEffectSignal,
|
|
2133
|
+
callbackSignal,
|
|
2134
|
+
ambiguousSignal,
|
|
2135
|
+
undocSignal
|
|
2136
|
+
];
|
|
2137
|
+
const score = Math.min(100, signals.reduce((sum, s) => sum + s.riskContribution, 0));
|
|
2138
|
+
let rating;
|
|
2139
|
+
if (score < 10) rating = "minimal";
|
|
2140
|
+
else if (score < 25) rating = "low";
|
|
2141
|
+
else if (score < 50) rating = "moderate";
|
|
2142
|
+
else if (score < 75) rating = "high";
|
|
2143
|
+
else rating = "severe";
|
|
2144
|
+
const topSignal = signals.reduce((a, b) => a.riskContribution > b.riskContribution ? a : b);
|
|
2145
|
+
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant hallucination risks detected";
|
|
2146
|
+
const recommendations = [];
|
|
2147
|
+
if (overloadSignal.riskContribution > 5) {
|
|
2148
|
+
recommendations.push(`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`);
|
|
2149
|
+
}
|
|
2150
|
+
if (magicSignal.riskContribution > 5) {
|
|
2151
|
+
recommendations.push(`Extract ${magicLiterals} magic literals into named constants`);
|
|
2152
|
+
}
|
|
2153
|
+
if (trapSignal.riskContribution > 5) {
|
|
2154
|
+
recommendations.push(`Replace ${booleanTraps} boolean traps with named options objects`);
|
|
2155
|
+
}
|
|
2156
|
+
if (undocSignal.riskContribution > 5) {
|
|
2157
|
+
recommendations.push(`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`);
|
|
2158
|
+
}
|
|
2159
|
+
if (sideEffectSignal.riskContribution > 5) {
|
|
2160
|
+
recommendations.push("Mark functions with side effects explicitly in their names or docs");
|
|
2161
|
+
}
|
|
2162
|
+
return {
|
|
2163
|
+
score: Math.round(score),
|
|
2164
|
+
rating,
|
|
2165
|
+
signals: signals.filter((s) => s.count > 0),
|
|
2166
|
+
topRisk,
|
|
2167
|
+
recommendations
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
function calculateAgentGrounding(params) {
|
|
2171
|
+
const {
|
|
2172
|
+
deepDirectories,
|
|
2173
|
+
totalDirectories,
|
|
2174
|
+
vagueFileNames,
|
|
2175
|
+
totalFiles,
|
|
2176
|
+
hasRootReadme,
|
|
2177
|
+
readmeIsFresh,
|
|
2178
|
+
barrelExports,
|
|
2179
|
+
untypedExports,
|
|
2180
|
+
totalExports,
|
|
2181
|
+
inconsistentDomainTerms,
|
|
2182
|
+
domainVocabularySize
|
|
2183
|
+
} = params;
|
|
2184
|
+
const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
|
|
2185
|
+
const structureClarityScore = Math.max(0, Math.round(100 - deepDirRatio * 80));
|
|
2186
|
+
const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
|
|
2187
|
+
const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
|
|
2188
|
+
let entryPointScore = 60;
|
|
2189
|
+
if (hasRootReadme) entryPointScore += 25;
|
|
2190
|
+
if (readmeIsFresh) entryPointScore += 10;
|
|
2191
|
+
const barrelRatio = totalFiles > 0 ? barrelExports / (totalFiles * 0.1) : 0;
|
|
2192
|
+
entryPointScore += Math.round(Math.min(5, barrelRatio * 5));
|
|
2193
|
+
entryPointScore = Math.min(100, entryPointScore);
|
|
2194
|
+
const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
|
|
2195
|
+
const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
|
|
2196
|
+
const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
|
|
2197
|
+
const domainConsistencyScore = Math.max(0, Math.round(100 - inconsistencyRatio * 80));
|
|
2198
|
+
const score = Math.round(
|
|
2199
|
+
structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
|
|
2200
|
+
);
|
|
2201
|
+
let rating;
|
|
2202
|
+
if (score >= 85) rating = "excellent";
|
|
2203
|
+
else if (score >= 70) rating = "good";
|
|
2204
|
+
else if (score >= 50) rating = "moderate";
|
|
2205
|
+
else if (score >= 30) rating = "poor";
|
|
2206
|
+
else rating = "disorienting";
|
|
2207
|
+
const recommendations = [];
|
|
2208
|
+
if (structureClarityScore < 70) {
|
|
2209
|
+
recommendations.push(`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`);
|
|
2210
|
+
}
|
|
2211
|
+
if (selfDocumentationScore < 70) {
|
|
2212
|
+
recommendations.push(`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`);
|
|
2213
|
+
}
|
|
2214
|
+
if (!hasRootReadme) {
|
|
2215
|
+
recommendations.push("Add a root README.md so agents understand the project context immediately");
|
|
2216
|
+
} else if (!readmeIsFresh) {
|
|
2217
|
+
recommendations.push("Update README.md \u2014 stale entry-point documentation disorients agents");
|
|
2218
|
+
}
|
|
2219
|
+
if (apiClarityScore < 70) {
|
|
2220
|
+
recommendations.push(`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`);
|
|
2221
|
+
}
|
|
2222
|
+
if (domainConsistencyScore < 70) {
|
|
2223
|
+
recommendations.push(`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`);
|
|
2224
|
+
}
|
|
2225
|
+
return {
|
|
2226
|
+
score,
|
|
2227
|
+
rating,
|
|
2228
|
+
dimensions: {
|
|
2229
|
+
structureClarityScore,
|
|
2230
|
+
selfDocumentationScore,
|
|
2231
|
+
entryPointScore,
|
|
2232
|
+
apiClarityScore,
|
|
2233
|
+
domainConsistencyScore
|
|
2234
|
+
},
|
|
2235
|
+
recommendations
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function calculateTestabilityIndex(params) {
|
|
2239
|
+
const {
|
|
2240
|
+
testFiles,
|
|
2241
|
+
sourceFiles,
|
|
2242
|
+
pureFunctions,
|
|
2243
|
+
totalFunctions,
|
|
2244
|
+
injectionPatterns,
|
|
2245
|
+
totalClasses,
|
|
2246
|
+
bloatedInterfaces,
|
|
2247
|
+
totalInterfaces,
|
|
2248
|
+
externalStateMutations,
|
|
2249
|
+
hasTestFramework
|
|
2250
|
+
} = params;
|
|
2251
|
+
const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
|
|
2252
|
+
const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
|
|
2253
|
+
const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
|
|
2254
|
+
const purityScore = Math.round(purityRatio * 100);
|
|
2255
|
+
const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
|
|
2256
|
+
const dependencyInjectionScore = Math.round(Math.min(100, injectionRatio * 100));
|
|
2257
|
+
const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
|
|
2258
|
+
const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
|
|
2259
|
+
const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
|
|
2260
|
+
const observabilityScore = Math.max(0, Math.round(100 - mutationRatio * 100));
|
|
2261
|
+
const frameworkWeight = hasTestFramework ? 1 : 0.8;
|
|
2262
|
+
const rawScore = (testCoverageRatio * 0.3 + purityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
|
|
2263
|
+
const score = Math.max(0, Math.min(100, Math.round(rawScore)));
|
|
2264
|
+
let rating;
|
|
2265
|
+
if (score >= 85) rating = "excellent";
|
|
2266
|
+
else if (score >= 70) rating = "good";
|
|
2267
|
+
else if (score >= 50) rating = "moderate";
|
|
2268
|
+
else if (score >= 30) rating = "poor";
|
|
2269
|
+
else rating = "unverifiable";
|
|
2270
|
+
let aiChangeSafetyRating;
|
|
2271
|
+
if (rawCoverageRatio >= 0.5 && score >= 70) aiChangeSafetyRating = "safe";
|
|
2272
|
+
else if (rawCoverageRatio >= 0.2 && score >= 50) aiChangeSafetyRating = "moderate-risk";
|
|
2273
|
+
else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
|
|
2274
|
+
else aiChangeSafetyRating = "blind-risk";
|
|
2275
|
+
const recommendations = [];
|
|
2276
|
+
if (!hasTestFramework) {
|
|
2277
|
+
recommendations.push("Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests");
|
|
2278
|
+
}
|
|
2279
|
+
if (rawCoverageRatio < 0.3) {
|
|
2280
|
+
const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
|
|
2281
|
+
recommendations.push(`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`);
|
|
2282
|
+
}
|
|
2283
|
+
if (purityScore < 50) {
|
|
2284
|
+
recommendations.push("Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable");
|
|
2285
|
+
}
|
|
2286
|
+
if (dependencyInjectionScore < 50 && totalClasses > 0) {
|
|
2287
|
+
recommendations.push("Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable");
|
|
2288
|
+
}
|
|
2289
|
+
if (externalStateMutations > totalFunctions * 0.3) {
|
|
2290
|
+
recommendations.push("Reduce direct state mutations \u2014 return values instead to improve observability");
|
|
2291
|
+
}
|
|
2292
|
+
return {
|
|
2293
|
+
score,
|
|
2294
|
+
rating,
|
|
2295
|
+
dimensions: {
|
|
2296
|
+
testCoverageRatio,
|
|
2297
|
+
purityScore,
|
|
2298
|
+
dependencyInjectionScore,
|
|
2299
|
+
interfaceFocusScore,
|
|
2300
|
+
observabilityScore
|
|
2301
|
+
},
|
|
2302
|
+
aiChangeSafetyRating,
|
|
2303
|
+
recommendations
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
function calculateExtendedFutureProofScore(params) {
|
|
2307
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2308
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2309
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2310
|
+
const hallucinationScore = 100 - params.hallucinationRisk.score;
|
|
2311
|
+
const groundingScore = params.agentGrounding.score;
|
|
2312
|
+
const testabilityScore = params.testability.score;
|
|
2313
|
+
const overall = Math.round(
|
|
2314
|
+
loadScore * 0.2 + entropyScore * 0.15 + cohesionScore * 0.15 + hallucinationScore * 0.2 + groundingScore * 0.15 + testabilityScore * 0.15
|
|
2315
|
+
);
|
|
2316
|
+
const factors = [
|
|
2317
|
+
{
|
|
2318
|
+
name: "Cognitive Load",
|
|
2319
|
+
impact: Math.round(loadScore - 50),
|
|
2320
|
+
description: params.cognitiveLoad.rating
|
|
2321
|
+
},
|
|
2322
|
+
{
|
|
2323
|
+
name: "Pattern Entropy",
|
|
2324
|
+
impact: Math.round(entropyScore - 50),
|
|
2325
|
+
description: params.patternEntropy.rating
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
name: "Concept Cohesion",
|
|
2329
|
+
impact: Math.round(cohesionScore - 50),
|
|
2330
|
+
description: params.conceptCohesion.rating
|
|
2331
|
+
},
|
|
2332
|
+
{
|
|
2333
|
+
name: "Hallucination Risk",
|
|
2334
|
+
impact: Math.round(hallucinationScore - 50),
|
|
2335
|
+
description: `${params.hallucinationRisk.rating} risk (${params.hallucinationRisk.score}/100 raw)`
|
|
2336
|
+
},
|
|
2337
|
+
{
|
|
2338
|
+
name: "Agent Grounding",
|
|
2339
|
+
impact: Math.round(groundingScore - 50),
|
|
2340
|
+
description: params.agentGrounding.rating
|
|
2341
|
+
},
|
|
2342
|
+
{
|
|
2343
|
+
name: "Testability",
|
|
2344
|
+
impact: Math.round(testabilityScore - 50),
|
|
2345
|
+
description: `${params.testability.rating} \u2014 AI changes are ${params.testability.aiChangeSafetyRating}`
|
|
2346
|
+
}
|
|
2347
|
+
];
|
|
2348
|
+
const recommendations = [];
|
|
2349
|
+
for (const rec of params.hallucinationRisk.recommendations) {
|
|
2350
|
+
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
2351
|
+
}
|
|
2352
|
+
for (const rec of params.agentGrounding.recommendations) {
|
|
2353
|
+
recommendations.push({ action: rec, estimatedImpact: 6, priority: "medium" });
|
|
2354
|
+
}
|
|
2355
|
+
for (const rec of params.testability.recommendations) {
|
|
2356
|
+
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
2357
|
+
recommendations.push({ action: rec, estimatedImpact: 10, priority });
|
|
2358
|
+
}
|
|
2359
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
2360
|
+
recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
|
|
2361
|
+
}
|
|
2362
|
+
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2363
|
+
return {
|
|
2364
|
+
toolName: "future-proof",
|
|
2365
|
+
score: overall,
|
|
2366
|
+
rawMetrics: {
|
|
2367
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2368
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2369
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2370
|
+
hallucinationRiskScore: params.hallucinationRisk.score,
|
|
2371
|
+
agentGroundingScore: params.agentGrounding.score,
|
|
2372
|
+
testabilityScore: params.testability.score,
|
|
2373
|
+
semanticDistanceAvg
|
|
2374
|
+
},
|
|
2375
|
+
factors,
|
|
2376
|
+
recommendations
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// src/utils/history.ts
|
|
2381
|
+
var import_fs4 = require("fs");
|
|
2382
|
+
var import_path4 = require("path");
|
|
2383
|
+
function getHistoryPath(rootDir) {
|
|
2384
|
+
return (0, import_path4.join)(rootDir, ".aiready", "history.json");
|
|
2385
|
+
}
|
|
2386
|
+
function loadScoreHistory(rootDir) {
|
|
2387
|
+
const historyPath = getHistoryPath(rootDir);
|
|
2388
|
+
if (!(0, import_fs4.existsSync)(historyPath)) {
|
|
2389
|
+
return [];
|
|
2390
|
+
}
|
|
2391
|
+
try {
|
|
2392
|
+
const data = (0, import_fs4.readFileSync)(historyPath, "utf-8");
|
|
2393
|
+
return JSON.parse(data);
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
console.warn("Failed to load score history:", error);
|
|
2396
|
+
return [];
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
function saveScoreEntry(rootDir, entry) {
|
|
2400
|
+
const historyPath = getHistoryPath(rootDir);
|
|
2401
|
+
const historyDir = (0, import_path4.dirname)(historyPath);
|
|
2402
|
+
if (!(0, import_fs4.existsSync)(historyDir)) {
|
|
2403
|
+
(0, import_fs4.mkdirSync)(historyDir, { recursive: true });
|
|
2404
|
+
}
|
|
2405
|
+
const history = loadScoreHistory(rootDir);
|
|
2406
|
+
const newEntry = {
|
|
2407
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2408
|
+
...entry
|
|
2409
|
+
};
|
|
2410
|
+
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1e3;
|
|
2411
|
+
const filteredHistory = history.filter(
|
|
2412
|
+
(e) => new Date(e.timestamp).getTime() > oneYearAgo
|
|
2413
|
+
);
|
|
2414
|
+
filteredHistory.push(newEntry);
|
|
2415
|
+
(0, import_fs4.writeFileSync)(historyPath, JSON.stringify(filteredHistory, null, 2));
|
|
2416
|
+
}
|
|
2417
|
+
function getHistorySummary(rootDir) {
|
|
2418
|
+
const history = loadScoreHistory(rootDir);
|
|
2419
|
+
if (history.length === 0) {
|
|
2420
|
+
return {
|
|
2421
|
+
totalScans: 0,
|
|
2422
|
+
firstScan: null,
|
|
2423
|
+
lastScan: null,
|
|
2424
|
+
avgScore: 0
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
const scores = history.map((e) => e.overallScore);
|
|
2428
|
+
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
2429
|
+
return {
|
|
2430
|
+
totalScans: history.length,
|
|
2431
|
+
firstScan: history[0].timestamp,
|
|
2432
|
+
lastScan: history[history.length - 1].timestamp,
|
|
2433
|
+
avgScore: Math.round(avgScore)
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function exportHistory(rootDir, format = "json") {
|
|
2437
|
+
const history = loadScoreHistory(rootDir);
|
|
2438
|
+
if (format === "csv") {
|
|
2439
|
+
const headers = "timestamp,overallScore,totalIssues,totalTokens,patternScore,contextScore,consistencyScore\n";
|
|
2440
|
+
const rows = history.map(
|
|
2441
|
+
(e) => `${e.timestamp},${e.overallScore},${e.totalIssues},${e.totalTokens},${e.breakdown?.["pattern-detect"] || ""},${e.breakdown?.["context-analyzer"] || ""},${e.breakdown?.["consistency"] || ""}`
|
|
2442
|
+
).join("\n");
|
|
2443
|
+
return headers + rows;
|
|
2444
|
+
}
|
|
2445
|
+
return JSON.stringify(history, null, 2);
|
|
2446
|
+
}
|
|
2447
|
+
function clearHistory(rootDir) {
|
|
2448
|
+
const historyPath = getHistoryPath(rootDir);
|
|
2449
|
+
if ((0, import_fs4.existsSync)(historyPath)) {
|
|
2450
|
+
(0, import_fs4.writeFileSync)(historyPath, JSON.stringify([]));
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
1461
2453
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1462
2454
|
0 && (module.exports = {
|
|
2455
|
+
CONTEXT_TIER_THRESHOLDS,
|
|
1463
2456
|
DEFAULT_COST_CONFIG,
|
|
1464
2457
|
DEFAULT_EXCLUDE,
|
|
1465
2458
|
DEFAULT_TOOL_WEIGHTS,
|
|
1466
2459
|
LANGUAGE_EXTENSIONS,
|
|
1467
2460
|
Language,
|
|
2461
|
+
MODEL_PRICING_PRESETS,
|
|
1468
2462
|
ParseError,
|
|
1469
2463
|
ParserFactory,
|
|
1470
2464
|
PythonParser,
|
|
2465
|
+
SIZE_ADJUSTED_THRESHOLDS,
|
|
1471
2466
|
TOOL_NAME_MAP,
|
|
1472
2467
|
TypeScriptParser,
|
|
2468
|
+
calculateAgentGrounding,
|
|
2469
|
+
calculateCognitiveLoad,
|
|
1473
2470
|
calculateComprehensionDifficulty,
|
|
2471
|
+
calculateConceptCohesion,
|
|
2472
|
+
calculateExtendedFutureProofScore,
|
|
2473
|
+
calculateFutureProofScore,
|
|
2474
|
+
calculateHallucinationRisk,
|
|
1474
2475
|
calculateImportSimilarity,
|
|
2476
|
+
calculateKnowledgeConcentration,
|
|
1475
2477
|
calculateMonthlyCost,
|
|
1476
2478
|
calculateOverallScore,
|
|
2479
|
+
calculatePatternEntropy,
|
|
1477
2480
|
calculateProductivityImpact,
|
|
2481
|
+
calculateRemediationVelocity,
|
|
2482
|
+
calculateScoreTrend,
|
|
2483
|
+
calculateSemanticDistance,
|
|
2484
|
+
calculateTechnicalDebtInterest,
|
|
2485
|
+
calculateTestabilityIndex,
|
|
2486
|
+
clearHistory,
|
|
1478
2487
|
estimateTokens,
|
|
2488
|
+
exportHistory,
|
|
1479
2489
|
extractFunctions,
|
|
1480
2490
|
extractImports,
|
|
1481
2491
|
formatAcceptanceRate,
|
|
@@ -1484,11 +2494,17 @@ function getSupportedLanguages() {
|
|
|
1484
2494
|
formatScore,
|
|
1485
2495
|
formatToolScore,
|
|
1486
2496
|
generateHTML,
|
|
2497
|
+
getDebtBreakdown,
|
|
1487
2498
|
getElapsedTime,
|
|
1488
2499
|
getFileExtension,
|
|
2500
|
+
getHistorySummary,
|
|
2501
|
+
getModelPreset,
|
|
1489
2502
|
getParser,
|
|
2503
|
+
getProjectSizeTier,
|
|
1490
2504
|
getRating,
|
|
1491
2505
|
getRatingDisplay,
|
|
2506
|
+
getRatingWithContext,
|
|
2507
|
+
getRecommendedThreshold,
|
|
1492
2508
|
getSupportedLanguages,
|
|
1493
2509
|
getToolWeight,
|
|
1494
2510
|
handleCLIError,
|
|
@@ -1497,6 +2513,7 @@ function getSupportedLanguages() {
|
|
|
1497
2513
|
isSourceFile,
|
|
1498
2514
|
loadConfig,
|
|
1499
2515
|
loadMergedConfig,
|
|
2516
|
+
loadScoreHistory,
|
|
1500
2517
|
mergeConfigWithDefaults,
|
|
1501
2518
|
normalizeToolName,
|
|
1502
2519
|
parseCode,
|
|
@@ -1505,5 +2522,6 @@ function getSupportedLanguages() {
|
|
|
1505
2522
|
predictAcceptanceRate,
|
|
1506
2523
|
readFileContent,
|
|
1507
2524
|
resolveOutputPath,
|
|
2525
|
+
saveScoreEntry,
|
|
1508
2526
|
scanFiles
|
|
1509
2527
|
});
|