@aiready/core 0.9.38 → 0.9.39
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/client.d.mts +14 -6
- package/dist/client.d.ts +14 -6
- package/dist/index.d.mts +167 -280
- package/dist/index.d.ts +167 -280
- package/dist/index.js +412 -540
- package/dist/index.mjs +405 -536
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -40,16 +40,19 @@ __export(index_exports, {
|
|
|
40
40
|
ParseError: () => ParseError,
|
|
41
41
|
ParserFactory: () => ParserFactory,
|
|
42
42
|
PythonParser: () => PythonParser,
|
|
43
|
+
SEVERITY_TIME_ESTIMATES: () => SEVERITY_TIME_ESTIMATES,
|
|
43
44
|
SIZE_ADJUSTED_THRESHOLDS: () => SIZE_ADJUSTED_THRESHOLDS,
|
|
44
45
|
TOOL_NAME_MAP: () => TOOL_NAME_MAP,
|
|
45
46
|
TypeScriptParser: () => TypeScriptParser,
|
|
46
47
|
VAGUE_FILE_NAMES: () => VAGUE_FILE_NAMES,
|
|
47
48
|
calculateAgentGrounding: () => calculateAgentGrounding,
|
|
48
49
|
calculateAiSignalClarity: () => calculateAiSignalClarity,
|
|
50
|
+
calculateBusinessROI: () => calculateBusinessROI,
|
|
49
51
|
calculateChangeAmplification: () => calculateChangeAmplification,
|
|
50
52
|
calculateCognitiveLoad: () => calculateCognitiveLoad,
|
|
51
53
|
calculateComprehensionDifficulty: () => calculateComprehensionDifficulty,
|
|
52
54
|
calculateConceptCohesion: () => calculateConceptCohesion,
|
|
55
|
+
calculateDebtInterest: () => calculateDebtInterest,
|
|
53
56
|
calculateDependencyHealth: () => calculateDependencyHealth,
|
|
54
57
|
calculateDocDrift: () => calculateDocDrift,
|
|
55
58
|
calculateExtendedFutureProofScore: () => calculateExtendedFutureProofScore,
|
|
@@ -60,10 +63,8 @@ __export(index_exports, {
|
|
|
60
63
|
calculateOverallScore: () => calculateOverallScore,
|
|
61
64
|
calculatePatternEntropy: () => calculatePatternEntropy,
|
|
62
65
|
calculateProductivityImpact: () => calculateProductivityImpact,
|
|
63
|
-
calculateRemediationVelocity: () => calculateRemediationVelocity,
|
|
64
|
-
calculateScoreTrend: () => calculateScoreTrend,
|
|
65
66
|
calculateSemanticDistance: () => calculateSemanticDistance,
|
|
66
|
-
|
|
67
|
+
calculateTechnicalValueChain: () => calculateTechnicalValueChain,
|
|
67
68
|
calculateTestabilityIndex: () => calculateTestabilityIndex,
|
|
68
69
|
calculateTokenBudget: () => calculateTokenBudget,
|
|
69
70
|
clearHistory: () => clearHistory,
|
|
@@ -79,7 +80,6 @@ __export(index_exports, {
|
|
|
79
80
|
formatToolScore: () => formatToolScore,
|
|
80
81
|
generateHTML: () => generateHTML,
|
|
81
82
|
generateValueChain: () => generateValueChain,
|
|
82
|
-
getDebtBreakdown: () => getDebtBreakdown,
|
|
83
83
|
getElapsedTime: () => getElapsedTime,
|
|
84
84
|
getFileCommitTimestamps: () => getFileCommitTimestamps,
|
|
85
85
|
getFileExtension: () => getFileExtension,
|
|
@@ -93,6 +93,9 @@ __export(index_exports, {
|
|
|
93
93
|
getRatingWithContext: () => getRatingWithContext,
|
|
94
94
|
getRecommendedThreshold: () => getRecommendedThreshold,
|
|
95
95
|
getRepoMetadata: () => getRepoMetadata,
|
|
96
|
+
getSafetyIcon: () => getSafetyIcon,
|
|
97
|
+
getScoreBar: () => getScoreBar,
|
|
98
|
+
getSeverityColor: () => getSeverityColor,
|
|
96
99
|
getSupportedLanguages: () => getSupportedLanguages,
|
|
97
100
|
getToolWeight: () => getToolWeight,
|
|
98
101
|
handleCLIError: () => handleCLIError,
|
|
@@ -640,6 +643,41 @@ function handleCLIError(error, commandName) {
|
|
|
640
643
|
function getElapsedTime(startTime) {
|
|
641
644
|
return ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
642
645
|
}
|
|
646
|
+
function getScoreBar(val) {
|
|
647
|
+
return "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
|
|
648
|
+
}
|
|
649
|
+
function getSafetyIcon(rating) {
|
|
650
|
+
switch (rating) {
|
|
651
|
+
case "safe":
|
|
652
|
+
return "\u2705";
|
|
653
|
+
case "moderate-risk":
|
|
654
|
+
return "\u26A0\uFE0F ";
|
|
655
|
+
case "high-risk":
|
|
656
|
+
return "\u{1F534}";
|
|
657
|
+
case "blind-risk":
|
|
658
|
+
return "\u{1F480}";
|
|
659
|
+
default:
|
|
660
|
+
return "\u2753";
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function getSeverityColor(severity, chalk) {
|
|
664
|
+
switch (severity.toLowerCase()) {
|
|
665
|
+
case "critical":
|
|
666
|
+
case "high-risk":
|
|
667
|
+
case "blind-risk":
|
|
668
|
+
return chalk.red;
|
|
669
|
+
case "major":
|
|
670
|
+
case "moderate-risk":
|
|
671
|
+
return chalk.yellow;
|
|
672
|
+
case "minor":
|
|
673
|
+
case "safe":
|
|
674
|
+
return chalk.green;
|
|
675
|
+
case "info":
|
|
676
|
+
return chalk.blue;
|
|
677
|
+
default:
|
|
678
|
+
return chalk.white;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
643
681
|
|
|
644
682
|
// src/utils/visualization.ts
|
|
645
683
|
function generateHTML(graph) {
|
|
@@ -999,7 +1037,7 @@ function formatToolScore(output) {
|
|
|
999
1037
|
return result;
|
|
1000
1038
|
}
|
|
1001
1039
|
|
|
1002
|
-
// src/business-
|
|
1040
|
+
// src/business/pricing-models.ts
|
|
1003
1041
|
var MODEL_PRICING_PRESETS = {
|
|
1004
1042
|
"gpt-5.3": {
|
|
1005
1043
|
name: "GPT-5.3",
|
|
@@ -1061,30 +1099,17 @@ var MODEL_PRICING_PRESETS = {
|
|
|
1061
1099
|
function getModelPreset(modelId) {
|
|
1062
1100
|
return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
|
|
1063
1101
|
}
|
|
1102
|
+
|
|
1103
|
+
// src/business/cost-metrics.ts
|
|
1064
1104
|
var DEFAULT_COST_CONFIG = {
|
|
1065
1105
|
pricePer1KTokens: 5e-3,
|
|
1066
|
-
// GPT-4o input price (updated from GPT-4 era 0.01)
|
|
1067
1106
|
queriesPerDevPerDay: 60,
|
|
1068
|
-
// Average AI queries per developer (updated: 40→60 as of 2026)
|
|
1069
1107
|
developerCount: 5,
|
|
1070
|
-
// Default team size
|
|
1071
1108
|
daysPerMonth: 30
|
|
1072
1109
|
};
|
|
1073
|
-
var SEVERITY_TIME_ESTIMATES = {
|
|
1074
|
-
critical: 4,
|
|
1075
|
-
// Complex architectural issues (industry avg: 6.8h)
|
|
1076
|
-
major: 2,
|
|
1077
|
-
// Significant refactoring needed (avg: 3.4h)
|
|
1078
|
-
minor: 0.5,
|
|
1079
|
-
// Simple naming/style fixes (avg: 0.8h)
|
|
1080
|
-
info: 0.25
|
|
1081
|
-
// Documentation improvements
|
|
1082
|
-
};
|
|
1083
|
-
var DEFAULT_HOURLY_RATE = 75;
|
|
1084
1110
|
function calculateMonthlyCost(tokenWaste, config = {}) {
|
|
1085
1111
|
const budget = calculateTokenBudget({
|
|
1086
1112
|
totalContextTokens: tokenWaste * 2.5,
|
|
1087
|
-
// Heuristic: context is larger than waste
|
|
1088
1113
|
wastedTokens: {
|
|
1089
1114
|
duplication: tokenWaste * 0.7,
|
|
1090
1115
|
fragmentation: tokenWaste * 0.3,
|
|
@@ -1118,7 +1143,6 @@ function calculateTokenBudget(params) {
|
|
|
1118
1143
|
},
|
|
1119
1144
|
efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
|
|
1120
1145
|
potentialRetrievableTokens: Math.round(totalWaste * 0.8)
|
|
1121
|
-
// Heuristic: 80% is fixable
|
|
1122
1146
|
};
|
|
1123
1147
|
}
|
|
1124
1148
|
function estimateCostFromBudget(budget, model, config = {}) {
|
|
@@ -1142,6 +1166,15 @@ function estimateCostFromBudget(budget, model, config = {}) {
|
|
|
1142
1166
|
confidence
|
|
1143
1167
|
};
|
|
1144
1168
|
}
|
|
1169
|
+
|
|
1170
|
+
// src/business/productivity-metrics.ts
|
|
1171
|
+
var SEVERITY_TIME_ESTIMATES = {
|
|
1172
|
+
critical: 4,
|
|
1173
|
+
major: 2,
|
|
1174
|
+
minor: 0.5,
|
|
1175
|
+
info: 0.25
|
|
1176
|
+
};
|
|
1177
|
+
var DEFAULT_HOURLY_RATE = 75;
|
|
1145
1178
|
function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
1146
1179
|
const counts = {
|
|
1147
1180
|
critical: issues.filter((i) => i.severity === "critical").length,
|
|
@@ -1182,108 +1215,158 @@ function predictAcceptanceRate(toolOutputs) {
|
|
|
1182
1215
|
const baseRate = 0.3;
|
|
1183
1216
|
const patterns = toolOutputs.get("pattern-detect");
|
|
1184
1217
|
if (patterns) {
|
|
1185
|
-
const patternImpact = (patterns.score - 50) * 3e-3;
|
|
1186
1218
|
factors.push({
|
|
1187
1219
|
name: "Semantic Duplication",
|
|
1188
|
-
impact: Math.round(
|
|
1220
|
+
impact: Math.round((patterns.score - 50) * 3e-3 * 100)
|
|
1189
1221
|
});
|
|
1190
1222
|
}
|
|
1191
1223
|
const context = toolOutputs.get("context-analyzer");
|
|
1192
1224
|
if (context) {
|
|
1193
|
-
const contextImpact = (context.score - 50) * 4e-3;
|
|
1194
1225
|
factors.push({
|
|
1195
1226
|
name: "Context Efficiency",
|
|
1196
|
-
impact: Math.round(
|
|
1227
|
+
impact: Math.round((context.score - 50) * 4e-3 * 100)
|
|
1197
1228
|
});
|
|
1198
1229
|
}
|
|
1199
1230
|
const consistency = toolOutputs.get("consistency");
|
|
1200
1231
|
if (consistency) {
|
|
1201
|
-
const consistencyImpact = (consistency.score - 50) * 2e-3;
|
|
1202
1232
|
factors.push({
|
|
1203
1233
|
name: "Code Consistency",
|
|
1204
|
-
impact: Math.round(
|
|
1234
|
+
impact: Math.round((consistency.score - 50) * 2e-3 * 100)
|
|
1205
1235
|
});
|
|
1206
1236
|
}
|
|
1207
1237
|
const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
|
|
1208
1238
|
if (aiSignalClarity) {
|
|
1209
|
-
const hrImpact = (50 - aiSignalClarity.score) * 2e-3;
|
|
1210
1239
|
factors.push({
|
|
1211
1240
|
name: "AI Signal Clarity",
|
|
1212
|
-
impact: Math.round(
|
|
1241
|
+
impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
|
|
1213
1242
|
});
|
|
1214
1243
|
}
|
|
1215
1244
|
const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
|
|
1216
1245
|
const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
|
|
1217
|
-
let confidence;
|
|
1246
|
+
let confidence = 0.35;
|
|
1218
1247
|
if (toolOutputs.size >= 4) confidence = 0.75;
|
|
1219
1248
|
else if (toolOutputs.size >= 3) confidence = 0.65;
|
|
1220
1249
|
else if (toolOutputs.size >= 2) confidence = 0.5;
|
|
1221
|
-
|
|
1222
|
-
return {
|
|
1223
|
-
rate: Math.round(rate * 100) / 100,
|
|
1224
|
-
confidence,
|
|
1225
|
-
factors
|
|
1226
|
-
};
|
|
1250
|
+
return { rate: Math.round(rate * 100) / 100, confidence, factors };
|
|
1227
1251
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
const
|
|
1233
|
-
const budgetRange = criticalBudget - idealBudget;
|
|
1234
|
-
const budgetFactor = Math.min(
|
|
1235
|
-
100,
|
|
1236
|
-
Math.max(0, (contextBudget - idealBudget) / budgetRange * 100)
|
|
1237
|
-
);
|
|
1238
|
-
const depthFactor = Math.min(
|
|
1239
|
-
100,
|
|
1240
|
-
Math.max(0, (importDepth - idealDepth) * 10)
|
|
1241
|
-
);
|
|
1242
|
-
const fragmentationFactor = Math.min(
|
|
1243
|
-
100,
|
|
1244
|
-
Math.max(0, (fragmentation - 0.3) * 250)
|
|
1245
|
-
);
|
|
1246
|
-
const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
|
|
1247
|
-
const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
|
|
1252
|
+
|
|
1253
|
+
// src/business/risk-metrics.ts
|
|
1254
|
+
function calculateKnowledgeConcentration(params) {
|
|
1255
|
+
const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
|
|
1256
|
+
const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
|
|
1248
1257
|
const score = Math.round(
|
|
1249
|
-
|
|
1258
|
+
Math.min(
|
|
1259
|
+
100,
|
|
1260
|
+
concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
|
|
1261
|
+
)
|
|
1250
1262
|
);
|
|
1251
1263
|
let rating;
|
|
1252
|
-
if (score <
|
|
1253
|
-
else if (score <
|
|
1254
|
-
else if (score <
|
|
1255
|
-
else
|
|
1256
|
-
|
|
1264
|
+
if (score < 30) rating = "low";
|
|
1265
|
+
else if (score < 50) rating = "moderate";
|
|
1266
|
+
else if (score < 75) rating = "high";
|
|
1267
|
+
else rating = "critical";
|
|
1268
|
+
const recommendations = [];
|
|
1269
|
+
if (singleAuthorFiles > 0)
|
|
1270
|
+
recommendations.push(
|
|
1271
|
+
`Distribute knowledge for ${singleAuthorFiles} single-author files.`
|
|
1272
|
+
);
|
|
1273
|
+
if (orphanFiles > 0)
|
|
1274
|
+
recommendations.push(
|
|
1275
|
+
`Link ${orphanFiles} orphan files to the rest of the codebase.`
|
|
1276
|
+
);
|
|
1257
1277
|
return {
|
|
1258
1278
|
score,
|
|
1259
1279
|
rating,
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1280
|
+
recommendations,
|
|
1281
|
+
analysis: {
|
|
1282
|
+
uniqueConceptFiles,
|
|
1283
|
+
totalFiles,
|
|
1284
|
+
concentrationRatio,
|
|
1285
|
+
singleAuthorFiles,
|
|
1286
|
+
orphanFiles
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function calculateDebtInterest(principal, monthlyGrowthRate) {
|
|
1291
|
+
const monthlyRate = monthlyGrowthRate;
|
|
1292
|
+
const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
|
|
1293
|
+
const monthlyCost = principal * monthlyRate;
|
|
1294
|
+
return {
|
|
1295
|
+
monthlyRate,
|
|
1296
|
+
annualRate,
|
|
1297
|
+
principal,
|
|
1298
|
+
monthlyCost,
|
|
1299
|
+
projections: {
|
|
1300
|
+
months6: principal * Math.pow(1 + monthlyRate, 6),
|
|
1301
|
+
months12: principal * Math.pow(1 + monthlyRate, 12),
|
|
1302
|
+
months24: principal * Math.pow(1 + monthlyRate, 24)
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/business/comprehension-metrics.ts
|
|
1308
|
+
function calculateTechnicalValueChain(params) {
|
|
1309
|
+
const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
|
|
1310
|
+
const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
|
|
1311
|
+
return {
|
|
1312
|
+
score: Math.round(Math.max(0, Math.min(100, score))),
|
|
1313
|
+
density: businessLogicDensity,
|
|
1314
|
+
complexity: dataAccessComplexity,
|
|
1315
|
+
surface: apiSurfaceArea
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
|
|
1319
|
+
const tierMap = {
|
|
1320
|
+
compact: "compact",
|
|
1321
|
+
standard: "standard",
|
|
1322
|
+
extended: "extended",
|
|
1323
|
+
frontier: "frontier",
|
|
1324
|
+
easy: "frontier",
|
|
1325
|
+
// Map legacy 'easy' to 'frontier'
|
|
1326
|
+
moderate: "standard",
|
|
1327
|
+
difficult: "compact"
|
|
1328
|
+
};
|
|
1329
|
+
const tier = tierMap[modelTier] || "frontier";
|
|
1330
|
+
const threshold = CONTEXT_TIER_THRESHOLDS[tier];
|
|
1331
|
+
const budgetRatio = contextBudget / threshold.idealTokens;
|
|
1332
|
+
const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
|
|
1333
|
+
const finalScore = Math.round(Math.max(0, Math.min(100, score)));
|
|
1334
|
+
let rating;
|
|
1335
|
+
if (finalScore < 20) rating = "trivial";
|
|
1336
|
+
else if (finalScore < 40) rating = "easy";
|
|
1337
|
+
else if (finalScore < 60) rating = "moderate";
|
|
1338
|
+
else if (finalScore < 85) rating = "difficult";
|
|
1339
|
+
else rating = "expert";
|
|
1340
|
+
return {
|
|
1341
|
+
score: finalScore,
|
|
1342
|
+
rating,
|
|
1343
|
+
factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/business-metrics.ts
|
|
1348
|
+
function calculateBusinessROI(params) {
|
|
1349
|
+
const model = getModelPreset(params.modelId || "claude-4.6");
|
|
1350
|
+
const devCount = params.developerCount || 5;
|
|
1351
|
+
const budget = calculateTokenBudget({
|
|
1352
|
+
totalContextTokens: params.tokenWaste * 2.5,
|
|
1353
|
+
wastedTokens: {
|
|
1354
|
+
duplication: params.tokenWaste * 0.7,
|
|
1355
|
+
fragmentation: params.tokenWaste * 0.3,
|
|
1356
|
+
chattiness: 0
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
const cost = estimateCostFromBudget(budget, model, {
|
|
1360
|
+
developerCount: devCount
|
|
1361
|
+
});
|
|
1362
|
+
const productivity = calculateProductivityImpact(params.issues);
|
|
1363
|
+
const monthlySavings = cost.total;
|
|
1364
|
+
const productivityGainHours = productivity.totalHours;
|
|
1365
|
+
const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
|
|
1366
|
+
return {
|
|
1367
|
+
monthlySavings: Math.round(monthlySavings),
|
|
1368
|
+
productivityGainHours: Math.round(productivityGainHours),
|
|
1369
|
+
annualValue: Math.round(annualValue)
|
|
1287
1370
|
};
|
|
1288
1371
|
}
|
|
1289
1372
|
function formatCost(cost) {
|
|
@@ -1309,220 +1392,6 @@ function formatHours(hours) {
|
|
|
1309
1392
|
function formatAcceptanceRate(rate) {
|
|
1310
1393
|
return `${Math.round(rate * 100)}%`;
|
|
1311
1394
|
}
|
|
1312
|
-
function calculateScoreTrend(history) {
|
|
1313
|
-
if (history.length < 2) {
|
|
1314
|
-
return {
|
|
1315
|
-
direction: "stable",
|
|
1316
|
-
change30Days: 0,
|
|
1317
|
-
change90Days: 0,
|
|
1318
|
-
velocity: 0,
|
|
1319
|
-
projectedScore: history[0]?.overallScore || 100
|
|
1320
|
-
};
|
|
1321
|
-
}
|
|
1322
|
-
const now = /* @__PURE__ */ new Date();
|
|
1323
|
-
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
1324
|
-
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
|
|
1325
|
-
const last30Days = history.filter(
|
|
1326
|
-
(e) => new Date(e.timestamp) >= thirtyDaysAgo
|
|
1327
|
-
);
|
|
1328
|
-
const last90Days = history.filter(
|
|
1329
|
-
(e) => new Date(e.timestamp) >= ninetyDaysAgo
|
|
1330
|
-
);
|
|
1331
|
-
const currentScore = history[history.length - 1].overallScore;
|
|
1332
|
-
const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
|
|
1333
|
-
const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
|
|
1334
|
-
const change30Days = currentScore - thirtyDaysAgoScore;
|
|
1335
|
-
const change90Days = currentScore - ninetyDaysAgoScore;
|
|
1336
|
-
const weeksOfData = Math.max(1, history.length / 7);
|
|
1337
|
-
const totalChange = currentScore - history[0].overallScore;
|
|
1338
|
-
const velocity = totalChange / weeksOfData;
|
|
1339
|
-
let direction;
|
|
1340
|
-
if (change30Days > 3) direction = "improving";
|
|
1341
|
-
else if (change30Days < -3) direction = "degrading";
|
|
1342
|
-
else direction = "stable";
|
|
1343
|
-
const projectedScore = Math.max(
|
|
1344
|
-
0,
|
|
1345
|
-
Math.min(100, currentScore + velocity * 4)
|
|
1346
|
-
);
|
|
1347
|
-
return {
|
|
1348
|
-
direction,
|
|
1349
|
-
change30Days,
|
|
1350
|
-
change90Days,
|
|
1351
|
-
velocity: Math.round(velocity * 10) / 10,
|
|
1352
|
-
projectedScore: Math.round(projectedScore)
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
function calculateRemediationVelocity(history, currentIssues) {
|
|
1356
|
-
if (history.length < 2) {
|
|
1357
|
-
return {
|
|
1358
|
-
issuesFixedThisWeek: 0,
|
|
1359
|
-
avgIssuesPerWeek: 0,
|
|
1360
|
-
trend: "stable",
|
|
1361
|
-
estimatedCompletionWeeks: currentIssues > 0 ? Infinity : 0
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
const now = /* @__PURE__ */ new Date();
|
|
1365
|
-
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
|
|
1366
|
-
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1e3);
|
|
1367
|
-
const thisWeek = history.filter((e) => new Date(e.timestamp) >= oneWeekAgo);
|
|
1368
|
-
const lastWeek = history.filter(
|
|
1369
|
-
(e) => new Date(e.timestamp) >= twoWeeksAgo && new Date(e.timestamp) < oneWeekAgo
|
|
1370
|
-
);
|
|
1371
|
-
const issuesFixedThisWeek = thisWeek.length > 1 ? thisWeek[0].totalIssues - thisWeek[thisWeek.length - 1].totalIssues : 0;
|
|
1372
|
-
const totalIssuesFixed = history[0].totalIssues - history[history.length - 1].totalIssues;
|
|
1373
|
-
const weeksOfData = Math.max(1, history.length / 7);
|
|
1374
|
-
const avgIssuesPerWeek = totalIssuesFixed / weeksOfData;
|
|
1375
|
-
let trend;
|
|
1376
|
-
if (lastWeek.length > 1) {
|
|
1377
|
-
const lastWeekFixed = lastWeek[0].totalIssues - lastWeek[lastWeek.length - 1].totalIssues;
|
|
1378
|
-
if (issuesFixedThisWeek > lastWeekFixed * 1.2) trend = "accelerating";
|
|
1379
|
-
else if (issuesFixedThisWeek < lastWeekFixed * 0.8) trend = "decelerating";
|
|
1380
|
-
else trend = "stable";
|
|
1381
|
-
} else {
|
|
1382
|
-
trend = "stable";
|
|
1383
|
-
}
|
|
1384
|
-
const estimatedCompletionWeeks = avgIssuesPerWeek > 0 ? Math.ceil(currentIssues / avgIssuesPerWeek) : Infinity;
|
|
1385
|
-
return {
|
|
1386
|
-
issuesFixedThisWeek: Math.max(0, issuesFixedThisWeek),
|
|
1387
|
-
avgIssuesPerWeek: Math.round(avgIssuesPerWeek * 10) / 10,
|
|
1388
|
-
trend,
|
|
1389
|
-
estimatedCompletionWeeks
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
function calculateKnowledgeConcentration(files, authorData) {
|
|
1393
|
-
if (files.length === 0) {
|
|
1394
|
-
return {
|
|
1395
|
-
score: 0,
|
|
1396
|
-
rating: "low",
|
|
1397
|
-
analysis: {
|
|
1398
|
-
uniqueConceptFiles: 0,
|
|
1399
|
-
totalFiles: 0,
|
|
1400
|
-
concentrationRatio: 0,
|
|
1401
|
-
singleAuthorFiles: 0,
|
|
1402
|
-
orphanFiles: 0
|
|
1403
|
-
},
|
|
1404
|
-
recommendations: ["No files to analyze"]
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1407
|
-
const orphanFiles = files.filter(
|
|
1408
|
-
(f) => f.exports < 2 && f.imports < 2
|
|
1409
|
-
).length;
|
|
1410
|
-
const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
|
|
1411
|
-
const uniqueConceptFiles = files.filter(
|
|
1412
|
-
(f) => f.exports > avgExports * 2
|
|
1413
|
-
).length;
|
|
1414
|
-
const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
|
|
1415
|
-
const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
|
|
1416
|
-
let singleAuthorFiles = 0;
|
|
1417
|
-
if (authorData) {
|
|
1418
|
-
for (const files2 of authorData.values()) {
|
|
1419
|
-
if (files2.length === 1) singleAuthorFiles++;
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
const orphanRisk = orphanFiles / files.length * 30;
|
|
1423
|
-
const uniqueRisk = concentrationRatio * 40;
|
|
1424
|
-
const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
|
|
1425
|
-
const score = Math.min(
|
|
1426
|
-
100,
|
|
1427
|
-
Math.round(orphanRisk + uniqueRisk + singleAuthorRisk)
|
|
1428
|
-
);
|
|
1429
|
-
let rating;
|
|
1430
|
-
if (score < 20) rating = "low";
|
|
1431
|
-
else if (score < 40) rating = "moderate";
|
|
1432
|
-
else if (score < 70) rating = "high";
|
|
1433
|
-
else rating = "critical";
|
|
1434
|
-
const recommendations = [];
|
|
1435
|
-
if (orphanFiles > files.length * 0.2) {
|
|
1436
|
-
recommendations.push(
|
|
1437
|
-
`Reduce ${orphanFiles} orphan files by connecting them to main modules`
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1440
|
-
if (uniqueConceptFiles > files.length * 0.1) {
|
|
1441
|
-
recommendations.push(
|
|
1442
|
-
"Distribute high-export files into more focused modules"
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
if (authorData && singleAuthorFiles > files.length * 0.3) {
|
|
1446
|
-
recommendations.push(
|
|
1447
|
-
"Increase knowledge sharing to reduce single-author dependencies"
|
|
1448
|
-
);
|
|
1449
|
-
}
|
|
1450
|
-
return {
|
|
1451
|
-
score,
|
|
1452
|
-
rating,
|
|
1453
|
-
analysis: {
|
|
1454
|
-
uniqueConceptFiles,
|
|
1455
|
-
totalFiles: files.length,
|
|
1456
|
-
concentrationRatio: Math.round(concentrationRatio * 100) / 100,
|
|
1457
|
-
singleAuthorFiles,
|
|
1458
|
-
orphanFiles
|
|
1459
|
-
},
|
|
1460
|
-
recommendations
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
function calculateTechnicalDebtInterest(params) {
|
|
1464
|
-
const { currentMonthlyCost, issues, monthsOpen } = params;
|
|
1465
|
-
const criticalCount = issues.filter((i) => i.severity === "critical").length;
|
|
1466
|
-
const majorCount = issues.filter((i) => i.severity === "major").length;
|
|
1467
|
-
const minorCount = issues.filter((i) => i.severity === "minor").length;
|
|
1468
|
-
const severityWeight = (criticalCount * 3 + majorCount * 2 + minorCount * 1) / Math.max(1, issues.length);
|
|
1469
|
-
const baseRate = 0.02 + severityWeight * 0.01;
|
|
1470
|
-
const timeMultiplier = Math.max(1, 1 + monthsOpen * 0.1);
|
|
1471
|
-
const monthlyRate = baseRate * timeMultiplier;
|
|
1472
|
-
const projectDebt = (principal2, months) => {
|
|
1473
|
-
let debt = principal2;
|
|
1474
|
-
for (let i = 0; i < months; i++) {
|
|
1475
|
-
debt = debt * (1 + monthlyRate);
|
|
1476
|
-
}
|
|
1477
|
-
return Math.round(debt);
|
|
1478
|
-
};
|
|
1479
|
-
const principal = currentMonthlyCost * 12;
|
|
1480
|
-
const projections = {
|
|
1481
|
-
months6: projectDebt(principal, 6),
|
|
1482
|
-
months12: projectDebt(principal, 12),
|
|
1483
|
-
months24: projectDebt(principal, 24)
|
|
1484
|
-
};
|
|
1485
|
-
return {
|
|
1486
|
-
monthlyRate: Math.round(monthlyRate * 1e4) / 100,
|
|
1487
|
-
annualRate: Math.round((Math.pow(1 + monthlyRate, 12) - 1) * 1e4) / 100,
|
|
1488
|
-
principal,
|
|
1489
|
-
projections,
|
|
1490
|
-
monthlyCost: Math.round(currentMonthlyCost * (1 + monthlyRate) * 100) / 100
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
function getDebtBreakdown(patternCost, contextCost, consistencyCost) {
|
|
1494
|
-
const breakdowns = [
|
|
1495
|
-
{
|
|
1496
|
-
category: "Semantic Duplication",
|
|
1497
|
-
currentCost: patternCost,
|
|
1498
|
-
monthlyGrowthRate: 5,
|
|
1499
|
-
// Grows as devs copy-paste
|
|
1500
|
-
priority: patternCost > 1e3 ? "high" : "medium",
|
|
1501
|
-
fixCost: patternCost * 3
|
|
1502
|
-
// Fixing costs 3x current waste
|
|
1503
|
-
},
|
|
1504
|
-
{
|
|
1505
|
-
category: "Context Fragmentation",
|
|
1506
|
-
currentCost: contextCost,
|
|
1507
|
-
monthlyGrowthRate: 3,
|
|
1508
|
-
// Grows with new features
|
|
1509
|
-
priority: contextCost > 500 ? "high" : "medium",
|
|
1510
|
-
fixCost: contextCost * 2.5
|
|
1511
|
-
},
|
|
1512
|
-
{
|
|
1513
|
-
category: "Consistency Issues",
|
|
1514
|
-
currentCost: consistencyCost,
|
|
1515
|
-
monthlyGrowthRate: 2,
|
|
1516
|
-
// Grows with new devs
|
|
1517
|
-
priority: consistencyCost > 200 ? "medium" : "low",
|
|
1518
|
-
fixCost: consistencyCost * 1.5
|
|
1519
|
-
}
|
|
1520
|
-
];
|
|
1521
|
-
return breakdowns.sort((a, b) => {
|
|
1522
|
-
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
1523
|
-
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
1395
|
function generateValueChain(params) {
|
|
1527
1396
|
const { issueType, count, severity } = params;
|
|
1528
1397
|
const impacts = {
|
|
@@ -1562,9 +1431,7 @@ function generateValueChain(params) {
|
|
|
1562
1431
|
},
|
|
1563
1432
|
businessOutcome: {
|
|
1564
1433
|
directCost: count * 12,
|
|
1565
|
-
// Placeholder linear cost
|
|
1566
1434
|
opportunityCost: productivityLoss * 15e3,
|
|
1567
|
-
// Monthly avg dev cost $15k
|
|
1568
1435
|
riskLevel: impact.risk
|
|
1569
1436
|
}
|
|
1570
1437
|
};
|
|
@@ -2079,7 +1946,55 @@ function getSupportedLanguages() {
|
|
|
2079
1946
|
return ParserFactory.getInstance().getSupportedLanguages();
|
|
2080
1947
|
}
|
|
2081
1948
|
|
|
2082
|
-
// src/
|
|
1949
|
+
// src/metrics/remediation-utils.ts
|
|
1950
|
+
function collectFutureProofRecommendations(params) {
|
|
1951
|
+
const recommendations = [];
|
|
1952
|
+
for (const rec of params.aiSignalClarity.recommendations) {
|
|
1953
|
+
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
1954
|
+
}
|
|
1955
|
+
for (const rec of params.agentGrounding.recommendations) {
|
|
1956
|
+
recommendations.push({
|
|
1957
|
+
action: rec,
|
|
1958
|
+
estimatedImpact: 6,
|
|
1959
|
+
priority: "medium"
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
for (const rec of params.testability.recommendations) {
|
|
1963
|
+
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
1964
|
+
recommendations.push({ action: rec, estimatedImpact: 10, priority });
|
|
1965
|
+
}
|
|
1966
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
1967
|
+
recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
|
|
1968
|
+
}
|
|
1969
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
1970
|
+
recommendations.push({
|
|
1971
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
1972
|
+
estimatedImpact: 8,
|
|
1973
|
+
priority: "high"
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
if (params.docDrift) {
|
|
1977
|
+
for (const rec of params.docDrift.recommendations) {
|
|
1978
|
+
recommendations.push({
|
|
1979
|
+
action: rec,
|
|
1980
|
+
estimatedImpact: 8,
|
|
1981
|
+
priority: "high"
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (params.dependencyHealth) {
|
|
1986
|
+
for (const rec of params.dependencyHealth.recommendations) {
|
|
1987
|
+
recommendations.push({
|
|
1988
|
+
action: rec,
|
|
1989
|
+
estimatedImpact: 7,
|
|
1990
|
+
priority: "medium"
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
return recommendations;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// src/metrics/cognitive-load.ts
|
|
2083
1998
|
function calculateCognitiveLoad(params) {
|
|
2084
1999
|
const {
|
|
2085
2000
|
linesOfCode,
|
|
@@ -2138,13 +2053,20 @@ function calculateCognitiveLoad(params) {
|
|
|
2138
2053
|
}
|
|
2139
2054
|
};
|
|
2140
2055
|
}
|
|
2056
|
+
|
|
2057
|
+
// src/metrics/semantic-distance.ts
|
|
2141
2058
|
function calculateSemanticDistance(params) {
|
|
2142
|
-
const {
|
|
2059
|
+
const {
|
|
2060
|
+
file1,
|
|
2061
|
+
file2,
|
|
2062
|
+
file1Domain,
|
|
2063
|
+
file2Domain,
|
|
2064
|
+
sharedDependencies,
|
|
2065
|
+
file1Imports,
|
|
2066
|
+
file2Imports
|
|
2067
|
+
} = params;
|
|
2143
2068
|
const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
|
|
2144
|
-
const importOverlap = sharedDependencies.length / Math.max(
|
|
2145
|
-
1,
|
|
2146
|
-
Math.min(params.file1Imports.length, params.file2Imports.length)
|
|
2147
|
-
);
|
|
2069
|
+
const importOverlap = sharedDependencies.length / Math.max(1, Math.min(file1Imports.length, file2Imports.length));
|
|
2148
2070
|
const importDistance = 1 - importOverlap;
|
|
2149
2071
|
const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
|
|
2150
2072
|
let relationship;
|
|
@@ -2163,6 +2085,8 @@ function calculateSemanticDistance(params) {
|
|
|
2163
2085
|
reason: relationship === "same-domain" ? `Both in "${file1Domain}" domain` : relationship === "cross-domain" ? `Share ${sharedDependencies.length} dependency(ies)` : "No strong semantic relationship detected"
|
|
2164
2086
|
};
|
|
2165
2087
|
}
|
|
2088
|
+
|
|
2089
|
+
// src/metrics/structural-metrics.ts
|
|
2166
2090
|
function calculatePatternEntropy(files) {
|
|
2167
2091
|
if (files.length === 0) {
|
|
2168
2092
|
return {
|
|
@@ -2212,23 +2136,18 @@ function calculatePatternEntropy(files) {
|
|
|
2212
2136
|
else if (normalizedEntropy < 0.8) rating = "fragmented";
|
|
2213
2137
|
else rating = "chaotic";
|
|
2214
2138
|
const recommendations = [];
|
|
2215
|
-
if (normalizedEntropy > 0.5)
|
|
2139
|
+
if (normalizedEntropy > 0.5)
|
|
2216
2140
|
recommendations.push(
|
|
2217
2141
|
`Consolidate ${files.length} files into fewer directories by domain`
|
|
2218
2142
|
);
|
|
2219
|
-
|
|
2220
|
-
if (dirGroups.size > 5) {
|
|
2143
|
+
if (dirGroups.size > 5)
|
|
2221
2144
|
recommendations.push(
|
|
2222
2145
|
"Consider barrel exports to reduce directory navigation"
|
|
2223
2146
|
);
|
|
2224
|
-
|
|
2225
|
-
if (gini > 0.5) {
|
|
2147
|
+
if (gini > 0.5)
|
|
2226
2148
|
recommendations.push("Redistribute files more evenly across directories");
|
|
2227
|
-
}
|
|
2228
|
-
const firstFile = files.length > 0 ? files[0] : null;
|
|
2229
|
-
const domainValue = firstFile ? firstFile.domain : "mixed";
|
|
2230
2149
|
return {
|
|
2231
|
-
domain:
|
|
2150
|
+
domain: files[0]?.domain || "mixed",
|
|
2232
2151
|
entropy: Math.round(normalizedEntropy * 100) / 100,
|
|
2233
2152
|
rating,
|
|
2234
2153
|
distribution: {
|
|
@@ -2259,9 +2178,8 @@ function calculateConceptCohesion(params) {
|
|
|
2259
2178
|
}
|
|
2260
2179
|
const uniqueDomains = new Set(allDomains);
|
|
2261
2180
|
const domainCounts = /* @__PURE__ */ new Map();
|
|
2262
|
-
for (const d of allDomains)
|
|
2181
|
+
for (const d of allDomains)
|
|
2263
2182
|
domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
|
|
2264
|
-
}
|
|
2265
2183
|
const maxCount = Math.max(...Array.from(domainCounts.values()), 1);
|
|
2266
2184
|
const domainConcentration = maxCount / allDomains.length;
|
|
2267
2185
|
const exportPurposeClarity = 1 - (uniqueDomains.size - 1) / Math.max(1, exports2.length);
|
|
@@ -2281,59 +2199,8 @@ function calculateConceptCohesion(params) {
|
|
|
2281
2199
|
}
|
|
2282
2200
|
};
|
|
2283
2201
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2287
|
-
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2288
|
-
const overall = Math.round(
|
|
2289
|
-
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
2290
|
-
);
|
|
2291
|
-
const factors = [
|
|
2292
|
-
{
|
|
2293
|
-
name: "Cognitive Load",
|
|
2294
|
-
impact: Math.round(loadScore - 50),
|
|
2295
|
-
description: params.cognitiveLoad.rating
|
|
2296
|
-
},
|
|
2297
|
-
{
|
|
2298
|
-
name: "Pattern Entropy",
|
|
2299
|
-
impact: Math.round(entropyScore - 50),
|
|
2300
|
-
description: params.patternEntropy.rating
|
|
2301
|
-
},
|
|
2302
|
-
{
|
|
2303
|
-
name: "Concept Cohesion",
|
|
2304
|
-
impact: Math.round(cohesionScore - 50),
|
|
2305
|
-
description: params.conceptCohesion.rating
|
|
2306
|
-
}
|
|
2307
|
-
];
|
|
2308
|
-
const recommendations = [];
|
|
2309
|
-
for (const rec of params.patternEntropy.recommendations) {
|
|
2310
|
-
recommendations.push({
|
|
2311
|
-
action: rec,
|
|
2312
|
-
estimatedImpact: 5,
|
|
2313
|
-
priority: "medium"
|
|
2314
|
-
});
|
|
2315
|
-
}
|
|
2316
|
-
if (params.conceptCohesion.rating === "poor") {
|
|
2317
|
-
recommendations.push({
|
|
2318
|
-
action: "Improve concept cohesion by grouping related exports",
|
|
2319
|
-
estimatedImpact: 8,
|
|
2320
|
-
priority: "high"
|
|
2321
|
-
});
|
|
2322
|
-
}
|
|
2323
|
-
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2324
|
-
return {
|
|
2325
|
-
toolName: "future-proof",
|
|
2326
|
-
score: overall,
|
|
2327
|
-
rawMetrics: {
|
|
2328
|
-
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2329
|
-
entropyScore: params.patternEntropy.entropy,
|
|
2330
|
-
cohesionScore: params.conceptCohesion.score,
|
|
2331
|
-
semanticDistanceAvg
|
|
2332
|
-
},
|
|
2333
|
-
factors,
|
|
2334
|
-
recommendations
|
|
2335
|
-
};
|
|
2336
|
-
}
|
|
2202
|
+
|
|
2203
|
+
// src/metrics/ai-signal-clarity.ts
|
|
2337
2204
|
function calculateAiSignalClarity(params) {
|
|
2338
2205
|
const {
|
|
2339
2206
|
overloadedSymbols,
|
|
@@ -2355,75 +2222,53 @@ function calculateAiSignalClarity(params) {
|
|
|
2355
2222
|
recommendations: []
|
|
2356
2223
|
};
|
|
2357
2224
|
}
|
|
2358
|
-
const overloadRatio = Math.
|
|
2359
|
-
1,
|
|
2360
|
-
overloadedSymbols / Math.max(1, totalSymbols)
|
|
2361
|
-
);
|
|
2225
|
+
const overloadRatio = overloadedSymbols / Math.max(1, totalSymbols);
|
|
2362
2226
|
const overloadSignal = {
|
|
2363
2227
|
name: "Symbol Overloading",
|
|
2364
2228
|
count: overloadedSymbols,
|
|
2365
|
-
riskContribution: Math.round(overloadRatio * 100 * 0.25),
|
|
2366
|
-
// 25% weight
|
|
2229
|
+
riskContribution: Math.round(Math.min(1, overloadRatio) * 100 * 0.25),
|
|
2367
2230
|
description: `${overloadedSymbols} overloaded symbols \u2014 AI picks wrong signature`
|
|
2368
2231
|
};
|
|
2369
|
-
const magicRatio =
|
|
2232
|
+
const magicRatio = magicLiterals / Math.max(1, totalSymbols * 2);
|
|
2370
2233
|
const magicSignal = {
|
|
2371
2234
|
name: "Magic Literals",
|
|
2372
2235
|
count: magicLiterals,
|
|
2373
|
-
riskContribution: Math.round(magicRatio * 100 * 0.2),
|
|
2374
|
-
// 20% weight
|
|
2236
|
+
riskContribution: Math.round(Math.min(1, magicRatio) * 100 * 0.2),
|
|
2375
2237
|
description: `${magicLiterals} unnamed constants \u2014 AI invents wrong values`
|
|
2376
2238
|
};
|
|
2377
|
-
const trapRatio =
|
|
2239
|
+
const trapRatio = booleanTraps / Math.max(1, totalSymbols);
|
|
2378
2240
|
const trapSignal = {
|
|
2379
2241
|
name: "Boolean Traps",
|
|
2380
2242
|
count: booleanTraps,
|
|
2381
|
-
riskContribution: Math.round(trapRatio * 100 * 0.2),
|
|
2382
|
-
// 20% weight
|
|
2243
|
+
riskContribution: Math.round(Math.min(1, trapRatio) * 100 * 0.2),
|
|
2383
2244
|
description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
|
|
2384
2245
|
};
|
|
2385
|
-
const sideEffectRatio = Math.
|
|
2386
|
-
1,
|
|
2387
|
-
implicitSideEffects / Math.max(1, totalExports)
|
|
2388
|
-
);
|
|
2246
|
+
const sideEffectRatio = implicitSideEffects / Math.max(1, totalExports);
|
|
2389
2247
|
const sideEffectSignal = {
|
|
2390
2248
|
name: "Implicit Side Effects",
|
|
2391
2249
|
count: implicitSideEffects,
|
|
2392
|
-
riskContribution: Math.round(sideEffectRatio * 100 * 0.15),
|
|
2393
|
-
// 15% weight
|
|
2250
|
+
riskContribution: Math.round(Math.min(1, sideEffectRatio) * 100 * 0.15),
|
|
2394
2251
|
description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
|
|
2395
2252
|
};
|
|
2396
|
-
const callbackRatio = Math.
|
|
2397
|
-
1,
|
|
2398
|
-
deepCallbacks / Math.max(1, totalSymbols * 0.1)
|
|
2399
|
-
);
|
|
2253
|
+
const callbackRatio = deepCallbacks / Math.max(1, totalSymbols * 0.1);
|
|
2400
2254
|
const callbackSignal = {
|
|
2401
2255
|
name: "Callback Nesting",
|
|
2402
2256
|
count: deepCallbacks,
|
|
2403
|
-
riskContribution: Math.round(callbackRatio * 100 * 0.1),
|
|
2404
|
-
// 10% weight
|
|
2257
|
+
riskContribution: Math.round(Math.min(1, callbackRatio) * 100 * 0.1),
|
|
2405
2258
|
description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
|
|
2406
2259
|
};
|
|
2407
|
-
const ambiguousRatio = Math.
|
|
2408
|
-
1,
|
|
2409
|
-
ambiguousNames / Math.max(1, totalSymbols)
|
|
2410
|
-
);
|
|
2260
|
+
const ambiguousRatio = ambiguousNames / Math.max(1, totalSymbols);
|
|
2411
2261
|
const ambiguousSignal = {
|
|
2412
2262
|
name: "Ambiguous Names",
|
|
2413
2263
|
count: ambiguousNames,
|
|
2414
|
-
riskContribution: Math.round(ambiguousRatio * 100 * 0.1),
|
|
2415
|
-
// 10% weight
|
|
2264
|
+
riskContribution: Math.round(Math.min(1, ambiguousRatio) * 100 * 0.1),
|
|
2416
2265
|
description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
|
|
2417
2266
|
};
|
|
2418
|
-
const undocRatio = Math.
|
|
2419
|
-
1,
|
|
2420
|
-
undocumentedExports / Math.max(1, totalExports)
|
|
2421
|
-
);
|
|
2267
|
+
const undocRatio = undocumentedExports / Math.max(1, totalExports);
|
|
2422
2268
|
const undocSignal = {
|
|
2423
2269
|
name: "Undocumented Exports",
|
|
2424
2270
|
count: undocumentedExports,
|
|
2425
|
-
riskContribution: Math.round(undocRatio * 100 * 0.1),
|
|
2426
|
-
// 10% weight
|
|
2271
|
+
riskContribution: Math.round(Math.min(1, undocRatio) * 100 * 0.1),
|
|
2427
2272
|
description: `${undocumentedExports} public functions without docs \u2014 AI fabricates behavior`
|
|
2428
2273
|
};
|
|
2429
2274
|
const signals = [
|
|
@@ -2448,33 +2293,28 @@ function calculateAiSignalClarity(params) {
|
|
|
2448
2293
|
const topSignal = signals.reduce(
|
|
2449
2294
|
(a, b) => a.riskContribution > b.riskContribution ? a : b
|
|
2450
2295
|
);
|
|
2451
|
-
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant
|
|
2296
|
+
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant issues detected";
|
|
2452
2297
|
const recommendations = [];
|
|
2453
|
-
if (overloadSignal.riskContribution > 5)
|
|
2298
|
+
if (overloadSignal.riskContribution > 5)
|
|
2454
2299
|
recommendations.push(
|
|
2455
2300
|
`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
|
|
2456
2301
|
);
|
|
2457
|
-
|
|
2458
|
-
if (magicSignal.riskContribution > 5) {
|
|
2302
|
+
if (magicSignal.riskContribution > 5)
|
|
2459
2303
|
recommendations.push(
|
|
2460
2304
|
`Extract ${magicLiterals} magic literals into named constants`
|
|
2461
2305
|
);
|
|
2462
|
-
|
|
2463
|
-
if (trapSignal.riskContribution > 5) {
|
|
2306
|
+
if (trapSignal.riskContribution > 5)
|
|
2464
2307
|
recommendations.push(
|
|
2465
2308
|
`Replace ${booleanTraps} boolean traps with named options objects`
|
|
2466
2309
|
);
|
|
2467
|
-
|
|
2468
|
-
if (undocSignal.riskContribution > 5) {
|
|
2310
|
+
if (undocSignal.riskContribution > 5)
|
|
2469
2311
|
recommendations.push(
|
|
2470
2312
|
`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
|
|
2471
2313
|
);
|
|
2472
|
-
|
|
2473
|
-
if (sideEffectSignal.riskContribution > 5) {
|
|
2314
|
+
if (sideEffectSignal.riskContribution > 5)
|
|
2474
2315
|
recommendations.push(
|
|
2475
2316
|
"Mark functions with side effects explicitly in their names or docs"
|
|
2476
2317
|
);
|
|
2477
|
-
}
|
|
2478
2318
|
return {
|
|
2479
2319
|
score: Math.round(score),
|
|
2480
2320
|
rating,
|
|
@@ -2483,6 +2323,8 @@ function calculateAiSignalClarity(params) {
|
|
|
2483
2323
|
recommendations
|
|
2484
2324
|
};
|
|
2485
2325
|
}
|
|
2326
|
+
|
|
2327
|
+
// src/metrics/agent-grounding.ts
|
|
2486
2328
|
function calculateAgentGrounding(params) {
|
|
2487
2329
|
const {
|
|
2488
2330
|
deepDirectories,
|
|
@@ -2497,25 +2339,33 @@ function calculateAgentGrounding(params) {
|
|
|
2497
2339
|
inconsistentDomainTerms,
|
|
2498
2340
|
domainVocabularySize
|
|
2499
2341
|
} = params;
|
|
2500
|
-
const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
|
|
2501
2342
|
const structureClarityScore = Math.max(
|
|
2502
2343
|
0,
|
|
2503
|
-
Math.round(
|
|
2344
|
+
Math.round(
|
|
2345
|
+
100 - (totalDirectories > 0 ? deepDirectories / totalDirectories * 80 : 0)
|
|
2346
|
+
)
|
|
2347
|
+
);
|
|
2348
|
+
const selfDocumentationScore = Math.max(
|
|
2349
|
+
0,
|
|
2350
|
+
Math.round(100 - (totalFiles > 0 ? vagueFileNames / totalFiles * 90 : 0))
|
|
2504
2351
|
);
|
|
2505
|
-
const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
|
|
2506
|
-
const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
|
|
2507
2352
|
let entryPointScore = 60;
|
|
2508
2353
|
if (hasRootReadme) entryPointScore += 25;
|
|
2509
2354
|
if (readmeIsFresh) entryPointScore += 10;
|
|
2510
2355
|
const barrelRatio = totalFiles > 0 ? barrelExports / (totalFiles * 0.1) : 0;
|
|
2511
2356
|
entryPointScore += Math.round(Math.min(5, barrelRatio * 5));
|
|
2512
2357
|
entryPointScore = Math.min(100, entryPointScore);
|
|
2513
|
-
const
|
|
2514
|
-
|
|
2515
|
-
|
|
2358
|
+
const apiClarityScore = Math.max(
|
|
2359
|
+
0,
|
|
2360
|
+
Math.round(
|
|
2361
|
+
100 - (totalExports > 0 ? untypedExports / totalExports * 70 : 0)
|
|
2362
|
+
)
|
|
2363
|
+
);
|
|
2516
2364
|
const domainConsistencyScore = Math.max(
|
|
2517
2365
|
0,
|
|
2518
|
-
Math.round(
|
|
2366
|
+
Math.round(
|
|
2367
|
+
100 - (domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize * 80 : 0)
|
|
2368
|
+
)
|
|
2519
2369
|
);
|
|
2520
2370
|
const score = Math.round(
|
|
2521
2371
|
structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
|
|
@@ -2527,35 +2377,30 @@ function calculateAgentGrounding(params) {
|
|
|
2527
2377
|
else if (score >= 30) rating = "poor";
|
|
2528
2378
|
else rating = "disorienting";
|
|
2529
2379
|
const recommendations = [];
|
|
2530
|
-
if (structureClarityScore < 70)
|
|
2380
|
+
if (structureClarityScore < 70)
|
|
2531
2381
|
recommendations.push(
|
|
2532
2382
|
`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
|
|
2533
2383
|
);
|
|
2534
|
-
|
|
2535
|
-
if (selfDocumentationScore < 70) {
|
|
2384
|
+
if (selfDocumentationScore < 70)
|
|
2536
2385
|
recommendations.push(
|
|
2537
2386
|
`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
|
|
2538
2387
|
);
|
|
2539
|
-
|
|
2540
|
-
if (!hasRootReadme) {
|
|
2388
|
+
if (!hasRootReadme)
|
|
2541
2389
|
recommendations.push(
|
|
2542
2390
|
"Add a root README.md so agents understand the project context immediately"
|
|
2543
2391
|
);
|
|
2544
|
-
|
|
2392
|
+
else if (!readmeIsFresh)
|
|
2545
2393
|
recommendations.push(
|
|
2546
2394
|
"Update README.md \u2014 stale entry-point documentation disorients agents"
|
|
2547
2395
|
);
|
|
2548
|
-
|
|
2549
|
-
if (apiClarityScore < 70) {
|
|
2396
|
+
if (apiClarityScore < 70)
|
|
2550
2397
|
recommendations.push(
|
|
2551
2398
|
`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
|
|
2552
2399
|
);
|
|
2553
|
-
|
|
2554
|
-
if (domainConsistencyScore < 70) {
|
|
2400
|
+
if (domainConsistencyScore < 70)
|
|
2555
2401
|
recommendations.push(
|
|
2556
2402
|
`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
|
|
2557
2403
|
);
|
|
2558
|
-
}
|
|
2559
2404
|
return {
|
|
2560
2405
|
score,
|
|
2561
2406
|
rating,
|
|
@@ -2569,6 +2414,8 @@ function calculateAgentGrounding(params) {
|
|
|
2569
2414
|
recommendations
|
|
2570
2415
|
};
|
|
2571
2416
|
}
|
|
2417
|
+
|
|
2418
|
+
// src/metrics/testability-index.ts
|
|
2572
2419
|
function calculateTestabilityIndex(params) {
|
|
2573
2420
|
const {
|
|
2574
2421
|
testFiles,
|
|
@@ -2584,16 +2431,27 @@ function calculateTestabilityIndex(params) {
|
|
|
2584
2431
|
} = params;
|
|
2585
2432
|
const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
|
|
2586
2433
|
const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
|
|
2587
|
-
const
|
|
2588
|
-
|
|
2589
|
-
|
|
2434
|
+
const purityScore = Math.round(
|
|
2435
|
+
(totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
|
|
2436
|
+
);
|
|
2590
2437
|
const dependencyInjectionScore = Math.round(
|
|
2591
|
-
Math.min(
|
|
2438
|
+
Math.min(
|
|
2439
|
+
100,
|
|
2440
|
+
(totalClasses > 0 ? injectionPatterns / totalClasses : 0.5) * 100
|
|
2441
|
+
)
|
|
2442
|
+
);
|
|
2443
|
+
const interfaceFocusScore = Math.max(
|
|
2444
|
+
0,
|
|
2445
|
+
Math.round(
|
|
2446
|
+
100 - (totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces * 80 : 0)
|
|
2447
|
+
)
|
|
2448
|
+
);
|
|
2449
|
+
const observabilityScore = Math.max(
|
|
2450
|
+
0,
|
|
2451
|
+
Math.round(
|
|
2452
|
+
100 - (totalFunctions > 0 ? externalStateMutations / totalFunctions * 100 : 0)
|
|
2453
|
+
)
|
|
2592
2454
|
);
|
|
2593
|
-
const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
|
|
2594
|
-
const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
|
|
2595
|
-
const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
|
|
2596
|
-
const observabilityScore = Math.max(0, Math.round(100 - mutationRatio * 100));
|
|
2597
2455
|
const frameworkWeight = hasTestFramework ? 1 : 0.8;
|
|
2598
2456
|
const rawScore = (testCoverageRatio * 0.3 + purityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
|
|
2599
2457
|
const score = Math.max(0, Math.min(100, Math.round(rawScore)));
|
|
@@ -2610,32 +2468,28 @@ function calculateTestabilityIndex(params) {
|
|
|
2610
2468
|
else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
|
|
2611
2469
|
else aiChangeSafetyRating = "blind-risk";
|
|
2612
2470
|
const recommendations = [];
|
|
2613
|
-
if (!hasTestFramework)
|
|
2471
|
+
if (!hasTestFramework)
|
|
2614
2472
|
recommendations.push(
|
|
2615
2473
|
"Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
|
|
2616
2474
|
);
|
|
2617
|
-
}
|
|
2618
2475
|
if (rawCoverageRatio < 0.3) {
|
|
2619
2476
|
const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
|
|
2620
2477
|
recommendations.push(
|
|
2621
2478
|
`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
|
|
2622
2479
|
);
|
|
2623
2480
|
}
|
|
2624
|
-
if (purityScore < 50)
|
|
2481
|
+
if (purityScore < 50)
|
|
2625
2482
|
recommendations.push(
|
|
2626
2483
|
"Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
|
|
2627
2484
|
);
|
|
2628
|
-
|
|
2629
|
-
if (dependencyInjectionScore < 50 && totalClasses > 0) {
|
|
2485
|
+
if (dependencyInjectionScore < 50 && totalClasses > 0)
|
|
2630
2486
|
recommendations.push(
|
|
2631
2487
|
"Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
|
|
2632
2488
|
);
|
|
2633
|
-
|
|
2634
|
-
if (externalStateMutations > totalFunctions * 0.3) {
|
|
2489
|
+
if (externalStateMutations > totalFunctions * 0.3)
|
|
2635
2490
|
recommendations.push(
|
|
2636
2491
|
"Reduce direct state mutations \u2014 return values instead to improve observability"
|
|
2637
2492
|
);
|
|
2638
|
-
}
|
|
2639
2493
|
return {
|
|
2640
2494
|
score,
|
|
2641
2495
|
rating,
|
|
@@ -2650,6 +2504,8 @@ function calculateTestabilityIndex(params) {
|
|
|
2650
2504
|
recommendations
|
|
2651
2505
|
};
|
|
2652
2506
|
}
|
|
2507
|
+
|
|
2508
|
+
// src/metrics/doc-drift.ts
|
|
2653
2509
|
function calculateDocDrift(params) {
|
|
2654
2510
|
const {
|
|
2655
2511
|
uncommentedExports,
|
|
@@ -2672,21 +2528,18 @@ function calculateDocDrift(params) {
|
|
|
2672
2528
|
else if (finalScore < 85) rating = "high";
|
|
2673
2529
|
else rating = "severe";
|
|
2674
2530
|
const recommendations = [];
|
|
2675
|
-
if (outdatedComments > 0)
|
|
2531
|
+
if (outdatedComments > 0)
|
|
2676
2532
|
recommendations.push(
|
|
2677
2533
|
`Update or remove ${outdatedComments} outdated comments that contradict the code.`
|
|
2678
2534
|
);
|
|
2679
|
-
|
|
2680
|
-
if (uncommentedRatio > 0.3) {
|
|
2535
|
+
if (uncommentedRatio > 0.3)
|
|
2681
2536
|
recommendations.push(
|
|
2682
2537
|
`Add JSDoc to ${uncommentedExports} uncommented exports.`
|
|
2683
2538
|
);
|
|
2684
|
-
|
|
2685
|
-
if (undocumentedComplexity > 0) {
|
|
2539
|
+
if (undocumentedComplexity > 0)
|
|
2686
2540
|
recommendations.push(
|
|
2687
2541
|
`Explain the business logic for ${undocumentedComplexity} highly complex functions.`
|
|
2688
2542
|
);
|
|
2689
|
-
}
|
|
2690
2543
|
return {
|
|
2691
2544
|
score: finalScore,
|
|
2692
2545
|
rating,
|
|
@@ -2698,6 +2551,8 @@ function calculateDocDrift(params) {
|
|
|
2698
2551
|
recommendations
|
|
2699
2552
|
};
|
|
2700
2553
|
}
|
|
2554
|
+
|
|
2555
|
+
// src/metrics/dependency-health.ts
|
|
2701
2556
|
function calculateDependencyHealth(params) {
|
|
2702
2557
|
const {
|
|
2703
2558
|
totalPackages,
|
|
@@ -2710,8 +2565,12 @@ function calculateDependencyHealth(params) {
|
|
|
2710
2565
|
const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
|
|
2711
2566
|
const deprecatedScore = Math.max(0, 100 - deprecatedRatio * 500);
|
|
2712
2567
|
const skewScore = Math.max(0, 100 - trainingCutoffSkew * 100);
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2568
|
+
const score = Math.round(
|
|
2569
|
+
Math.min(
|
|
2570
|
+
100,
|
|
2571
|
+
Math.max(0, outdatedScore * 0.3 + deprecatedScore * 0.4 + skewScore * 0.3)
|
|
2572
|
+
)
|
|
2573
|
+
);
|
|
2715
2574
|
let rating;
|
|
2716
2575
|
if (score >= 85) rating = "excellent";
|
|
2717
2576
|
else if (score >= 70) rating = "good";
|
|
@@ -2726,33 +2585,22 @@ function calculateDependencyHealth(params) {
|
|
|
2726
2585
|
else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
|
|
2727
2586
|
else aiKnowledgeConfidence = "blind";
|
|
2728
2587
|
const recommendations = [];
|
|
2729
|
-
if (deprecatedPackages > 0)
|
|
2730
|
-
recommendations.push(
|
|
2731
|
-
|
|
2732
|
-
);
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
recommendations.push(
|
|
2736
|
-
`Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`
|
|
2737
|
-
);
|
|
2738
|
-
}
|
|
2739
|
-
if (trainingCutoffSkew > 0.5) {
|
|
2740
|
-
recommendations.push(
|
|
2741
|
-
"High training cutoff skew detected. AI may hallucinate APIs that were introduced recently."
|
|
2742
|
-
);
|
|
2743
|
-
}
|
|
2588
|
+
if (deprecatedPackages > 0)
|
|
2589
|
+
recommendations.push(`Replace ${deprecatedPackages} deprecated packages.`);
|
|
2590
|
+
if (outdatedRatio > 0.2)
|
|
2591
|
+
recommendations.push(`Update ${outdatedPackages} outdated packages.`);
|
|
2592
|
+
if (trainingCutoffSkew > 0.5)
|
|
2593
|
+
recommendations.push("High training cutoff skew detected.");
|
|
2744
2594
|
return {
|
|
2745
2595
|
score,
|
|
2746
2596
|
rating,
|
|
2747
|
-
dimensions: {
|
|
2748
|
-
outdatedPackages,
|
|
2749
|
-
deprecatedPackages,
|
|
2750
|
-
trainingCutoffSkew
|
|
2751
|
-
},
|
|
2597
|
+
dimensions: { outdatedPackages, deprecatedPackages, trainingCutoffSkew },
|
|
2752
2598
|
aiKnowledgeConfidence,
|
|
2753
2599
|
recommendations
|
|
2754
2600
|
};
|
|
2755
2601
|
}
|
|
2602
|
+
|
|
2603
|
+
// src/metrics/change-amplification.ts
|
|
2756
2604
|
function calculateChangeAmplification(params) {
|
|
2757
2605
|
const { files } = params;
|
|
2758
2606
|
if (files.length === 0) {
|
|
@@ -2765,15 +2613,16 @@ function calculateChangeAmplification(params) {
|
|
|
2765
2613
|
recommendations: []
|
|
2766
2614
|
};
|
|
2767
2615
|
}
|
|
2768
|
-
const hotspots = files.map((f) => {
|
|
2769
|
-
const amplificationFactor = f.fanOut + f.fanIn * 0.5;
|
|
2770
|
-
return { ...f, amplificationFactor };
|
|
2771
|
-
}).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
|
|
2616
|
+
const hotspots = files.map((f) => ({ ...f, amplificationFactor: f.fanOut + f.fanIn * 0.5 })).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
|
|
2772
2617
|
const maxAmplification = hotspots[0].amplificationFactor;
|
|
2773
2618
|
const avgAmplification = hotspots.reduce((sum, h) => sum + h.amplificationFactor, 0) / hotspots.length;
|
|
2774
|
-
let score =
|
|
2775
|
-
|
|
2776
|
-
|
|
2619
|
+
let score = Math.max(
|
|
2620
|
+
0,
|
|
2621
|
+
Math.min(
|
|
2622
|
+
100,
|
|
2623
|
+
100 - avgAmplification * 5 - (maxAmplification > 20 ? maxAmplification - 20 : 0)
|
|
2624
|
+
)
|
|
2625
|
+
);
|
|
2777
2626
|
let rating = "isolated";
|
|
2778
2627
|
if (score < 40) rating = "explosive";
|
|
2779
2628
|
else if (score < 70) rating = "amplified";
|
|
@@ -2781,12 +2630,12 @@ function calculateChangeAmplification(params) {
|
|
|
2781
2630
|
const recommendations = [];
|
|
2782
2631
|
if (score < 70 && hotspots.length > 0) {
|
|
2783
2632
|
recommendations.push(
|
|
2784
|
-
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling
|
|
2633
|
+
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling.`
|
|
2785
2634
|
);
|
|
2786
2635
|
}
|
|
2787
2636
|
if (maxAmplification > 30) {
|
|
2788
2637
|
recommendations.push(
|
|
2789
|
-
|
|
2638
|
+
"Break down key bottlenecks with amplification factor > 30."
|
|
2790
2639
|
);
|
|
2791
2640
|
}
|
|
2792
2641
|
return {
|
|
@@ -2798,6 +2647,61 @@ function calculateChangeAmplification(params) {
|
|
|
2798
2647
|
recommendations
|
|
2799
2648
|
};
|
|
2800
2649
|
}
|
|
2650
|
+
|
|
2651
|
+
// src/future-proof-metrics.ts
|
|
2652
|
+
function calculateFutureProofScore(params) {
|
|
2653
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2654
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2655
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2656
|
+
const overall = Math.round(
|
|
2657
|
+
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
2658
|
+
);
|
|
2659
|
+
const factors = [
|
|
2660
|
+
{
|
|
2661
|
+
name: "Cognitive Load",
|
|
2662
|
+
impact: Math.round(loadScore - 50),
|
|
2663
|
+
description: params.cognitiveLoad.rating
|
|
2664
|
+
},
|
|
2665
|
+
{
|
|
2666
|
+
name: "Pattern Entropy",
|
|
2667
|
+
impact: Math.round(entropyScore - 50),
|
|
2668
|
+
description: params.patternEntropy.rating
|
|
2669
|
+
},
|
|
2670
|
+
{
|
|
2671
|
+
name: "Concept Cohesion",
|
|
2672
|
+
impact: Math.round(cohesionScore - 50),
|
|
2673
|
+
description: params.conceptCohesion.rating
|
|
2674
|
+
}
|
|
2675
|
+
];
|
|
2676
|
+
const recommendations = [];
|
|
2677
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
2678
|
+
recommendations.push({
|
|
2679
|
+
action: rec,
|
|
2680
|
+
estimatedImpact: 5,
|
|
2681
|
+
priority: "medium"
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
2685
|
+
recommendations.push({
|
|
2686
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
2687
|
+
estimatedImpact: 8,
|
|
2688
|
+
priority: "high"
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2692
|
+
return {
|
|
2693
|
+
toolName: "future-proof",
|
|
2694
|
+
score: overall,
|
|
2695
|
+
rawMetrics: {
|
|
2696
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2697
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2698
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2699
|
+
semanticDistanceAvg
|
|
2700
|
+
},
|
|
2701
|
+
factors,
|
|
2702
|
+
recommendations
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2801
2705
|
function calculateExtendedFutureProofScore(params) {
|
|
2802
2706
|
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2803
2707
|
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
@@ -2806,7 +2710,7 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2806
2710
|
const groundingScore = params.agentGrounding.score;
|
|
2807
2711
|
const testabilityScore = params.testability.score;
|
|
2808
2712
|
const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
|
|
2809
|
-
const depsHealthScore = params.dependencyHealth
|
|
2713
|
+
const depsHealthScore = params.dependencyHealth?.score ?? 100;
|
|
2810
2714
|
let totalWeight = 0.8;
|
|
2811
2715
|
let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
|
|
2812
2716
|
if (params.docDrift) {
|
|
@@ -2837,7 +2741,7 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2837
2741
|
{
|
|
2838
2742
|
name: "AI Signal Clarity",
|
|
2839
2743
|
impact: Math.round(aiSignalClarityScore - 50),
|
|
2840
|
-
description: `${params.aiSignalClarity.rating} risk
|
|
2744
|
+
description: `${params.aiSignalClarity.rating} risk`
|
|
2841
2745
|
},
|
|
2842
2746
|
{
|
|
2843
2747
|
name: "Agent Grounding",
|
|
@@ -2847,60 +2751,25 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2847
2751
|
{
|
|
2848
2752
|
name: "Testability",
|
|
2849
2753
|
impact: Math.round(testabilityScore - 50),
|
|
2850
|
-
description:
|
|
2754
|
+
description: params.testability.rating
|
|
2851
2755
|
}
|
|
2852
2756
|
];
|
|
2853
2757
|
if (params.docDrift) {
|
|
2854
2758
|
factors.push({
|
|
2855
2759
|
name: "Documentation Drift",
|
|
2856
2760
|
impact: Math.round(docDriftScore - 50),
|
|
2857
|
-
description:
|
|
2761
|
+
description: params.docDrift.rating
|
|
2858
2762
|
});
|
|
2859
2763
|
}
|
|
2860
2764
|
if (params.dependencyHealth) {
|
|
2861
2765
|
factors.push({
|
|
2862
2766
|
name: "Dependency Health",
|
|
2863
2767
|
impact: Math.round(depsHealthScore - 50),
|
|
2864
|
-
description:
|
|
2865
|
-
});
|
|
2866
|
-
}
|
|
2867
|
-
const recommendations = [];
|
|
2868
|
-
for (const rec of params.aiSignalClarity.recommendations) {
|
|
2869
|
-
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
2870
|
-
}
|
|
2871
|
-
for (const rec of params.agentGrounding.recommendations) {
|
|
2872
|
-
recommendations.push({
|
|
2873
|
-
action: rec,
|
|
2874
|
-
estimatedImpact: 6,
|
|
2875
|
-
priority: "medium"
|
|
2768
|
+
description: params.dependencyHealth.rating
|
|
2876
2769
|
});
|
|
2877
2770
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
recommendations.push({ action: rec, estimatedImpact: 10, priority });
|
|
2881
|
-
}
|
|
2882
|
-
for (const rec of params.patternEntropy.recommendations) {
|
|
2883
|
-
recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
|
|
2884
|
-
}
|
|
2885
|
-
if (params.docDrift) {
|
|
2886
|
-
for (const rec of params.docDrift.recommendations) {
|
|
2887
|
-
recommendations.push({
|
|
2888
|
-
action: rec,
|
|
2889
|
-
estimatedImpact: 8,
|
|
2890
|
-
priority: "high"
|
|
2891
|
-
});
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
if (params.dependencyHealth) {
|
|
2895
|
-
for (const rec of params.dependencyHealth.recommendations) {
|
|
2896
|
-
recommendations.push({
|
|
2897
|
-
action: rec,
|
|
2898
|
-
estimatedImpact: 7,
|
|
2899
|
-
priority: "medium"
|
|
2900
|
-
});
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2771
|
+
const recommendations = collectFutureProofRecommendations(params);
|
|
2772
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2904
2773
|
return {
|
|
2905
2774
|
toolName: "future-proof",
|
|
2906
2775
|
score: overall,
|
|
@@ -3077,16 +2946,19 @@ function getRepoMetadata(directory) {
|
|
|
3077
2946
|
ParseError,
|
|
3078
2947
|
ParserFactory,
|
|
3079
2948
|
PythonParser,
|
|
2949
|
+
SEVERITY_TIME_ESTIMATES,
|
|
3080
2950
|
SIZE_ADJUSTED_THRESHOLDS,
|
|
3081
2951
|
TOOL_NAME_MAP,
|
|
3082
2952
|
TypeScriptParser,
|
|
3083
2953
|
VAGUE_FILE_NAMES,
|
|
3084
2954
|
calculateAgentGrounding,
|
|
3085
2955
|
calculateAiSignalClarity,
|
|
2956
|
+
calculateBusinessROI,
|
|
3086
2957
|
calculateChangeAmplification,
|
|
3087
2958
|
calculateCognitiveLoad,
|
|
3088
2959
|
calculateComprehensionDifficulty,
|
|
3089
2960
|
calculateConceptCohesion,
|
|
2961
|
+
calculateDebtInterest,
|
|
3090
2962
|
calculateDependencyHealth,
|
|
3091
2963
|
calculateDocDrift,
|
|
3092
2964
|
calculateExtendedFutureProofScore,
|
|
@@ -3097,10 +2969,8 @@ function getRepoMetadata(directory) {
|
|
|
3097
2969
|
calculateOverallScore,
|
|
3098
2970
|
calculatePatternEntropy,
|
|
3099
2971
|
calculateProductivityImpact,
|
|
3100
|
-
calculateRemediationVelocity,
|
|
3101
|
-
calculateScoreTrend,
|
|
3102
2972
|
calculateSemanticDistance,
|
|
3103
|
-
|
|
2973
|
+
calculateTechnicalValueChain,
|
|
3104
2974
|
calculateTestabilityIndex,
|
|
3105
2975
|
calculateTokenBudget,
|
|
3106
2976
|
clearHistory,
|
|
@@ -3116,7 +2986,6 @@ function getRepoMetadata(directory) {
|
|
|
3116
2986
|
formatToolScore,
|
|
3117
2987
|
generateHTML,
|
|
3118
2988
|
generateValueChain,
|
|
3119
|
-
getDebtBreakdown,
|
|
3120
2989
|
getElapsedTime,
|
|
3121
2990
|
getFileCommitTimestamps,
|
|
3122
2991
|
getFileExtension,
|
|
@@ -3130,6 +2999,9 @@ function getRepoMetadata(directory) {
|
|
|
3130
2999
|
getRatingWithContext,
|
|
3131
3000
|
getRecommendedThreshold,
|
|
3132
3001
|
getRepoMetadata,
|
|
3002
|
+
getSafetyIcon,
|
|
3003
|
+
getScoreBar,
|
|
3004
|
+
getSeverityColor,
|
|
3133
3005
|
getSupportedLanguages,
|
|
3134
3006
|
getToolWeight,
|
|
3135
3007
|
handleCLIError,
|