@getcodesentinel/codesentinel 1.6.5 → 1.8.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 +85 -22
- package/dist/index.js +1328 -37
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1392,6 +1392,223 @@ var createSummaryShape = (summary) => ({
|
|
|
1392
1392
|
});
|
|
1393
1393
|
var formatAnalyzeOutput = (summary, mode) => mode === "json" ? JSON.stringify(summary, null, 2) : JSON.stringify(createSummaryShape(summary), null, 2);
|
|
1394
1394
|
|
|
1395
|
+
// src/application/format-explain-output.ts
|
|
1396
|
+
var sortFactorByContribution = (left, right) => right.contribution - left.contribution || left.factorId.localeCompare(right.factorId);
|
|
1397
|
+
var toRiskBand = (score) => {
|
|
1398
|
+
if (score < 25) {
|
|
1399
|
+
return "low";
|
|
1400
|
+
}
|
|
1401
|
+
if (score < 50) {
|
|
1402
|
+
return "moderate";
|
|
1403
|
+
}
|
|
1404
|
+
if (score < 75) {
|
|
1405
|
+
return "high";
|
|
1406
|
+
}
|
|
1407
|
+
return "very_high";
|
|
1408
|
+
};
|
|
1409
|
+
var factorLabelById = {
|
|
1410
|
+
"repository.structural": "Structural complexity",
|
|
1411
|
+
"repository.evolution": "Change volatility",
|
|
1412
|
+
"repository.external": "External dependency pressure",
|
|
1413
|
+
"repository.composite.interactions": "Intersection amplification",
|
|
1414
|
+
"file.structural": "File structural complexity",
|
|
1415
|
+
"file.evolution": "File change volatility",
|
|
1416
|
+
"file.external": "File external pressure",
|
|
1417
|
+
"file.composite.interactions": "File interaction amplification",
|
|
1418
|
+
"module.average_file_risk": "Average file risk",
|
|
1419
|
+
"module.peak_file_risk": "Peak file risk",
|
|
1420
|
+
"dependency.signals": "Dependency risk signals",
|
|
1421
|
+
"dependency.staleness": "Dependency staleness",
|
|
1422
|
+
"dependency.maintainer_concentration": "Maintainer concentration",
|
|
1423
|
+
"dependency.topology": "Dependency topology pressure",
|
|
1424
|
+
"dependency.bus_factor": "Dependency bus factor",
|
|
1425
|
+
"dependency.popularity_dampening": "Popularity dampening"
|
|
1426
|
+
};
|
|
1427
|
+
var formatFactorLabel = (factorId) => factorLabelById[factorId] ?? factorId;
|
|
1428
|
+
var formatNumber = (value) => value === null || value === void 0 ? "n/a" : `${value}`;
|
|
1429
|
+
var formatFactorSummary = (factor) => `${formatFactorLabel(factor.factorId)} (+${factor.contribution}, confidence=${factor.confidence})`;
|
|
1430
|
+
var formatFactorEvidence = (factor) => {
|
|
1431
|
+
if (factor.factorId === "repository.structural") {
|
|
1432
|
+
return `structural dimension=${formatNumber(factor.rawMetrics["structuralDimension"])}`;
|
|
1433
|
+
}
|
|
1434
|
+
if (factor.factorId === "repository.evolution") {
|
|
1435
|
+
return `evolution dimension=${formatNumber(factor.rawMetrics["evolutionDimension"])}`;
|
|
1436
|
+
}
|
|
1437
|
+
if (factor.factorId === "repository.external") {
|
|
1438
|
+
return `external dimension=${formatNumber(factor.rawMetrics["externalDimension"])}`;
|
|
1439
|
+
}
|
|
1440
|
+
if (factor.factorId === "repository.composite.interactions") {
|
|
1441
|
+
return `structural\xD7evolution=${formatNumber(factor.rawMetrics["structuralEvolution"])}, central instability=${formatNumber(factor.rawMetrics["centralInstability"])}, dependency amplification=${formatNumber(factor.rawMetrics["dependencyAmplification"])}`;
|
|
1442
|
+
}
|
|
1443
|
+
if (factor.factorId === "file.structural") {
|
|
1444
|
+
return `fanIn=${formatNumber(factor.rawMetrics["fanIn"])}, fanOut=${formatNumber(factor.rawMetrics["fanOut"])}, depth=${formatNumber(factor.rawMetrics["depth"])}, inCycle=${formatNumber(factor.rawMetrics["cycleParticipation"])}`;
|
|
1445
|
+
}
|
|
1446
|
+
if (factor.factorId === "file.evolution") {
|
|
1447
|
+
return `commitCount=${formatNumber(factor.rawMetrics["commitCount"])}, churnTotal=${formatNumber(factor.rawMetrics["churnTotal"])}, recentVolatility=${formatNumber(factor.rawMetrics["recentVolatility"])}`;
|
|
1448
|
+
}
|
|
1449
|
+
if (factor.factorId === "file.external") {
|
|
1450
|
+
return `repositoryExternalPressure=${formatNumber(factor.rawMetrics["repositoryExternalPressure"])}, dependencyAffinity=${formatNumber(factor.rawMetrics["dependencyAffinity"])}`;
|
|
1451
|
+
}
|
|
1452
|
+
if (factor.factorId === "file.composite.interactions") {
|
|
1453
|
+
return `structural\xD7evolution=${formatNumber(factor.rawMetrics["structuralEvolutionInteraction"])}, central instability=${formatNumber(factor.rawMetrics["centralInstabilityInteraction"])}, dependency amplification=${formatNumber(factor.rawMetrics["dependencyAmplificationInteraction"])}`;
|
|
1454
|
+
}
|
|
1455
|
+
return "evidence available in trace";
|
|
1456
|
+
};
|
|
1457
|
+
var findRepositoryTarget = (targets) => targets.find((target) => target.targetType === "repository");
|
|
1458
|
+
var buildRepositoryActions = (payload, repositoryTarget) => {
|
|
1459
|
+
if (repositoryTarget === void 0) {
|
|
1460
|
+
return ["No repository trace available."];
|
|
1461
|
+
}
|
|
1462
|
+
const topHotspots = payload.summary.risk.hotspots.slice(0, 3).map((hotspot) => hotspot.file);
|
|
1463
|
+
const highRiskDependencies = payload.summary.external.available ? payload.summary.external.highRiskDependencies.slice(0, 3) : [];
|
|
1464
|
+
const actions = [];
|
|
1465
|
+
for (const lever of repositoryTarget.reductionLevers) {
|
|
1466
|
+
if (lever.factorId === "repository.evolution") {
|
|
1467
|
+
actions.push(
|
|
1468
|
+
`Reduce volatility/churn in top hotspots first: ${topHotspots.join(", ") || "no hotspots available"}.`
|
|
1469
|
+
);
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
if (lever.factorId === "repository.structural") {
|
|
1473
|
+
actions.push(
|
|
1474
|
+
`Lower fan-in/fan-out and break cycles in central files: ${topHotspots.join(", ") || "no hotspots available"}.`
|
|
1475
|
+
);
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
if (lever.factorId === "repository.composite.interactions") {
|
|
1479
|
+
actions.push(
|
|
1480
|
+
"Stabilize central files before refactors; interaction effects are amplifying risk."
|
|
1481
|
+
);
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (lever.factorId === "repository.external") {
|
|
1485
|
+
actions.push(
|
|
1486
|
+
`Review high-risk direct dependencies: ${highRiskDependencies.join(", ") || "none detected"}.`
|
|
1487
|
+
);
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (actions.length === 0) {
|
|
1492
|
+
actions.push("No clear reduction levers available from current trace.");
|
|
1493
|
+
}
|
|
1494
|
+
return actions.slice(0, 3);
|
|
1495
|
+
};
|
|
1496
|
+
var renderTargetText = (target) => {
|
|
1497
|
+
const lines = [];
|
|
1498
|
+
lines.push(`${target.targetType}: ${target.targetId}`);
|
|
1499
|
+
lines.push(` score: ${target.totalScore} (${target.normalizedScore})`);
|
|
1500
|
+
lines.push(" top factors:");
|
|
1501
|
+
const topFactors = [...target.factors].sort(sortFactorByContribution).slice(0, 5);
|
|
1502
|
+
for (const factor of topFactors) {
|
|
1503
|
+
lines.push(
|
|
1504
|
+
` - ${formatFactorSummary(factor)}`
|
|
1505
|
+
);
|
|
1506
|
+
lines.push(
|
|
1507
|
+
` evidence: ${formatFactorEvidence(factor)}`
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
lines.push(" reduction levers:");
|
|
1511
|
+
for (const lever of target.reductionLevers) {
|
|
1512
|
+
lines.push(
|
|
1513
|
+
` - ${formatFactorLabel(lever.factorId)} | estimatedImpact=${lever.estimatedImpact}`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
return lines.join("\n");
|
|
1517
|
+
};
|
|
1518
|
+
var renderText = (payload) => {
|
|
1519
|
+
const lines = [];
|
|
1520
|
+
const repositoryTarget = findRepositoryTarget(payload.selectedTargets) ?? findRepositoryTarget(payload.trace.targets);
|
|
1521
|
+
const repositoryTopFactors = repositoryTarget === void 0 ? [] : [...repositoryTarget.factors].sort(sortFactorByContribution).slice(0, 3);
|
|
1522
|
+
const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
|
|
1523
|
+
lines.push(`target: ${payload.summary.structural.targetPath}`);
|
|
1524
|
+
lines.push(`repositoryScore: ${payload.summary.risk.repositoryScore}`);
|
|
1525
|
+
lines.push(`riskBand: ${toRiskBand(payload.summary.risk.repositoryScore)}`);
|
|
1526
|
+
lines.push(`selectedTargets: ${payload.selectedTargets.length}`);
|
|
1527
|
+
lines.push("");
|
|
1528
|
+
lines.push("explanation:");
|
|
1529
|
+
lines.push(
|
|
1530
|
+
` why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
|
|
1531
|
+
);
|
|
1532
|
+
lines.push(
|
|
1533
|
+
` what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
|
|
1534
|
+
);
|
|
1535
|
+
lines.push(
|
|
1536
|
+
` dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
|
|
1537
|
+
);
|
|
1538
|
+
lines.push(
|
|
1539
|
+
` intersected signals: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
|
|
1540
|
+
);
|
|
1541
|
+
lines.push(
|
|
1542
|
+
` what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
|
|
1543
|
+
);
|
|
1544
|
+
lines.push("");
|
|
1545
|
+
for (const target of payload.selectedTargets) {
|
|
1546
|
+
lines.push(renderTargetText(target));
|
|
1547
|
+
lines.push("");
|
|
1548
|
+
}
|
|
1549
|
+
return lines.join("\n").trimEnd();
|
|
1550
|
+
};
|
|
1551
|
+
var renderMarkdown = (payload) => {
|
|
1552
|
+
const lines = [];
|
|
1553
|
+
const repositoryTarget = findRepositoryTarget(payload.selectedTargets) ?? findRepositoryTarget(payload.trace.targets);
|
|
1554
|
+
const repositoryTopFactors = repositoryTarget === void 0 ? [] : [...repositoryTarget.factors].sort(sortFactorByContribution).slice(0, 3);
|
|
1555
|
+
const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
|
|
1556
|
+
lines.push(`# CodeSentinel Explanation`);
|
|
1557
|
+
lines.push(`- target: \`${payload.summary.structural.targetPath}\``);
|
|
1558
|
+
lines.push(`- repositoryScore: \`${payload.summary.risk.repositoryScore}\``);
|
|
1559
|
+
lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.repositoryScore)}\``);
|
|
1560
|
+
lines.push(`- selectedTargets: \`${payload.selectedTargets.length}\``);
|
|
1561
|
+
lines.push("");
|
|
1562
|
+
lines.push(`## Summary`);
|
|
1563
|
+
lines.push(
|
|
1564
|
+
`- why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
|
|
1565
|
+
);
|
|
1566
|
+
lines.push(
|
|
1567
|
+
`- what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
|
|
1568
|
+
);
|
|
1569
|
+
lines.push(
|
|
1570
|
+
`- dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
|
|
1571
|
+
);
|
|
1572
|
+
lines.push(
|
|
1573
|
+
`- intersected signals: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
|
|
1574
|
+
);
|
|
1575
|
+
lines.push(
|
|
1576
|
+
`- what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
|
|
1577
|
+
);
|
|
1578
|
+
lines.push("");
|
|
1579
|
+
for (const target of payload.selectedTargets) {
|
|
1580
|
+
lines.push(`## ${target.targetType}: \`${target.targetId}\``);
|
|
1581
|
+
lines.push(`- score: \`${target.totalScore}\` (\`${target.normalizedScore}\`)`);
|
|
1582
|
+
lines.push(`- dominantFactors: \`${target.dominantFactors.join(", ")}\``);
|
|
1583
|
+
lines.push(`- Top factors:`);
|
|
1584
|
+
for (const factor of [...target.factors].sort(sortFactorByContribution).slice(0, 5)) {
|
|
1585
|
+
lines.push(
|
|
1586
|
+
` - \`${formatFactorLabel(factor.factorId)}\` contribution=\`${factor.contribution}\` confidence=\`${factor.confidence}\``
|
|
1587
|
+
);
|
|
1588
|
+
lines.push(
|
|
1589
|
+
` - evidence: \`${formatFactorEvidence(factor)}\``
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
lines.push(`- Reduction levers:`);
|
|
1593
|
+
for (const lever of target.reductionLevers) {
|
|
1594
|
+
lines.push(
|
|
1595
|
+
` - \`${formatFactorLabel(lever.factorId)}\` estimatedImpact=\`${lever.estimatedImpact}\``
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
lines.push("");
|
|
1599
|
+
}
|
|
1600
|
+
return lines.join("\n").trimEnd();
|
|
1601
|
+
};
|
|
1602
|
+
var formatExplainOutput = (payload, format) => {
|
|
1603
|
+
if (format === "json") {
|
|
1604
|
+
return JSON.stringify(payload, null, 2);
|
|
1605
|
+
}
|
|
1606
|
+
if (format === "md") {
|
|
1607
|
+
return renderMarkdown(payload);
|
|
1608
|
+
}
|
|
1609
|
+
return renderText(payload);
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1395
1612
|
// src/application/format-dependency-risk-output.ts
|
|
1396
1613
|
var createSummaryShape2 = (result) => {
|
|
1397
1614
|
if (!result.available) {
|
|
@@ -1401,18 +1618,19 @@ var createSummaryShape2 = (result) => {
|
|
|
1401
1618
|
dependency: result.dependency
|
|
1402
1619
|
};
|
|
1403
1620
|
}
|
|
1404
|
-
const direct = result.external.dependencies[0];
|
|
1621
|
+
const direct = result.external.available ? result.external.dependencies[0] : void 0;
|
|
1405
1622
|
return {
|
|
1406
1623
|
available: true,
|
|
1407
1624
|
dependency: result.dependency,
|
|
1408
1625
|
graph: result.graph,
|
|
1409
1626
|
assumptions: result.assumptions,
|
|
1410
1627
|
external: {
|
|
1411
|
-
|
|
1628
|
+
available: result.external.available,
|
|
1629
|
+
metrics: result.external.available ? result.external.metrics : null,
|
|
1412
1630
|
ownRiskSignals: direct?.ownRiskSignals ?? [],
|
|
1413
1631
|
inheritedRiskSignals: direct?.inheritedRiskSignals ?? [],
|
|
1414
|
-
highRiskDependenciesTop: result.external.highRiskDependencies.slice(0, 10),
|
|
1415
|
-
transitiveExposureDependenciesTop: result.external.transitiveExposureDependencies.slice(0, 10)
|
|
1632
|
+
highRiskDependenciesTop: result.external.available ? result.external.highRiskDependencies.slice(0, 10) : [],
|
|
1633
|
+
transitiveExposureDependenciesTop: result.external.available ? result.external.transitiveExposureDependencies.slice(0, 10) : []
|
|
1416
1634
|
}
|
|
1417
1635
|
};
|
|
1418
1636
|
};
|
|
@@ -2696,11 +2914,86 @@ var computeDependencySignalScore = (ownSignals, inheritedSignals, inheritedSigna
|
|
|
2696
2914
|
}
|
|
2697
2915
|
return toUnitInterval(weightedTotal / maxWeightedTotal);
|
|
2698
2916
|
};
|
|
2917
|
+
var clampConfidence = (value) => round44(toUnitInterval(value));
|
|
2918
|
+
var buildFactorTraces = (totalScore, inputs) => {
|
|
2919
|
+
const positiveInputs = inputs.filter((input) => input.strength > 0);
|
|
2920
|
+
const strengthTotal = positiveInputs.reduce((sum, input) => sum + input.strength, 0);
|
|
2921
|
+
const traces = inputs.map((input) => ({
|
|
2922
|
+
factorId: input.factorId,
|
|
2923
|
+
family: input.family,
|
|
2924
|
+
contribution: 0,
|
|
2925
|
+
rawMetrics: input.rawMetrics,
|
|
2926
|
+
normalizedMetrics: input.normalizedMetrics,
|
|
2927
|
+
weight: input.weight,
|
|
2928
|
+
amplification: input.amplification,
|
|
2929
|
+
evidence: input.evidence,
|
|
2930
|
+
confidence: clampConfidence(input.confidence)
|
|
2931
|
+
}));
|
|
2932
|
+
if (strengthTotal <= 0 || totalScore <= 0) {
|
|
2933
|
+
return traces;
|
|
2934
|
+
}
|
|
2935
|
+
const scored = positiveInputs.map((input) => ({
|
|
2936
|
+
factorId: input.factorId,
|
|
2937
|
+
contribution: totalScore * input.strength / strengthTotal
|
|
2938
|
+
}));
|
|
2939
|
+
let distributed = 0;
|
|
2940
|
+
for (let index = 0; index < scored.length; index += 1) {
|
|
2941
|
+
const current = scored[index];
|
|
2942
|
+
if (current === void 0) {
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
const traceIndex = traces.findIndex((trace) => trace.factorId === current.factorId);
|
|
2946
|
+
if (traceIndex < 0) {
|
|
2947
|
+
continue;
|
|
2948
|
+
}
|
|
2949
|
+
const existing = traces[traceIndex];
|
|
2950
|
+
if (existing === void 0) {
|
|
2951
|
+
continue;
|
|
2952
|
+
}
|
|
2953
|
+
if (index === scored.length - 1) {
|
|
2954
|
+
const remaining = round44(totalScore - distributed);
|
|
2955
|
+
traces[traceIndex] = {
|
|
2956
|
+
...existing,
|
|
2957
|
+
contribution: Math.max(0, remaining)
|
|
2958
|
+
};
|
|
2959
|
+
distributed += Math.max(0, remaining);
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
const rounded = round44(current.contribution);
|
|
2963
|
+
traces[traceIndex] = {
|
|
2964
|
+
...existing,
|
|
2965
|
+
contribution: rounded
|
|
2966
|
+
};
|
|
2967
|
+
distributed += rounded;
|
|
2968
|
+
}
|
|
2969
|
+
return traces;
|
|
2970
|
+
};
|
|
2971
|
+
var buildReductionLevers = (factors) => factors.filter((factor) => factor.contribution > 0).sort(
|
|
2972
|
+
(a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)
|
|
2973
|
+
).slice(0, 3).map((factor) => ({
|
|
2974
|
+
factorId: factor.factorId,
|
|
2975
|
+
estimatedImpact: round44(factor.contribution)
|
|
2976
|
+
}));
|
|
2977
|
+
var buildTargetTrace = (targetType, targetId, totalScore, normalizedScore, factors) => {
|
|
2978
|
+
const dominantFactors = [...factors].filter((factor) => factor.contribution > 0).sort(
|
|
2979
|
+
(a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)
|
|
2980
|
+
).slice(0, 3).map((factor) => factor.factorId);
|
|
2981
|
+
return {
|
|
2982
|
+
targetType,
|
|
2983
|
+
targetId,
|
|
2984
|
+
totalScore: round44(totalScore),
|
|
2985
|
+
normalizedScore: round44(normalizedScore),
|
|
2986
|
+
factors,
|
|
2987
|
+
dominantFactors,
|
|
2988
|
+
reductionLevers: buildReductionLevers(factors)
|
|
2989
|
+
};
|
|
2990
|
+
};
|
|
2699
2991
|
var computeDependencyScores = (external, config) => {
|
|
2700
2992
|
if (!external.available) {
|
|
2701
2993
|
return {
|
|
2702
2994
|
dependencyScores: [],
|
|
2703
|
-
repositoryExternalPressure: 0
|
|
2995
|
+
repositoryExternalPressure: 0,
|
|
2996
|
+
dependencyContexts: /* @__PURE__ */ new Map()
|
|
2704
2997
|
};
|
|
2705
2998
|
}
|
|
2706
2999
|
const transitiveCounts = external.dependencies.map(
|
|
@@ -2723,6 +3016,7 @@ var computeDependencyScores = (external, config) => {
|
|
|
2723
3016
|
config.quantileClamp.lower,
|
|
2724
3017
|
config.quantileClamp.upper
|
|
2725
3018
|
);
|
|
3019
|
+
const dependencyContexts = /* @__PURE__ */ new Map();
|
|
2726
3020
|
const dependencyScores = external.dependencies.map((dependency) => {
|
|
2727
3021
|
const signalScore = computeDependencySignalScore(
|
|
2728
3022
|
dependency.ownRiskSignals,
|
|
@@ -2751,6 +3045,33 @@ var computeDependencyScores = (external, config) => {
|
|
|
2751
3045
|
config.dependencySignals.popularityHalfLifeDownloads
|
|
2752
3046
|
) * config.dependencySignals.popularityMaxDampening;
|
|
2753
3047
|
const normalizedScore = toUnitInterval(baseScore * popularityDampener);
|
|
3048
|
+
const availableMetricCount = [
|
|
3049
|
+
dependency.daysSinceLastRelease,
|
|
3050
|
+
dependency.maintainerCount,
|
|
3051
|
+
dependency.busFactor,
|
|
3052
|
+
dependency.weeklyDownloads
|
|
3053
|
+
].filter((value) => value !== null).length;
|
|
3054
|
+
const confidence = toUnitInterval(0.5 + availableMetricCount * 0.125);
|
|
3055
|
+
dependencyContexts.set(dependency.name, {
|
|
3056
|
+
signalScore: round44(signalScore),
|
|
3057
|
+
stalenessRisk: round44(stalenessRisk),
|
|
3058
|
+
maintainerConcentrationRisk: round44(maintainerConcentrationRisk),
|
|
3059
|
+
transitiveBurdenRisk: round44(transitiveBurdenRisk),
|
|
3060
|
+
centralityRisk: round44(centralityRisk),
|
|
3061
|
+
chainDepthRisk: round44(chainDepthRisk),
|
|
3062
|
+
busFactorRisk: round44(busFactorRisk),
|
|
3063
|
+
popularityDampener: round44(popularityDampener),
|
|
3064
|
+
rawMetrics: {
|
|
3065
|
+
daysSinceLastRelease: dependency.daysSinceLastRelease,
|
|
3066
|
+
maintainerCount: dependency.maintainerCount,
|
|
3067
|
+
transitiveCount: dependency.transitiveDependencies.length,
|
|
3068
|
+
dependents: dependency.dependents,
|
|
3069
|
+
dependencyDepth: dependency.dependencyDepth,
|
|
3070
|
+
busFactor: dependency.busFactor,
|
|
3071
|
+
weeklyDownloads: dependency.weeklyDownloads
|
|
3072
|
+
},
|
|
3073
|
+
confidence: round44(confidence)
|
|
3074
|
+
});
|
|
2754
3075
|
return {
|
|
2755
3076
|
dependency: dependency.name,
|
|
2756
3077
|
score: round44(normalizedScore * 100),
|
|
@@ -2773,7 +3094,8 @@ var computeDependencyScores = (external, config) => {
|
|
|
2773
3094
|
);
|
|
2774
3095
|
return {
|
|
2775
3096
|
dependencyScores,
|
|
2776
|
-
repositoryExternalPressure: round44(repositoryExternalPressure)
|
|
3097
|
+
repositoryExternalPressure: round44(repositoryExternalPressure),
|
|
3098
|
+
dependencyContexts
|
|
2777
3099
|
};
|
|
2778
3100
|
};
|
|
2779
3101
|
var mapEvolutionByFile = (evolution) => {
|
|
@@ -2925,7 +3247,8 @@ var buildFragileClusters = (structural, evolution, fileScoresByFile, config) =>
|
|
|
2925
3247
|
(a, b) => b.score - a.score || a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)
|
|
2926
3248
|
);
|
|
2927
3249
|
};
|
|
2928
|
-
var computeRiskSummary = (structural, evolution, external, config) => {
|
|
3250
|
+
var computeRiskSummary = (structural, evolution, external, config, traceCollector) => {
|
|
3251
|
+
const collector = traceCollector;
|
|
2929
3252
|
const dependencyComputation = computeDependencyScores(external, config);
|
|
2930
3253
|
const evolutionByFile = mapEvolutionByFile(evolution);
|
|
2931
3254
|
const evolutionScales = computeEvolutionScales(evolutionByFile, config);
|
|
@@ -2964,19 +3287,20 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
2964
3287
|
);
|
|
2965
3288
|
const structuralCentrality = toUnitInterval((fanInRisk + fanOutRisk) / 2);
|
|
2966
3289
|
let evolutionFactor = 0;
|
|
3290
|
+
let frequencyRisk = 0;
|
|
3291
|
+
let churnRisk = 0;
|
|
3292
|
+
let volatilityRisk = 0;
|
|
3293
|
+
let ownershipConcentrationRisk = 0;
|
|
3294
|
+
let busFactorRisk = 0;
|
|
2967
3295
|
const evolutionMetrics = evolutionByFile.get(filePath);
|
|
2968
3296
|
if (evolution.available && evolutionMetrics !== void 0) {
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3297
|
+
frequencyRisk = normalizeWithScale(logScale(evolutionMetrics.commitCount), evolutionScales.commitCount);
|
|
3298
|
+
churnRisk = normalizeWithScale(logScale(evolutionMetrics.churnTotal), evolutionScales.churnTotal);
|
|
3299
|
+
volatilityRisk = toUnitInterval(evolutionMetrics.recentVolatility);
|
|
3300
|
+
ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.topAuthorShare);
|
|
3301
|
+
busFactorRisk = toUnitInterval(
|
|
3302
|
+
1 - normalizeWithScale(evolutionMetrics.busFactor, evolutionScales.busFactor)
|
|
2972
3303
|
);
|
|
2973
|
-
const churnRisk = normalizeWithScale(
|
|
2974
|
-
logScale(evolutionMetrics.churnTotal),
|
|
2975
|
-
evolutionScales.churnTotal
|
|
2976
|
-
);
|
|
2977
|
-
const volatilityRisk = toUnitInterval(evolutionMetrics.recentVolatility);
|
|
2978
|
-
const ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.topAuthorShare);
|
|
2979
|
-
const busFactorRisk = toUnitInterval(1 - normalizeWithScale(evolutionMetrics.busFactor, evolutionScales.busFactor));
|
|
2980
3304
|
const evolutionWeights = config.evolutionFactorWeights;
|
|
2981
3305
|
evolutionFactor = toUnitInterval(
|
|
2982
3306
|
frequencyRisk * evolutionWeights.frequency + churnRisk * evolutionWeights.churn + volatilityRisk * evolutionWeights.recentVolatility + ownershipConcentrationRisk * evolutionWeights.ownershipConcentration + busFactorRisk * evolutionWeights.busFactorRisk
|
|
@@ -2984,11 +3308,17 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
2984
3308
|
}
|
|
2985
3309
|
const dependencyAffinity = toUnitInterval(structuralCentrality * 0.6 + evolutionFactor * 0.4);
|
|
2986
3310
|
const externalFactor = external.available ? toUnitInterval(dependencyComputation.repositoryExternalPressure * dependencyAffinity) : 0;
|
|
2987
|
-
const
|
|
3311
|
+
const structuralBase = structuralFactor * dimensionWeights.structural;
|
|
3312
|
+
const evolutionBase = evolutionFactor * dimensionWeights.evolution;
|
|
3313
|
+
const externalBase = externalFactor * dimensionWeights.external;
|
|
3314
|
+
const baseline = structuralBase + evolutionBase + externalBase;
|
|
3315
|
+
const interactionStructuralEvolution = structuralFactor * evolutionFactor * config.interactionWeights.structuralEvolution;
|
|
3316
|
+
const interactionCentralInstability = structuralCentrality * evolutionFactor * config.interactionWeights.centralInstability;
|
|
3317
|
+
const interactionDependencyAmplification = externalFactor * Math.max(structuralFactor, evolutionFactor) * config.interactionWeights.dependencyAmplification;
|
|
2988
3318
|
const interactions = [
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3319
|
+
interactionStructuralEvolution,
|
|
3320
|
+
interactionCentralInstability,
|
|
3321
|
+
interactionDependencyAmplification
|
|
2992
3322
|
];
|
|
2993
3323
|
const normalizedScore = saturatingComposite(baseline, interactions);
|
|
2994
3324
|
return {
|
|
@@ -3000,7 +3330,38 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
3000
3330
|
evolution: round44(evolutionFactor),
|
|
3001
3331
|
external: round44(externalFactor)
|
|
3002
3332
|
},
|
|
3003
|
-
structuralCentrality: round44(structuralCentrality)
|
|
3333
|
+
structuralCentrality: round44(structuralCentrality),
|
|
3334
|
+
traceTerms: {
|
|
3335
|
+
structuralBase: round44(structuralBase),
|
|
3336
|
+
evolutionBase: round44(evolutionBase),
|
|
3337
|
+
externalBase: round44(externalBase),
|
|
3338
|
+
interactionStructuralEvolution: round44(interactionStructuralEvolution),
|
|
3339
|
+
interactionCentralInstability: round44(interactionCentralInstability),
|
|
3340
|
+
interactionDependencyAmplification: round44(interactionDependencyAmplification)
|
|
3341
|
+
},
|
|
3342
|
+
rawMetrics: {
|
|
3343
|
+
fanIn: file.fanIn,
|
|
3344
|
+
fanOut: file.fanOut,
|
|
3345
|
+
depth: file.depth,
|
|
3346
|
+
cycleParticipation: inCycle,
|
|
3347
|
+
commitCount: evolutionMetrics?.commitCount ?? null,
|
|
3348
|
+
churnTotal: evolutionMetrics?.churnTotal ?? null,
|
|
3349
|
+
recentVolatility: evolutionMetrics?.recentVolatility ?? null,
|
|
3350
|
+
topAuthorShare: evolutionMetrics?.topAuthorShare ?? null,
|
|
3351
|
+
busFactor: evolutionMetrics?.busFactor ?? null,
|
|
3352
|
+
dependencyAffinity: round44(dependencyAffinity),
|
|
3353
|
+
repositoryExternalPressure: round44(dependencyComputation.repositoryExternalPressure)
|
|
3354
|
+
},
|
|
3355
|
+
normalizedMetrics: {
|
|
3356
|
+
fanInRisk: round44(fanInRisk),
|
|
3357
|
+
fanOutRisk: round44(fanOutRisk),
|
|
3358
|
+
depthRisk: round44(depthRisk),
|
|
3359
|
+
frequencyRisk: round44(frequencyRisk),
|
|
3360
|
+
churnRisk: round44(churnRisk),
|
|
3361
|
+
volatilityRisk: round44(volatilityRisk),
|
|
3362
|
+
ownershipConcentrationRisk: round44(ownershipConcentrationRisk),
|
|
3363
|
+
busFactorRisk: round44(busFactorRisk)
|
|
3364
|
+
}
|
|
3004
3365
|
};
|
|
3005
3366
|
}).sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
|
|
3006
3367
|
const fileScores = fileRiskContexts.map((context) => ({
|
|
@@ -3009,6 +3370,103 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
3009
3370
|
normalizedScore: context.normalizedScore,
|
|
3010
3371
|
factors: context.factors
|
|
3011
3372
|
}));
|
|
3373
|
+
if (collector !== void 0) {
|
|
3374
|
+
for (const context of fileRiskContexts) {
|
|
3375
|
+
const evidence = [
|
|
3376
|
+
{ kind: "file_metric", target: context.file, metric: "fanIn" },
|
|
3377
|
+
{ kind: "file_metric", target: context.file, metric: "fanOut" },
|
|
3378
|
+
{ kind: "file_metric", target: context.file, metric: "depth" }
|
|
3379
|
+
];
|
|
3380
|
+
if (context.rawMetrics.cycleParticipation > 0) {
|
|
3381
|
+
evidence.push({
|
|
3382
|
+
kind: "graph_cycle",
|
|
3383
|
+
cycleId: `file:${context.file}`,
|
|
3384
|
+
files: [context.file]
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3387
|
+
const fileFactors = buildFactorTraces(context.score, [
|
|
3388
|
+
{
|
|
3389
|
+
factorId: "file.structural",
|
|
3390
|
+
family: "structural",
|
|
3391
|
+
strength: context.traceTerms.structuralBase,
|
|
3392
|
+
rawMetrics: {
|
|
3393
|
+
fanIn: context.rawMetrics.fanIn,
|
|
3394
|
+
fanOut: context.rawMetrics.fanOut,
|
|
3395
|
+
depth: context.rawMetrics.depth,
|
|
3396
|
+
cycleParticipation: context.rawMetrics.cycleParticipation
|
|
3397
|
+
},
|
|
3398
|
+
normalizedMetrics: {
|
|
3399
|
+
fanInRisk: context.normalizedMetrics.fanInRisk,
|
|
3400
|
+
fanOutRisk: context.normalizedMetrics.fanOutRisk,
|
|
3401
|
+
depthRisk: context.normalizedMetrics.depthRisk,
|
|
3402
|
+
structuralFactor: context.factors.structural
|
|
3403
|
+
},
|
|
3404
|
+
weight: dimensionWeights.structural,
|
|
3405
|
+
amplification: null,
|
|
3406
|
+
evidence,
|
|
3407
|
+
confidence: 1
|
|
3408
|
+
},
|
|
3409
|
+
{
|
|
3410
|
+
factorId: "file.evolution",
|
|
3411
|
+
family: "evolution",
|
|
3412
|
+
strength: context.traceTerms.evolutionBase,
|
|
3413
|
+
rawMetrics: {
|
|
3414
|
+
commitCount: context.rawMetrics.commitCount,
|
|
3415
|
+
churnTotal: context.rawMetrics.churnTotal,
|
|
3416
|
+
recentVolatility: context.rawMetrics.recentVolatility,
|
|
3417
|
+
topAuthorShare: context.rawMetrics.topAuthorShare,
|
|
3418
|
+
busFactor: context.rawMetrics.busFactor
|
|
3419
|
+
},
|
|
3420
|
+
normalizedMetrics: {
|
|
3421
|
+
frequencyRisk: context.normalizedMetrics.frequencyRisk,
|
|
3422
|
+
churnRisk: context.normalizedMetrics.churnRisk,
|
|
3423
|
+
volatilityRisk: context.normalizedMetrics.volatilityRisk,
|
|
3424
|
+
ownershipConcentrationRisk: context.normalizedMetrics.ownershipConcentrationRisk,
|
|
3425
|
+
busFactorRisk: context.normalizedMetrics.busFactorRisk,
|
|
3426
|
+
evolutionFactor: context.factors.evolution
|
|
3427
|
+
},
|
|
3428
|
+
weight: dimensionWeights.evolution,
|
|
3429
|
+
amplification: null,
|
|
3430
|
+
evidence: [{ kind: "file_metric", target: context.file, metric: "commitCount" }],
|
|
3431
|
+
confidence: evolution.available ? 1 : 0
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
factorId: "file.external",
|
|
3435
|
+
family: "external",
|
|
3436
|
+
strength: context.traceTerms.externalBase,
|
|
3437
|
+
rawMetrics: {
|
|
3438
|
+
repositoryExternalPressure: context.rawMetrics.repositoryExternalPressure,
|
|
3439
|
+
dependencyAffinity: context.rawMetrics.dependencyAffinity
|
|
3440
|
+
},
|
|
3441
|
+
normalizedMetrics: {
|
|
3442
|
+
externalFactor: context.factors.external
|
|
3443
|
+
},
|
|
3444
|
+
weight: dimensionWeights.external,
|
|
3445
|
+
amplification: null,
|
|
3446
|
+
evidence: [{ kind: "repository_metric", metric: "repositoryExternalPressure" }],
|
|
3447
|
+
confidence: external.available ? 0.7 : 0
|
|
3448
|
+
},
|
|
3449
|
+
{
|
|
3450
|
+
factorId: "file.composite.interactions",
|
|
3451
|
+
family: "composite",
|
|
3452
|
+
strength: context.traceTerms.interactionStructuralEvolution + context.traceTerms.interactionCentralInstability + context.traceTerms.interactionDependencyAmplification,
|
|
3453
|
+
rawMetrics: {
|
|
3454
|
+
structuralEvolutionInteraction: context.traceTerms.interactionStructuralEvolution,
|
|
3455
|
+
centralInstabilityInteraction: context.traceTerms.interactionCentralInstability,
|
|
3456
|
+
dependencyAmplificationInteraction: context.traceTerms.interactionDependencyAmplification
|
|
3457
|
+
},
|
|
3458
|
+
normalizedMetrics: {},
|
|
3459
|
+
weight: null,
|
|
3460
|
+
amplification: config.interactionWeights.structuralEvolution + config.interactionWeights.centralInstability + config.interactionWeights.dependencyAmplification,
|
|
3461
|
+
evidence: [{ kind: "repository_metric", metric: "interactionWeights" }],
|
|
3462
|
+
confidence: 0.9
|
|
3463
|
+
}
|
|
3464
|
+
]);
|
|
3465
|
+
collector.record(
|
|
3466
|
+
buildTargetTrace("file", context.file, context.score, context.normalizedScore, fileFactors)
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3012
3470
|
const fileScoresByFile = new Map(fileScores.map((fileScore) => [fileScore.file, fileScore]));
|
|
3013
3471
|
const hotspotsCount = Math.min(
|
|
3014
3472
|
config.hotspotMaxFiles,
|
|
@@ -3037,6 +3495,39 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
3037
3495
|
fileCount: values.length
|
|
3038
3496
|
};
|
|
3039
3497
|
}).sort((a, b) => b.score - a.score || a.module.localeCompare(b.module));
|
|
3498
|
+
if (collector !== void 0) {
|
|
3499
|
+
for (const [module, values] of moduleFiles.entries()) {
|
|
3500
|
+
const averageScore = average(values);
|
|
3501
|
+
const peakScore = values.reduce((max, value) => Math.max(max, value), 0);
|
|
3502
|
+
const normalizedScore = toUnitInterval(averageScore * 0.65 + peakScore * 0.35);
|
|
3503
|
+
const totalScore = round44(normalizedScore * 100);
|
|
3504
|
+
const factors = buildFactorTraces(totalScore, [
|
|
3505
|
+
{
|
|
3506
|
+
factorId: "module.average_file_risk",
|
|
3507
|
+
family: "composite",
|
|
3508
|
+
strength: averageScore * 0.65,
|
|
3509
|
+
rawMetrics: { averageFileRisk: round44(averageScore), fileCount: values.length },
|
|
3510
|
+
normalizedMetrics: { normalizedModuleRisk: round44(normalizedScore) },
|
|
3511
|
+
weight: 0.65,
|
|
3512
|
+
amplification: null,
|
|
3513
|
+
evidence: [{ kind: "repository_metric", metric: "moduleAggregation.average" }],
|
|
3514
|
+
confidence: 1
|
|
3515
|
+
},
|
|
3516
|
+
{
|
|
3517
|
+
factorId: "module.peak_file_risk",
|
|
3518
|
+
family: "composite",
|
|
3519
|
+
strength: peakScore * 0.35,
|
|
3520
|
+
rawMetrics: { peakFileRisk: round44(peakScore), fileCount: values.length },
|
|
3521
|
+
normalizedMetrics: { normalizedModuleRisk: round44(normalizedScore) },
|
|
3522
|
+
weight: 0.35,
|
|
3523
|
+
amplification: null,
|
|
3524
|
+
evidence: [{ kind: "repository_metric", metric: "moduleAggregation.peak" }],
|
|
3525
|
+
confidence: 1
|
|
3526
|
+
}
|
|
3527
|
+
]);
|
|
3528
|
+
collector.record(buildTargetTrace("module", module, totalScore, normalizedScore, factors));
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3040
3531
|
const fragileClusters = buildFragileClusters(structural, evolution, fileScoresByFile, config);
|
|
3041
3532
|
const externalPressures = fileScores.map((fileScore) => fileScore.factors.external);
|
|
3042
3533
|
const pressureThreshold = Math.max(
|
|
@@ -3057,6 +3548,107 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
3057
3548
|
...zone,
|
|
3058
3549
|
externalPressure: round44(zone.externalPressure)
|
|
3059
3550
|
}));
|
|
3551
|
+
if (collector !== void 0 && external.available) {
|
|
3552
|
+
const dependencyByName = new Map(external.dependencies.map((dependency) => [dependency.name, dependency]));
|
|
3553
|
+
for (const dependencyScore of dependencyComputation.dependencyScores) {
|
|
3554
|
+
const dependency = dependencyByName.get(dependencyScore.dependency);
|
|
3555
|
+
const context = dependencyComputation.dependencyContexts.get(dependencyScore.dependency);
|
|
3556
|
+
if (dependency === void 0 || context === void 0) {
|
|
3557
|
+
continue;
|
|
3558
|
+
}
|
|
3559
|
+
const hasMetadata = context.rawMetrics.daysSinceLastRelease !== null && context.rawMetrics.maintainerCount !== null;
|
|
3560
|
+
const factors = buildFactorTraces(dependencyScore.score, [
|
|
3561
|
+
{
|
|
3562
|
+
factorId: "dependency.signals",
|
|
3563
|
+
family: "external",
|
|
3564
|
+
strength: context.signalScore * config.dependencyFactorWeights.signals,
|
|
3565
|
+
rawMetrics: {
|
|
3566
|
+
ownSignals: dependency.ownRiskSignals.length,
|
|
3567
|
+
inheritedSignals: dependency.inheritedRiskSignals.length
|
|
3568
|
+
},
|
|
3569
|
+
normalizedMetrics: { signalScore: context.signalScore },
|
|
3570
|
+
weight: config.dependencyFactorWeights.signals,
|
|
3571
|
+
amplification: config.dependencySignals.inheritedSignalMultiplier,
|
|
3572
|
+
evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "riskSignals" }],
|
|
3573
|
+
confidence: 0.95
|
|
3574
|
+
},
|
|
3575
|
+
{
|
|
3576
|
+
factorId: "dependency.staleness",
|
|
3577
|
+
family: "external",
|
|
3578
|
+
strength: context.stalenessRisk * config.dependencyFactorWeights.staleness,
|
|
3579
|
+
rawMetrics: { daysSinceLastRelease: context.rawMetrics.daysSinceLastRelease },
|
|
3580
|
+
normalizedMetrics: { stalenessRisk: context.stalenessRisk },
|
|
3581
|
+
weight: config.dependencyFactorWeights.staleness,
|
|
3582
|
+
amplification: null,
|
|
3583
|
+
evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "daysSinceLastRelease" }],
|
|
3584
|
+
confidence: hasMetadata ? 0.9 : 0.5
|
|
3585
|
+
},
|
|
3586
|
+
{
|
|
3587
|
+
factorId: "dependency.maintainer_concentration",
|
|
3588
|
+
family: "external",
|
|
3589
|
+
strength: context.maintainerConcentrationRisk * config.dependencyFactorWeights.maintainerConcentration,
|
|
3590
|
+
rawMetrics: { maintainerCount: context.rawMetrics.maintainerCount },
|
|
3591
|
+
normalizedMetrics: {
|
|
3592
|
+
maintainerConcentrationRisk: context.maintainerConcentrationRisk
|
|
3593
|
+
},
|
|
3594
|
+
weight: config.dependencyFactorWeights.maintainerConcentration,
|
|
3595
|
+
amplification: null,
|
|
3596
|
+
evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "maintainerCount" }],
|
|
3597
|
+
confidence: hasMetadata ? 0.9 : 0.5
|
|
3598
|
+
},
|
|
3599
|
+
{
|
|
3600
|
+
factorId: "dependency.topology",
|
|
3601
|
+
family: "external",
|
|
3602
|
+
strength: context.transitiveBurdenRisk * config.dependencyFactorWeights.transitiveBurden + context.centralityRisk * config.dependencyFactorWeights.centrality + context.chainDepthRisk * config.dependencyFactorWeights.chainDepth,
|
|
3603
|
+
rawMetrics: {
|
|
3604
|
+
transitiveCount: context.rawMetrics.transitiveCount,
|
|
3605
|
+
dependents: context.rawMetrics.dependents,
|
|
3606
|
+
dependencyDepth: context.rawMetrics.dependencyDepth
|
|
3607
|
+
},
|
|
3608
|
+
normalizedMetrics: {
|
|
3609
|
+
transitiveBurdenRisk: context.transitiveBurdenRisk,
|
|
3610
|
+
centralityRisk: context.centralityRisk,
|
|
3611
|
+
chainDepthRisk: context.chainDepthRisk
|
|
3612
|
+
},
|
|
3613
|
+
weight: config.dependencyFactorWeights.transitiveBurden + config.dependencyFactorWeights.centrality + config.dependencyFactorWeights.chainDepth,
|
|
3614
|
+
amplification: null,
|
|
3615
|
+
evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "dependencyDepth" }],
|
|
3616
|
+
confidence: 1
|
|
3617
|
+
},
|
|
3618
|
+
{
|
|
3619
|
+
factorId: "dependency.bus_factor",
|
|
3620
|
+
family: "external",
|
|
3621
|
+
strength: context.busFactorRisk * config.dependencyFactorWeights.busFactorRisk,
|
|
3622
|
+
rawMetrics: { busFactor: context.rawMetrics.busFactor },
|
|
3623
|
+
normalizedMetrics: { busFactorRisk: context.busFactorRisk },
|
|
3624
|
+
weight: config.dependencyFactorWeights.busFactorRisk,
|
|
3625
|
+
amplification: null,
|
|
3626
|
+
evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "busFactor" }],
|
|
3627
|
+
confidence: context.rawMetrics.busFactor === null ? 0.5 : 0.85
|
|
3628
|
+
},
|
|
3629
|
+
{
|
|
3630
|
+
factorId: "dependency.popularity_dampening",
|
|
3631
|
+
family: "composite",
|
|
3632
|
+
strength: 1 - context.popularityDampener,
|
|
3633
|
+
rawMetrics: { weeklyDownloads: context.rawMetrics.weeklyDownloads },
|
|
3634
|
+
normalizedMetrics: { popularityDampener: context.popularityDampener },
|
|
3635
|
+
weight: config.dependencySignals.popularityMaxDampening,
|
|
3636
|
+
amplification: null,
|
|
3637
|
+
evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "weeklyDownloads" }],
|
|
3638
|
+
confidence: context.rawMetrics.weeklyDownloads === null ? 0.4 : 0.9
|
|
3639
|
+
}
|
|
3640
|
+
]);
|
|
3641
|
+
collector.record(
|
|
3642
|
+
buildTargetTrace(
|
|
3643
|
+
"dependency",
|
|
3644
|
+
dependencyScore.dependency,
|
|
3645
|
+
dependencyScore.score,
|
|
3646
|
+
dependencyScore.normalizedScore,
|
|
3647
|
+
factors
|
|
3648
|
+
)
|
|
3649
|
+
);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3060
3652
|
const structuralDimension = average(fileScores.map((fileScore) => fileScore.factors.structural));
|
|
3061
3653
|
const evolutionDimension = average(fileScores.map((fileScore) => fileScore.factors.evolution));
|
|
3062
3654
|
const externalDimension = dependencyComputation.repositoryExternalPressure;
|
|
@@ -3077,8 +3669,79 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
3077
3669
|
criticalInstability * config.interactionWeights.centralInstability,
|
|
3078
3670
|
dependencyAmplification * config.interactionWeights.dependencyAmplification
|
|
3079
3671
|
]);
|
|
3672
|
+
const repositoryScore = round44(repositoryNormalizedScore * 100);
|
|
3673
|
+
if (collector !== void 0) {
|
|
3674
|
+
const repositoryFactors = buildFactorTraces(repositoryScore, [
|
|
3675
|
+
{
|
|
3676
|
+
factorId: "repository.structural",
|
|
3677
|
+
family: "structural",
|
|
3678
|
+
strength: structuralDimension * dimensionWeights.structural,
|
|
3679
|
+
rawMetrics: { structuralDimension: round44(structuralDimension) },
|
|
3680
|
+
normalizedMetrics: { dimensionWeight: round44(dimensionWeights.structural) },
|
|
3681
|
+
weight: dimensionWeights.structural,
|
|
3682
|
+
amplification: null,
|
|
3683
|
+
evidence: [{ kind: "repository_metric", metric: "structuralDimension" }],
|
|
3684
|
+
confidence: 1
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
factorId: "repository.evolution",
|
|
3688
|
+
family: "evolution",
|
|
3689
|
+
strength: evolutionDimension * dimensionWeights.evolution,
|
|
3690
|
+
rawMetrics: { evolutionDimension: round44(evolutionDimension) },
|
|
3691
|
+
normalizedMetrics: { dimensionWeight: round44(dimensionWeights.evolution) },
|
|
3692
|
+
weight: dimensionWeights.evolution,
|
|
3693
|
+
amplification: null,
|
|
3694
|
+
evidence: [{ kind: "repository_metric", metric: "evolutionDimension" }],
|
|
3695
|
+
confidence: evolution.available ? 1 : 0
|
|
3696
|
+
},
|
|
3697
|
+
{
|
|
3698
|
+
factorId: "repository.external",
|
|
3699
|
+
family: "external",
|
|
3700
|
+
strength: externalDimension * dimensionWeights.external,
|
|
3701
|
+
rawMetrics: { externalDimension: round44(externalDimension) },
|
|
3702
|
+
normalizedMetrics: { dimensionWeight: round44(dimensionWeights.external) },
|
|
3703
|
+
weight: dimensionWeights.external,
|
|
3704
|
+
amplification: null,
|
|
3705
|
+
evidence: [{ kind: "repository_metric", metric: "externalDimension" }],
|
|
3706
|
+
confidence: external.available ? 0.8 : 0
|
|
3707
|
+
},
|
|
3708
|
+
{
|
|
3709
|
+
factorId: "repository.composite.interactions",
|
|
3710
|
+
family: "composite",
|
|
3711
|
+
strength: structuralDimension * evolutionDimension * config.interactionWeights.structuralEvolution + criticalInstability * config.interactionWeights.centralInstability + dependencyAmplification * config.interactionWeights.dependencyAmplification,
|
|
3712
|
+
rawMetrics: {
|
|
3713
|
+
structuralEvolution: round44(
|
|
3714
|
+
structuralDimension * evolutionDimension * config.interactionWeights.structuralEvolution
|
|
3715
|
+
),
|
|
3716
|
+
centralInstability: round44(
|
|
3717
|
+
criticalInstability * config.interactionWeights.centralInstability
|
|
3718
|
+
),
|
|
3719
|
+
dependencyAmplification: round44(
|
|
3720
|
+
dependencyAmplification * config.interactionWeights.dependencyAmplification
|
|
3721
|
+
)
|
|
3722
|
+
},
|
|
3723
|
+
normalizedMetrics: {
|
|
3724
|
+
criticalInstability: round44(criticalInstability),
|
|
3725
|
+
dependencyAmplification: round44(dependencyAmplification)
|
|
3726
|
+
},
|
|
3727
|
+
weight: null,
|
|
3728
|
+
amplification: config.interactionWeights.structuralEvolution + config.interactionWeights.centralInstability + config.interactionWeights.dependencyAmplification,
|
|
3729
|
+
evidence: [{ kind: "repository_metric", metric: "interactionTerms" }],
|
|
3730
|
+
confidence: 0.9
|
|
3731
|
+
}
|
|
3732
|
+
]);
|
|
3733
|
+
collector.record(
|
|
3734
|
+
buildTargetTrace(
|
|
3735
|
+
"repository",
|
|
3736
|
+
structural.targetPath,
|
|
3737
|
+
repositoryScore,
|
|
3738
|
+
repositoryNormalizedScore,
|
|
3739
|
+
repositoryFactors
|
|
3740
|
+
)
|
|
3741
|
+
);
|
|
3742
|
+
}
|
|
3080
3743
|
return {
|
|
3081
|
-
repositoryScore
|
|
3744
|
+
repositoryScore,
|
|
3082
3745
|
normalizedScore: round44(repositoryNormalizedScore),
|
|
3083
3746
|
hotspots,
|
|
3084
3747
|
fragileClusters,
|
|
@@ -3088,6 +3751,37 @@ var computeRiskSummary = (structural, evolution, external, config) => {
|
|
|
3088
3751
|
dependencyScores: dependencyComputation.dependencyScores
|
|
3089
3752
|
};
|
|
3090
3753
|
};
|
|
3754
|
+
var NoopTraceCollector = class {
|
|
3755
|
+
record(_target) {
|
|
3756
|
+
}
|
|
3757
|
+
build() {
|
|
3758
|
+
return void 0;
|
|
3759
|
+
}
|
|
3760
|
+
};
|
|
3761
|
+
var RecordingTraceCollector = class {
|
|
3762
|
+
targets = [];
|
|
3763
|
+
record(target) {
|
|
3764
|
+
this.targets.push(target);
|
|
3765
|
+
}
|
|
3766
|
+
build() {
|
|
3767
|
+
const orderedTargets = [...this.targets].sort((a, b) => {
|
|
3768
|
+
if (a.targetType !== b.targetType) {
|
|
3769
|
+
return a.targetType.localeCompare(b.targetType);
|
|
3770
|
+
}
|
|
3771
|
+
if (b.totalScore !== a.totalScore) {
|
|
3772
|
+
return b.totalScore - a.totalScore;
|
|
3773
|
+
}
|
|
3774
|
+
return a.targetId.localeCompare(b.targetId);
|
|
3775
|
+
});
|
|
3776
|
+
return {
|
|
3777
|
+
schemaVersion: "1",
|
|
3778
|
+
contributionTolerance: 1e-4,
|
|
3779
|
+
targets: orderedTargets
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
var noopCollectorSingleton = new NoopTraceCollector();
|
|
3784
|
+
var createTraceCollector = (enabled) => enabled ? new RecordingTraceCollector() : noopCollectorSingleton;
|
|
3091
3785
|
var mergeConfig = (overrides) => {
|
|
3092
3786
|
if (overrides === void 0) {
|
|
3093
3787
|
return DEFAULT_RISK_ENGINE_CONFIG;
|
|
@@ -3142,8 +3836,29 @@ var mergeConfig = (overrides) => {
|
|
|
3142
3836
|
};
|
|
3143
3837
|
};
|
|
3144
3838
|
var computeRepositoryRiskSummary = (input) => {
|
|
3839
|
+
return evaluateRepositoryRisk(input, { explain: false }).summary;
|
|
3840
|
+
};
|
|
3841
|
+
var evaluateRepositoryRisk = (input, options = {}) => {
|
|
3145
3842
|
const config = mergeConfig(input.config);
|
|
3146
|
-
|
|
3843
|
+
const collector = createTraceCollector(options.explain === true);
|
|
3844
|
+
const summary = computeRiskSummary(
|
|
3845
|
+
input.structural,
|
|
3846
|
+
input.evolution,
|
|
3847
|
+
input.external,
|
|
3848
|
+
config,
|
|
3849
|
+
collector
|
|
3850
|
+
);
|
|
3851
|
+
const trace = collector.build();
|
|
3852
|
+
if (options.explain !== true) {
|
|
3853
|
+
return { summary };
|
|
3854
|
+
}
|
|
3855
|
+
if (trace === void 0) {
|
|
3856
|
+
return { summary };
|
|
3857
|
+
}
|
|
3858
|
+
return {
|
|
3859
|
+
summary,
|
|
3860
|
+
trace
|
|
3861
|
+
};
|
|
3147
3862
|
};
|
|
3148
3863
|
|
|
3149
3864
|
// src/application/run-analyze-command.ts
|
|
@@ -3259,7 +3974,7 @@ var createEvolutionProgressReporter = (logger) => {
|
|
|
3259
3974
|
}
|
|
3260
3975
|
};
|
|
3261
3976
|
};
|
|
3262
|
-
var
|
|
3977
|
+
var collectAnalysisInputs = async (inputPath, authorIdentityMode, logger = createSilentLogger()) => {
|
|
3263
3978
|
const invocationCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
3264
3979
|
const targetPath = resolveTargetPath(inputPath, invocationCwd);
|
|
3265
3980
|
logger.info(`analyzing repository: ${targetPath}`);
|
|
@@ -3272,10 +3987,13 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, logger = createSil
|
|
|
3272
3987
|
`structural metrics: nodes=${structural.metrics.nodeCount}, edges=${structural.metrics.edgeCount}, cycles=${structural.metrics.cycleCount}`
|
|
3273
3988
|
);
|
|
3274
3989
|
logger.info(`analyzing git evolution (author identity: ${authorIdentityMode})`);
|
|
3275
|
-
const evolution = analyzeRepositoryEvolutionFromGit(
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3990
|
+
const evolution = analyzeRepositoryEvolutionFromGit(
|
|
3991
|
+
{
|
|
3992
|
+
repositoryPath: targetPath,
|
|
3993
|
+
config: { authorIdentityMode }
|
|
3994
|
+
},
|
|
3995
|
+
createEvolutionProgressReporter(logger)
|
|
3996
|
+
);
|
|
3279
3997
|
if (evolution.available) {
|
|
3280
3998
|
logger.debug(
|
|
3281
3999
|
`evolution metrics: commits=${evolution.metrics.totalCommits}, files=${evolution.metrics.totalFiles}, hotspotThreshold=${evolution.metrics.hotspotThresholdCommitCount}`
|
|
@@ -3295,20 +4013,528 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, logger = createSil
|
|
|
3295
4013
|
} else {
|
|
3296
4014
|
logger.warn(`external analysis unavailable: ${external.reason}`);
|
|
3297
4015
|
}
|
|
3298
|
-
|
|
3299
|
-
const risk = computeRepositoryRiskSummary({
|
|
4016
|
+
return {
|
|
3300
4017
|
structural,
|
|
3301
4018
|
evolution,
|
|
3302
4019
|
external
|
|
3303
|
-
}
|
|
4020
|
+
};
|
|
4021
|
+
};
|
|
4022
|
+
var runAnalyzeCommand = async (inputPath, authorIdentityMode, logger = createSilentLogger()) => {
|
|
4023
|
+
const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, logger);
|
|
4024
|
+
logger.info("computing risk summary");
|
|
4025
|
+
const risk = computeRepositoryRiskSummary(analysisInputs);
|
|
3304
4026
|
logger.info(`analysis completed (repositoryScore=${risk.repositoryScore})`);
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
evolution,
|
|
3308
|
-
external,
|
|
4027
|
+
return {
|
|
4028
|
+
...analysisInputs,
|
|
3309
4029
|
risk
|
|
3310
4030
|
};
|
|
3311
|
-
|
|
4031
|
+
};
|
|
4032
|
+
|
|
4033
|
+
// src/application/run-report-command.ts
|
|
4034
|
+
import { readFile, writeFile } from "fs/promises";
|
|
4035
|
+
|
|
4036
|
+
// ../reporter/dist/index.js
|
|
4037
|
+
var SNAPSHOT_SCHEMA_VERSION = "codesentinel.snapshot.v1";
|
|
4038
|
+
var REPORT_SCHEMA_VERSION = "codesentinel.report.v1";
|
|
4039
|
+
var RISK_MODEL_VERSION = "deterministic-v1";
|
|
4040
|
+
var round45 = (value) => Number(value.toFixed(4));
|
|
4041
|
+
var toRiskTier = (score) => {
|
|
4042
|
+
if (score < 20) {
|
|
4043
|
+
return "low";
|
|
4044
|
+
}
|
|
4045
|
+
if (score < 40) {
|
|
4046
|
+
return "moderate";
|
|
4047
|
+
}
|
|
4048
|
+
if (score < 60) {
|
|
4049
|
+
return "elevated";
|
|
4050
|
+
}
|
|
4051
|
+
if (score < 80) {
|
|
4052
|
+
return "high";
|
|
4053
|
+
}
|
|
4054
|
+
return "very_high";
|
|
4055
|
+
};
|
|
4056
|
+
var factorLabelById2 = {
|
|
4057
|
+
"repository.structural": "Structural complexity",
|
|
4058
|
+
"repository.evolution": "Change volatility",
|
|
4059
|
+
"repository.external": "External dependency pressure",
|
|
4060
|
+
"repository.composite.interactions": "Intersection amplification",
|
|
4061
|
+
"file.structural": "File structural complexity",
|
|
4062
|
+
"file.evolution": "File change volatility",
|
|
4063
|
+
"file.external": "File external pressure",
|
|
4064
|
+
"file.composite.interactions": "File interaction amplification",
|
|
4065
|
+
"module.average_file_risk": "Average file risk",
|
|
4066
|
+
"module.peak_file_risk": "Peak file risk",
|
|
4067
|
+
"dependency.signals": "Dependency risk signals",
|
|
4068
|
+
"dependency.staleness": "Dependency staleness",
|
|
4069
|
+
"dependency.maintainer_concentration": "Maintainer concentration",
|
|
4070
|
+
"dependency.topology": "Dependency topology pressure",
|
|
4071
|
+
"dependency.bus_factor": "Dependency bus factor",
|
|
4072
|
+
"dependency.popularity_dampening": "Popularity dampening"
|
|
4073
|
+
};
|
|
4074
|
+
var factorLabel = (factorId) => factorLabelById2[factorId] ?? factorId;
|
|
4075
|
+
var summarizeEvidence = (factor) => {
|
|
4076
|
+
const entries = Object.entries(factor.rawMetrics).filter(([, value]) => value !== null).sort((a, b) => a[0].localeCompare(b[0])).slice(0, 3).map(([key, value]) => `${key}=${value}`);
|
|
4077
|
+
if (entries.length > 0) {
|
|
4078
|
+
return entries.join(", ");
|
|
4079
|
+
}
|
|
4080
|
+
const evidence = [...factor.evidence].map((entry) => {
|
|
4081
|
+
if (entry.kind === "file_metric") {
|
|
4082
|
+
return `${entry.target}:${entry.metric}`;
|
|
4083
|
+
}
|
|
4084
|
+
if (entry.kind === "dependency_metric") {
|
|
4085
|
+
return `${entry.target}:${entry.metric}`;
|
|
4086
|
+
}
|
|
4087
|
+
if (entry.kind === "repository_metric") {
|
|
4088
|
+
return entry.metric;
|
|
4089
|
+
}
|
|
4090
|
+
if (entry.kind === "graph_cycle") {
|
|
4091
|
+
return `cycle:${entry.cycleId}`;
|
|
4092
|
+
}
|
|
4093
|
+
return `${entry.fileA}<->${entry.fileB}`;
|
|
4094
|
+
}).sort((a, b) => a.localeCompare(b));
|
|
4095
|
+
return evidence.join(", ");
|
|
4096
|
+
};
|
|
4097
|
+
var diffSets = (current, baseline) => {
|
|
4098
|
+
const currentSet = new Set(current);
|
|
4099
|
+
const baselineSet = new Set(baseline);
|
|
4100
|
+
const added = [...currentSet].filter((item) => !baselineSet.has(item)).sort((a, b) => a.localeCompare(b));
|
|
4101
|
+
const removed = [...baselineSet].filter((item) => !currentSet.has(item)).sort((a, b) => a.localeCompare(b));
|
|
4102
|
+
return { added, removed };
|
|
4103
|
+
};
|
|
4104
|
+
var diffScoreMap = (current, baseline) => {
|
|
4105
|
+
const keys = [.../* @__PURE__ */ new Set([...current.keys(), ...baseline.keys()])].sort((a, b) => a.localeCompare(b));
|
|
4106
|
+
return keys.map((key) => {
|
|
4107
|
+
const before = baseline.get(key) ?? 0;
|
|
4108
|
+
const after = current.get(key) ?? 0;
|
|
4109
|
+
const delta = round45(after - before);
|
|
4110
|
+
return {
|
|
4111
|
+
target: key,
|
|
4112
|
+
before: round45(before),
|
|
4113
|
+
after: round45(after),
|
|
4114
|
+
delta
|
|
4115
|
+
};
|
|
4116
|
+
}).filter((entry) => entry.delta !== 0).sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta) || a.target.localeCompare(b.target));
|
|
4117
|
+
};
|
|
4118
|
+
var cycleKey = (nodes) => [...nodes].sort((a, b) => a.localeCompare(b)).join(" -> ");
|
|
4119
|
+
var compareSnapshots = (current, baseline) => {
|
|
4120
|
+
const currentFileScores = new Map(
|
|
4121
|
+
current.analysis.risk.fileScores.map((item) => [item.file, item.score])
|
|
4122
|
+
);
|
|
4123
|
+
const baselineFileScores = new Map(
|
|
4124
|
+
baseline.analysis.risk.fileScores.map((item) => [item.file, item.score])
|
|
4125
|
+
);
|
|
4126
|
+
const currentModuleScores = new Map(
|
|
4127
|
+
current.analysis.risk.moduleScores.map((item) => [item.module, item.score])
|
|
4128
|
+
);
|
|
4129
|
+
const baselineModuleScores = new Map(
|
|
4130
|
+
baseline.analysis.risk.moduleScores.map((item) => [item.module, item.score])
|
|
4131
|
+
);
|
|
4132
|
+
const currentHotspots = current.analysis.risk.hotspots.slice(0, 10).map((item) => item.file);
|
|
4133
|
+
const baselineHotspots = baseline.analysis.risk.hotspots.slice(0, 10).map((item) => item.file);
|
|
4134
|
+
const currentCycles = current.analysis.structural.cycles.map((cycle) => cycleKey(cycle.nodes));
|
|
4135
|
+
const baselineCycles = baseline.analysis.structural.cycles.map((cycle) => cycleKey(cycle.nodes));
|
|
4136
|
+
const currentExternal = current.analysis.external.available ? current.analysis.external : {
|
|
4137
|
+
highRiskDependencies: [],
|
|
4138
|
+
singleMaintainerDependencies: [],
|
|
4139
|
+
abandonedDependencies: []
|
|
4140
|
+
};
|
|
4141
|
+
const baselineExternal = baseline.analysis.external.available ? baseline.analysis.external : {
|
|
4142
|
+
highRiskDependencies: [],
|
|
4143
|
+
singleMaintainerDependencies: [],
|
|
4144
|
+
abandonedDependencies: []
|
|
4145
|
+
};
|
|
4146
|
+
const highRisk = diffSets(currentExternal.highRiskDependencies, baselineExternal.highRiskDependencies);
|
|
4147
|
+
const singleMaintainer = diffSets(
|
|
4148
|
+
currentExternal.singleMaintainerDependencies,
|
|
4149
|
+
baselineExternal.singleMaintainerDependencies
|
|
4150
|
+
);
|
|
4151
|
+
const abandoned = diffSets(currentExternal.abandonedDependencies, baselineExternal.abandonedDependencies);
|
|
4152
|
+
const hotspots = diffSets(currentHotspots, baselineHotspots);
|
|
4153
|
+
const cycles = diffSets(currentCycles, baselineCycles);
|
|
4154
|
+
return {
|
|
4155
|
+
repositoryScoreDelta: round45(current.analysis.risk.repositoryScore - baseline.analysis.risk.repositoryScore),
|
|
4156
|
+
normalizedScoreDelta: round45(current.analysis.risk.normalizedScore - baseline.analysis.risk.normalizedScore),
|
|
4157
|
+
fileRiskChanges: diffScoreMap(currentFileScores, baselineFileScores),
|
|
4158
|
+
moduleRiskChanges: diffScoreMap(currentModuleScores, baselineModuleScores),
|
|
4159
|
+
newHotspots: hotspots.added,
|
|
4160
|
+
resolvedHotspots: hotspots.removed,
|
|
4161
|
+
newCycles: cycles.added,
|
|
4162
|
+
resolvedCycles: cycles.removed,
|
|
4163
|
+
externalChanges: {
|
|
4164
|
+
highRiskAdded: highRisk.added,
|
|
4165
|
+
highRiskRemoved: highRisk.removed,
|
|
4166
|
+
singleMaintainerAdded: singleMaintainer.added,
|
|
4167
|
+
singleMaintainerRemoved: singleMaintainer.removed,
|
|
4168
|
+
abandonedAdded: abandoned.added,
|
|
4169
|
+
abandonedRemoved: abandoned.removed
|
|
4170
|
+
}
|
|
4171
|
+
};
|
|
4172
|
+
};
|
|
4173
|
+
var findTraceTarget = (snapshot, targetType, targetId) => snapshot.trace?.targets.find(
|
|
4174
|
+
(target) => target.targetType === targetType && target.targetId === targetId
|
|
4175
|
+
);
|
|
4176
|
+
var toRenderedFactors = (target) => {
|
|
4177
|
+
if (target === void 0) {
|
|
4178
|
+
return [];
|
|
4179
|
+
}
|
|
4180
|
+
return [...target.factors].sort((a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)).slice(0, 4).map((factor) => ({
|
|
4181
|
+
id: factor.factorId,
|
|
4182
|
+
label: factorLabel(factor.factorId),
|
|
4183
|
+
contribution: round45(factor.contribution),
|
|
4184
|
+
confidence: round45(factor.confidence),
|
|
4185
|
+
evidence: summarizeEvidence(factor)
|
|
4186
|
+
}));
|
|
4187
|
+
};
|
|
4188
|
+
var suggestedActions = (target) => {
|
|
4189
|
+
if (target === void 0) {
|
|
4190
|
+
return [];
|
|
4191
|
+
}
|
|
4192
|
+
const actions = [];
|
|
4193
|
+
for (const lever of target.reductionLevers) {
|
|
4194
|
+
switch (lever.factorId) {
|
|
4195
|
+
case "file.evolution":
|
|
4196
|
+
case "repository.evolution":
|
|
4197
|
+
actions.push("Reduce recent churn and volatile edit frequency in this area.");
|
|
4198
|
+
break;
|
|
4199
|
+
case "file.structural":
|
|
4200
|
+
case "repository.structural":
|
|
4201
|
+
actions.push("Reduce fan-in/fan-out concentration and simplify deep dependency paths.");
|
|
4202
|
+
break;
|
|
4203
|
+
case "file.composite.interactions":
|
|
4204
|
+
case "repository.composite.interactions":
|
|
4205
|
+
actions.push("Stabilize central files before concurrent structural changes.");
|
|
4206
|
+
break;
|
|
4207
|
+
case "file.external":
|
|
4208
|
+
case "repository.external":
|
|
4209
|
+
actions.push("Review external dependency pressure for this hotspot.");
|
|
4210
|
+
break;
|
|
4211
|
+
default:
|
|
4212
|
+
actions.push(`Reduce ${factorLabel(lever.factorId).toLowerCase()} influence.`);
|
|
4213
|
+
break;
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
return [...new Set(actions)].slice(0, 3);
|
|
4217
|
+
};
|
|
4218
|
+
var hotspotItems = (snapshot) => snapshot.analysis.risk.hotspots.slice(0, 10).map((hotspot) => {
|
|
4219
|
+
const fileScore = snapshot.analysis.risk.fileScores.find((item) => item.file === hotspot.file);
|
|
4220
|
+
const traceTarget = findTraceTarget(snapshot, "file", hotspot.file);
|
|
4221
|
+
const factors = toRenderedFactors(traceTarget);
|
|
4222
|
+
return {
|
|
4223
|
+
target: hotspot.file,
|
|
4224
|
+
score: hotspot.score,
|
|
4225
|
+
normalizedScore: fileScore?.normalizedScore ?? round45(hotspot.score / 100),
|
|
4226
|
+
topFactors: factors,
|
|
4227
|
+
suggestedActions: suggestedActions(traceTarget),
|
|
4228
|
+
biggestLevers: (traceTarget?.reductionLevers ?? []).slice(0, 3).map((lever) => `${factorLabel(lever.factorId)} (${lever.estimatedImpact})`)
|
|
4229
|
+
};
|
|
4230
|
+
});
|
|
4231
|
+
var repositoryConfidence = (snapshot) => {
|
|
4232
|
+
const target = findTraceTarget(snapshot, "repository", snapshot.analysis.structural.targetPath);
|
|
4233
|
+
if (target === void 0 || target.factors.length === 0) {
|
|
4234
|
+
return null;
|
|
4235
|
+
}
|
|
4236
|
+
const weight = target.factors.reduce((sum, factor) => sum + factor.contribution, 0);
|
|
4237
|
+
if (weight <= 0) {
|
|
4238
|
+
return null;
|
|
4239
|
+
}
|
|
4240
|
+
const weighted = target.factors.reduce(
|
|
4241
|
+
(sum, factor) => sum + factor.confidence * factor.contribution,
|
|
4242
|
+
0
|
|
4243
|
+
);
|
|
4244
|
+
return round45(weighted / weight);
|
|
4245
|
+
};
|
|
4246
|
+
var createReport = (snapshot, diff) => {
|
|
4247
|
+
const external = snapshot.analysis.external;
|
|
4248
|
+
return {
|
|
4249
|
+
schemaVersion: REPORT_SCHEMA_VERSION,
|
|
4250
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4251
|
+
repository: {
|
|
4252
|
+
targetPath: snapshot.analysis.structural.targetPath,
|
|
4253
|
+
repositoryScore: snapshot.analysis.risk.repositoryScore,
|
|
4254
|
+
normalizedScore: snapshot.analysis.risk.normalizedScore,
|
|
4255
|
+
riskTier: toRiskTier(snapshot.analysis.risk.repositoryScore),
|
|
4256
|
+
confidence: repositoryConfidence(snapshot)
|
|
4257
|
+
},
|
|
4258
|
+
hotspots: hotspotItems(snapshot),
|
|
4259
|
+
structural: {
|
|
4260
|
+
cycleCount: snapshot.analysis.structural.metrics.cycleCount,
|
|
4261
|
+
cycles: snapshot.analysis.structural.cycles.map(
|
|
4262
|
+
(cycle) => [...cycle.nodes].sort((a, b) => a.localeCompare(b)).join(" -> ")
|
|
4263
|
+
),
|
|
4264
|
+
fragileClusters: snapshot.analysis.risk.fragileClusters.map((cluster) => ({
|
|
4265
|
+
id: cluster.id,
|
|
4266
|
+
kind: cluster.kind,
|
|
4267
|
+
score: cluster.score,
|
|
4268
|
+
files: [...cluster.files].sort((a, b) => a.localeCompare(b))
|
|
4269
|
+
}))
|
|
4270
|
+
},
|
|
4271
|
+
external: !external.available ? {
|
|
4272
|
+
available: false,
|
|
4273
|
+
reason: external.reason
|
|
4274
|
+
} : {
|
|
4275
|
+
available: true,
|
|
4276
|
+
highRiskDependencies: [...external.highRiskDependencies].sort((a, b) => a.localeCompare(b)),
|
|
4277
|
+
highRiskDevelopmentDependencies: [...external.highRiskDevelopmentDependencies].sort((a, b) => a.localeCompare(b)),
|
|
4278
|
+
singleMaintainerDependencies: [...external.singleMaintainerDependencies].sort((a, b) => a.localeCompare(b)),
|
|
4279
|
+
abandonedDependencies: [...external.abandonedDependencies].sort((a, b) => a.localeCompare(b))
|
|
4280
|
+
},
|
|
4281
|
+
appendix: {
|
|
4282
|
+
snapshotSchemaVersion: snapshot.schemaVersion,
|
|
4283
|
+
riskModelVersion: snapshot.riskModelVersion,
|
|
4284
|
+
timestamp: snapshot.generatedAt,
|
|
4285
|
+
normalization: "Scores are deterministic 0-100 outputs from risk-engine normalized factors and interaction terms.",
|
|
4286
|
+
...snapshot.analysisConfig === void 0 ? {} : { analysisConfig: snapshot.analysisConfig }
|
|
4287
|
+
},
|
|
4288
|
+
...diff === void 0 ? {} : { diff }
|
|
4289
|
+
};
|
|
4290
|
+
};
|
|
4291
|
+
var renderTextDiff = (report) => {
|
|
4292
|
+
if (report.diff === void 0) {
|
|
4293
|
+
return [];
|
|
4294
|
+
}
|
|
4295
|
+
return [
|
|
4296
|
+
"",
|
|
4297
|
+
"Diff",
|
|
4298
|
+
` repositoryScoreDelta: ${report.diff.repositoryScoreDelta}`,
|
|
4299
|
+
` normalizedScoreDelta: ${report.diff.normalizedScoreDelta}`,
|
|
4300
|
+
` newHotspots: ${report.diff.newHotspots.join(", ") || "none"}`,
|
|
4301
|
+
` resolvedHotspots: ${report.diff.resolvedHotspots.join(", ") || "none"}`,
|
|
4302
|
+
` newCycles: ${report.diff.newCycles.join(", ") || "none"}`,
|
|
4303
|
+
` resolvedCycles: ${report.diff.resolvedCycles.join(", ") || "none"}`
|
|
4304
|
+
];
|
|
4305
|
+
};
|
|
4306
|
+
var renderTextReport = (report) => {
|
|
4307
|
+
const lines = [];
|
|
4308
|
+
lines.push("Repository Summary");
|
|
4309
|
+
lines.push(` target: ${report.repository.targetPath}`);
|
|
4310
|
+
lines.push(` repositoryScore: ${report.repository.repositoryScore}`);
|
|
4311
|
+
lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
|
|
4312
|
+
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
4313
|
+
lines.push(` confidence: ${report.repository.confidence ?? "n/a"}`);
|
|
4314
|
+
lines.push("");
|
|
4315
|
+
lines.push("Top Hotspots");
|
|
4316
|
+
for (const hotspot of report.hotspots) {
|
|
4317
|
+
lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
|
|
4318
|
+
for (const factor of hotspot.topFactors) {
|
|
4319
|
+
lines.push(
|
|
4320
|
+
` factor: ${factor.label} contribution=${factor.contribution} confidence=${factor.confidence}`
|
|
4321
|
+
);
|
|
4322
|
+
lines.push(` evidence: ${factor.evidence}`);
|
|
4323
|
+
}
|
|
4324
|
+
lines.push(` actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
4325
|
+
lines.push(` levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
|
|
4326
|
+
}
|
|
4327
|
+
lines.push("");
|
|
4328
|
+
lines.push("Structural Observations");
|
|
4329
|
+
lines.push(` cycleCount: ${report.structural.cycleCount}`);
|
|
4330
|
+
lines.push(` cycles: ${report.structural.cycles.join(" ; ") || "none"}`);
|
|
4331
|
+
lines.push(` fragileClusters: ${report.structural.fragileClusters.length}`);
|
|
4332
|
+
lines.push("");
|
|
4333
|
+
lines.push("External Exposure");
|
|
4334
|
+
if (!report.external.available) {
|
|
4335
|
+
lines.push(` unavailable: ${report.external.reason}`);
|
|
4336
|
+
} else {
|
|
4337
|
+
lines.push(` highRiskDependencies: ${report.external.highRiskDependencies.join(", ") || "none"}`);
|
|
4338
|
+
lines.push(
|
|
4339
|
+
` highRiskDevelopmentDependencies: ${report.external.highRiskDevelopmentDependencies.join(", ") || "none"}`
|
|
4340
|
+
);
|
|
4341
|
+
lines.push(
|
|
4342
|
+
` singleMaintainerDependencies: ${report.external.singleMaintainerDependencies.join(", ") || "none"}`
|
|
4343
|
+
);
|
|
4344
|
+
lines.push(` abandonedDependencies: ${report.external.abandonedDependencies.join(", ") || "none"}`);
|
|
4345
|
+
}
|
|
4346
|
+
lines.push("");
|
|
4347
|
+
lines.push("Appendix");
|
|
4348
|
+
lines.push(` snapshotSchemaVersion: ${report.appendix.snapshotSchemaVersion}`);
|
|
4349
|
+
lines.push(` riskModelVersion: ${report.appendix.riskModelVersion}`);
|
|
4350
|
+
lines.push(` timestamp: ${report.appendix.timestamp}`);
|
|
4351
|
+
lines.push(` normalization: ${report.appendix.normalization}`);
|
|
4352
|
+
lines.push(...renderTextDiff(report));
|
|
4353
|
+
return lines.join("\n");
|
|
4354
|
+
};
|
|
4355
|
+
var renderMarkdownDiff = (report) => {
|
|
4356
|
+
if (report.diff === void 0) {
|
|
4357
|
+
return [];
|
|
4358
|
+
}
|
|
4359
|
+
return [
|
|
4360
|
+
"",
|
|
4361
|
+
"## Diff",
|
|
4362
|
+
`- repositoryScoreDelta: \`${report.diff.repositoryScoreDelta}\``,
|
|
4363
|
+
`- normalizedScoreDelta: \`${report.diff.normalizedScoreDelta}\``,
|
|
4364
|
+
`- newHotspots: ${report.diff.newHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
4365
|
+
`- resolvedHotspots: ${report.diff.resolvedHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
4366
|
+
`- newCycles: ${report.diff.newCycles.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
4367
|
+
`- resolvedCycles: ${report.diff.resolvedCycles.map((item) => `\`${item}\``).join(", ") || "none"}`
|
|
4368
|
+
];
|
|
4369
|
+
};
|
|
4370
|
+
var renderMarkdownReport = (report) => {
|
|
4371
|
+
const lines = [];
|
|
4372
|
+
lines.push("# CodeSentinel Report");
|
|
4373
|
+
lines.push("");
|
|
4374
|
+
lines.push("## Repository Summary");
|
|
4375
|
+
lines.push(`- target: \`${report.repository.targetPath}\``);
|
|
4376
|
+
lines.push(`- repositoryScore: \`${report.repository.repositoryScore}\``);
|
|
4377
|
+
lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
|
|
4378
|
+
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
4379
|
+
lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
|
|
4380
|
+
lines.push("");
|
|
4381
|
+
lines.push("## Top Hotspots");
|
|
4382
|
+
for (const hotspot of report.hotspots) {
|
|
4383
|
+
lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
|
|
4384
|
+
lines.push(` - Top factors:`);
|
|
4385
|
+
for (const factor of hotspot.topFactors) {
|
|
4386
|
+
lines.push(
|
|
4387
|
+
` - ${factor.label}: contribution=\`${factor.contribution}\`, confidence=\`${factor.confidence}\``
|
|
4388
|
+
);
|
|
4389
|
+
lines.push(` - evidence: \`${factor.evidence}\``);
|
|
4390
|
+
}
|
|
4391
|
+
lines.push(` - Suggested actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
4392
|
+
lines.push(` - Biggest levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
|
|
4393
|
+
}
|
|
4394
|
+
lines.push("");
|
|
4395
|
+
lines.push("## Structural Observations");
|
|
4396
|
+
lines.push(`- cycles detected: \`${report.structural.cycleCount}\``);
|
|
4397
|
+
lines.push(`- cycles: ${report.structural.cycles.map((cycle) => `\`${cycle}\``).join(", ") || "none"}`);
|
|
4398
|
+
lines.push(`- fragile clusters: \`${report.structural.fragileClusters.length}\``);
|
|
4399
|
+
lines.push("");
|
|
4400
|
+
lines.push("## External Exposure Summary");
|
|
4401
|
+
if (!report.external.available) {
|
|
4402
|
+
lines.push(`- unavailable: \`${report.external.reason}\``);
|
|
4403
|
+
} else {
|
|
4404
|
+
lines.push(`- high-risk dependencies: ${report.external.highRiskDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`);
|
|
4405
|
+
lines.push(
|
|
4406
|
+
`- high-risk development dependencies: ${report.external.highRiskDevelopmentDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
|
|
4407
|
+
);
|
|
4408
|
+
lines.push(
|
|
4409
|
+
`- single maintainer dependencies: ${report.external.singleMaintainerDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
|
|
4410
|
+
);
|
|
4411
|
+
lines.push(`- abandoned dependencies: ${report.external.abandonedDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`);
|
|
4412
|
+
}
|
|
4413
|
+
lines.push("");
|
|
4414
|
+
lines.push("## Appendix");
|
|
4415
|
+
lines.push(`- snapshot schema: \`${report.appendix.snapshotSchemaVersion}\``);
|
|
4416
|
+
lines.push(`- risk model version: \`${report.appendix.riskModelVersion}\``);
|
|
4417
|
+
lines.push(`- timestamp: \`${report.appendix.timestamp}\``);
|
|
4418
|
+
lines.push(`- normalization: ${report.appendix.normalization}`);
|
|
4419
|
+
lines.push(...renderMarkdownDiff(report));
|
|
4420
|
+
return lines.join("\n");
|
|
4421
|
+
};
|
|
4422
|
+
var createSnapshot = (input) => ({
|
|
4423
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
4424
|
+
generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4425
|
+
riskModelVersion: RISK_MODEL_VERSION,
|
|
4426
|
+
source: {
|
|
4427
|
+
targetPath: input.analysis.structural.targetPath
|
|
4428
|
+
},
|
|
4429
|
+
analysis: input.analysis,
|
|
4430
|
+
...input.trace === void 0 ? {} : { trace: input.trace },
|
|
4431
|
+
...input.analysisConfig === void 0 ? {} : { analysisConfig: input.analysisConfig }
|
|
4432
|
+
});
|
|
4433
|
+
var parseSnapshot = (raw) => {
|
|
4434
|
+
const parsed = JSON.parse(raw);
|
|
4435
|
+
if (parsed.schemaVersion !== SNAPSHOT_SCHEMA_VERSION) {
|
|
4436
|
+
throw new Error("unsupported_snapshot_schema");
|
|
4437
|
+
}
|
|
4438
|
+
if (typeof parsed.generatedAt !== "string") {
|
|
4439
|
+
throw new Error("invalid_snapshot_generated_at");
|
|
4440
|
+
}
|
|
4441
|
+
if (parsed.analysis === void 0 || parsed.analysis === null) {
|
|
4442
|
+
throw new Error("invalid_snapshot_analysis");
|
|
4443
|
+
}
|
|
4444
|
+
if (parsed.source === void 0 || typeof parsed.source.targetPath !== "string") {
|
|
4445
|
+
throw new Error("invalid_snapshot_source");
|
|
4446
|
+
}
|
|
4447
|
+
return parsed;
|
|
4448
|
+
};
|
|
4449
|
+
var formatReport = (report, format) => {
|
|
4450
|
+
if (format === "json") {
|
|
4451
|
+
return JSON.stringify(report, null, 2);
|
|
4452
|
+
}
|
|
4453
|
+
if (format === "md") {
|
|
4454
|
+
return renderMarkdownReport(report);
|
|
4455
|
+
}
|
|
4456
|
+
return renderTextReport(report);
|
|
4457
|
+
};
|
|
4458
|
+
|
|
4459
|
+
// src/application/run-report-command.ts
|
|
4460
|
+
var buildSnapshot = async (inputPath, authorIdentityMode, includeTrace, logger) => {
|
|
4461
|
+
const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, logger);
|
|
4462
|
+
const evaluation = evaluateRepositoryRisk(analysisInputs, { explain: includeTrace });
|
|
4463
|
+
const summary = {
|
|
4464
|
+
...analysisInputs,
|
|
4465
|
+
risk: evaluation.summary
|
|
4466
|
+
};
|
|
4467
|
+
return createSnapshot({
|
|
4468
|
+
analysis: summary,
|
|
4469
|
+
...evaluation.trace === void 0 ? {} : { trace: evaluation.trace },
|
|
4470
|
+
analysisConfig: {
|
|
4471
|
+
authorIdentityMode,
|
|
4472
|
+
includeTrace
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
};
|
|
4476
|
+
var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
|
|
4477
|
+
logger.info("building analysis snapshot");
|
|
4478
|
+
const current = await buildSnapshot(inputPath, authorIdentityMode, options.includeTrace, logger);
|
|
4479
|
+
if (options.snapshotPath !== void 0) {
|
|
4480
|
+
await writeFile(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
|
|
4481
|
+
logger.info(`snapshot written: ${options.snapshotPath}`);
|
|
4482
|
+
}
|
|
4483
|
+
let report;
|
|
4484
|
+
if (options.comparePath === void 0) {
|
|
4485
|
+
report = createReport(current);
|
|
4486
|
+
} else {
|
|
4487
|
+
logger.info(`loading baseline snapshot: ${options.comparePath}`);
|
|
4488
|
+
const baselineRaw = await readFile(options.comparePath, "utf8");
|
|
4489
|
+
const baseline = parseSnapshot(baselineRaw);
|
|
4490
|
+
const diff = compareSnapshots(current, baseline);
|
|
4491
|
+
report = createReport(current, diff);
|
|
4492
|
+
}
|
|
4493
|
+
const rendered = formatReport(report, options.format);
|
|
4494
|
+
if (options.outputPath !== void 0) {
|
|
4495
|
+
await writeFile(options.outputPath, rendered, "utf8");
|
|
4496
|
+
logger.info(`report written: ${options.outputPath}`);
|
|
4497
|
+
}
|
|
4498
|
+
return { report, rendered };
|
|
4499
|
+
};
|
|
4500
|
+
|
|
4501
|
+
// src/application/run-explain-command.ts
|
|
4502
|
+
var selectTargets = (trace, summary, options) => {
|
|
4503
|
+
if (options.file !== void 0) {
|
|
4504
|
+
const normalized = options.file.replaceAll("\\", "/");
|
|
4505
|
+
return trace.targets.filter(
|
|
4506
|
+
(target) => target.targetType === "file" && target.targetId === normalized
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
4509
|
+
if (options.module !== void 0) {
|
|
4510
|
+
return trace.targets.filter(
|
|
4511
|
+
(target) => target.targetType === "module" && target.targetId === options.module
|
|
4512
|
+
);
|
|
4513
|
+
}
|
|
4514
|
+
const top = Math.max(1, options.top);
|
|
4515
|
+
const topFiles = summary.risk.hotspots.slice(0, top).map((entry) => entry.file);
|
|
4516
|
+
const fileSet = new Set(topFiles);
|
|
4517
|
+
return trace.targets.filter(
|
|
4518
|
+
(target) => target.targetType === "repository" || target.targetType === "file" && fileSet.has(target.targetId)
|
|
4519
|
+
);
|
|
4520
|
+
};
|
|
4521
|
+
var runExplainCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
|
|
4522
|
+
const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, logger);
|
|
4523
|
+
logger.info("computing explainable risk summary");
|
|
4524
|
+
const evaluation = evaluateRepositoryRisk(analysisInputs, { explain: true });
|
|
4525
|
+
if (evaluation.trace === void 0) {
|
|
4526
|
+
throw new Error("risk trace unavailable");
|
|
4527
|
+
}
|
|
4528
|
+
const summary = {
|
|
4529
|
+
...analysisInputs,
|
|
4530
|
+
risk: evaluation.summary
|
|
4531
|
+
};
|
|
4532
|
+
logger.info(`explanation completed (repositoryScore=${summary.risk.repositoryScore})`);
|
|
4533
|
+
return {
|
|
4534
|
+
summary,
|
|
4535
|
+
trace: evaluation.trace,
|
|
4536
|
+
selectedTargets: selectTargets(evaluation.trace, summary, options)
|
|
4537
|
+
};
|
|
3312
4538
|
};
|
|
3313
4539
|
|
|
3314
4540
|
// src/index.ts
|
|
@@ -3340,6 +4566,38 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
3340
4566
|
`);
|
|
3341
4567
|
}
|
|
3342
4568
|
);
|
|
4569
|
+
program.command("explain").argument("[path]", "path to the project to analyze").addOption(
|
|
4570
|
+
new Option(
|
|
4571
|
+
"--author-identity <mode>",
|
|
4572
|
+
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
4573
|
+
).choices(["likely_merge", "strict_email"]).default("likely_merge")
|
|
4574
|
+
).addOption(
|
|
4575
|
+
new Option(
|
|
4576
|
+
"--log-level <level>",
|
|
4577
|
+
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
4578
|
+
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
4579
|
+
).option("--file <path>", "explain a specific file target").option("--module <name>", "explain a specific module target").option("--top <count>", "number of top hotspots to explain when no target is selected", "5").addOption(
|
|
4580
|
+
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")
|
|
4581
|
+
).action(
|
|
4582
|
+
async (path, options) => {
|
|
4583
|
+
const logger = createStderrLogger(options.logLevel);
|
|
4584
|
+
const top = Number.parseInt(options.top, 10);
|
|
4585
|
+
const explainOptions = {
|
|
4586
|
+
...options.file === void 0 ? {} : { file: options.file },
|
|
4587
|
+
...options.module === void 0 ? {} : { module: options.module },
|
|
4588
|
+
top: Number.isFinite(top) ? top : 5,
|
|
4589
|
+
format: options.format
|
|
4590
|
+
};
|
|
4591
|
+
const result = await runExplainCommand(
|
|
4592
|
+
path,
|
|
4593
|
+
options.authorIdentity,
|
|
4594
|
+
explainOptions,
|
|
4595
|
+
logger
|
|
4596
|
+
);
|
|
4597
|
+
process.stdout.write(`${formatExplainOutput(result, options.format)}
|
|
4598
|
+
`);
|
|
4599
|
+
}
|
|
4600
|
+
);
|
|
3343
4601
|
program.command("dependency-risk").argument("<dependency>", "dependency spec to evaluate (for example: react or react@19.0.0)").addOption(
|
|
3344
4602
|
new Option(
|
|
3345
4603
|
"--log-level <level>",
|
|
@@ -3373,6 +4631,39 @@ program.command("dependency-risk").argument("<dependency>", "dependency spec to
|
|
|
3373
4631
|
`);
|
|
3374
4632
|
}
|
|
3375
4633
|
);
|
|
4634
|
+
program.command("report").argument("[path]", "path to the project to analyze").addOption(
|
|
4635
|
+
new Option(
|
|
4636
|
+
"--author-identity <mode>",
|
|
4637
|
+
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
4638
|
+
).choices(["likely_merge", "strict_email"]).default("likely_merge")
|
|
4639
|
+
).addOption(
|
|
4640
|
+
new Option(
|
|
4641
|
+
"--log-level <level>",
|
|
4642
|
+
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
4643
|
+
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
4644
|
+
).addOption(
|
|
4645
|
+
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")
|
|
4646
|
+
).option("--output <path>", "write rendered report to a file path").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").action(
|
|
4647
|
+
async (path, options) => {
|
|
4648
|
+
const logger = createStderrLogger(options.logLevel);
|
|
4649
|
+
const result = await runReportCommand(
|
|
4650
|
+
path,
|
|
4651
|
+
options.authorIdentity,
|
|
4652
|
+
{
|
|
4653
|
+
format: options.format,
|
|
4654
|
+
...options.output === void 0 ? {} : { outputPath: options.output },
|
|
4655
|
+
...options.compare === void 0 ? {} : { comparePath: options.compare },
|
|
4656
|
+
...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
|
|
4657
|
+
includeTrace: options.trace
|
|
4658
|
+
},
|
|
4659
|
+
logger
|
|
4660
|
+
);
|
|
4661
|
+
if (options.output === void 0) {
|
|
4662
|
+
process.stdout.write(`${result.rendered}
|
|
4663
|
+
`);
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
);
|
|
3376
4667
|
if (process.argv.length <= 2) {
|
|
3377
4668
|
program.outputHelp();
|
|
3378
4669
|
process.exit(0);
|