@aiready/core 0.9.38 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/client.d.mts +14 -6
- package/dist/client.d.ts +14 -6
- package/dist/index.d.mts +221 -281
- package/dist/index.d.ts +221 -281
- package/dist/index.js +464 -542
- package/dist/index.mjs +455 -537
- package/package.json +1 -1
package/dist/index.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,
|
|
@@ -112,7 +115,8 @@ __export(index_exports, {
|
|
|
112
115
|
resolveOutputPath: () => resolveOutputPath,
|
|
113
116
|
saveScoreEntry: () => saveScoreEntry,
|
|
114
117
|
scanEntries: () => scanEntries,
|
|
115
|
-
scanFiles: () => scanFiles
|
|
118
|
+
scanFiles: () => scanFiles,
|
|
119
|
+
validateSpokeOutput: () => validateSpokeOutput
|
|
116
120
|
});
|
|
117
121
|
module.exports = __toCommonJS(index_exports);
|
|
118
122
|
|
|
@@ -147,6 +151,54 @@ var ParseError = class extends Error {
|
|
|
147
151
|
}
|
|
148
152
|
};
|
|
149
153
|
|
|
154
|
+
// src/types/contract.ts
|
|
155
|
+
function validateSpokeOutput(toolName, output) {
|
|
156
|
+
const errors = [];
|
|
157
|
+
if (!output) {
|
|
158
|
+
return { valid: false, errors: ["Output is null or undefined"] };
|
|
159
|
+
}
|
|
160
|
+
if (!Array.isArray(output.results)) {
|
|
161
|
+
errors.push(`${toolName}: 'results' must be an array`);
|
|
162
|
+
} else {
|
|
163
|
+
output.results.forEach((res, idx) => {
|
|
164
|
+
const fileName = res.fileName || res.file || res.filePath;
|
|
165
|
+
if (!fileName)
|
|
166
|
+
errors.push(
|
|
167
|
+
`${toolName}: results[${idx}] missing 'fileName', 'file' or 'filePath'`
|
|
168
|
+
);
|
|
169
|
+
const issues = res.issues;
|
|
170
|
+
if (!Array.isArray(issues)) {
|
|
171
|
+
errors.push(`${toolName}: results[${idx}] 'issues' must be an array`);
|
|
172
|
+
} else if (issues.length > 0) {
|
|
173
|
+
issues.forEach((issue, iidx) => {
|
|
174
|
+
if (typeof issue === "string") return;
|
|
175
|
+
if (!issue.type && !res.file)
|
|
176
|
+
errors.push(
|
|
177
|
+
`${toolName}: results[${idx}].issues[${iidx}] missing 'type'`
|
|
178
|
+
);
|
|
179
|
+
if (!issue.severity && !res.severity)
|
|
180
|
+
errors.push(
|
|
181
|
+
`${toolName}: results[${idx}].issues[${iidx}] missing 'severity'`
|
|
182
|
+
);
|
|
183
|
+
const severity = issue.severity || res.severity;
|
|
184
|
+
if (severity && !["critical", "major", "minor", "info"].includes(severity)) {
|
|
185
|
+
errors.push(
|
|
186
|
+
`${toolName}: results[${idx}].issues[${iidx}] has invalid severity: ${severity}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (!output.summary) {
|
|
194
|
+
errors.push(`${toolName}: missing 'summary'`);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
valid: errors.length === 0,
|
|
198
|
+
errors
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
150
202
|
// src/utils/file-scanner.ts
|
|
151
203
|
var import_glob = require("glob");
|
|
152
204
|
var import_promises = require("fs/promises");
|
|
@@ -640,6 +692,41 @@ function handleCLIError(error, commandName) {
|
|
|
640
692
|
function getElapsedTime(startTime) {
|
|
641
693
|
return ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
642
694
|
}
|
|
695
|
+
function getScoreBar(val) {
|
|
696
|
+
return "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
|
|
697
|
+
}
|
|
698
|
+
function getSafetyIcon(rating) {
|
|
699
|
+
switch (rating) {
|
|
700
|
+
case "safe":
|
|
701
|
+
return "\u2705";
|
|
702
|
+
case "moderate-risk":
|
|
703
|
+
return "\u26A0\uFE0F ";
|
|
704
|
+
case "high-risk":
|
|
705
|
+
return "\u{1F534}";
|
|
706
|
+
case "blind-risk":
|
|
707
|
+
return "\u{1F480}";
|
|
708
|
+
default:
|
|
709
|
+
return "\u2753";
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function getSeverityColor(severity, chalk) {
|
|
713
|
+
switch (severity.toLowerCase()) {
|
|
714
|
+
case "critical":
|
|
715
|
+
case "high-risk":
|
|
716
|
+
case "blind-risk":
|
|
717
|
+
return chalk.red;
|
|
718
|
+
case "major":
|
|
719
|
+
case "moderate-risk":
|
|
720
|
+
return chalk.yellow;
|
|
721
|
+
case "minor":
|
|
722
|
+
case "safe":
|
|
723
|
+
return chalk.green;
|
|
724
|
+
case "info":
|
|
725
|
+
return chalk.blue;
|
|
726
|
+
default:
|
|
727
|
+
return chalk.white;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
643
730
|
|
|
644
731
|
// src/utils/visualization.ts
|
|
645
732
|
function generateHTML(graph) {
|
|
@@ -999,7 +1086,7 @@ function formatToolScore(output) {
|
|
|
999
1086
|
return result;
|
|
1000
1087
|
}
|
|
1001
1088
|
|
|
1002
|
-
// src/business-
|
|
1089
|
+
// src/business/pricing-models.ts
|
|
1003
1090
|
var MODEL_PRICING_PRESETS = {
|
|
1004
1091
|
"gpt-5.3": {
|
|
1005
1092
|
name: "GPT-5.3",
|
|
@@ -1061,30 +1148,17 @@ var MODEL_PRICING_PRESETS = {
|
|
|
1061
1148
|
function getModelPreset(modelId) {
|
|
1062
1149
|
return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["claude-4.6"];
|
|
1063
1150
|
}
|
|
1151
|
+
|
|
1152
|
+
// src/business/cost-metrics.ts
|
|
1064
1153
|
var DEFAULT_COST_CONFIG = {
|
|
1065
1154
|
pricePer1KTokens: 5e-3,
|
|
1066
|
-
// GPT-4o input price (updated from GPT-4 era 0.01)
|
|
1067
1155
|
queriesPerDevPerDay: 60,
|
|
1068
|
-
// Average AI queries per developer (updated: 40→60 as of 2026)
|
|
1069
1156
|
developerCount: 5,
|
|
1070
|
-
// Default team size
|
|
1071
1157
|
daysPerMonth: 30
|
|
1072
1158
|
};
|
|
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
1159
|
function calculateMonthlyCost(tokenWaste, config = {}) {
|
|
1085
1160
|
const budget = calculateTokenBudget({
|
|
1086
1161
|
totalContextTokens: tokenWaste * 2.5,
|
|
1087
|
-
// Heuristic: context is larger than waste
|
|
1088
1162
|
wastedTokens: {
|
|
1089
1163
|
duplication: tokenWaste * 0.7,
|
|
1090
1164
|
fragmentation: tokenWaste * 0.3,
|
|
@@ -1118,7 +1192,6 @@ function calculateTokenBudget(params) {
|
|
|
1118
1192
|
},
|
|
1119
1193
|
efficiencyRatio: Math.round(efficiencyRatio * 100) / 100,
|
|
1120
1194
|
potentialRetrievableTokens: Math.round(totalWaste * 0.8)
|
|
1121
|
-
// Heuristic: 80% is fixable
|
|
1122
1195
|
};
|
|
1123
1196
|
}
|
|
1124
1197
|
function estimateCostFromBudget(budget, model, config = {}) {
|
|
@@ -1142,6 +1215,15 @@ function estimateCostFromBudget(budget, model, config = {}) {
|
|
|
1142
1215
|
confidence
|
|
1143
1216
|
};
|
|
1144
1217
|
}
|
|
1218
|
+
|
|
1219
|
+
// src/business/productivity-metrics.ts
|
|
1220
|
+
var SEVERITY_TIME_ESTIMATES = {
|
|
1221
|
+
critical: 4,
|
|
1222
|
+
major: 2,
|
|
1223
|
+
minor: 0.5,
|
|
1224
|
+
info: 0.25
|
|
1225
|
+
};
|
|
1226
|
+
var DEFAULT_HOURLY_RATE = 75;
|
|
1145
1227
|
function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
1146
1228
|
const counts = {
|
|
1147
1229
|
critical: issues.filter((i) => i.severity === "critical").length,
|
|
@@ -1182,108 +1264,158 @@ function predictAcceptanceRate(toolOutputs) {
|
|
|
1182
1264
|
const baseRate = 0.3;
|
|
1183
1265
|
const patterns = toolOutputs.get("pattern-detect");
|
|
1184
1266
|
if (patterns) {
|
|
1185
|
-
const patternImpact = (patterns.score - 50) * 3e-3;
|
|
1186
1267
|
factors.push({
|
|
1187
1268
|
name: "Semantic Duplication",
|
|
1188
|
-
impact: Math.round(
|
|
1269
|
+
impact: Math.round((patterns.score - 50) * 3e-3 * 100)
|
|
1189
1270
|
});
|
|
1190
1271
|
}
|
|
1191
1272
|
const context = toolOutputs.get("context-analyzer");
|
|
1192
1273
|
if (context) {
|
|
1193
|
-
const contextImpact = (context.score - 50) * 4e-3;
|
|
1194
1274
|
factors.push({
|
|
1195
1275
|
name: "Context Efficiency",
|
|
1196
|
-
impact: Math.round(
|
|
1276
|
+
impact: Math.round((context.score - 50) * 4e-3 * 100)
|
|
1197
1277
|
});
|
|
1198
1278
|
}
|
|
1199
1279
|
const consistency = toolOutputs.get("consistency");
|
|
1200
1280
|
if (consistency) {
|
|
1201
|
-
const consistencyImpact = (consistency.score - 50) * 2e-3;
|
|
1202
1281
|
factors.push({
|
|
1203
1282
|
name: "Code Consistency",
|
|
1204
|
-
impact: Math.round(
|
|
1283
|
+
impact: Math.round((consistency.score - 50) * 2e-3 * 100)
|
|
1205
1284
|
});
|
|
1206
1285
|
}
|
|
1207
1286
|
const aiSignalClarity = toolOutputs.get("ai-signal-clarity");
|
|
1208
1287
|
if (aiSignalClarity) {
|
|
1209
|
-
const hrImpact = (50 - aiSignalClarity.score) * 2e-3;
|
|
1210
1288
|
factors.push({
|
|
1211
1289
|
name: "AI Signal Clarity",
|
|
1212
|
-
impact: Math.round(
|
|
1290
|
+
impact: Math.round((50 - aiSignalClarity.score) * 2e-3 * 100)
|
|
1213
1291
|
});
|
|
1214
1292
|
}
|
|
1215
1293
|
const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
|
|
1216
1294
|
const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
|
|
1217
|
-
let confidence;
|
|
1295
|
+
let confidence = 0.35;
|
|
1218
1296
|
if (toolOutputs.size >= 4) confidence = 0.75;
|
|
1219
1297
|
else if (toolOutputs.size >= 3) confidence = 0.65;
|
|
1220
1298
|
else if (toolOutputs.size >= 2) confidence = 0.5;
|
|
1221
|
-
|
|
1222
|
-
return {
|
|
1223
|
-
rate: Math.round(rate * 100) / 100,
|
|
1224
|
-
confidence,
|
|
1225
|
-
factors
|
|
1226
|
-
};
|
|
1299
|
+
return { rate: Math.round(rate * 100) / 100, confidence, factors };
|
|
1227
1300
|
}
|
|
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));
|
|
1301
|
+
|
|
1302
|
+
// src/business/risk-metrics.ts
|
|
1303
|
+
function calculateKnowledgeConcentration(params) {
|
|
1304
|
+
const { uniqueConceptFiles, totalFiles, singleAuthorFiles, orphanFiles } = params;
|
|
1305
|
+
const concentrationRatio = totalFiles > 0 ? (uniqueConceptFiles + singleAuthorFiles) / (totalFiles * 2) : 0;
|
|
1248
1306
|
const score = Math.round(
|
|
1249
|
-
|
|
1307
|
+
Math.min(
|
|
1308
|
+
100,
|
|
1309
|
+
concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
|
|
1310
|
+
)
|
|
1250
1311
|
);
|
|
1251
1312
|
let rating;
|
|
1252
|
-
if (score <
|
|
1253
|
-
else if (score <
|
|
1254
|
-
else if (score <
|
|
1255
|
-
else
|
|
1256
|
-
|
|
1313
|
+
if (score < 30) rating = "low";
|
|
1314
|
+
else if (score < 50) rating = "moderate";
|
|
1315
|
+
else if (score < 75) rating = "high";
|
|
1316
|
+
else rating = "critical";
|
|
1317
|
+
const recommendations = [];
|
|
1318
|
+
if (singleAuthorFiles > 0)
|
|
1319
|
+
recommendations.push(
|
|
1320
|
+
`Distribute knowledge for ${singleAuthorFiles} single-author files.`
|
|
1321
|
+
);
|
|
1322
|
+
if (orphanFiles > 0)
|
|
1323
|
+
recommendations.push(
|
|
1324
|
+
`Link ${orphanFiles} orphan files to the rest of the codebase.`
|
|
1325
|
+
);
|
|
1257
1326
|
return {
|
|
1258
1327
|
score,
|
|
1259
1328
|
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
|
-
|
|
1329
|
+
recommendations,
|
|
1330
|
+
analysis: {
|
|
1331
|
+
uniqueConceptFiles,
|
|
1332
|
+
totalFiles,
|
|
1333
|
+
concentrationRatio,
|
|
1334
|
+
singleAuthorFiles,
|
|
1335
|
+
orphanFiles
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function calculateDebtInterest(principal, monthlyGrowthRate) {
|
|
1340
|
+
const monthlyRate = monthlyGrowthRate;
|
|
1341
|
+
const annualRate = Math.pow(1 + monthlyRate, 12) - 1;
|
|
1342
|
+
const monthlyCost = principal * monthlyRate;
|
|
1343
|
+
return {
|
|
1344
|
+
monthlyRate,
|
|
1345
|
+
annualRate,
|
|
1346
|
+
principal,
|
|
1347
|
+
monthlyCost,
|
|
1348
|
+
projections: {
|
|
1349
|
+
months6: principal * Math.pow(1 + monthlyRate, 6),
|
|
1350
|
+
months12: principal * Math.pow(1 + monthlyRate, 12),
|
|
1351
|
+
months24: principal * Math.pow(1 + monthlyRate, 24)
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// src/business/comprehension-metrics.ts
|
|
1357
|
+
function calculateTechnicalValueChain(params) {
|
|
1358
|
+
const { businessLogicDensity, dataAccessComplexity, apiSurfaceArea } = params;
|
|
1359
|
+
const score = (businessLogicDensity * 0.5 + (1 - dataAccessComplexity / 10) * 0.3 + (1 - apiSurfaceArea / 20) * 0.2) * 100;
|
|
1360
|
+
return {
|
|
1361
|
+
score: Math.round(Math.max(0, Math.min(100, score))),
|
|
1362
|
+
density: businessLogicDensity,
|
|
1363
|
+
complexity: dataAccessComplexity,
|
|
1364
|
+
surface: apiSurfaceArea
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, modelTier = "frontier") {
|
|
1368
|
+
const tierMap = {
|
|
1369
|
+
compact: "compact",
|
|
1370
|
+
standard: "standard",
|
|
1371
|
+
extended: "extended",
|
|
1372
|
+
frontier: "frontier",
|
|
1373
|
+
easy: "frontier",
|
|
1374
|
+
// Map legacy 'easy' to 'frontier'
|
|
1375
|
+
moderate: "standard",
|
|
1376
|
+
difficult: "compact"
|
|
1377
|
+
};
|
|
1378
|
+
const tier = tierMap[modelTier] || "frontier";
|
|
1379
|
+
const threshold = CONTEXT_TIER_THRESHOLDS[tier];
|
|
1380
|
+
const budgetRatio = contextBudget / threshold.idealTokens;
|
|
1381
|
+
const score = (budgetRatio * 0.6 + importDepth / 10 * 0.2 + fragmentation * 0.2) * 100;
|
|
1382
|
+
const finalScore = Math.round(Math.max(0, Math.min(100, score)));
|
|
1383
|
+
let rating;
|
|
1384
|
+
if (finalScore < 20) rating = "trivial";
|
|
1385
|
+
else if (finalScore < 40) rating = "easy";
|
|
1386
|
+
else if (finalScore < 60) rating = "moderate";
|
|
1387
|
+
else if (finalScore < 85) rating = "difficult";
|
|
1388
|
+
else rating = "expert";
|
|
1389
|
+
return {
|
|
1390
|
+
score: finalScore,
|
|
1391
|
+
rating,
|
|
1392
|
+
factors: { budgetRatio, depthRatio: importDepth / 10, fragmentation }
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/business-metrics.ts
|
|
1397
|
+
function calculateBusinessROI(params) {
|
|
1398
|
+
const model = getModelPreset(params.modelId || "claude-4.6");
|
|
1399
|
+
const devCount = params.developerCount || 5;
|
|
1400
|
+
const budget = calculateTokenBudget({
|
|
1401
|
+
totalContextTokens: params.tokenWaste * 2.5,
|
|
1402
|
+
wastedTokens: {
|
|
1403
|
+
duplication: params.tokenWaste * 0.7,
|
|
1404
|
+
fragmentation: params.tokenWaste * 0.3,
|
|
1405
|
+
chattiness: 0
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
const cost = estimateCostFromBudget(budget, model, {
|
|
1409
|
+
developerCount: devCount
|
|
1410
|
+
});
|
|
1411
|
+
const productivity = calculateProductivityImpact(params.issues);
|
|
1412
|
+
const monthlySavings = cost.total;
|
|
1413
|
+
const productivityGainHours = productivity.totalHours;
|
|
1414
|
+
const annualValue = (monthlySavings + productivityGainHours * 75) * 12;
|
|
1415
|
+
return {
|
|
1416
|
+
monthlySavings: Math.round(monthlySavings),
|
|
1417
|
+
productivityGainHours: Math.round(productivityGainHours),
|
|
1418
|
+
annualValue: Math.round(annualValue)
|
|
1287
1419
|
};
|
|
1288
1420
|
}
|
|
1289
1421
|
function formatCost(cost) {
|
|
@@ -1309,220 +1441,6 @@ function formatHours(hours) {
|
|
|
1309
1441
|
function formatAcceptanceRate(rate) {
|
|
1310
1442
|
return `${Math.round(rate * 100)}%`;
|
|
1311
1443
|
}
|
|
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
1444
|
function generateValueChain(params) {
|
|
1527
1445
|
const { issueType, count, severity } = params;
|
|
1528
1446
|
const impacts = {
|
|
@@ -1562,9 +1480,7 @@ function generateValueChain(params) {
|
|
|
1562
1480
|
},
|
|
1563
1481
|
businessOutcome: {
|
|
1564
1482
|
directCost: count * 12,
|
|
1565
|
-
// Placeholder linear cost
|
|
1566
1483
|
opportunityCost: productivityLoss * 15e3,
|
|
1567
|
-
// Monthly avg dev cost $15k
|
|
1568
1484
|
riskLevel: impact.risk
|
|
1569
1485
|
}
|
|
1570
1486
|
};
|
|
@@ -2079,7 +1995,55 @@ function getSupportedLanguages() {
|
|
|
2079
1995
|
return ParserFactory.getInstance().getSupportedLanguages();
|
|
2080
1996
|
}
|
|
2081
1997
|
|
|
2082
|
-
// src/
|
|
1998
|
+
// src/metrics/remediation-utils.ts
|
|
1999
|
+
function collectFutureProofRecommendations(params) {
|
|
2000
|
+
const recommendations = [];
|
|
2001
|
+
for (const rec of params.aiSignalClarity.recommendations) {
|
|
2002
|
+
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
2003
|
+
}
|
|
2004
|
+
for (const rec of params.agentGrounding.recommendations) {
|
|
2005
|
+
recommendations.push({
|
|
2006
|
+
action: rec,
|
|
2007
|
+
estimatedImpact: 6,
|
|
2008
|
+
priority: "medium"
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
for (const rec of params.testability.recommendations) {
|
|
2012
|
+
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
2013
|
+
recommendations.push({ action: rec, estimatedImpact: 10, priority });
|
|
2014
|
+
}
|
|
2015
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
2016
|
+
recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
|
|
2017
|
+
}
|
|
2018
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
2019
|
+
recommendations.push({
|
|
2020
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
2021
|
+
estimatedImpact: 8,
|
|
2022
|
+
priority: "high"
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
if (params.docDrift) {
|
|
2026
|
+
for (const rec of params.docDrift.recommendations) {
|
|
2027
|
+
recommendations.push({
|
|
2028
|
+
action: rec,
|
|
2029
|
+
estimatedImpact: 8,
|
|
2030
|
+
priority: "high"
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
if (params.dependencyHealth) {
|
|
2035
|
+
for (const rec of params.dependencyHealth.recommendations) {
|
|
2036
|
+
recommendations.push({
|
|
2037
|
+
action: rec,
|
|
2038
|
+
estimatedImpact: 7,
|
|
2039
|
+
priority: "medium"
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return recommendations;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// src/metrics/cognitive-load.ts
|
|
2083
2047
|
function calculateCognitiveLoad(params) {
|
|
2084
2048
|
const {
|
|
2085
2049
|
linesOfCode,
|
|
@@ -2138,13 +2102,20 @@ function calculateCognitiveLoad(params) {
|
|
|
2138
2102
|
}
|
|
2139
2103
|
};
|
|
2140
2104
|
}
|
|
2105
|
+
|
|
2106
|
+
// src/metrics/semantic-distance.ts
|
|
2141
2107
|
function calculateSemanticDistance(params) {
|
|
2142
|
-
const {
|
|
2108
|
+
const {
|
|
2109
|
+
file1,
|
|
2110
|
+
file2,
|
|
2111
|
+
file1Domain,
|
|
2112
|
+
file2Domain,
|
|
2113
|
+
sharedDependencies,
|
|
2114
|
+
file1Imports,
|
|
2115
|
+
file2Imports
|
|
2116
|
+
} = params;
|
|
2143
2117
|
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
|
-
);
|
|
2118
|
+
const importOverlap = sharedDependencies.length / Math.max(1, Math.min(file1Imports.length, file2Imports.length));
|
|
2148
2119
|
const importDistance = 1 - importOverlap;
|
|
2149
2120
|
const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
|
|
2150
2121
|
let relationship;
|
|
@@ -2163,6 +2134,8 @@ function calculateSemanticDistance(params) {
|
|
|
2163
2134
|
reason: relationship === "same-domain" ? `Both in "${file1Domain}" domain` : relationship === "cross-domain" ? `Share ${sharedDependencies.length} dependency(ies)` : "No strong semantic relationship detected"
|
|
2164
2135
|
};
|
|
2165
2136
|
}
|
|
2137
|
+
|
|
2138
|
+
// src/metrics/structural-metrics.ts
|
|
2166
2139
|
function calculatePatternEntropy(files) {
|
|
2167
2140
|
if (files.length === 0) {
|
|
2168
2141
|
return {
|
|
@@ -2212,23 +2185,18 @@ function calculatePatternEntropy(files) {
|
|
|
2212
2185
|
else if (normalizedEntropy < 0.8) rating = "fragmented";
|
|
2213
2186
|
else rating = "chaotic";
|
|
2214
2187
|
const recommendations = [];
|
|
2215
|
-
if (normalizedEntropy > 0.5)
|
|
2188
|
+
if (normalizedEntropy > 0.5)
|
|
2216
2189
|
recommendations.push(
|
|
2217
2190
|
`Consolidate ${files.length} files into fewer directories by domain`
|
|
2218
2191
|
);
|
|
2219
|
-
|
|
2220
|
-
if (dirGroups.size > 5) {
|
|
2192
|
+
if (dirGroups.size > 5)
|
|
2221
2193
|
recommendations.push(
|
|
2222
2194
|
"Consider barrel exports to reduce directory navigation"
|
|
2223
2195
|
);
|
|
2224
|
-
|
|
2225
|
-
if (gini > 0.5) {
|
|
2196
|
+
if (gini > 0.5)
|
|
2226
2197
|
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
2198
|
return {
|
|
2231
|
-
domain:
|
|
2199
|
+
domain: files[0]?.domain || "mixed",
|
|
2232
2200
|
entropy: Math.round(normalizedEntropy * 100) / 100,
|
|
2233
2201
|
rating,
|
|
2234
2202
|
distribution: {
|
|
@@ -2259,9 +2227,8 @@ function calculateConceptCohesion(params) {
|
|
|
2259
2227
|
}
|
|
2260
2228
|
const uniqueDomains = new Set(allDomains);
|
|
2261
2229
|
const domainCounts = /* @__PURE__ */ new Map();
|
|
2262
|
-
for (const d of allDomains)
|
|
2230
|
+
for (const d of allDomains)
|
|
2263
2231
|
domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
|
|
2264
|
-
}
|
|
2265
2232
|
const maxCount = Math.max(...Array.from(domainCounts.values()), 1);
|
|
2266
2233
|
const domainConcentration = maxCount / allDomains.length;
|
|
2267
2234
|
const exportPurposeClarity = 1 - (uniqueDomains.size - 1) / Math.max(1, exports2.length);
|
|
@@ -2281,59 +2248,8 @@ function calculateConceptCohesion(params) {
|
|
|
2281
2248
|
}
|
|
2282
2249
|
};
|
|
2283
2250
|
}
|
|
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
|
-
}
|
|
2251
|
+
|
|
2252
|
+
// src/metrics/ai-signal-clarity.ts
|
|
2337
2253
|
function calculateAiSignalClarity(params) {
|
|
2338
2254
|
const {
|
|
2339
2255
|
overloadedSymbols,
|
|
@@ -2355,75 +2271,53 @@ function calculateAiSignalClarity(params) {
|
|
|
2355
2271
|
recommendations: []
|
|
2356
2272
|
};
|
|
2357
2273
|
}
|
|
2358
|
-
const overloadRatio = Math.
|
|
2359
|
-
1,
|
|
2360
|
-
overloadedSymbols / Math.max(1, totalSymbols)
|
|
2361
|
-
);
|
|
2274
|
+
const overloadRatio = overloadedSymbols / Math.max(1, totalSymbols);
|
|
2362
2275
|
const overloadSignal = {
|
|
2363
2276
|
name: "Symbol Overloading",
|
|
2364
2277
|
count: overloadedSymbols,
|
|
2365
|
-
riskContribution: Math.round(overloadRatio * 100 * 0.25),
|
|
2366
|
-
// 25% weight
|
|
2278
|
+
riskContribution: Math.round(Math.min(1, overloadRatio) * 100 * 0.25),
|
|
2367
2279
|
description: `${overloadedSymbols} overloaded symbols \u2014 AI picks wrong signature`
|
|
2368
2280
|
};
|
|
2369
|
-
const magicRatio =
|
|
2281
|
+
const magicRatio = magicLiterals / Math.max(1, totalSymbols * 2);
|
|
2370
2282
|
const magicSignal = {
|
|
2371
2283
|
name: "Magic Literals",
|
|
2372
2284
|
count: magicLiterals,
|
|
2373
|
-
riskContribution: Math.round(magicRatio * 100 * 0.2),
|
|
2374
|
-
// 20% weight
|
|
2285
|
+
riskContribution: Math.round(Math.min(1, magicRatio) * 100 * 0.2),
|
|
2375
2286
|
description: `${magicLiterals} unnamed constants \u2014 AI invents wrong values`
|
|
2376
2287
|
};
|
|
2377
|
-
const trapRatio =
|
|
2288
|
+
const trapRatio = booleanTraps / Math.max(1, totalSymbols);
|
|
2378
2289
|
const trapSignal = {
|
|
2379
2290
|
name: "Boolean Traps",
|
|
2380
2291
|
count: booleanTraps,
|
|
2381
|
-
riskContribution: Math.round(trapRatio * 100 * 0.2),
|
|
2382
|
-
// 20% weight
|
|
2292
|
+
riskContribution: Math.round(Math.min(1, trapRatio) * 100 * 0.2),
|
|
2383
2293
|
description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
|
|
2384
2294
|
};
|
|
2385
|
-
const sideEffectRatio = Math.
|
|
2386
|
-
1,
|
|
2387
|
-
implicitSideEffects / Math.max(1, totalExports)
|
|
2388
|
-
);
|
|
2295
|
+
const sideEffectRatio = implicitSideEffects / Math.max(1, totalExports);
|
|
2389
2296
|
const sideEffectSignal = {
|
|
2390
2297
|
name: "Implicit Side Effects",
|
|
2391
2298
|
count: implicitSideEffects,
|
|
2392
|
-
riskContribution: Math.round(sideEffectRatio * 100 * 0.15),
|
|
2393
|
-
// 15% weight
|
|
2299
|
+
riskContribution: Math.round(Math.min(1, sideEffectRatio) * 100 * 0.15),
|
|
2394
2300
|
description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
|
|
2395
2301
|
};
|
|
2396
|
-
const callbackRatio = Math.
|
|
2397
|
-
1,
|
|
2398
|
-
deepCallbacks / Math.max(1, totalSymbols * 0.1)
|
|
2399
|
-
);
|
|
2302
|
+
const callbackRatio = deepCallbacks / Math.max(1, totalSymbols * 0.1);
|
|
2400
2303
|
const callbackSignal = {
|
|
2401
2304
|
name: "Callback Nesting",
|
|
2402
2305
|
count: deepCallbacks,
|
|
2403
|
-
riskContribution: Math.round(callbackRatio * 100 * 0.1),
|
|
2404
|
-
// 10% weight
|
|
2306
|
+
riskContribution: Math.round(Math.min(1, callbackRatio) * 100 * 0.1),
|
|
2405
2307
|
description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
|
|
2406
2308
|
};
|
|
2407
|
-
const ambiguousRatio = Math.
|
|
2408
|
-
1,
|
|
2409
|
-
ambiguousNames / Math.max(1, totalSymbols)
|
|
2410
|
-
);
|
|
2309
|
+
const ambiguousRatio = ambiguousNames / Math.max(1, totalSymbols);
|
|
2411
2310
|
const ambiguousSignal = {
|
|
2412
2311
|
name: "Ambiguous Names",
|
|
2413
2312
|
count: ambiguousNames,
|
|
2414
|
-
riskContribution: Math.round(ambiguousRatio * 100 * 0.1),
|
|
2415
|
-
// 10% weight
|
|
2313
|
+
riskContribution: Math.round(Math.min(1, ambiguousRatio) * 100 * 0.1),
|
|
2416
2314
|
description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
|
|
2417
2315
|
};
|
|
2418
|
-
const undocRatio = Math.
|
|
2419
|
-
1,
|
|
2420
|
-
undocumentedExports / Math.max(1, totalExports)
|
|
2421
|
-
);
|
|
2316
|
+
const undocRatio = undocumentedExports / Math.max(1, totalExports);
|
|
2422
2317
|
const undocSignal = {
|
|
2423
2318
|
name: "Undocumented Exports",
|
|
2424
2319
|
count: undocumentedExports,
|
|
2425
|
-
riskContribution: Math.round(undocRatio * 100 * 0.1),
|
|
2426
|
-
// 10% weight
|
|
2320
|
+
riskContribution: Math.round(Math.min(1, undocRatio) * 100 * 0.1),
|
|
2427
2321
|
description: `${undocumentedExports} public functions without docs \u2014 AI fabricates behavior`
|
|
2428
2322
|
};
|
|
2429
2323
|
const signals = [
|
|
@@ -2448,33 +2342,28 @@ function calculateAiSignalClarity(params) {
|
|
|
2448
2342
|
const topSignal = signals.reduce(
|
|
2449
2343
|
(a, b) => a.riskContribution > b.riskContribution ? a : b
|
|
2450
2344
|
);
|
|
2451
|
-
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant
|
|
2345
|
+
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant issues detected";
|
|
2452
2346
|
const recommendations = [];
|
|
2453
|
-
if (overloadSignal.riskContribution > 5)
|
|
2347
|
+
if (overloadSignal.riskContribution > 5)
|
|
2454
2348
|
recommendations.push(
|
|
2455
2349
|
`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
|
|
2456
2350
|
);
|
|
2457
|
-
|
|
2458
|
-
if (magicSignal.riskContribution > 5) {
|
|
2351
|
+
if (magicSignal.riskContribution > 5)
|
|
2459
2352
|
recommendations.push(
|
|
2460
2353
|
`Extract ${magicLiterals} magic literals into named constants`
|
|
2461
2354
|
);
|
|
2462
|
-
|
|
2463
|
-
if (trapSignal.riskContribution > 5) {
|
|
2355
|
+
if (trapSignal.riskContribution > 5)
|
|
2464
2356
|
recommendations.push(
|
|
2465
2357
|
`Replace ${booleanTraps} boolean traps with named options objects`
|
|
2466
2358
|
);
|
|
2467
|
-
|
|
2468
|
-
if (undocSignal.riskContribution > 5) {
|
|
2359
|
+
if (undocSignal.riskContribution > 5)
|
|
2469
2360
|
recommendations.push(
|
|
2470
2361
|
`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
|
|
2471
2362
|
);
|
|
2472
|
-
|
|
2473
|
-
if (sideEffectSignal.riskContribution > 5) {
|
|
2363
|
+
if (sideEffectSignal.riskContribution > 5)
|
|
2474
2364
|
recommendations.push(
|
|
2475
2365
|
"Mark functions with side effects explicitly in their names or docs"
|
|
2476
2366
|
);
|
|
2477
|
-
}
|
|
2478
2367
|
return {
|
|
2479
2368
|
score: Math.round(score),
|
|
2480
2369
|
rating,
|
|
@@ -2483,6 +2372,8 @@ function calculateAiSignalClarity(params) {
|
|
|
2483
2372
|
recommendations
|
|
2484
2373
|
};
|
|
2485
2374
|
}
|
|
2375
|
+
|
|
2376
|
+
// src/metrics/agent-grounding.ts
|
|
2486
2377
|
function calculateAgentGrounding(params) {
|
|
2487
2378
|
const {
|
|
2488
2379
|
deepDirectories,
|
|
@@ -2497,25 +2388,33 @@ function calculateAgentGrounding(params) {
|
|
|
2497
2388
|
inconsistentDomainTerms,
|
|
2498
2389
|
domainVocabularySize
|
|
2499
2390
|
} = params;
|
|
2500
|
-
const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
|
|
2501
2391
|
const structureClarityScore = Math.max(
|
|
2502
2392
|
0,
|
|
2503
|
-
Math.round(
|
|
2393
|
+
Math.round(
|
|
2394
|
+
100 - (totalDirectories > 0 ? deepDirectories / totalDirectories * 80 : 0)
|
|
2395
|
+
)
|
|
2396
|
+
);
|
|
2397
|
+
const selfDocumentationScore = Math.max(
|
|
2398
|
+
0,
|
|
2399
|
+
Math.round(100 - (totalFiles > 0 ? vagueFileNames / totalFiles * 90 : 0))
|
|
2504
2400
|
);
|
|
2505
|
-
const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
|
|
2506
|
-
const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
|
|
2507
2401
|
let entryPointScore = 60;
|
|
2508
2402
|
if (hasRootReadme) entryPointScore += 25;
|
|
2509
2403
|
if (readmeIsFresh) entryPointScore += 10;
|
|
2510
2404
|
const barrelRatio = totalFiles > 0 ? barrelExports / (totalFiles * 0.1) : 0;
|
|
2511
2405
|
entryPointScore += Math.round(Math.min(5, barrelRatio * 5));
|
|
2512
2406
|
entryPointScore = Math.min(100, entryPointScore);
|
|
2513
|
-
const
|
|
2514
|
-
|
|
2515
|
-
|
|
2407
|
+
const apiClarityScore = Math.max(
|
|
2408
|
+
0,
|
|
2409
|
+
Math.round(
|
|
2410
|
+
100 - (totalExports > 0 ? untypedExports / totalExports * 70 : 0)
|
|
2411
|
+
)
|
|
2412
|
+
);
|
|
2516
2413
|
const domainConsistencyScore = Math.max(
|
|
2517
2414
|
0,
|
|
2518
|
-
Math.round(
|
|
2415
|
+
Math.round(
|
|
2416
|
+
100 - (domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize * 80 : 0)
|
|
2417
|
+
)
|
|
2519
2418
|
);
|
|
2520
2419
|
const score = Math.round(
|
|
2521
2420
|
structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
|
|
@@ -2527,35 +2426,30 @@ function calculateAgentGrounding(params) {
|
|
|
2527
2426
|
else if (score >= 30) rating = "poor";
|
|
2528
2427
|
else rating = "disorienting";
|
|
2529
2428
|
const recommendations = [];
|
|
2530
|
-
if (structureClarityScore < 70)
|
|
2429
|
+
if (structureClarityScore < 70)
|
|
2531
2430
|
recommendations.push(
|
|
2532
2431
|
`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
|
|
2533
2432
|
);
|
|
2534
|
-
|
|
2535
|
-
if (selfDocumentationScore < 70) {
|
|
2433
|
+
if (selfDocumentationScore < 70)
|
|
2536
2434
|
recommendations.push(
|
|
2537
2435
|
`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
|
|
2538
2436
|
);
|
|
2539
|
-
|
|
2540
|
-
if (!hasRootReadme) {
|
|
2437
|
+
if (!hasRootReadme)
|
|
2541
2438
|
recommendations.push(
|
|
2542
2439
|
"Add a root README.md so agents understand the project context immediately"
|
|
2543
2440
|
);
|
|
2544
|
-
|
|
2441
|
+
else if (!readmeIsFresh)
|
|
2545
2442
|
recommendations.push(
|
|
2546
2443
|
"Update README.md \u2014 stale entry-point documentation disorients agents"
|
|
2547
2444
|
);
|
|
2548
|
-
|
|
2549
|
-
if (apiClarityScore < 70) {
|
|
2445
|
+
if (apiClarityScore < 70)
|
|
2550
2446
|
recommendations.push(
|
|
2551
2447
|
`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
|
|
2552
2448
|
);
|
|
2553
|
-
|
|
2554
|
-
if (domainConsistencyScore < 70) {
|
|
2449
|
+
if (domainConsistencyScore < 70)
|
|
2555
2450
|
recommendations.push(
|
|
2556
2451
|
`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
|
|
2557
2452
|
);
|
|
2558
|
-
}
|
|
2559
2453
|
return {
|
|
2560
2454
|
score,
|
|
2561
2455
|
rating,
|
|
@@ -2569,6 +2463,8 @@ function calculateAgentGrounding(params) {
|
|
|
2569
2463
|
recommendations
|
|
2570
2464
|
};
|
|
2571
2465
|
}
|
|
2466
|
+
|
|
2467
|
+
// src/metrics/testability-index.ts
|
|
2572
2468
|
function calculateTestabilityIndex(params) {
|
|
2573
2469
|
const {
|
|
2574
2470
|
testFiles,
|
|
@@ -2584,16 +2480,27 @@ function calculateTestabilityIndex(params) {
|
|
|
2584
2480
|
} = params;
|
|
2585
2481
|
const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
|
|
2586
2482
|
const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
|
|
2587
|
-
const
|
|
2588
|
-
|
|
2589
|
-
|
|
2483
|
+
const purityScore = Math.round(
|
|
2484
|
+
(totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
|
|
2485
|
+
);
|
|
2590
2486
|
const dependencyInjectionScore = Math.round(
|
|
2591
|
-
Math.min(
|
|
2487
|
+
Math.min(
|
|
2488
|
+
100,
|
|
2489
|
+
(totalClasses > 0 ? injectionPatterns / totalClasses : 0.5) * 100
|
|
2490
|
+
)
|
|
2491
|
+
);
|
|
2492
|
+
const interfaceFocusScore = Math.max(
|
|
2493
|
+
0,
|
|
2494
|
+
Math.round(
|
|
2495
|
+
100 - (totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces * 80 : 0)
|
|
2496
|
+
)
|
|
2497
|
+
);
|
|
2498
|
+
const observabilityScore = Math.max(
|
|
2499
|
+
0,
|
|
2500
|
+
Math.round(
|
|
2501
|
+
100 - (totalFunctions > 0 ? externalStateMutations / totalFunctions * 100 : 0)
|
|
2502
|
+
)
|
|
2592
2503
|
);
|
|
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
2504
|
const frameworkWeight = hasTestFramework ? 1 : 0.8;
|
|
2598
2505
|
const rawScore = (testCoverageRatio * 0.3 + purityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
|
|
2599
2506
|
const score = Math.max(0, Math.min(100, Math.round(rawScore)));
|
|
@@ -2610,32 +2517,28 @@ function calculateTestabilityIndex(params) {
|
|
|
2610
2517
|
else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
|
|
2611
2518
|
else aiChangeSafetyRating = "blind-risk";
|
|
2612
2519
|
const recommendations = [];
|
|
2613
|
-
if (!hasTestFramework)
|
|
2520
|
+
if (!hasTestFramework)
|
|
2614
2521
|
recommendations.push(
|
|
2615
2522
|
"Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
|
|
2616
2523
|
);
|
|
2617
|
-
}
|
|
2618
2524
|
if (rawCoverageRatio < 0.3) {
|
|
2619
2525
|
const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
|
|
2620
2526
|
recommendations.push(
|
|
2621
2527
|
`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
|
|
2622
2528
|
);
|
|
2623
2529
|
}
|
|
2624
|
-
if (purityScore < 50)
|
|
2530
|
+
if (purityScore < 50)
|
|
2625
2531
|
recommendations.push(
|
|
2626
2532
|
"Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
|
|
2627
2533
|
);
|
|
2628
|
-
|
|
2629
|
-
if (dependencyInjectionScore < 50 && totalClasses > 0) {
|
|
2534
|
+
if (dependencyInjectionScore < 50 && totalClasses > 0)
|
|
2630
2535
|
recommendations.push(
|
|
2631
2536
|
"Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
|
|
2632
2537
|
);
|
|
2633
|
-
|
|
2634
|
-
if (externalStateMutations > totalFunctions * 0.3) {
|
|
2538
|
+
if (externalStateMutations > totalFunctions * 0.3)
|
|
2635
2539
|
recommendations.push(
|
|
2636
2540
|
"Reduce direct state mutations \u2014 return values instead to improve observability"
|
|
2637
2541
|
);
|
|
2638
|
-
}
|
|
2639
2542
|
return {
|
|
2640
2543
|
score,
|
|
2641
2544
|
rating,
|
|
@@ -2650,6 +2553,8 @@ function calculateTestabilityIndex(params) {
|
|
|
2650
2553
|
recommendations
|
|
2651
2554
|
};
|
|
2652
2555
|
}
|
|
2556
|
+
|
|
2557
|
+
// src/metrics/doc-drift.ts
|
|
2653
2558
|
function calculateDocDrift(params) {
|
|
2654
2559
|
const {
|
|
2655
2560
|
uncommentedExports,
|
|
@@ -2672,21 +2577,18 @@ function calculateDocDrift(params) {
|
|
|
2672
2577
|
else if (finalScore < 85) rating = "high";
|
|
2673
2578
|
else rating = "severe";
|
|
2674
2579
|
const recommendations = [];
|
|
2675
|
-
if (outdatedComments > 0)
|
|
2580
|
+
if (outdatedComments > 0)
|
|
2676
2581
|
recommendations.push(
|
|
2677
2582
|
`Update or remove ${outdatedComments} outdated comments that contradict the code.`
|
|
2678
2583
|
);
|
|
2679
|
-
|
|
2680
|
-
if (uncommentedRatio > 0.3) {
|
|
2584
|
+
if (uncommentedRatio > 0.3)
|
|
2681
2585
|
recommendations.push(
|
|
2682
2586
|
`Add JSDoc to ${uncommentedExports} uncommented exports.`
|
|
2683
2587
|
);
|
|
2684
|
-
|
|
2685
|
-
if (undocumentedComplexity > 0) {
|
|
2588
|
+
if (undocumentedComplexity > 0)
|
|
2686
2589
|
recommendations.push(
|
|
2687
2590
|
`Explain the business logic for ${undocumentedComplexity} highly complex functions.`
|
|
2688
2591
|
);
|
|
2689
|
-
}
|
|
2690
2592
|
return {
|
|
2691
2593
|
score: finalScore,
|
|
2692
2594
|
rating,
|
|
@@ -2698,6 +2600,8 @@ function calculateDocDrift(params) {
|
|
|
2698
2600
|
recommendations
|
|
2699
2601
|
};
|
|
2700
2602
|
}
|
|
2603
|
+
|
|
2604
|
+
// src/metrics/dependency-health.ts
|
|
2701
2605
|
function calculateDependencyHealth(params) {
|
|
2702
2606
|
const {
|
|
2703
2607
|
totalPackages,
|
|
@@ -2710,8 +2614,12 @@ function calculateDependencyHealth(params) {
|
|
|
2710
2614
|
const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
|
|
2711
2615
|
const deprecatedScore = Math.max(0, 100 - deprecatedRatio * 500);
|
|
2712
2616
|
const skewScore = Math.max(0, 100 - trainingCutoffSkew * 100);
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2617
|
+
const score = Math.round(
|
|
2618
|
+
Math.min(
|
|
2619
|
+
100,
|
|
2620
|
+
Math.max(0, outdatedScore * 0.3 + deprecatedScore * 0.4 + skewScore * 0.3)
|
|
2621
|
+
)
|
|
2622
|
+
);
|
|
2715
2623
|
let rating;
|
|
2716
2624
|
if (score >= 85) rating = "excellent";
|
|
2717
2625
|
else if (score >= 70) rating = "good";
|
|
@@ -2726,33 +2634,22 @@ function calculateDependencyHealth(params) {
|
|
|
2726
2634
|
else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
|
|
2727
2635
|
else aiKnowledgeConfidence = "blind";
|
|
2728
2636
|
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
|
-
}
|
|
2637
|
+
if (deprecatedPackages > 0)
|
|
2638
|
+
recommendations.push(`Replace ${deprecatedPackages} deprecated packages.`);
|
|
2639
|
+
if (outdatedRatio > 0.2)
|
|
2640
|
+
recommendations.push(`Update ${outdatedPackages} outdated packages.`);
|
|
2641
|
+
if (trainingCutoffSkew > 0.5)
|
|
2642
|
+
recommendations.push("High training cutoff skew detected.");
|
|
2744
2643
|
return {
|
|
2745
2644
|
score,
|
|
2746
2645
|
rating,
|
|
2747
|
-
dimensions: {
|
|
2748
|
-
outdatedPackages,
|
|
2749
|
-
deprecatedPackages,
|
|
2750
|
-
trainingCutoffSkew
|
|
2751
|
-
},
|
|
2646
|
+
dimensions: { outdatedPackages, deprecatedPackages, trainingCutoffSkew },
|
|
2752
2647
|
aiKnowledgeConfidence,
|
|
2753
2648
|
recommendations
|
|
2754
2649
|
};
|
|
2755
2650
|
}
|
|
2651
|
+
|
|
2652
|
+
// src/metrics/change-amplification.ts
|
|
2756
2653
|
function calculateChangeAmplification(params) {
|
|
2757
2654
|
const { files } = params;
|
|
2758
2655
|
if (files.length === 0) {
|
|
@@ -2765,15 +2662,16 @@ function calculateChangeAmplification(params) {
|
|
|
2765
2662
|
recommendations: []
|
|
2766
2663
|
};
|
|
2767
2664
|
}
|
|
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);
|
|
2665
|
+
const hotspots = files.map((f) => ({ ...f, amplificationFactor: f.fanOut + f.fanIn * 0.5 })).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
|
|
2772
2666
|
const maxAmplification = hotspots[0].amplificationFactor;
|
|
2773
2667
|
const avgAmplification = hotspots.reduce((sum, h) => sum + h.amplificationFactor, 0) / hotspots.length;
|
|
2774
|
-
let score =
|
|
2775
|
-
|
|
2776
|
-
|
|
2668
|
+
let score = Math.max(
|
|
2669
|
+
0,
|
|
2670
|
+
Math.min(
|
|
2671
|
+
100,
|
|
2672
|
+
100 - avgAmplification * 5 - (maxAmplification > 20 ? maxAmplification - 20 : 0)
|
|
2673
|
+
)
|
|
2674
|
+
);
|
|
2777
2675
|
let rating = "isolated";
|
|
2778
2676
|
if (score < 40) rating = "explosive";
|
|
2779
2677
|
else if (score < 70) rating = "amplified";
|
|
@@ -2781,12 +2679,12 @@ function calculateChangeAmplification(params) {
|
|
|
2781
2679
|
const recommendations = [];
|
|
2782
2680
|
if (score < 70 && hotspots.length > 0) {
|
|
2783
2681
|
recommendations.push(
|
|
2784
|
-
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling
|
|
2682
|
+
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling.`
|
|
2785
2683
|
);
|
|
2786
2684
|
}
|
|
2787
2685
|
if (maxAmplification > 30) {
|
|
2788
2686
|
recommendations.push(
|
|
2789
|
-
|
|
2687
|
+
"Break down key bottlenecks with amplification factor > 30."
|
|
2790
2688
|
);
|
|
2791
2689
|
}
|
|
2792
2690
|
return {
|
|
@@ -2798,6 +2696,61 @@ function calculateChangeAmplification(params) {
|
|
|
2798
2696
|
recommendations
|
|
2799
2697
|
};
|
|
2800
2698
|
}
|
|
2699
|
+
|
|
2700
|
+
// src/future-proof-metrics.ts
|
|
2701
|
+
function calculateFutureProofScore(params) {
|
|
2702
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2703
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
2704
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
2705
|
+
const overall = Math.round(
|
|
2706
|
+
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
2707
|
+
);
|
|
2708
|
+
const factors = [
|
|
2709
|
+
{
|
|
2710
|
+
name: "Cognitive Load",
|
|
2711
|
+
impact: Math.round(loadScore - 50),
|
|
2712
|
+
description: params.cognitiveLoad.rating
|
|
2713
|
+
},
|
|
2714
|
+
{
|
|
2715
|
+
name: "Pattern Entropy",
|
|
2716
|
+
impact: Math.round(entropyScore - 50),
|
|
2717
|
+
description: params.patternEntropy.rating
|
|
2718
|
+
},
|
|
2719
|
+
{
|
|
2720
|
+
name: "Concept Cohesion",
|
|
2721
|
+
impact: Math.round(cohesionScore - 50),
|
|
2722
|
+
description: params.conceptCohesion.rating
|
|
2723
|
+
}
|
|
2724
|
+
];
|
|
2725
|
+
const recommendations = [];
|
|
2726
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
2727
|
+
recommendations.push({
|
|
2728
|
+
action: rec,
|
|
2729
|
+
estimatedImpact: 5,
|
|
2730
|
+
priority: "medium"
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
2734
|
+
recommendations.push({
|
|
2735
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
2736
|
+
estimatedImpact: 8,
|
|
2737
|
+
priority: "high"
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2741
|
+
return {
|
|
2742
|
+
toolName: "future-proof",
|
|
2743
|
+
score: overall,
|
|
2744
|
+
rawMetrics: {
|
|
2745
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
2746
|
+
entropyScore: params.patternEntropy.entropy,
|
|
2747
|
+
cohesionScore: params.conceptCohesion.score,
|
|
2748
|
+
semanticDistanceAvg
|
|
2749
|
+
},
|
|
2750
|
+
factors,
|
|
2751
|
+
recommendations
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2801
2754
|
function calculateExtendedFutureProofScore(params) {
|
|
2802
2755
|
const loadScore = 100 - params.cognitiveLoad.score;
|
|
2803
2756
|
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
@@ -2806,7 +2759,7 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2806
2759
|
const groundingScore = params.agentGrounding.score;
|
|
2807
2760
|
const testabilityScore = params.testability.score;
|
|
2808
2761
|
const docDriftScore = params.docDrift ? 100 - params.docDrift.score : 100;
|
|
2809
|
-
const depsHealthScore = params.dependencyHealth
|
|
2762
|
+
const depsHealthScore = params.dependencyHealth?.score ?? 100;
|
|
2810
2763
|
let totalWeight = 0.8;
|
|
2811
2764
|
let overall = loadScore * 0.15 + entropyScore * 0.1 + cohesionScore * 0.1 + aiSignalClarityScore * 0.15 + groundingScore * 0.15 + testabilityScore * 0.15;
|
|
2812
2765
|
if (params.docDrift) {
|
|
@@ -2837,7 +2790,7 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2837
2790
|
{
|
|
2838
2791
|
name: "AI Signal Clarity",
|
|
2839
2792
|
impact: Math.round(aiSignalClarityScore - 50),
|
|
2840
|
-
description: `${params.aiSignalClarity.rating} risk
|
|
2793
|
+
description: `${params.aiSignalClarity.rating} risk`
|
|
2841
2794
|
},
|
|
2842
2795
|
{
|
|
2843
2796
|
name: "Agent Grounding",
|
|
@@ -2847,60 +2800,25 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2847
2800
|
{
|
|
2848
2801
|
name: "Testability",
|
|
2849
2802
|
impact: Math.round(testabilityScore - 50),
|
|
2850
|
-
description:
|
|
2803
|
+
description: params.testability.rating
|
|
2851
2804
|
}
|
|
2852
2805
|
];
|
|
2853
2806
|
if (params.docDrift) {
|
|
2854
2807
|
factors.push({
|
|
2855
2808
|
name: "Documentation Drift",
|
|
2856
2809
|
impact: Math.round(docDriftScore - 50),
|
|
2857
|
-
description:
|
|
2810
|
+
description: params.docDrift.rating
|
|
2858
2811
|
});
|
|
2859
2812
|
}
|
|
2860
2813
|
if (params.dependencyHealth) {
|
|
2861
2814
|
factors.push({
|
|
2862
2815
|
name: "Dependency Health",
|
|
2863
2816
|
impact: Math.round(depsHealthScore - 50),
|
|
2864
|
-
description:
|
|
2817
|
+
description: params.dependencyHealth.rating
|
|
2865
2818
|
});
|
|
2866
2819
|
}
|
|
2867
|
-
const recommendations =
|
|
2868
|
-
|
|
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"
|
|
2876
|
-
});
|
|
2877
|
-
}
|
|
2878
|
-
for (const rec of params.testability.recommendations) {
|
|
2879
|
-
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
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;
|
|
2820
|
+
const recommendations = collectFutureProofRecommendations(params);
|
|
2821
|
+
const semanticDistanceAvg = params.semanticDistances?.length ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
2904
2822
|
return {
|
|
2905
2823
|
toolName: "future-proof",
|
|
2906
2824
|
score: overall,
|
|
@@ -3077,16 +2995,19 @@ function getRepoMetadata(directory) {
|
|
|
3077
2995
|
ParseError,
|
|
3078
2996
|
ParserFactory,
|
|
3079
2997
|
PythonParser,
|
|
2998
|
+
SEVERITY_TIME_ESTIMATES,
|
|
3080
2999
|
SIZE_ADJUSTED_THRESHOLDS,
|
|
3081
3000
|
TOOL_NAME_MAP,
|
|
3082
3001
|
TypeScriptParser,
|
|
3083
3002
|
VAGUE_FILE_NAMES,
|
|
3084
3003
|
calculateAgentGrounding,
|
|
3085
3004
|
calculateAiSignalClarity,
|
|
3005
|
+
calculateBusinessROI,
|
|
3086
3006
|
calculateChangeAmplification,
|
|
3087
3007
|
calculateCognitiveLoad,
|
|
3088
3008
|
calculateComprehensionDifficulty,
|
|
3089
3009
|
calculateConceptCohesion,
|
|
3010
|
+
calculateDebtInterest,
|
|
3090
3011
|
calculateDependencyHealth,
|
|
3091
3012
|
calculateDocDrift,
|
|
3092
3013
|
calculateExtendedFutureProofScore,
|
|
@@ -3097,10 +3018,8 @@ function getRepoMetadata(directory) {
|
|
|
3097
3018
|
calculateOverallScore,
|
|
3098
3019
|
calculatePatternEntropy,
|
|
3099
3020
|
calculateProductivityImpact,
|
|
3100
|
-
calculateRemediationVelocity,
|
|
3101
|
-
calculateScoreTrend,
|
|
3102
3021
|
calculateSemanticDistance,
|
|
3103
|
-
|
|
3022
|
+
calculateTechnicalValueChain,
|
|
3104
3023
|
calculateTestabilityIndex,
|
|
3105
3024
|
calculateTokenBudget,
|
|
3106
3025
|
clearHistory,
|
|
@@ -3116,7 +3035,6 @@ function getRepoMetadata(directory) {
|
|
|
3116
3035
|
formatToolScore,
|
|
3117
3036
|
generateHTML,
|
|
3118
3037
|
generateValueChain,
|
|
3119
|
-
getDebtBreakdown,
|
|
3120
3038
|
getElapsedTime,
|
|
3121
3039
|
getFileCommitTimestamps,
|
|
3122
3040
|
getFileExtension,
|
|
@@ -3130,6 +3048,9 @@ function getRepoMetadata(directory) {
|
|
|
3130
3048
|
getRatingWithContext,
|
|
3131
3049
|
getRecommendedThreshold,
|
|
3132
3050
|
getRepoMetadata,
|
|
3051
|
+
getSafetyIcon,
|
|
3052
|
+
getScoreBar,
|
|
3053
|
+
getSeverityColor,
|
|
3133
3054
|
getSupportedLanguages,
|
|
3134
3055
|
getToolWeight,
|
|
3135
3056
|
handleCLIError,
|
|
@@ -3149,5 +3070,6 @@ function getRepoMetadata(directory) {
|
|
|
3149
3070
|
resolveOutputPath,
|
|
3150
3071
|
saveScoreEntry,
|
|
3151
3072
|
scanEntries,
|
|
3152
|
-
scanFiles
|
|
3073
|
+
scanFiles,
|
|
3074
|
+
validateSpokeOutput
|
|
3153
3075
|
});
|