@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 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
- - `repositoryScore` and `normalizedScore`
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
- "repositoryScore": 0,
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
- - `repositoryScore`: overall repository fragility index (`0..100`).
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.repositoryScore` and `risk.fileScores[*].score` increase when:
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
- repositoryScoreDelta: round43(
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
- repositoryScore: snapshot.analysis.risk.repositoryScore,
1653
+ riskScore: snapshot.analysis.risk.riskScore,
1656
1654
  normalizedScore: snapshot.analysis.risk.normalizedScore,
1657
- riskTier: toRiskTier(snapshot.analysis.risk.repositoryScore),
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
- ` repositoryScoreDelta: ${report.diff.repositoryScoreDelta}`,
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(` repositoryScore: ${report.repository.repositoryScore}`);
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
- `- repositoryScoreDelta: \`${report.diff.repositoryScoreDelta}\``,
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(`- repositoryScore: \`${report.repository.repositoryScore}\``);
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.repositoryScore;
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: "repositoryScore" }]
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(`repositoryScore: ${snapshot.analysis.risk.repositoryScore}`);
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(`- repositoryScore: \`${snapshot.analysis.risk.repositoryScore}\``);
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
- repositoryScore: summary.risk.repositoryScore,
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(`repositoryScore: ${payload.summary.risk.repositoryScore}`);
2671
- lines.push(`riskBand: ${toRiskBand(payload.summary.risk.repositoryScore)}`);
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(`- repositoryScore: \`${payload.summary.risk.repositoryScore}\``);
2711
- lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.repositoryScore)}\``);
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 structuralFactor = toUnitInterval(
4865
+ const structuralFactorRaw = toUnitInterval(
4828
4866
  fanInRisk * structuralWeights.fanIn + fanOutRisk * structuralWeights.fanOut + depthRisk * structuralWeights.depth + inCycle * structuralWeights.cycleParticipation
4829
4867
  );
4830
- const structuralCentrality = toUnitInterval((fanInRisk + fanOutRisk) / 2);
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 repositoryScore = round45(repositoryNormalizedScore * 100);
5279
+ const riskScore = round45(repositoryNormalizedScore * 100);
5231
5280
  if (collector !== void 0) {
5232
- const repositoryFactors = buildFactorTraces(repositoryScore, [
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
- repositoryScore,
5344
+ riskScore,
5296
5345
  repositoryNormalizedScore,
5297
5346
  repositoryFactors
5298
5347
  )
5299
5348
  );
5300
5349
  }
5301
5350
  return {
5302
- repositoryScore,
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 risk = computeRepositoryRiskSummary(analysisInputs);
5592
- logger.info(`analysis completed (repositoryScore=${risk.repositoryScore})`);
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 evaluation = evaluateRepositoryRisk(analysisInputs, { explain: options.includeTrace });
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 evaluation = evaluateRepositoryRisk(analysisInputs, { explain: true });
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 (repositoryScore=${summary.risk.repositoryScore})`);
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
  },