@getcodesentinel/codesentinel 1.12.1 → 1.14.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 +24 -4
- package/dist/index.js +474 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1524,9 +1524,7 @@ var compareSnapshots = (current, baseline) => {
|
|
|
1524
1524
|
const hotspots = diffSets(currentHotspots, baselineHotspots);
|
|
1525
1525
|
const cycles = diffSets(currentCycles, baselineCycles);
|
|
1526
1526
|
return {
|
|
1527
|
-
|
|
1528
|
-
current.analysis.risk.repositoryScore - baseline.analysis.risk.repositoryScore
|
|
1529
|
-
),
|
|
1527
|
+
riskScoreDelta: round43(current.analysis.risk.riskScore - baseline.analysis.risk.riskScore),
|
|
1530
1528
|
normalizedScoreDelta: round43(
|
|
1531
1529
|
current.analysis.risk.normalizedScore - baseline.analysis.risk.normalizedScore
|
|
1532
1530
|
),
|
|
@@ -1652,9 +1650,9 @@ var createReport = (snapshot, diff) => {
|
|
|
1652
1650
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1653
1651
|
repository: {
|
|
1654
1652
|
targetPath: snapshot.analysis.structural.targetPath,
|
|
1655
|
-
|
|
1653
|
+
riskScore: snapshot.analysis.risk.riskScore,
|
|
1656
1654
|
normalizedScore: snapshot.analysis.risk.normalizedScore,
|
|
1657
|
-
riskTier: toRiskTier(snapshot.analysis.risk.
|
|
1655
|
+
riskTier: toRiskTier(snapshot.analysis.risk.riskScore),
|
|
1658
1656
|
confidence: repositoryConfidence(snapshot),
|
|
1659
1657
|
dimensionScores: repositoryDimensionScores(snapshot)
|
|
1660
1658
|
},
|
|
@@ -1706,7 +1704,7 @@ var renderTextDiff = (report) => {
|
|
|
1706
1704
|
return [
|
|
1707
1705
|
"",
|
|
1708
1706
|
"Diff",
|
|
1709
|
-
`
|
|
1707
|
+
` riskScoreDelta: ${report.diff.riskScoreDelta}`,
|
|
1710
1708
|
` normalizedScoreDelta: ${report.diff.normalizedScoreDelta}`,
|
|
1711
1709
|
` newHotspots: ${report.diff.newHotspots.join(", ") || "none"}`,
|
|
1712
1710
|
` resolvedHotspots: ${report.diff.resolvedHotspots.join(", ") || "none"}`,
|
|
@@ -1718,7 +1716,7 @@ var renderTextReport = (report) => {
|
|
|
1718
1716
|
const lines = [];
|
|
1719
1717
|
lines.push("Repository Summary");
|
|
1720
1718
|
lines.push(` target: ${report.repository.targetPath}`);
|
|
1721
|
-
lines.push(`
|
|
1719
|
+
lines.push(` riskScore: ${report.repository.riskScore}`);
|
|
1722
1720
|
lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
|
|
1723
1721
|
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
1724
1722
|
lines.push(` confidence: ${report.repository.confidence ?? "n/a"}`);
|
|
@@ -1738,8 +1736,7 @@ var renderTextReport = (report) => {
|
|
|
1738
1736
|
);
|
|
1739
1737
|
lines.push(` evidence: ${factor.evidence}`);
|
|
1740
1738
|
}
|
|
1741
|
-
lines.push(` actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
1742
|
-
lines.push(` levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
|
|
1739
|
+
lines.push(` priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
1743
1740
|
}
|
|
1744
1741
|
lines.push("");
|
|
1745
1742
|
lines.push("Structural Observations");
|
|
@@ -1780,7 +1777,7 @@ var renderMarkdownDiff = (report) => {
|
|
|
1780
1777
|
return [
|
|
1781
1778
|
"",
|
|
1782
1779
|
"## Diff",
|
|
1783
|
-
`-
|
|
1780
|
+
`- riskScoreDelta: \`${report.diff.riskScoreDelta}\``,
|
|
1784
1781
|
`- normalizedScoreDelta: \`${report.diff.normalizedScoreDelta}\``,
|
|
1785
1782
|
`- newHotspots: ${report.diff.newHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
1786
1783
|
`- resolvedHotspots: ${report.diff.resolvedHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
@@ -1794,7 +1791,7 @@ var renderMarkdownReport = (report) => {
|
|
|
1794
1791
|
lines.push("");
|
|
1795
1792
|
lines.push("## Repository Summary");
|
|
1796
1793
|
lines.push(`- target: \`${report.repository.targetPath}\``);
|
|
1797
|
-
lines.push(`-
|
|
1794
|
+
lines.push(`- riskScore: \`${report.repository.riskScore}\``);
|
|
1798
1795
|
lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
|
|
1799
1796
|
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
1800
1797
|
lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
|
|
@@ -1815,8 +1812,7 @@ var renderMarkdownReport = (report) => {
|
|
|
1815
1812
|
);
|
|
1816
1813
|
lines.push(` - evidence: \`${factor.evidence}\``);
|
|
1817
1814
|
}
|
|
1818
|
-
lines.push(` -
|
|
1819
|
-
lines.push(` - Biggest levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
|
|
1815
|
+
lines.push(` - Priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
1820
1816
|
}
|
|
1821
1817
|
lines.push("");
|
|
1822
1818
|
lines.push("## Structural Observations");
|
|
@@ -1973,7 +1969,7 @@ var evaluateGates = (input) => {
|
|
|
1973
1969
|
const evaluatedGates = [];
|
|
1974
1970
|
if (config.maxRepoScore !== void 0) {
|
|
1975
1971
|
evaluatedGates.push("max-repo-score");
|
|
1976
|
-
const current = input.current.analysis.risk.
|
|
1972
|
+
const current = input.current.analysis.risk.riskScore;
|
|
1977
1973
|
if (current > config.maxRepoScore) {
|
|
1978
1974
|
violations.push(
|
|
1979
1975
|
makeViolation(
|
|
@@ -1981,7 +1977,7 @@ var evaluateGates = (input) => {
|
|
|
1981
1977
|
"error",
|
|
1982
1978
|
`Repository score ${current} exceeds configured max ${config.maxRepoScore}.`,
|
|
1983
1979
|
[input.current.analysis.structural.targetPath],
|
|
1984
|
-
[{ kind: "repository_metric", metric: "
|
|
1980
|
+
[{ kind: "repository_metric", metric: "riskScore" }]
|
|
1985
1981
|
)
|
|
1986
1982
|
);
|
|
1987
1983
|
}
|
|
@@ -2093,7 +2089,7 @@ var renderCheckText = (snapshot, result) => {
|
|
|
2093
2089
|
const lines = [];
|
|
2094
2090
|
lines.push("CodeSentinel Check");
|
|
2095
2091
|
lines.push(`target: ${snapshot.analysis.structural.targetPath}`);
|
|
2096
|
-
lines.push(`
|
|
2092
|
+
lines.push(`riskScore: ${snapshot.analysis.risk.riskScore}`);
|
|
2097
2093
|
lines.push(`evaluatedGates: ${result.evaluatedGates.join(", ") || "none"}`);
|
|
2098
2094
|
lines.push(`violations: ${result.violations.length}`);
|
|
2099
2095
|
lines.push(`exitCode: ${result.exitCode}`);
|
|
@@ -2112,7 +2108,7 @@ var renderCheckMarkdown = (snapshot, result) => {
|
|
|
2112
2108
|
const lines = [];
|
|
2113
2109
|
lines.push("## CodeSentinel CI Summary");
|
|
2114
2110
|
lines.push(`- target: \`${snapshot.analysis.structural.targetPath}\``);
|
|
2115
|
-
lines.push(`-
|
|
2111
|
+
lines.push(`- riskScore: \`${snapshot.analysis.risk.riskScore}\``);
|
|
2116
2112
|
lines.push(
|
|
2117
2113
|
`- evaluatedGates: ${result.evaluatedGates.map((item) => `\`${item}\``).join(", ") || "none"}`
|
|
2118
2114
|
);
|
|
@@ -2463,6 +2459,7 @@ var resolveAutoBaselineRef = async (input) => {
|
|
|
2463
2459
|
|
|
2464
2460
|
// src/index.ts
|
|
2465
2461
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2462
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
2466
2463
|
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
2467
2464
|
import { fileURLToPath } from "url";
|
|
2468
2465
|
|
|
@@ -2495,7 +2492,7 @@ var createSummaryShape = (summary) => ({
|
|
|
2495
2492
|
reason: summary.external.reason
|
|
2496
2493
|
},
|
|
2497
2494
|
risk: {
|
|
2498
|
-
|
|
2495
|
+
riskScore: summary.risk.riskScore,
|
|
2499
2496
|
normalizedScore: summary.risk.normalizedScore,
|
|
2500
2497
|
hotspotsTop: summary.risk.hotspots.slice(0, 5).map((hotspot) => ({
|
|
2501
2498
|
file: hotspot.file,
|
|
@@ -2546,6 +2543,7 @@ var formatFactorLabel = (factorId) => factorLabelById2[factorId] ?? factorId;
|
|
|
2546
2543
|
var formatNumber = (value) => value === null || value === void 0 ? "n/a" : `${value}`;
|
|
2547
2544
|
var formatDimension = (value) => value === null ? "n/a" : `${value}`;
|
|
2548
2545
|
var formatFactorSummary = (factor) => `${formatFactorLabel(factor.factorId)} (+${factor.contribution}, confidence=${factor.confidence})`;
|
|
2546
|
+
var formatFactorContribution = (factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`;
|
|
2549
2547
|
var formatFactorEvidence = (factor) => {
|
|
2550
2548
|
if (factor.factorId === "repository.structural") {
|
|
2551
2549
|
return `structural dimension=${formatNumber(factor.rawMetrics["structuralDimension"])}`;
|
|
@@ -2560,7 +2558,7 @@ var formatFactorEvidence = (factor) => {
|
|
|
2560
2558
|
return `structural\xD7evolution=${formatNumber(factor.rawMetrics["structuralEvolution"])}, central instability=${formatNumber(factor.rawMetrics["centralInstability"])}, dependency amplification=${formatNumber(factor.rawMetrics["dependencyAmplification"])}`;
|
|
2561
2559
|
}
|
|
2562
2560
|
if (factor.factorId === "file.structural") {
|
|
2563
|
-
return `fanIn=${formatNumber(factor.rawMetrics["fanIn"])}, fanOut=${formatNumber(factor.rawMetrics["fanOut"])}, depth=${formatNumber(factor.rawMetrics["depth"])}, inCycle=${formatNumber(factor.rawMetrics["cycleParticipation"])}`;
|
|
2561
|
+
return `fanIn=${formatNumber(factor.rawMetrics["fanIn"])}, fanOut=${formatNumber(factor.rawMetrics["fanOut"])}, depth=${formatNumber(factor.rawMetrics["depth"])}, inCycle=${formatNumber(factor.rawMetrics["cycleParticipation"])}, structuralAttenuation=${formatNumber(factor.rawMetrics["structuralAttenuation"])}`;
|
|
2564
2562
|
}
|
|
2565
2563
|
if (factor.factorId === "file.evolution") {
|
|
2566
2564
|
return `commitCount=${formatNumber(factor.rawMetrics["commitCount"])}, churnTotal=${formatNumber(factor.rawMetrics["churnTotal"])}, recentVolatility=${formatNumber(factor.rawMetrics["recentVolatility"])}`;
|
|
@@ -2667,8 +2665,8 @@ var renderText = (payload) => {
|
|
|
2667
2665
|
const dimensionScores = repositoryDimensionScores2(repositoryTarget);
|
|
2668
2666
|
const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
|
|
2669
2667
|
lines.push(`target: ${payload.summary.structural.targetPath}`);
|
|
2670
|
-
lines.push(`
|
|
2671
|
-
lines.push(`riskBand: ${toRiskBand(payload.summary.risk.
|
|
2668
|
+
lines.push(`riskScore: ${payload.summary.risk.riskScore}`);
|
|
2669
|
+
lines.push(`riskBand: ${toRiskBand(payload.summary.risk.riskScore)}`);
|
|
2672
2670
|
lines.push(`selectedTargets: ${payload.selectedTargets.length}`);
|
|
2673
2671
|
lines.push("dimensionScores:");
|
|
2674
2672
|
lines.push(` structural: ${formatDimension(dimensionScores.structural)}`);
|
|
@@ -2678,20 +2676,15 @@ var renderText = (payload) => {
|
|
|
2678
2676
|
lines.push("");
|
|
2679
2677
|
lines.push("explanation:");
|
|
2680
2678
|
lines.push(
|
|
2681
|
-
`
|
|
2682
|
-
);
|
|
2683
|
-
lines.push(
|
|
2684
|
-
` what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
|
|
2679
|
+
` key drivers: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
|
|
2685
2680
|
);
|
|
2686
2681
|
lines.push(
|
|
2687
|
-
`
|
|
2682
|
+
` contributions: ${repositoryTopFactors.map(formatFactorContribution).join(", ") || "insufficient data"}`
|
|
2688
2683
|
);
|
|
2689
2684
|
lines.push(
|
|
2690
|
-
`
|
|
2691
|
-
);
|
|
2692
|
-
lines.push(
|
|
2693
|
-
` what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
|
|
2685
|
+
` interaction effects: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
|
|
2694
2686
|
);
|
|
2687
|
+
lines.push(` priority actions: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`);
|
|
2695
2688
|
lines.push("");
|
|
2696
2689
|
for (const target of payload.selectedTargets) {
|
|
2697
2690
|
lines.push(renderTargetText(target));
|
|
@@ -2707,8 +2700,8 @@ var renderMarkdown = (payload) => {
|
|
|
2707
2700
|
const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
|
|
2708
2701
|
lines.push(`# CodeSentinel Explanation`);
|
|
2709
2702
|
lines.push(`- target: \`${payload.summary.structural.targetPath}\``);
|
|
2710
|
-
lines.push(`-
|
|
2711
|
-
lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.
|
|
2703
|
+
lines.push(`- riskScore: \`${payload.summary.risk.riskScore}\``);
|
|
2704
|
+
lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.riskScore)}\``);
|
|
2712
2705
|
lines.push(`- selectedTargets: \`${payload.selectedTargets.length}\``);
|
|
2713
2706
|
lines.push("");
|
|
2714
2707
|
lines.push("## Dimension Scores (0-100)");
|
|
@@ -2719,25 +2712,19 @@ var renderMarkdown = (payload) => {
|
|
|
2719
2712
|
lines.push("");
|
|
2720
2713
|
lines.push(`## Summary`);
|
|
2721
2714
|
lines.push(
|
|
2722
|
-
`-
|
|
2723
|
-
);
|
|
2724
|
-
lines.push(
|
|
2725
|
-
`- what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
|
|
2726
|
-
);
|
|
2727
|
-
lines.push(
|
|
2728
|
-
`- dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
|
|
2715
|
+
`- key drivers: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
|
|
2729
2716
|
);
|
|
2730
2717
|
lines.push(
|
|
2731
|
-
`-
|
|
2718
|
+
`- contributions: ${repositoryTopFactors.map(formatFactorContribution).join(", ") || "insufficient data"}`
|
|
2732
2719
|
);
|
|
2733
2720
|
lines.push(
|
|
2734
|
-
`-
|
|
2721
|
+
`- interaction effects: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
|
|
2735
2722
|
);
|
|
2723
|
+
lines.push(`- priority actions: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`);
|
|
2736
2724
|
lines.push("");
|
|
2737
2725
|
for (const target of payload.selectedTargets) {
|
|
2738
2726
|
lines.push(`## ${target.targetType}: \`${target.targetId}\``);
|
|
2739
2727
|
lines.push(`- score: \`${target.totalScore}\` (\`${target.normalizedScore}\`)`);
|
|
2740
|
-
lines.push(`- dominantFactors: \`${target.dominantFactors.join(", ")}\``);
|
|
2741
2728
|
lines.push(`- Top factors:`);
|
|
2742
2729
|
for (const factor of [...target.factors].sort(sortFactorByContribution).slice(0, 5)) {
|
|
2743
2730
|
lines.push(
|
|
@@ -4308,6 +4295,17 @@ var DEFAULT_RISK_ENGINE_CONFIG = {
|
|
|
4308
4295
|
externalDimension: {
|
|
4309
4296
|
topDependencyPercentile: 0.85,
|
|
4310
4297
|
dependencyDepthHalfLife: 6
|
|
4298
|
+
},
|
|
4299
|
+
// Reduce false positives for thin aggregation hubs (for example, barrel/index re-export files)
|
|
4300
|
+
// that are structurally central but have low churn density and no cycle participation.
|
|
4301
|
+
aggregatorAttenuation: {
|
|
4302
|
+
enabled: true,
|
|
4303
|
+
minFanIn: 6,
|
|
4304
|
+
minFanOut: 4,
|
|
4305
|
+
minCommitCount: 4,
|
|
4306
|
+
maxChurnPerCommit: 24,
|
|
4307
|
+
maxChurnPerDependency: 10,
|
|
4308
|
+
maxStructuralReduction: 0.3
|
|
4311
4309
|
}
|
|
4312
4310
|
};
|
|
4313
4311
|
var toUnitInterval = (value) => Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
|
|
@@ -4400,6 +4398,35 @@ var normalizeWithScale = (value, scale) => {
|
|
|
4400
4398
|
return toUnitInterval((value - scale.lower) / (scale.upper - scale.lower));
|
|
4401
4399
|
};
|
|
4402
4400
|
var normalizePath2 = (path) => path.replaceAll("\\", "/");
|
|
4401
|
+
var computeAggregatorAttenuation = (input) => {
|
|
4402
|
+
const { fanIn, fanOut, inCycle, evolutionMetrics, config } = input;
|
|
4403
|
+
if (!config.enabled || inCycle > 0) {
|
|
4404
|
+
return 1;
|
|
4405
|
+
}
|
|
4406
|
+
if (fanIn < config.minFanIn || fanOut < config.minFanOut) {
|
|
4407
|
+
return 1;
|
|
4408
|
+
}
|
|
4409
|
+
if (evolutionMetrics === void 0 || evolutionMetrics.commitCount < config.minCommitCount) {
|
|
4410
|
+
return 1;
|
|
4411
|
+
}
|
|
4412
|
+
const churnPerCommit = evolutionMetrics.churnTotal / Math.max(1, evolutionMetrics.commitCount);
|
|
4413
|
+
const churnPerDependency = evolutionMetrics.churnTotal / Math.max(1, fanOut);
|
|
4414
|
+
if (churnPerCommit > config.maxChurnPerCommit || churnPerDependency > config.maxChurnPerDependency) {
|
|
4415
|
+
return 1;
|
|
4416
|
+
}
|
|
4417
|
+
const fanInSignal = toUnitInterval((fanIn - config.minFanIn) / Math.max(1, config.minFanIn));
|
|
4418
|
+
const fanOutSignal = toUnitInterval((fanOut - config.minFanOut) / Math.max(1, config.minFanOut));
|
|
4419
|
+
const lowChurnPerCommitSignal = 1 - toUnitInterval(churnPerCommit / config.maxChurnPerCommit);
|
|
4420
|
+
const lowChurnPerDependencySignal = 1 - toUnitInterval(churnPerDependency / config.maxChurnPerDependency);
|
|
4421
|
+
const attenuationConfidence = average([
|
|
4422
|
+
fanInSignal,
|
|
4423
|
+
fanOutSignal,
|
|
4424
|
+
lowChurnPerCommitSignal,
|
|
4425
|
+
lowChurnPerDependencySignal
|
|
4426
|
+
]);
|
|
4427
|
+
const reduction = toUnitInterval(config.maxStructuralReduction) * attenuationConfidence;
|
|
4428
|
+
return round45(toUnitInterval(1 - reduction));
|
|
4429
|
+
};
|
|
4403
4430
|
var dependencySignalWeights = {
|
|
4404
4431
|
single_maintainer: 0.3,
|
|
4405
4432
|
abandoned: 0.3,
|
|
@@ -4824,10 +4851,10 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4824
4851
|
const fanOutRisk = normalizeWithScale(logScale(file.fanOut), fanOutScale);
|
|
4825
4852
|
const depthRisk = normalizeWithScale(file.depth, depthScale);
|
|
4826
4853
|
const structuralWeights = config.structuralFactorWeights;
|
|
4827
|
-
const
|
|
4854
|
+
const structuralFactorRaw = toUnitInterval(
|
|
4828
4855
|
fanInRisk * structuralWeights.fanIn + fanOutRisk * structuralWeights.fanOut + depthRisk * structuralWeights.depth + inCycle * structuralWeights.cycleParticipation
|
|
4829
4856
|
);
|
|
4830
|
-
const
|
|
4857
|
+
const structuralCentralityRaw = toUnitInterval((fanInRisk + fanOutRisk) / 2);
|
|
4831
4858
|
let evolutionFactor = 0;
|
|
4832
4859
|
let frequencyRisk = 0;
|
|
4833
4860
|
let churnRisk = 0;
|
|
@@ -4854,6 +4881,15 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4854
4881
|
frequencyRisk * evolutionWeights.frequency + churnRisk * evolutionWeights.churn + volatilityRisk * evolutionWeights.recentVolatility + ownershipConcentrationRisk * evolutionWeights.ownershipConcentration + busFactorRisk * evolutionWeights.busFactorRisk
|
|
4855
4882
|
);
|
|
4856
4883
|
}
|
|
4884
|
+
const structuralAttenuation = computeAggregatorAttenuation({
|
|
4885
|
+
fanIn: file.fanIn,
|
|
4886
|
+
fanOut: file.fanOut,
|
|
4887
|
+
inCycle,
|
|
4888
|
+
evolutionMetrics,
|
|
4889
|
+
config: config.aggregatorAttenuation
|
|
4890
|
+
});
|
|
4891
|
+
const structuralFactor = toUnitInterval(structuralFactorRaw * structuralAttenuation);
|
|
4892
|
+
const structuralCentrality = toUnitInterval(structuralCentralityRaw * structuralAttenuation);
|
|
4857
4893
|
const dependencyAffinity = toUnitInterval(structuralCentrality * 0.6 + evolutionFactor * 0.4);
|
|
4858
4894
|
const externalFactor = external.available ? toUnitInterval(dependencyComputation.repositoryExternalPressure * dependencyAffinity) : 0;
|
|
4859
4895
|
const structuralBase = structuralFactor * dimensionWeights.structural;
|
|
@@ -4898,7 +4934,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4898
4934
|
topAuthorShare: evolutionMetrics?.topAuthorShare ?? null,
|
|
4899
4935
|
busFactor: evolutionMetrics?.busFactor ?? null,
|
|
4900
4936
|
dependencyAffinity: round45(dependencyAffinity),
|
|
4901
|
-
repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure)
|
|
4937
|
+
repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure),
|
|
4938
|
+
structuralAttenuation: round45(structuralAttenuation)
|
|
4902
4939
|
},
|
|
4903
4940
|
normalizedMetrics: {
|
|
4904
4941
|
fanInRisk: round45(fanInRisk),
|
|
@@ -4941,7 +4978,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4941
4978
|
fanIn: context.rawMetrics.fanIn,
|
|
4942
4979
|
fanOut: context.rawMetrics.fanOut,
|
|
4943
4980
|
depth: context.rawMetrics.depth,
|
|
4944
|
-
cycleParticipation: context.rawMetrics.cycleParticipation
|
|
4981
|
+
cycleParticipation: context.rawMetrics.cycleParticipation,
|
|
4982
|
+
structuralAttenuation: context.rawMetrics.structuralAttenuation
|
|
4945
4983
|
},
|
|
4946
4984
|
normalizedMetrics: {
|
|
4947
4985
|
fanInRisk: context.normalizedMetrics.fanInRisk,
|
|
@@ -5227,9 +5265,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
5227
5265
|
criticalInstability * config.interactionWeights.centralInstability,
|
|
5228
5266
|
dependencyAmplification * config.interactionWeights.dependencyAmplification
|
|
5229
5267
|
]);
|
|
5230
|
-
const
|
|
5268
|
+
const riskScore = round45(repositoryNormalizedScore * 100);
|
|
5231
5269
|
if (collector !== void 0) {
|
|
5232
|
-
const repositoryFactors = buildFactorTraces(
|
|
5270
|
+
const repositoryFactors = buildFactorTraces(riskScore, [
|
|
5233
5271
|
{
|
|
5234
5272
|
factorId: "repository.structural",
|
|
5235
5273
|
family: "structural",
|
|
@@ -5292,14 +5330,14 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
5292
5330
|
buildTargetTrace(
|
|
5293
5331
|
"repository",
|
|
5294
5332
|
structural.targetPath,
|
|
5295
|
-
|
|
5333
|
+
riskScore,
|
|
5296
5334
|
repositoryNormalizedScore,
|
|
5297
5335
|
repositoryFactors
|
|
5298
5336
|
)
|
|
5299
5337
|
);
|
|
5300
5338
|
}
|
|
5301
5339
|
return {
|
|
5302
|
-
|
|
5340
|
+
riskScore,
|
|
5303
5341
|
normalizedScore: round45(repositoryNormalizedScore),
|
|
5304
5342
|
hotspots,
|
|
5305
5343
|
fragileClusters,
|
|
@@ -5390,6 +5428,10 @@ var mergeConfig = (overrides) => {
|
|
|
5390
5428
|
externalDimension: {
|
|
5391
5429
|
...DEFAULT_RISK_ENGINE_CONFIG.externalDimension,
|
|
5392
5430
|
...overrides.externalDimension
|
|
5431
|
+
},
|
|
5432
|
+
aggregatorAttenuation: {
|
|
5433
|
+
...DEFAULT_RISK_ENGINE_CONFIG.aggregatorAttenuation,
|
|
5434
|
+
...overrides.aggregatorAttenuation
|
|
5393
5435
|
}
|
|
5394
5436
|
};
|
|
5395
5437
|
};
|
|
@@ -5421,6 +5463,21 @@ var evaluateRepositoryRisk = (input, options = {}) => {
|
|
|
5421
5463
|
|
|
5422
5464
|
// src/application/run-analyze-command.ts
|
|
5423
5465
|
var resolveTargetPath = (inputPath, cwd) => resolve3(cwd, inputPath ?? ".");
|
|
5466
|
+
var riskProfileConfig = {
|
|
5467
|
+
default: void 0,
|
|
5468
|
+
personal: {
|
|
5469
|
+
evolutionFactorWeights: {
|
|
5470
|
+
frequency: 0.26,
|
|
5471
|
+
churn: 0.24,
|
|
5472
|
+
recentVolatility: 0.2,
|
|
5473
|
+
ownershipConcentration: 0.08,
|
|
5474
|
+
busFactorRisk: 0.04
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
};
|
|
5478
|
+
var resolveRiskConfigForProfile = (riskProfile) => {
|
|
5479
|
+
return riskProfileConfig[riskProfile ?? "default"];
|
|
5480
|
+
};
|
|
5424
5481
|
var createExternalProgressReporter = (logger) => {
|
|
5425
5482
|
let lastLoggedProgress = 0;
|
|
5426
5483
|
return (event) => {
|
|
@@ -5588,8 +5645,12 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
|
|
|
5588
5645
|
logger
|
|
5589
5646
|
);
|
|
5590
5647
|
logger.info("computing risk summary");
|
|
5591
|
-
const
|
|
5592
|
-
|
|
5648
|
+
const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
|
|
5649
|
+
const risk = computeRepositoryRiskSummary({
|
|
5650
|
+
...analysisInputs,
|
|
5651
|
+
...riskConfig === void 0 ? {} : { config: riskConfig }
|
|
5652
|
+
});
|
|
5653
|
+
logger.info(`analysis completed (riskScore=${risk.riskScore})`);
|
|
5593
5654
|
return {
|
|
5594
5655
|
...analysisInputs,
|
|
5595
5656
|
risk
|
|
@@ -5609,7 +5670,14 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
|
|
|
5609
5670
|
},
|
|
5610
5671
|
logger
|
|
5611
5672
|
);
|
|
5612
|
-
const
|
|
5673
|
+
const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
|
|
5674
|
+
const evaluation = evaluateRepositoryRisk(
|
|
5675
|
+
{
|
|
5676
|
+
...analysisInputs,
|
|
5677
|
+
...riskConfig === void 0 ? {} : { config: riskConfig }
|
|
5678
|
+
},
|
|
5679
|
+
{ explain: options.includeTrace }
|
|
5680
|
+
);
|
|
5613
5681
|
const summary = {
|
|
5614
5682
|
...analysisInputs,
|
|
5615
5683
|
risk: evaluation.summary
|
|
@@ -5620,6 +5688,7 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
|
|
|
5620
5688
|
analysisConfig: {
|
|
5621
5689
|
authorIdentityMode,
|
|
5622
5690
|
includeTrace: options.includeTrace,
|
|
5691
|
+
riskProfile: options.riskProfile ?? "default",
|
|
5623
5692
|
recentWindowDays: analysisInputs.evolution.available ? analysisInputs.evolution.metrics.recentWindowDays : options.recentWindowDays ?? null
|
|
5624
5693
|
}
|
|
5625
5694
|
});
|
|
@@ -5654,6 +5723,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
|
|
|
5654
5723
|
authorIdentityMode,
|
|
5655
5724
|
{
|
|
5656
5725
|
includeTrace: options.includeTrace,
|
|
5726
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5657
5727
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5658
5728
|
},
|
|
5659
5729
|
logger
|
|
@@ -5722,6 +5792,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
5722
5792
|
authorIdentityMode,
|
|
5723
5793
|
{
|
|
5724
5794
|
includeTrace: options.includeTrace,
|
|
5795
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5725
5796
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5726
5797
|
},
|
|
5727
5798
|
logger
|
|
@@ -5780,6 +5851,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
5780
5851
|
authorIdentityMode,
|
|
5781
5852
|
{
|
|
5782
5853
|
includeTrace: options.includeTrace,
|
|
5854
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5783
5855
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5784
5856
|
},
|
|
5785
5857
|
logger
|
|
@@ -5855,6 +5927,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
|
|
|
5855
5927
|
authorIdentityMode,
|
|
5856
5928
|
{
|
|
5857
5929
|
includeTrace: options.includeTrace,
|
|
5930
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5858
5931
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5859
5932
|
},
|
|
5860
5933
|
logger
|
|
@@ -5911,7 +5984,14 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
|
|
|
5911
5984
|
logger
|
|
5912
5985
|
);
|
|
5913
5986
|
logger.info("computing explainable risk summary");
|
|
5914
|
-
const
|
|
5987
|
+
const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
|
|
5988
|
+
const evaluation = evaluateRepositoryRisk(
|
|
5989
|
+
{
|
|
5990
|
+
...analysisInputs,
|
|
5991
|
+
...riskConfig === void 0 ? {} : { config: riskConfig }
|
|
5992
|
+
},
|
|
5993
|
+
{ explain: true }
|
|
5994
|
+
);
|
|
5915
5995
|
if (evaluation.trace === void 0) {
|
|
5916
5996
|
throw new Error("risk trace unavailable");
|
|
5917
5997
|
}
|
|
@@ -5919,7 +5999,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
|
|
|
5919
5999
|
...analysisInputs,
|
|
5920
6000
|
risk: evaluation.summary
|
|
5921
6001
|
};
|
|
5922
|
-
logger.info(`explanation completed (
|
|
6002
|
+
logger.info(`explanation completed (riskScore=${summary.risk.riskScore})`);
|
|
5923
6003
|
return {
|
|
5924
6004
|
summary,
|
|
5925
6005
|
trace: evaluation.trace,
|
|
@@ -5938,8 +6018,124 @@ var parseRecentWindowDays = (value) => {
|
|
|
5938
6018
|
}
|
|
5939
6019
|
return parsed;
|
|
5940
6020
|
};
|
|
6021
|
+
var stripLeadingMarkdownHeading = (value, heading) => {
|
|
6022
|
+
const prefix = `${heading}
|
|
6023
|
+
`;
|
|
6024
|
+
if (value.startsWith(prefix)) {
|
|
6025
|
+
return value.slice(prefix.length).trimStart();
|
|
6026
|
+
}
|
|
6027
|
+
return value;
|
|
6028
|
+
};
|
|
6029
|
+
var extractExplainTextSummary = (text) => {
|
|
6030
|
+
const splitIndex = text.search(/\n\n(?:file|module|dependency|repository): /);
|
|
6031
|
+
if (splitIndex < 0) {
|
|
6032
|
+
return text.trim();
|
|
6033
|
+
}
|
|
6034
|
+
return text.slice(0, splitIndex).trim();
|
|
6035
|
+
};
|
|
6036
|
+
var extractExplainMarkdownSummary = (markdown) => {
|
|
6037
|
+
const splitIndex = markdown.search(/\n\n## (?:file|module|dependency|repository): /);
|
|
6038
|
+
if (splitIndex < 0) {
|
|
6039
|
+
return markdown.trim();
|
|
6040
|
+
}
|
|
6041
|
+
return markdown.slice(0, splitIndex).trim();
|
|
6042
|
+
};
|
|
6043
|
+
var extractSummaryValue = (summary, key) => {
|
|
6044
|
+
const prefix = `${key}: `;
|
|
6045
|
+
for (const rawLine of summary.split("\n")) {
|
|
6046
|
+
const line = rawLine.trimStart();
|
|
6047
|
+
if (line.startsWith(prefix)) {
|
|
6048
|
+
return line.slice(prefix.length).trim();
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
return void 0;
|
|
6052
|
+
};
|
|
6053
|
+
var renderReportHighlightsText = (report) => {
|
|
6054
|
+
const lines = [];
|
|
6055
|
+
lines.push("Repository Summary");
|
|
6056
|
+
lines.push(` target: ${report.repository.targetPath}`);
|
|
6057
|
+
lines.push(` riskScore: ${report.repository.riskScore}`);
|
|
6058
|
+
lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
|
|
6059
|
+
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
6060
|
+
lines.push("");
|
|
6061
|
+
lines.push("Top Hotspots");
|
|
6062
|
+
for (const hotspot of report.hotspots.slice(0, 5)) {
|
|
6063
|
+
lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
|
|
6064
|
+
lines.push(` priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
6065
|
+
}
|
|
6066
|
+
return lines.join("\n");
|
|
6067
|
+
};
|
|
6068
|
+
var renderReportHighlightsMarkdown = (report) => {
|
|
6069
|
+
const lines = [];
|
|
6070
|
+
lines.push("## Repository Summary");
|
|
6071
|
+
lines.push(`- target: \`${report.repository.targetPath}\``);
|
|
6072
|
+
lines.push(`- riskScore: \`${report.repository.riskScore}\``);
|
|
6073
|
+
lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
|
|
6074
|
+
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
6075
|
+
lines.push("");
|
|
6076
|
+
lines.push("## Top Hotspots");
|
|
6077
|
+
for (const hotspot of report.hotspots.slice(0, 5)) {
|
|
6078
|
+
lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
|
|
6079
|
+
lines.push(` - priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
|
|
6080
|
+
}
|
|
6081
|
+
return lines.join("\n");
|
|
6082
|
+
};
|
|
6083
|
+
var renderCompactText = (report, explainSummary) => {
|
|
6084
|
+
const lines = [];
|
|
6085
|
+
lines.push("CodeSentinel Run (compact)");
|
|
6086
|
+
lines.push("");
|
|
6087
|
+
lines.push("Repository");
|
|
6088
|
+
lines.push(` target: ${report.repository.targetPath}`);
|
|
6089
|
+
lines.push(` riskScore: ${report.repository.riskScore}`);
|
|
6090
|
+
lines.push(` riskTier: ${report.repository.riskTier}`);
|
|
6091
|
+
lines.push(
|
|
6092
|
+
` dimensions: structural=${report.repository.dimensionScores.structural ?? "n/a"}, evolution=${report.repository.dimensionScores.evolution ?? "n/a"}, external=${report.repository.dimensionScores.external ?? "n/a"}, interactions=${report.repository.dimensionScores.interactions ?? "n/a"}`
|
|
6093
|
+
);
|
|
6094
|
+
lines.push("");
|
|
6095
|
+
lines.push(
|
|
6096
|
+
`Key Drivers: ${extractSummaryValue(explainSummary, "key drivers") ?? "insufficient data"}`
|
|
6097
|
+
);
|
|
6098
|
+
lines.push(
|
|
6099
|
+
`Priority Actions: ${extractSummaryValue(explainSummary, "priority actions") ?? "insufficient data"}`
|
|
6100
|
+
);
|
|
6101
|
+
lines.push("");
|
|
6102
|
+
lines.push("Top Hotspots");
|
|
6103
|
+
for (const hotspot of report.hotspots.slice(0, 3)) {
|
|
6104
|
+
lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
|
|
6105
|
+
}
|
|
6106
|
+
return lines.join("\n");
|
|
6107
|
+
};
|
|
6108
|
+
var renderCompactMarkdown = (report, explainSummary) => {
|
|
6109
|
+
const lines = [];
|
|
6110
|
+
lines.push("# CodeSentinel Run (compact)");
|
|
6111
|
+
lines.push("");
|
|
6112
|
+
lines.push("## Repository");
|
|
6113
|
+
lines.push(`- target: \`${report.repository.targetPath}\``);
|
|
6114
|
+
lines.push(`- riskScore: \`${report.repository.riskScore}\``);
|
|
6115
|
+
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
6116
|
+
lines.push(
|
|
6117
|
+
`- dimensions: structural=\`${report.repository.dimensionScores.structural ?? "n/a"}\`, evolution=\`${report.repository.dimensionScores.evolution ?? "n/a"}\`, external=\`${report.repository.dimensionScores.external ?? "n/a"}\`, interactions=\`${report.repository.dimensionScores.interactions ?? "n/a"}\``
|
|
6118
|
+
);
|
|
6119
|
+
lines.push("");
|
|
6120
|
+
lines.push(
|
|
6121
|
+
`- key drivers: ${extractSummaryValue(explainSummary, "- key drivers") ?? "insufficient data"}`
|
|
6122
|
+
);
|
|
6123
|
+
lines.push(
|
|
6124
|
+
`- priority actions: ${extractSummaryValue(explainSummary, "- priority actions") ?? "insufficient data"}`
|
|
6125
|
+
);
|
|
6126
|
+
lines.push("");
|
|
6127
|
+
lines.push("## Top Hotspots");
|
|
6128
|
+
for (const hotspot of report.hotspots.slice(0, 3)) {
|
|
6129
|
+
lines.push(`- \`${hotspot.target}\` (score: \`${hotspot.score}\`)`);
|
|
6130
|
+
}
|
|
6131
|
+
return lines.join("\n");
|
|
6132
|
+
};
|
|
6133
|
+
var riskProfileOption = () => new Option(
|
|
6134
|
+
"--risk-profile <profile>",
|
|
6135
|
+
"risk profile: default (balanced) or personal (down-weights single-maintainer ownership penalties)"
|
|
6136
|
+
).choices(["default", "personal"]).default("default");
|
|
5941
6137
|
program.name("codesentinel").description("Structural and evolutionary risk analysis for TypeScript/JavaScript codebases").version(version);
|
|
5942
|
-
program.command("analyze").argument("[path]", "path to the project to analyze").addOption(
|
|
6138
|
+
program.command("analyze").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
5943
6139
|
new Option(
|
|
5944
6140
|
"--author-identity <mode>",
|
|
5945
6141
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -5962,7 +6158,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
5962
6158
|
const summary = await runAnalyzeCommand(
|
|
5963
6159
|
path,
|
|
5964
6160
|
options.authorIdentity,
|
|
5965
|
-
{ recentWindowDays: options.recentWindowDays },
|
|
6161
|
+
{ recentWindowDays: options.recentWindowDays, riskProfile: options.riskProfile },
|
|
5966
6162
|
logger
|
|
5967
6163
|
);
|
|
5968
6164
|
const outputMode = options.json === true ? "json" : options.output;
|
|
@@ -5970,7 +6166,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
5970
6166
|
`);
|
|
5971
6167
|
}
|
|
5972
6168
|
);
|
|
5973
|
-
program.command("explain").argument("[path]", "path to the project to analyze").addOption(
|
|
6169
|
+
program.command("explain").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
5974
6170
|
new Option(
|
|
5975
6171
|
"--author-identity <mode>",
|
|
5976
6172
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -5996,6 +6192,7 @@ program.command("explain").argument("[path]", "path to the project to analyze").
|
|
|
5996
6192
|
...options.module === void 0 ? {} : { module: options.module },
|
|
5997
6193
|
top: Number.isFinite(top) ? top : 5,
|
|
5998
6194
|
recentWindowDays: options.recentWindowDays,
|
|
6195
|
+
riskProfile: options.riskProfile,
|
|
5999
6196
|
format: options.format
|
|
6000
6197
|
};
|
|
6001
6198
|
const result = await runExplainCommand(path, options.authorIdentity, explainOptions, logger);
|
|
@@ -6033,7 +6230,7 @@ program.command("dependency-risk").argument("<dependency>", "dependency spec to
|
|
|
6033
6230
|
`);
|
|
6034
6231
|
}
|
|
6035
6232
|
);
|
|
6036
|
-
program.command("report").argument("[path]", "path to the project to analyze").addOption(
|
|
6233
|
+
program.command("report").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6037
6234
|
new Option(
|
|
6038
6235
|
"--author-identity <mode>",
|
|
6039
6236
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -6062,6 +6259,7 @@ program.command("report").argument("[path]", "path to the project to analyze").a
|
|
|
6062
6259
|
...options.compare === void 0 ? {} : { comparePath: options.compare },
|
|
6063
6260
|
...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
|
|
6064
6261
|
includeTrace: options.trace,
|
|
6262
|
+
riskProfile: options.riskProfile,
|
|
6065
6263
|
recentWindowDays: options.recentWindowDays
|
|
6066
6264
|
},
|
|
6067
6265
|
logger
|
|
@@ -6072,6 +6270,219 @@ program.command("report").argument("[path]", "path to the project to analyze").a
|
|
|
6072
6270
|
}
|
|
6073
6271
|
}
|
|
6074
6272
|
);
|
|
6273
|
+
program.command("run").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6274
|
+
new Option(
|
|
6275
|
+
"--author-identity <mode>",
|
|
6276
|
+
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
6277
|
+
).choices(["likely_merge", "strict_email"]).default("likely_merge")
|
|
6278
|
+
).addOption(
|
|
6279
|
+
new Option(
|
|
6280
|
+
"--log-level <level>",
|
|
6281
|
+
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
6282
|
+
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
6283
|
+
).addOption(
|
|
6284
|
+
new Option("--format <mode>", "combined output format: text, md, json").choices(["text", "md", "json"]).default("text")
|
|
6285
|
+
).addOption(
|
|
6286
|
+
new Option("--detail <level>", "run detail level: compact (default), standard, full").choices(["compact", "standard", "full"]).default("compact")
|
|
6287
|
+
).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").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").addOption(
|
|
6288
|
+
new Option(
|
|
6289
|
+
"--recent-window-days <days>",
|
|
6290
|
+
"git recency window in days used for evolution volatility metrics"
|
|
6291
|
+
).argParser(parseRecentWindowDays).default(30)
|
|
6292
|
+
).action(
|
|
6293
|
+
async (path, options) => {
|
|
6294
|
+
const logger = createStderrLogger(options.logLevel);
|
|
6295
|
+
const top = Number.parseInt(options.top, 10);
|
|
6296
|
+
const explain = await runExplainCommand(
|
|
6297
|
+
path,
|
|
6298
|
+
options.authorIdentity,
|
|
6299
|
+
{
|
|
6300
|
+
...options.file === void 0 ? {} : { file: options.file },
|
|
6301
|
+
...options.module === void 0 ? {} : { module: options.module },
|
|
6302
|
+
top: Number.isFinite(top) ? top : 5,
|
|
6303
|
+
format: options.format,
|
|
6304
|
+
recentWindowDays: options.recentWindowDays,
|
|
6305
|
+
riskProfile: options.riskProfile
|
|
6306
|
+
},
|
|
6307
|
+
logger
|
|
6308
|
+
);
|
|
6309
|
+
const snapshot = createSnapshot({
|
|
6310
|
+
analysis: explain.summary,
|
|
6311
|
+
...options.trace === true ? { trace: explain.trace } : {}
|
|
6312
|
+
});
|
|
6313
|
+
if (options.snapshot !== void 0) {
|
|
6314
|
+
await writeFile5(options.snapshot, JSON.stringify(snapshot, null, 2), "utf8");
|
|
6315
|
+
logger.info(`snapshot written: ${options.snapshot}`);
|
|
6316
|
+
}
|
|
6317
|
+
const report = options.compare === void 0 ? createReport(snapshot) : createReport(
|
|
6318
|
+
snapshot,
|
|
6319
|
+
compareSnapshots(snapshot, parseSnapshot(await readFile5(options.compare, "utf8")))
|
|
6320
|
+
);
|
|
6321
|
+
if (options.format === "json") {
|
|
6322
|
+
const analyzeSummaryOutput = formatAnalyzeOutput(explain.summary, "summary");
|
|
6323
|
+
if (options.detail === "compact") {
|
|
6324
|
+
process.stdout.write(
|
|
6325
|
+
`${JSON.stringify(
|
|
6326
|
+
{
|
|
6327
|
+
schemaVersion: "codesentinel.run.v1",
|
|
6328
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6329
|
+
repository: report.repository,
|
|
6330
|
+
keyDrivers: extractSummaryValue(
|
|
6331
|
+
extractExplainTextSummary(formatExplainOutput(explain, "text")),
|
|
6332
|
+
"key drivers"
|
|
6333
|
+
),
|
|
6334
|
+
priorityActions: extractSummaryValue(
|
|
6335
|
+
extractExplainTextSummary(formatExplainOutput(explain, "text")),
|
|
6336
|
+
"priority actions"
|
|
6337
|
+
),
|
|
6338
|
+
topHotspots: report.hotspots.slice(0, 3).map((hotspot) => ({
|
|
6339
|
+
target: hotspot.target,
|
|
6340
|
+
score: hotspot.score
|
|
6341
|
+
}))
|
|
6342
|
+
},
|
|
6343
|
+
null,
|
|
6344
|
+
2
|
|
6345
|
+
)}
|
|
6346
|
+
`
|
|
6347
|
+
);
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
if (options.detail === "standard") {
|
|
6351
|
+
const analyzeSummaryPayload = JSON.parse(analyzeSummaryOutput);
|
|
6352
|
+
process.stdout.write(
|
|
6353
|
+
`${JSON.stringify(
|
|
6354
|
+
{
|
|
6355
|
+
schemaVersion: "codesentinel.run.v1",
|
|
6356
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6357
|
+
analyze: analyzeSummaryPayload,
|
|
6358
|
+
explain: {
|
|
6359
|
+
summary: extractExplainTextSummary(formatExplainOutput(explain, "text"))
|
|
6360
|
+
},
|
|
6361
|
+
report: {
|
|
6362
|
+
repository: report.repository,
|
|
6363
|
+
hotspots: report.hotspots.slice(0, 5),
|
|
6364
|
+
structural: report.structural,
|
|
6365
|
+
external: report.external
|
|
6366
|
+
}
|
|
6367
|
+
},
|
|
6368
|
+
null,
|
|
6369
|
+
2
|
|
6370
|
+
)}
|
|
6371
|
+
`
|
|
6372
|
+
);
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6375
|
+
process.stdout.write(
|
|
6376
|
+
`${JSON.stringify(
|
|
6377
|
+
{
|
|
6378
|
+
schemaVersion: "codesentinel.run.v1",
|
|
6379
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6380
|
+
analyze: explain.summary,
|
|
6381
|
+
explain,
|
|
6382
|
+
report
|
|
6383
|
+
},
|
|
6384
|
+
null,
|
|
6385
|
+
2
|
|
6386
|
+
)}
|
|
6387
|
+
`
|
|
6388
|
+
);
|
|
6389
|
+
return;
|
|
6390
|
+
}
|
|
6391
|
+
const analyzeRendered = formatAnalyzeOutput(explain.summary, "summary");
|
|
6392
|
+
const explainRendered = formatExplainOutput(explain, options.format);
|
|
6393
|
+
if (options.detail === "compact") {
|
|
6394
|
+
if (options.format === "md") {
|
|
6395
|
+
process.stdout.write(
|
|
6396
|
+
`${renderCompactMarkdown(
|
|
6397
|
+
report,
|
|
6398
|
+
extractExplainMarkdownSummary(formatExplainOutput(explain, "md"))
|
|
6399
|
+
)}
|
|
6400
|
+
`
|
|
6401
|
+
);
|
|
6402
|
+
return;
|
|
6403
|
+
}
|
|
6404
|
+
process.stdout.write(
|
|
6405
|
+
`${renderCompactText(report, extractExplainTextSummary(formatExplainOutput(explain, "text")))}
|
|
6406
|
+
`
|
|
6407
|
+
);
|
|
6408
|
+
return;
|
|
6409
|
+
}
|
|
6410
|
+
if (options.detail === "standard") {
|
|
6411
|
+
if (options.format === "md") {
|
|
6412
|
+
process.stdout.write(
|
|
6413
|
+
`# CodeSentinel Run
|
|
6414
|
+
|
|
6415
|
+
## Analyze
|
|
6416
|
+
\`\`\`json
|
|
6417
|
+
${analyzeRendered}
|
|
6418
|
+
\`\`\`
|
|
6419
|
+
|
|
6420
|
+
## Explain
|
|
6421
|
+
${extractExplainMarkdownSummary(
|
|
6422
|
+
formatExplainOutput(explain, "md")
|
|
6423
|
+
)}
|
|
6424
|
+
|
|
6425
|
+
${renderReportHighlightsMarkdown(report)}
|
|
6426
|
+
`
|
|
6427
|
+
);
|
|
6428
|
+
return;
|
|
6429
|
+
}
|
|
6430
|
+
process.stdout.write(
|
|
6431
|
+
`CodeSentinel Run
|
|
6432
|
+
|
|
6433
|
+
Analyze
|
|
6434
|
+
${analyzeRendered}
|
|
6435
|
+
|
|
6436
|
+
Explain
|
|
6437
|
+
${extractExplainTextSummary(
|
|
6438
|
+
formatExplainOutput(explain, "text")
|
|
6439
|
+
)}
|
|
6440
|
+
|
|
6441
|
+
Report
|
|
6442
|
+
${renderReportHighlightsText(report)}
|
|
6443
|
+
`
|
|
6444
|
+
);
|
|
6445
|
+
return;
|
|
6446
|
+
}
|
|
6447
|
+
const reportRendered = formatReport(report, options.format);
|
|
6448
|
+
if (options.format === "md") {
|
|
6449
|
+
const explainSection = stripLeadingMarkdownHeading(
|
|
6450
|
+
explainRendered,
|
|
6451
|
+
"# CodeSentinel Explanation"
|
|
6452
|
+
);
|
|
6453
|
+
const reportSection = stripLeadingMarkdownHeading(reportRendered, "# CodeSentinel Report");
|
|
6454
|
+
process.stdout.write(
|
|
6455
|
+
`# CodeSentinel Run
|
|
6456
|
+
|
|
6457
|
+
## Analyze
|
|
6458
|
+
\`\`\`json
|
|
6459
|
+
${analyzeRendered}
|
|
6460
|
+
\`\`\`
|
|
6461
|
+
|
|
6462
|
+
## Explain
|
|
6463
|
+
${explainSection}
|
|
6464
|
+
|
|
6465
|
+
## Report
|
|
6466
|
+
${reportSection}
|
|
6467
|
+
`
|
|
6468
|
+
);
|
|
6469
|
+
return;
|
|
6470
|
+
}
|
|
6471
|
+
process.stdout.write(
|
|
6472
|
+
`CodeSentinel Run
|
|
6473
|
+
|
|
6474
|
+
Analyze
|
|
6475
|
+
${analyzeRendered}
|
|
6476
|
+
|
|
6477
|
+
Explain
|
|
6478
|
+
${explainRendered}
|
|
6479
|
+
|
|
6480
|
+
Report
|
|
6481
|
+
${reportRendered}
|
|
6482
|
+
`
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6485
|
+
);
|
|
6075
6486
|
var parseGateNumber = (value, optionName) => {
|
|
6076
6487
|
if (value === void 0) {
|
|
6077
6488
|
return void 0;
|
|
@@ -6113,7 +6524,7 @@ var buildGateConfigFromOptions = (options) => {
|
|
|
6113
6524
|
failOn: options.failOn
|
|
6114
6525
|
};
|
|
6115
6526
|
};
|
|
6116
|
-
program.command("check").argument("[path]", "path to the project to analyze").addOption(
|
|
6527
|
+
program.command("check").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6117
6528
|
new Option(
|
|
6118
6529
|
"--author-identity <mode>",
|
|
6119
6530
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -6143,6 +6554,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
6143
6554
|
{
|
|
6144
6555
|
...options.compare === void 0 ? {} : { baselinePath: options.compare },
|
|
6145
6556
|
includeTrace: options.trace,
|
|
6557
|
+
riskProfile: options.riskProfile,
|
|
6146
6558
|
recentWindowDays: options.recentWindowDays,
|
|
6147
6559
|
gateConfig,
|
|
6148
6560
|
outputFormat: options.format,
|
|
@@ -6166,7 +6578,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
6166
6578
|
}
|
|
6167
6579
|
}
|
|
6168
6580
|
);
|
|
6169
|
-
program.command("ci").argument("[path]", "path to the project to analyze").addOption(
|
|
6581
|
+
program.command("ci").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6170
6582
|
new Option(
|
|
6171
6583
|
"--author-identity <mode>",
|
|
6172
6584
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -6215,6 +6627,7 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
|
|
|
6215
6627
|
...options.report === void 0 ? {} : { reportPath: options.report },
|
|
6216
6628
|
...options.jsonOutput === void 0 ? {} : { jsonOutputPath: options.jsonOutput },
|
|
6217
6629
|
includeTrace: options.trace,
|
|
6630
|
+
riskProfile: options.riskProfile,
|
|
6218
6631
|
recentWindowDays: options.recentWindowDays,
|
|
6219
6632
|
gateConfig
|
|
6220
6633
|
},
|