@getcodesentinel/codesentinel 1.12.1 → 1.13.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 +11 -4
- package/dist/index.js +137 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ CodeSentinel combines three signals into a single, explainable risk profile:
|
|
|
94
94
|
|
|
95
95
|
The CLI output now includes a deterministic `risk` block composed from those dimensions:
|
|
96
96
|
|
|
97
|
-
- `
|
|
97
|
+
- `riskScore` and `normalizedScore`
|
|
98
98
|
- ranked `hotspots`
|
|
99
99
|
- `fragileClusters` (structural cycles + change coupling components)
|
|
100
100
|
- `dependencyAmplificationZones`
|
|
@@ -178,6 +178,9 @@ codesentinel analyze . --author-identity likely_merge
|
|
|
178
178
|
# Deterministic: strict email identity, no heuristic merging
|
|
179
179
|
codesentinel analyze . --author-identity strict_email
|
|
180
180
|
|
|
181
|
+
# Personal-project profile (down-weights single-maintainer ownership penalties)
|
|
182
|
+
codesentinel analyze . --risk-profile personal
|
|
183
|
+
|
|
181
184
|
# Tune recency window (days) used for evolution volatility
|
|
182
185
|
codesentinel analyze . --recent-window-days 60
|
|
183
186
|
|
|
@@ -228,6 +231,10 @@ Notes:
|
|
|
228
231
|
- `--output summary` (default) prints a compact result for terminal use.
|
|
229
232
|
- `--output json` (or `--json`) prints the full analysis object.
|
|
230
233
|
- `--recent-window-days <days>` customizes the git recency window used to compute `recentVolatility` (default: `30`).
|
|
234
|
+
- `--risk-profile default|personal` selects scoring profile.
|
|
235
|
+
- `default`: balanced team-oriented defaults.
|
|
236
|
+
- `personal`: lowers ownership concentration and bus-factor penalties for solo-maintainer repos.
|
|
237
|
+
- `personal` does not remove structural, churn, volatility, external, or interaction risk; scores can still be elevated when those signals are high.
|
|
231
238
|
|
|
232
239
|
When running through pnpm, pass CLI arguments after `--`:
|
|
233
240
|
|
|
@@ -355,7 +362,7 @@ Minimal shape:
|
|
|
355
362
|
"evolution": { "...": "..." },
|
|
356
363
|
"external": { "...": "..." },
|
|
357
364
|
"risk": {
|
|
358
|
-
"
|
|
365
|
+
"riskScore": 0,
|
|
359
366
|
"normalizedScore": 0,
|
|
360
367
|
"hotspots": [],
|
|
361
368
|
"fragileClusters": [],
|
|
@@ -366,7 +373,7 @@ Minimal shape:
|
|
|
366
373
|
|
|
367
374
|
How to read `risk` first:
|
|
368
375
|
|
|
369
|
-
- `
|
|
376
|
+
- `riskScore`: overall repository fragility index (`0..100`).
|
|
370
377
|
- `hotspots`: ranked files to inspect first.
|
|
371
378
|
- `fragileClusters`: groups of files with structural-cycle or co-change fragility.
|
|
372
379
|
- `dependencyAmplificationZones`: files where external dependency pressure intersects with local fragility.
|
|
@@ -391,7 +398,7 @@ These ranges are heuristics for triage, not incident probability.
|
|
|
391
398
|
|
|
392
399
|
### What Moves Scores
|
|
393
400
|
|
|
394
|
-
`risk.
|
|
401
|
+
`risk.riskScore` and `risk.fileScores[*].score` increase when:
|
|
395
402
|
|
|
396
403
|
- structurally central files/modules change frequently,
|
|
397
404
|
- ownership is highly concentrated in volatile files,
|
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"}`);
|
|
@@ -1780,7 +1778,7 @@ var renderMarkdownDiff = (report) => {
|
|
|
1780
1778
|
return [
|
|
1781
1779
|
"",
|
|
1782
1780
|
"## Diff",
|
|
1783
|
-
`-
|
|
1781
|
+
`- riskScoreDelta: \`${report.diff.riskScoreDelta}\``,
|
|
1784
1782
|
`- normalizedScoreDelta: \`${report.diff.normalizedScoreDelta}\``,
|
|
1785
1783
|
`- newHotspots: ${report.diff.newHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
1786
1784
|
`- resolvedHotspots: ${report.diff.resolvedHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
|
|
@@ -1794,7 +1792,7 @@ var renderMarkdownReport = (report) => {
|
|
|
1794
1792
|
lines.push("");
|
|
1795
1793
|
lines.push("## Repository Summary");
|
|
1796
1794
|
lines.push(`- target: \`${report.repository.targetPath}\``);
|
|
1797
|
-
lines.push(`-
|
|
1795
|
+
lines.push(`- riskScore: \`${report.repository.riskScore}\``);
|
|
1798
1796
|
lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
|
|
1799
1797
|
lines.push(`- riskTier: \`${report.repository.riskTier}\``);
|
|
1800
1798
|
lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
|
|
@@ -1973,7 +1971,7 @@ var evaluateGates = (input) => {
|
|
|
1973
1971
|
const evaluatedGates = [];
|
|
1974
1972
|
if (config.maxRepoScore !== void 0) {
|
|
1975
1973
|
evaluatedGates.push("max-repo-score");
|
|
1976
|
-
const current = input.current.analysis.risk.
|
|
1974
|
+
const current = input.current.analysis.risk.riskScore;
|
|
1977
1975
|
if (current > config.maxRepoScore) {
|
|
1978
1976
|
violations.push(
|
|
1979
1977
|
makeViolation(
|
|
@@ -1981,7 +1979,7 @@ var evaluateGates = (input) => {
|
|
|
1981
1979
|
"error",
|
|
1982
1980
|
`Repository score ${current} exceeds configured max ${config.maxRepoScore}.`,
|
|
1983
1981
|
[input.current.analysis.structural.targetPath],
|
|
1984
|
-
[{ kind: "repository_metric", metric: "
|
|
1982
|
+
[{ kind: "repository_metric", metric: "riskScore" }]
|
|
1985
1983
|
)
|
|
1986
1984
|
);
|
|
1987
1985
|
}
|
|
@@ -2093,7 +2091,7 @@ var renderCheckText = (snapshot, result) => {
|
|
|
2093
2091
|
const lines = [];
|
|
2094
2092
|
lines.push("CodeSentinel Check");
|
|
2095
2093
|
lines.push(`target: ${snapshot.analysis.structural.targetPath}`);
|
|
2096
|
-
lines.push(`
|
|
2094
|
+
lines.push(`riskScore: ${snapshot.analysis.risk.riskScore}`);
|
|
2097
2095
|
lines.push(`evaluatedGates: ${result.evaluatedGates.join(", ") || "none"}`);
|
|
2098
2096
|
lines.push(`violations: ${result.violations.length}`);
|
|
2099
2097
|
lines.push(`exitCode: ${result.exitCode}`);
|
|
@@ -2112,7 +2110,7 @@ var renderCheckMarkdown = (snapshot, result) => {
|
|
|
2112
2110
|
const lines = [];
|
|
2113
2111
|
lines.push("## CodeSentinel CI Summary");
|
|
2114
2112
|
lines.push(`- target: \`${snapshot.analysis.structural.targetPath}\``);
|
|
2115
|
-
lines.push(`-
|
|
2113
|
+
lines.push(`- riskScore: \`${snapshot.analysis.risk.riskScore}\``);
|
|
2116
2114
|
lines.push(
|
|
2117
2115
|
`- evaluatedGates: ${result.evaluatedGates.map((item) => `\`${item}\``).join(", ") || "none"}`
|
|
2118
2116
|
);
|
|
@@ -2495,7 +2493,7 @@ var createSummaryShape = (summary) => ({
|
|
|
2495
2493
|
reason: summary.external.reason
|
|
2496
2494
|
},
|
|
2497
2495
|
risk: {
|
|
2498
|
-
|
|
2496
|
+
riskScore: summary.risk.riskScore,
|
|
2499
2497
|
normalizedScore: summary.risk.normalizedScore,
|
|
2500
2498
|
hotspotsTop: summary.risk.hotspots.slice(0, 5).map((hotspot) => ({
|
|
2501
2499
|
file: hotspot.file,
|
|
@@ -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)}`);
|
|
@@ -2707,8 +2705,8 @@ var renderMarkdown = (payload) => {
|
|
|
2707
2705
|
const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
|
|
2708
2706
|
lines.push(`# CodeSentinel Explanation`);
|
|
2709
2707
|
lines.push(`- target: \`${payload.summary.structural.targetPath}\``);
|
|
2710
|
-
lines.push(`-
|
|
2711
|
-
lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.
|
|
2708
|
+
lines.push(`- riskScore: \`${payload.summary.risk.riskScore}\``);
|
|
2709
|
+
lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.riskScore)}\``);
|
|
2712
2710
|
lines.push(`- selectedTargets: \`${payload.selectedTargets.length}\``);
|
|
2713
2711
|
lines.push("");
|
|
2714
2712
|
lines.push("## Dimension Scores (0-100)");
|
|
@@ -4308,6 +4306,17 @@ var DEFAULT_RISK_ENGINE_CONFIG = {
|
|
|
4308
4306
|
externalDimension: {
|
|
4309
4307
|
topDependencyPercentile: 0.85,
|
|
4310
4308
|
dependencyDepthHalfLife: 6
|
|
4309
|
+
},
|
|
4310
|
+
// Reduce false positives for thin aggregation hubs (for example, barrel/index re-export files)
|
|
4311
|
+
// that are structurally central but have low churn density and no cycle participation.
|
|
4312
|
+
aggregatorAttenuation: {
|
|
4313
|
+
enabled: true,
|
|
4314
|
+
minFanIn: 6,
|
|
4315
|
+
minFanOut: 4,
|
|
4316
|
+
minCommitCount: 4,
|
|
4317
|
+
maxChurnPerCommit: 24,
|
|
4318
|
+
maxChurnPerDependency: 10,
|
|
4319
|
+
maxStructuralReduction: 0.3
|
|
4311
4320
|
}
|
|
4312
4321
|
};
|
|
4313
4322
|
var toUnitInterval = (value) => Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
|
|
@@ -4400,6 +4409,35 @@ var normalizeWithScale = (value, scale) => {
|
|
|
4400
4409
|
return toUnitInterval((value - scale.lower) / (scale.upper - scale.lower));
|
|
4401
4410
|
};
|
|
4402
4411
|
var normalizePath2 = (path) => path.replaceAll("\\", "/");
|
|
4412
|
+
var computeAggregatorAttenuation = (input) => {
|
|
4413
|
+
const { fanIn, fanOut, inCycle, evolutionMetrics, config } = input;
|
|
4414
|
+
if (!config.enabled || inCycle > 0) {
|
|
4415
|
+
return 1;
|
|
4416
|
+
}
|
|
4417
|
+
if (fanIn < config.minFanIn || fanOut < config.minFanOut) {
|
|
4418
|
+
return 1;
|
|
4419
|
+
}
|
|
4420
|
+
if (evolutionMetrics === void 0 || evolutionMetrics.commitCount < config.minCommitCount) {
|
|
4421
|
+
return 1;
|
|
4422
|
+
}
|
|
4423
|
+
const churnPerCommit = evolutionMetrics.churnTotal / Math.max(1, evolutionMetrics.commitCount);
|
|
4424
|
+
const churnPerDependency = evolutionMetrics.churnTotal / Math.max(1, fanOut);
|
|
4425
|
+
if (churnPerCommit > config.maxChurnPerCommit || churnPerDependency > config.maxChurnPerDependency) {
|
|
4426
|
+
return 1;
|
|
4427
|
+
}
|
|
4428
|
+
const fanInSignal = toUnitInterval((fanIn - config.minFanIn) / Math.max(1, config.minFanIn));
|
|
4429
|
+
const fanOutSignal = toUnitInterval((fanOut - config.minFanOut) / Math.max(1, config.minFanOut));
|
|
4430
|
+
const lowChurnPerCommitSignal = 1 - toUnitInterval(churnPerCommit / config.maxChurnPerCommit);
|
|
4431
|
+
const lowChurnPerDependencySignal = 1 - toUnitInterval(churnPerDependency / config.maxChurnPerDependency);
|
|
4432
|
+
const attenuationConfidence = average([
|
|
4433
|
+
fanInSignal,
|
|
4434
|
+
fanOutSignal,
|
|
4435
|
+
lowChurnPerCommitSignal,
|
|
4436
|
+
lowChurnPerDependencySignal
|
|
4437
|
+
]);
|
|
4438
|
+
const reduction = toUnitInterval(config.maxStructuralReduction) * attenuationConfidence;
|
|
4439
|
+
return round45(toUnitInterval(1 - reduction));
|
|
4440
|
+
};
|
|
4403
4441
|
var dependencySignalWeights = {
|
|
4404
4442
|
single_maintainer: 0.3,
|
|
4405
4443
|
abandoned: 0.3,
|
|
@@ -4824,10 +4862,10 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4824
4862
|
const fanOutRisk = normalizeWithScale(logScale(file.fanOut), fanOutScale);
|
|
4825
4863
|
const depthRisk = normalizeWithScale(file.depth, depthScale);
|
|
4826
4864
|
const structuralWeights = config.structuralFactorWeights;
|
|
4827
|
-
const
|
|
4865
|
+
const structuralFactorRaw = toUnitInterval(
|
|
4828
4866
|
fanInRisk * structuralWeights.fanIn + fanOutRisk * structuralWeights.fanOut + depthRisk * structuralWeights.depth + inCycle * structuralWeights.cycleParticipation
|
|
4829
4867
|
);
|
|
4830
|
-
const
|
|
4868
|
+
const structuralCentralityRaw = toUnitInterval((fanInRisk + fanOutRisk) / 2);
|
|
4831
4869
|
let evolutionFactor = 0;
|
|
4832
4870
|
let frequencyRisk = 0;
|
|
4833
4871
|
let churnRisk = 0;
|
|
@@ -4854,6 +4892,15 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4854
4892
|
frequencyRisk * evolutionWeights.frequency + churnRisk * evolutionWeights.churn + volatilityRisk * evolutionWeights.recentVolatility + ownershipConcentrationRisk * evolutionWeights.ownershipConcentration + busFactorRisk * evolutionWeights.busFactorRisk
|
|
4855
4893
|
);
|
|
4856
4894
|
}
|
|
4895
|
+
const structuralAttenuation = computeAggregatorAttenuation({
|
|
4896
|
+
fanIn: file.fanIn,
|
|
4897
|
+
fanOut: file.fanOut,
|
|
4898
|
+
inCycle,
|
|
4899
|
+
evolutionMetrics,
|
|
4900
|
+
config: config.aggregatorAttenuation
|
|
4901
|
+
});
|
|
4902
|
+
const structuralFactor = toUnitInterval(structuralFactorRaw * structuralAttenuation);
|
|
4903
|
+
const structuralCentrality = toUnitInterval(structuralCentralityRaw * structuralAttenuation);
|
|
4857
4904
|
const dependencyAffinity = toUnitInterval(structuralCentrality * 0.6 + evolutionFactor * 0.4);
|
|
4858
4905
|
const externalFactor = external.available ? toUnitInterval(dependencyComputation.repositoryExternalPressure * dependencyAffinity) : 0;
|
|
4859
4906
|
const structuralBase = structuralFactor * dimensionWeights.structural;
|
|
@@ -4898,7 +4945,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4898
4945
|
topAuthorShare: evolutionMetrics?.topAuthorShare ?? null,
|
|
4899
4946
|
busFactor: evolutionMetrics?.busFactor ?? null,
|
|
4900
4947
|
dependencyAffinity: round45(dependencyAffinity),
|
|
4901
|
-
repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure)
|
|
4948
|
+
repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure),
|
|
4949
|
+
structuralAttenuation: round45(structuralAttenuation)
|
|
4902
4950
|
},
|
|
4903
4951
|
normalizedMetrics: {
|
|
4904
4952
|
fanInRisk: round45(fanInRisk),
|
|
@@ -4941,7 +4989,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
4941
4989
|
fanIn: context.rawMetrics.fanIn,
|
|
4942
4990
|
fanOut: context.rawMetrics.fanOut,
|
|
4943
4991
|
depth: context.rawMetrics.depth,
|
|
4944
|
-
cycleParticipation: context.rawMetrics.cycleParticipation
|
|
4992
|
+
cycleParticipation: context.rawMetrics.cycleParticipation,
|
|
4993
|
+
structuralAttenuation: context.rawMetrics.structuralAttenuation
|
|
4945
4994
|
},
|
|
4946
4995
|
normalizedMetrics: {
|
|
4947
4996
|
fanInRisk: context.normalizedMetrics.fanInRisk,
|
|
@@ -5227,9 +5276,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
5227
5276
|
criticalInstability * config.interactionWeights.centralInstability,
|
|
5228
5277
|
dependencyAmplification * config.interactionWeights.dependencyAmplification
|
|
5229
5278
|
]);
|
|
5230
|
-
const
|
|
5279
|
+
const riskScore = round45(repositoryNormalizedScore * 100);
|
|
5231
5280
|
if (collector !== void 0) {
|
|
5232
|
-
const repositoryFactors = buildFactorTraces(
|
|
5281
|
+
const repositoryFactors = buildFactorTraces(riskScore, [
|
|
5233
5282
|
{
|
|
5234
5283
|
factorId: "repository.structural",
|
|
5235
5284
|
family: "structural",
|
|
@@ -5292,14 +5341,14 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
5292
5341
|
buildTargetTrace(
|
|
5293
5342
|
"repository",
|
|
5294
5343
|
structural.targetPath,
|
|
5295
|
-
|
|
5344
|
+
riskScore,
|
|
5296
5345
|
repositoryNormalizedScore,
|
|
5297
5346
|
repositoryFactors
|
|
5298
5347
|
)
|
|
5299
5348
|
);
|
|
5300
5349
|
}
|
|
5301
5350
|
return {
|
|
5302
|
-
|
|
5351
|
+
riskScore,
|
|
5303
5352
|
normalizedScore: round45(repositoryNormalizedScore),
|
|
5304
5353
|
hotspots,
|
|
5305
5354
|
fragileClusters,
|
|
@@ -5390,6 +5439,10 @@ var mergeConfig = (overrides) => {
|
|
|
5390
5439
|
externalDimension: {
|
|
5391
5440
|
...DEFAULT_RISK_ENGINE_CONFIG.externalDimension,
|
|
5392
5441
|
...overrides.externalDimension
|
|
5442
|
+
},
|
|
5443
|
+
aggregatorAttenuation: {
|
|
5444
|
+
...DEFAULT_RISK_ENGINE_CONFIG.aggregatorAttenuation,
|
|
5445
|
+
...overrides.aggregatorAttenuation
|
|
5393
5446
|
}
|
|
5394
5447
|
};
|
|
5395
5448
|
};
|
|
@@ -5421,6 +5474,21 @@ var evaluateRepositoryRisk = (input, options = {}) => {
|
|
|
5421
5474
|
|
|
5422
5475
|
// src/application/run-analyze-command.ts
|
|
5423
5476
|
var resolveTargetPath = (inputPath, cwd) => resolve3(cwd, inputPath ?? ".");
|
|
5477
|
+
var riskProfileConfig = {
|
|
5478
|
+
default: void 0,
|
|
5479
|
+
personal: {
|
|
5480
|
+
evolutionFactorWeights: {
|
|
5481
|
+
frequency: 0.26,
|
|
5482
|
+
churn: 0.24,
|
|
5483
|
+
recentVolatility: 0.2,
|
|
5484
|
+
ownershipConcentration: 0.08,
|
|
5485
|
+
busFactorRisk: 0.04
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
};
|
|
5489
|
+
var resolveRiskConfigForProfile = (riskProfile) => {
|
|
5490
|
+
return riskProfileConfig[riskProfile ?? "default"];
|
|
5491
|
+
};
|
|
5424
5492
|
var createExternalProgressReporter = (logger) => {
|
|
5425
5493
|
let lastLoggedProgress = 0;
|
|
5426
5494
|
return (event) => {
|
|
@@ -5588,8 +5656,12 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
|
|
|
5588
5656
|
logger
|
|
5589
5657
|
);
|
|
5590
5658
|
logger.info("computing risk summary");
|
|
5591
|
-
const
|
|
5592
|
-
|
|
5659
|
+
const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
|
|
5660
|
+
const risk = computeRepositoryRiskSummary({
|
|
5661
|
+
...analysisInputs,
|
|
5662
|
+
...riskConfig === void 0 ? {} : { config: riskConfig }
|
|
5663
|
+
});
|
|
5664
|
+
logger.info(`analysis completed (riskScore=${risk.riskScore})`);
|
|
5593
5665
|
return {
|
|
5594
5666
|
...analysisInputs,
|
|
5595
5667
|
risk
|
|
@@ -5609,7 +5681,14 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
|
|
|
5609
5681
|
},
|
|
5610
5682
|
logger
|
|
5611
5683
|
);
|
|
5612
|
-
const
|
|
5684
|
+
const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
|
|
5685
|
+
const evaluation = evaluateRepositoryRisk(
|
|
5686
|
+
{
|
|
5687
|
+
...analysisInputs,
|
|
5688
|
+
...riskConfig === void 0 ? {} : { config: riskConfig }
|
|
5689
|
+
},
|
|
5690
|
+
{ explain: options.includeTrace }
|
|
5691
|
+
);
|
|
5613
5692
|
const summary = {
|
|
5614
5693
|
...analysisInputs,
|
|
5615
5694
|
risk: evaluation.summary
|
|
@@ -5620,6 +5699,7 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
|
|
|
5620
5699
|
analysisConfig: {
|
|
5621
5700
|
authorIdentityMode,
|
|
5622
5701
|
includeTrace: options.includeTrace,
|
|
5702
|
+
riskProfile: options.riskProfile ?? "default",
|
|
5623
5703
|
recentWindowDays: analysisInputs.evolution.available ? analysisInputs.evolution.metrics.recentWindowDays : options.recentWindowDays ?? null
|
|
5624
5704
|
}
|
|
5625
5705
|
});
|
|
@@ -5654,6 +5734,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
|
|
|
5654
5734
|
authorIdentityMode,
|
|
5655
5735
|
{
|
|
5656
5736
|
includeTrace: options.includeTrace,
|
|
5737
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5657
5738
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5658
5739
|
},
|
|
5659
5740
|
logger
|
|
@@ -5722,6 +5803,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
5722
5803
|
authorIdentityMode,
|
|
5723
5804
|
{
|
|
5724
5805
|
includeTrace: options.includeTrace,
|
|
5806
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5725
5807
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5726
5808
|
},
|
|
5727
5809
|
logger
|
|
@@ -5780,6 +5862,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
5780
5862
|
authorIdentityMode,
|
|
5781
5863
|
{
|
|
5782
5864
|
includeTrace: options.includeTrace,
|
|
5865
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5783
5866
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5784
5867
|
},
|
|
5785
5868
|
logger
|
|
@@ -5855,6 +5938,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
|
|
|
5855
5938
|
authorIdentityMode,
|
|
5856
5939
|
{
|
|
5857
5940
|
includeTrace: options.includeTrace,
|
|
5941
|
+
...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
|
|
5858
5942
|
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5859
5943
|
},
|
|
5860
5944
|
logger
|
|
@@ -5911,7 +5995,14 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
|
|
|
5911
5995
|
logger
|
|
5912
5996
|
);
|
|
5913
5997
|
logger.info("computing explainable risk summary");
|
|
5914
|
-
const
|
|
5998
|
+
const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
|
|
5999
|
+
const evaluation = evaluateRepositoryRisk(
|
|
6000
|
+
{
|
|
6001
|
+
...analysisInputs,
|
|
6002
|
+
...riskConfig === void 0 ? {} : { config: riskConfig }
|
|
6003
|
+
},
|
|
6004
|
+
{ explain: true }
|
|
6005
|
+
);
|
|
5915
6006
|
if (evaluation.trace === void 0) {
|
|
5916
6007
|
throw new Error("risk trace unavailable");
|
|
5917
6008
|
}
|
|
@@ -5919,7 +6010,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
|
|
|
5919
6010
|
...analysisInputs,
|
|
5920
6011
|
risk: evaluation.summary
|
|
5921
6012
|
};
|
|
5922
|
-
logger.info(`explanation completed (
|
|
6013
|
+
logger.info(`explanation completed (riskScore=${summary.risk.riskScore})`);
|
|
5923
6014
|
return {
|
|
5924
6015
|
summary,
|
|
5925
6016
|
trace: evaluation.trace,
|
|
@@ -5938,8 +6029,12 @@ var parseRecentWindowDays = (value) => {
|
|
|
5938
6029
|
}
|
|
5939
6030
|
return parsed;
|
|
5940
6031
|
};
|
|
6032
|
+
var riskProfileOption = () => new Option(
|
|
6033
|
+
"--risk-profile <profile>",
|
|
6034
|
+
"risk profile: default (balanced) or personal (down-weights single-maintainer ownership penalties)"
|
|
6035
|
+
).choices(["default", "personal"]).default("default");
|
|
5941
6036
|
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(
|
|
6037
|
+
program.command("analyze").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
5943
6038
|
new Option(
|
|
5944
6039
|
"--author-identity <mode>",
|
|
5945
6040
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -5962,7 +6057,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
5962
6057
|
const summary = await runAnalyzeCommand(
|
|
5963
6058
|
path,
|
|
5964
6059
|
options.authorIdentity,
|
|
5965
|
-
{ recentWindowDays: options.recentWindowDays },
|
|
6060
|
+
{ recentWindowDays: options.recentWindowDays, riskProfile: options.riskProfile },
|
|
5966
6061
|
logger
|
|
5967
6062
|
);
|
|
5968
6063
|
const outputMode = options.json === true ? "json" : options.output;
|
|
@@ -5970,7 +6065,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
5970
6065
|
`);
|
|
5971
6066
|
}
|
|
5972
6067
|
);
|
|
5973
|
-
program.command("explain").argument("[path]", "path to the project to analyze").addOption(
|
|
6068
|
+
program.command("explain").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
5974
6069
|
new Option(
|
|
5975
6070
|
"--author-identity <mode>",
|
|
5976
6071
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -5996,6 +6091,7 @@ program.command("explain").argument("[path]", "path to the project to analyze").
|
|
|
5996
6091
|
...options.module === void 0 ? {} : { module: options.module },
|
|
5997
6092
|
top: Number.isFinite(top) ? top : 5,
|
|
5998
6093
|
recentWindowDays: options.recentWindowDays,
|
|
6094
|
+
riskProfile: options.riskProfile,
|
|
5999
6095
|
format: options.format
|
|
6000
6096
|
};
|
|
6001
6097
|
const result = await runExplainCommand(path, options.authorIdentity, explainOptions, logger);
|
|
@@ -6033,7 +6129,7 @@ program.command("dependency-risk").argument("<dependency>", "dependency spec to
|
|
|
6033
6129
|
`);
|
|
6034
6130
|
}
|
|
6035
6131
|
);
|
|
6036
|
-
program.command("report").argument("[path]", "path to the project to analyze").addOption(
|
|
6132
|
+
program.command("report").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6037
6133
|
new Option(
|
|
6038
6134
|
"--author-identity <mode>",
|
|
6039
6135
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -6062,6 +6158,7 @@ program.command("report").argument("[path]", "path to the project to analyze").a
|
|
|
6062
6158
|
...options.compare === void 0 ? {} : { comparePath: options.compare },
|
|
6063
6159
|
...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
|
|
6064
6160
|
includeTrace: options.trace,
|
|
6161
|
+
riskProfile: options.riskProfile,
|
|
6065
6162
|
recentWindowDays: options.recentWindowDays
|
|
6066
6163
|
},
|
|
6067
6164
|
logger
|
|
@@ -6113,7 +6210,7 @@ var buildGateConfigFromOptions = (options) => {
|
|
|
6113
6210
|
failOn: options.failOn
|
|
6114
6211
|
};
|
|
6115
6212
|
};
|
|
6116
|
-
program.command("check").argument("[path]", "path to the project to analyze").addOption(
|
|
6213
|
+
program.command("check").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6117
6214
|
new Option(
|
|
6118
6215
|
"--author-identity <mode>",
|
|
6119
6216
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -6143,6 +6240,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
6143
6240
|
{
|
|
6144
6241
|
...options.compare === void 0 ? {} : { baselinePath: options.compare },
|
|
6145
6242
|
includeTrace: options.trace,
|
|
6243
|
+
riskProfile: options.riskProfile,
|
|
6146
6244
|
recentWindowDays: options.recentWindowDays,
|
|
6147
6245
|
gateConfig,
|
|
6148
6246
|
outputFormat: options.format,
|
|
@@ -6166,7 +6264,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
6166
6264
|
}
|
|
6167
6265
|
}
|
|
6168
6266
|
);
|
|
6169
|
-
program.command("ci").argument("[path]", "path to the project to analyze").addOption(
|
|
6267
|
+
program.command("ci").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
|
|
6170
6268
|
new Option(
|
|
6171
6269
|
"--author-identity <mode>",
|
|
6172
6270
|
"author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
|
|
@@ -6215,6 +6313,7 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
|
|
|
6215
6313
|
...options.report === void 0 ? {} : { reportPath: options.report },
|
|
6216
6314
|
...options.jsonOutput === void 0 ? {} : { jsonOutputPath: options.jsonOutput },
|
|
6217
6315
|
includeTrace: options.trace,
|
|
6316
|
+
riskProfile: options.riskProfile,
|
|
6218
6317
|
recentWindowDays: options.recentWindowDays,
|
|
6219
6318
|
gateConfig
|
|
6220
6319
|
},
|