@aiready/core 0.9.38 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
- calculateTechnicalDebtInterest: () => calculateTechnicalDebtInterest,
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-metrics.ts
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(patternImpact * 100)
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(contextImpact * 100)
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(consistencyImpact * 100)
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(hrImpact * 100)
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
- else confidence = 0.35;
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
- function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles, modelTier = "standard") {
1229
- const tierThresholds = CONTEXT_TIER_THRESHOLDS[modelTier];
1230
- const idealBudget = tierThresholds.idealTokens;
1231
- const criticalBudget = tierThresholds.criticalTokens;
1232
- const idealDepth = tierThresholds.idealDepth;
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
- budgetFactor * 0.35 + depthFactor * 0.2 + fragmentationFactor * 0.2 + consistencyFactor * 0.15 + fileFactor * 0.1
1307
+ Math.min(
1308
+ 100,
1309
+ concentrationRatio * 100 + orphanFiles / Math.max(1, totalFiles) * 20
1310
+ )
1250
1311
  );
1251
1312
  let rating;
1252
- if (score < 20) rating = "trivial";
1253
- else if (score < 40) rating = "easy";
1254
- else if (score < 60) rating = "moderate";
1255
- else if (score < 80) rating = "difficult";
1256
- else rating = "expert";
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
- factors: [
1261
- {
1262
- name: "Context Budget",
1263
- contribution: Math.round(budgetFactor * 0.35),
1264
- description: `${Math.round(contextBudget)} tokens required (${modelTier} model tier: ideal <${idealBudget.toLocaleString()})`
1265
- },
1266
- {
1267
- name: "Import Depth",
1268
- contribution: Math.round(depthFactor * 0.2),
1269
- description: `${importDepth.toFixed(1)} average levels (ideal <${idealDepth} for ${modelTier})`
1270
- },
1271
- {
1272
- name: "Code Fragmentation",
1273
- contribution: Math.round(fragmentationFactor * 0.2),
1274
- description: `${(fragmentation * 100).toFixed(0)}% fragmentation`
1275
- },
1276
- {
1277
- name: "Consistency",
1278
- contribution: Math.round(consistencyFactor * 0.15),
1279
- description: `${consistencyScore}/100 consistency score`
1280
- },
1281
- {
1282
- name: "Project Scale",
1283
- contribution: Math.round(fileFactor * 0.1),
1284
- description: `${totalFiles} files analyzed`
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/future-proof-metrics.ts
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 { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
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: domainValue,
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
- function calculateFutureProofScore(params) {
2285
- const loadScore = 100 - params.cognitiveLoad.score;
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.min(
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 = Math.min(1, magicLiterals / Math.max(1, totalSymbols * 2));
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 = Math.min(1, booleanTraps / Math.max(1, totalSymbols));
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.min(
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.min(
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.min(
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.min(
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 AI signal claritys detected";
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(100 - deepDirRatio * 80)
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 untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
2514
- const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
2515
- const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
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(100 - inconsistencyRatio * 80)
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
- } else if (!readmeIsFresh) {
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 purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
2588
- const purityScore = Math.round(purityRatio * 100);
2589
- const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
2483
+ const purityScore = Math.round(
2484
+ (totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5) * 100
2485
+ );
2590
2486
  const dependencyInjectionScore = Math.round(
2591
- Math.min(100, injectionRatio * 100)
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 rawScore = outdatedScore * 0.3 + deprecatedScore * 0.4 + skewScore * 0.3;
2714
- const score = Math.round(Math.min(100, Math.max(0, rawScore)));
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
- `Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`
2732
- );
2733
- }
2734
- if (outdatedRatio > 0.2) {
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 = 100 - avgAmplification * 5;
2775
- if (maxAmplification > 20) score -= maxAmplification - 20;
2776
- score = Math.max(0, Math.min(100, score));
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 (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`
2682
+ `Refactor top hotspot '${hotspots[0].file}' to reduce coupling.`
2785
2683
  );
2786
2684
  }
2787
2685
  if (maxAmplification > 30) {
2788
2686
  recommendations.push(
2789
- `Break down key bottlenecks with amplification factor > 30.`
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 ? params.dependencyHealth.score : 100;
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 (${params.aiSignalClarity.score}/100 raw)`
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: `${params.testability.rating} \u2014 AI changes are ${params.testability.aiChangeSafetyRating}`
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: `${params.docDrift.rating} risk of AI signal clarity from drift`
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: `${params.dependencyHealth.rating} health \u2014 AI knowledge is ${params.dependencyHealth.aiKnowledgeConfidence}`
2817
+ description: params.dependencyHealth.rating
2865
2818
  });
2866
2819
  }
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"
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
- calculateTechnicalDebtInterest,
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
  });