@getcodesentinel/codesentinel 1.12.0 → 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,
@@ -4429,6 +4467,29 @@ var computeDependencySignalScore = (ownSignals, inheritedSignals, inheritedSigna
4429
4467
  return toUnitInterval(weightedTotal / maxWeightedTotal);
4430
4468
  };
4431
4469
  var clampConfidence = (value) => round45(toUnitInterval(value));
4470
+ var computeExternalMetadataConfidence = (external) => {
4471
+ if (!external.available) {
4472
+ return 0;
4473
+ }
4474
+ return round45(toUnitInterval(0.35 + external.metrics.metadataCoverage * 0.65));
4475
+ };
4476
+ var computeEvolutionHistoryConfidence = (structural, evolution, evolutionByFile) => {
4477
+ if (!evolution.available) {
4478
+ return 0;
4479
+ }
4480
+ const totalFiles = structural.files.length;
4481
+ if (totalFiles === 0) {
4482
+ return 1;
4483
+ }
4484
+ let coveredFiles = 0;
4485
+ for (const file of structural.files) {
4486
+ if (evolutionByFile.has(normalizePath2(file.id))) {
4487
+ coveredFiles += 1;
4488
+ }
4489
+ }
4490
+ const coverage = coveredFiles / totalFiles;
4491
+ return round45(toUnitInterval(0.3 + coverage * 0.7));
4492
+ };
4432
4493
  var buildFactorTraces = (totalScore, inputs) => {
4433
4494
  const positiveInputs = inputs.filter((input) => input.strength > 0);
4434
4495
  const strengthTotal = positiveInputs.reduce((sum, input) => sum + input.strength, 0);
@@ -4529,6 +4590,7 @@ var computeDependencyScores = (external, config) => {
4529
4590
  config.quantileClamp.upper
4530
4591
  );
4531
4592
  const dependencyContexts = /* @__PURE__ */ new Map();
4593
+ const metadataConfidence = computeExternalMetadataConfidence(external);
4532
4594
  const dependencyScores = external.dependencies.map((dependency) => {
4533
4595
  const signalScore = computeDependencySignalScore(
4534
4596
  dependency.ownRiskSignals,
@@ -4563,7 +4625,7 @@ var computeDependencyScores = (external, config) => {
4563
4625
  dependency.busFactor,
4564
4626
  dependency.weeklyDownloads
4565
4627
  ].filter((value) => value !== null).length;
4566
- const confidence = toUnitInterval(0.5 + availableMetricCount * 0.125);
4628
+ const confidence = toUnitInterval((0.5 + availableMetricCount * 0.125) * metadataConfidence);
4567
4629
  dependencyContexts.set(dependency.name, {
4568
4630
  signalScore: round45(signalScore),
4569
4631
  stalenessRisk: round45(stalenessRisk),
@@ -4762,7 +4824,13 @@ var buildFragileClusters = (structural, evolution, fileScoresByFile, config) =>
4762
4824
  var computeRiskSummary = (structural, evolution, external, config, traceCollector) => {
4763
4825
  const collector = traceCollector;
4764
4826
  const dependencyComputation = computeDependencyScores(external, config);
4827
+ const externalMetadataConfidence = computeExternalMetadataConfidence(external);
4765
4828
  const evolutionByFile = mapEvolutionByFile(evolution);
4829
+ const evolutionHistoryConfidence = computeEvolutionHistoryConfidence(
4830
+ structural,
4831
+ evolution,
4832
+ evolutionByFile
4833
+ );
4766
4834
  const evolutionScales = computeEvolutionScales(evolutionByFile, config);
4767
4835
  const cycleFileSet = new Set(
4768
4836
  structural.cycles.flatMap((cycle) => cycle.nodes.map((node) => normalizePath2(node)))
@@ -4794,10 +4862,10 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4794
4862
  const fanOutRisk = normalizeWithScale(logScale(file.fanOut), fanOutScale);
4795
4863
  const depthRisk = normalizeWithScale(file.depth, depthScale);
4796
4864
  const structuralWeights = config.structuralFactorWeights;
4797
- const structuralFactor = toUnitInterval(
4865
+ const structuralFactorRaw = toUnitInterval(
4798
4866
  fanInRisk * structuralWeights.fanIn + fanOutRisk * structuralWeights.fanOut + depthRisk * structuralWeights.depth + inCycle * structuralWeights.cycleParticipation
4799
4867
  );
4800
- const structuralCentrality = toUnitInterval((fanInRisk + fanOutRisk) / 2);
4868
+ const structuralCentralityRaw = toUnitInterval((fanInRisk + fanOutRisk) / 2);
4801
4869
  let evolutionFactor = 0;
4802
4870
  let frequencyRisk = 0;
4803
4871
  let churnRisk = 0;
@@ -4824,6 +4892,15 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4824
4892
  frequencyRisk * evolutionWeights.frequency + churnRisk * evolutionWeights.churn + volatilityRisk * evolutionWeights.recentVolatility + ownershipConcentrationRisk * evolutionWeights.ownershipConcentration + busFactorRisk * evolutionWeights.busFactorRisk
4825
4893
  );
4826
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);
4827
4904
  const dependencyAffinity = toUnitInterval(structuralCentrality * 0.6 + evolutionFactor * 0.4);
4828
4905
  const externalFactor = external.available ? toUnitInterval(dependencyComputation.repositoryExternalPressure * dependencyAffinity) : 0;
4829
4906
  const structuralBase = structuralFactor * dimensionWeights.structural;
@@ -4868,7 +4945,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4868
4945
  topAuthorShare: evolutionMetrics?.topAuthorShare ?? null,
4869
4946
  busFactor: evolutionMetrics?.busFactor ?? null,
4870
4947
  dependencyAffinity: round45(dependencyAffinity),
4871
- repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure)
4948
+ repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure),
4949
+ structuralAttenuation: round45(structuralAttenuation)
4872
4950
  },
4873
4951
  normalizedMetrics: {
4874
4952
  fanInRisk: round45(fanInRisk),
@@ -4911,7 +4989,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4911
4989
  fanIn: context.rawMetrics.fanIn,
4912
4990
  fanOut: context.rawMetrics.fanOut,
4913
4991
  depth: context.rawMetrics.depth,
4914
- cycleParticipation: context.rawMetrics.cycleParticipation
4992
+ cycleParticipation: context.rawMetrics.cycleParticipation,
4993
+ structuralAttenuation: context.rawMetrics.structuralAttenuation
4915
4994
  },
4916
4995
  normalizedMetrics: {
4917
4996
  fanInRisk: context.normalizedMetrics.fanInRisk,
@@ -4946,7 +5025,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4946
5025
  weight: dimensionWeights.evolution,
4947
5026
  amplification: null,
4948
5027
  evidence: [{ kind: "file_metric", target: context.file, metric: "commitCount" }],
4949
- confidence: evolution.available ? 1 : 0
5028
+ confidence: evolution.available ? evolutionHistoryConfidence * (context.rawMetrics.commitCount === null ? 0.5 : 1) : 0
4950
5029
  },
4951
5030
  {
4952
5031
  factorId: "file.external",
@@ -4962,7 +5041,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4962
5041
  weight: dimensionWeights.external,
4963
5042
  amplification: null,
4964
5043
  evidence: [{ kind: "repository_metric", metric: "repositoryExternalPressure" }],
4965
- confidence: external.available ? 0.7 : 0
5044
+ confidence: external.available ? 0.7 * externalMetadataConfidence : 0
4966
5045
  },
4967
5046
  {
4968
5047
  factorId: "file.composite.interactions",
@@ -5090,7 +5169,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5090
5169
  weight: config.dependencyFactorWeights.signals,
5091
5170
  amplification: config.dependencySignals.inheritedSignalMultiplier,
5092
5171
  evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "riskSignals" }],
5093
- confidence: 0.95
5172
+ confidence: 0.95 * externalMetadataConfidence
5094
5173
  },
5095
5174
  {
5096
5175
  factorId: "dependency.staleness",
@@ -5103,7 +5182,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5103
5182
  evidence: [
5104
5183
  { kind: "dependency_metric", target: dependency.name, metric: "daysSinceLastRelease" }
5105
5184
  ],
5106
- confidence: hasMetadata ? 0.9 : 0.5
5185
+ confidence: (hasMetadata ? 0.9 : 0.5) * externalMetadataConfidence
5107
5186
  },
5108
5187
  {
5109
5188
  factorId: "dependency.maintainer_concentration",
@@ -5118,7 +5197,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5118
5197
  evidence: [
5119
5198
  { kind: "dependency_metric", target: dependency.name, metric: "maintainerCount" }
5120
5199
  ],
5121
- confidence: hasMetadata ? 0.9 : 0.5
5200
+ confidence: (hasMetadata ? 0.9 : 0.5) * externalMetadataConfidence
5122
5201
  },
5123
5202
  {
5124
5203
  factorId: "dependency.topology",
@@ -5139,7 +5218,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5139
5218
  evidence: [
5140
5219
  { kind: "dependency_metric", target: dependency.name, metric: "dependencyDepth" }
5141
5220
  ],
5142
- confidence: 1
5221
+ confidence: 1 * externalMetadataConfidence
5143
5222
  },
5144
5223
  {
5145
5224
  factorId: "dependency.bus_factor",
@@ -5150,7 +5229,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5150
5229
  weight: config.dependencyFactorWeights.busFactorRisk,
5151
5230
  amplification: null,
5152
5231
  evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "busFactor" }],
5153
- confidence: context.rawMetrics.busFactor === null ? 0.5 : 0.85
5232
+ confidence: (context.rawMetrics.busFactor === null ? 0.5 : 0.85) * externalMetadataConfidence
5154
5233
  },
5155
5234
  {
5156
5235
  factorId: "dependency.popularity_dampening",
@@ -5163,7 +5242,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5163
5242
  evidence: [
5164
5243
  { kind: "dependency_metric", target: dependency.name, metric: "weeklyDownloads" }
5165
5244
  ],
5166
- confidence: context.rawMetrics.weeklyDownloads === null ? 0.4 : 0.9
5245
+ confidence: (context.rawMetrics.weeklyDownloads === null ? 0.4 : 0.9) * externalMetadataConfidence
5167
5246
  }
5168
5247
  ]);
5169
5248
  collector.record(
@@ -5197,9 +5276,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5197
5276
  criticalInstability * config.interactionWeights.centralInstability,
5198
5277
  dependencyAmplification * config.interactionWeights.dependencyAmplification
5199
5278
  ]);
5200
- const repositoryScore = round45(repositoryNormalizedScore * 100);
5279
+ const riskScore = round45(repositoryNormalizedScore * 100);
5201
5280
  if (collector !== void 0) {
5202
- const repositoryFactors = buildFactorTraces(repositoryScore, [
5281
+ const repositoryFactors = buildFactorTraces(riskScore, [
5203
5282
  {
5204
5283
  factorId: "repository.structural",
5205
5284
  family: "structural",
@@ -5220,7 +5299,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5220
5299
  weight: dimensionWeights.evolution,
5221
5300
  amplification: null,
5222
5301
  evidence: [{ kind: "repository_metric", metric: "evolutionDimension" }],
5223
- confidence: evolution.available ? 1 : 0
5302
+ confidence: evolution.available ? evolutionHistoryConfidence : 0
5224
5303
  },
5225
5304
  {
5226
5305
  factorId: "repository.external",
@@ -5231,7 +5310,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5231
5310
  weight: dimensionWeights.external,
5232
5311
  amplification: null,
5233
5312
  evidence: [{ kind: "repository_metric", metric: "externalDimension" }],
5234
- confidence: external.available ? 0.8 : 0
5313
+ confidence: external.available ? 0.8 * externalMetadataConfidence : 0
5235
5314
  },
5236
5315
  {
5237
5316
  factorId: "repository.composite.interactions",
@@ -5262,14 +5341,14 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5262
5341
  buildTargetTrace(
5263
5342
  "repository",
5264
5343
  structural.targetPath,
5265
- repositoryScore,
5344
+ riskScore,
5266
5345
  repositoryNormalizedScore,
5267
5346
  repositoryFactors
5268
5347
  )
5269
5348
  );
5270
5349
  }
5271
5350
  return {
5272
- repositoryScore,
5351
+ riskScore,
5273
5352
  normalizedScore: round45(repositoryNormalizedScore),
5274
5353
  hotspots,
5275
5354
  fragileClusters,
@@ -5360,6 +5439,10 @@ var mergeConfig = (overrides) => {
5360
5439
  externalDimension: {
5361
5440
  ...DEFAULT_RISK_ENGINE_CONFIG.externalDimension,
5362
5441
  ...overrides.externalDimension
5442
+ },
5443
+ aggregatorAttenuation: {
5444
+ ...DEFAULT_RISK_ENGINE_CONFIG.aggregatorAttenuation,
5445
+ ...overrides.aggregatorAttenuation
5363
5446
  }
5364
5447
  };
5365
5448
  };
@@ -5391,6 +5474,21 @@ var evaluateRepositoryRisk = (input, options = {}) => {
5391
5474
 
5392
5475
  // src/application/run-analyze-command.ts
5393
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
+ };
5394
5492
  var createExternalProgressReporter = (logger) => {
5395
5493
  let lastLoggedProgress = 0;
5396
5494
  return (event) => {
@@ -5558,8 +5656,12 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
5558
5656
  logger
5559
5657
  );
5560
5658
  logger.info("computing risk summary");
5561
- const risk = computeRepositoryRiskSummary(analysisInputs);
5562
- 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})`);
5563
5665
  return {
5564
5666
  ...analysisInputs,
5565
5667
  risk
@@ -5579,7 +5681,14 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
5579
5681
  },
5580
5682
  logger
5581
5683
  );
5582
- 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
+ );
5583
5692
  const summary = {
5584
5693
  ...analysisInputs,
5585
5694
  risk: evaluation.summary
@@ -5590,6 +5699,7 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
5590
5699
  analysisConfig: {
5591
5700
  authorIdentityMode,
5592
5701
  includeTrace: options.includeTrace,
5702
+ riskProfile: options.riskProfile ?? "default",
5593
5703
  recentWindowDays: analysisInputs.evolution.available ? analysisInputs.evolution.metrics.recentWindowDays : options.recentWindowDays ?? null
5594
5704
  }
5595
5705
  });
@@ -5624,6 +5734,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5624
5734
  authorIdentityMode,
5625
5735
  {
5626
5736
  includeTrace: options.includeTrace,
5737
+ ...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
5627
5738
  ...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
5628
5739
  },
5629
5740
  logger
@@ -5692,6 +5803,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5692
5803
  authorIdentityMode,
5693
5804
  {
5694
5805
  includeTrace: options.includeTrace,
5806
+ ...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
5695
5807
  ...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
5696
5808
  },
5697
5809
  logger
@@ -5750,6 +5862,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5750
5862
  authorIdentityMode,
5751
5863
  {
5752
5864
  includeTrace: options.includeTrace,
5865
+ ...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
5753
5866
  ...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
5754
5867
  },
5755
5868
  logger
@@ -5825,6 +5938,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
5825
5938
  authorIdentityMode,
5826
5939
  {
5827
5940
  includeTrace: options.includeTrace,
5941
+ ...options.riskProfile === void 0 ? {} : { riskProfile: options.riskProfile },
5828
5942
  ...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
5829
5943
  },
5830
5944
  logger
@@ -5881,7 +5995,14 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
5881
5995
  logger
5882
5996
  );
5883
5997
  logger.info("computing explainable risk summary");
5884
- 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
+ );
5885
6006
  if (evaluation.trace === void 0) {
5886
6007
  throw new Error("risk trace unavailable");
5887
6008
  }
@@ -5889,7 +6010,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
5889
6010
  ...analysisInputs,
5890
6011
  risk: evaluation.summary
5891
6012
  };
5892
- logger.info(`explanation completed (repositoryScore=${summary.risk.repositoryScore})`);
6013
+ logger.info(`explanation completed (riskScore=${summary.risk.riskScore})`);
5893
6014
  return {
5894
6015
  summary,
5895
6016
  trace: evaluation.trace,
@@ -5908,8 +6029,12 @@ var parseRecentWindowDays = (value) => {
5908
6029
  }
5909
6030
  return parsed;
5910
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");
5911
6036
  program.name("codesentinel").description("Structural and evolutionary risk analysis for TypeScript/JavaScript codebases").version(version);
5912
- 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(
5913
6038
  new Option(
5914
6039
  "--author-identity <mode>",
5915
6040
  "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
@@ -5932,7 +6057,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
5932
6057
  const summary = await runAnalyzeCommand(
5933
6058
  path,
5934
6059
  options.authorIdentity,
5935
- { recentWindowDays: options.recentWindowDays },
6060
+ { recentWindowDays: options.recentWindowDays, riskProfile: options.riskProfile },
5936
6061
  logger
5937
6062
  );
5938
6063
  const outputMode = options.json === true ? "json" : options.output;
@@ -5940,7 +6065,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
5940
6065
  `);
5941
6066
  }
5942
6067
  );
5943
- 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(
5944
6069
  new Option(
5945
6070
  "--author-identity <mode>",
5946
6071
  "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
@@ -5966,6 +6091,7 @@ program.command("explain").argument("[path]", "path to the project to analyze").
5966
6091
  ...options.module === void 0 ? {} : { module: options.module },
5967
6092
  top: Number.isFinite(top) ? top : 5,
5968
6093
  recentWindowDays: options.recentWindowDays,
6094
+ riskProfile: options.riskProfile,
5969
6095
  format: options.format
5970
6096
  };
5971
6097
  const result = await runExplainCommand(path, options.authorIdentity, explainOptions, logger);
@@ -6003,7 +6129,7 @@ program.command("dependency-risk").argument("<dependency>", "dependency spec to
6003
6129
  `);
6004
6130
  }
6005
6131
  );
6006
- 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(
6007
6133
  new Option(
6008
6134
  "--author-identity <mode>",
6009
6135
  "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
@@ -6032,6 +6158,7 @@ program.command("report").argument("[path]", "path to the project to analyze").a
6032
6158
  ...options.compare === void 0 ? {} : { comparePath: options.compare },
6033
6159
  ...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
6034
6160
  includeTrace: options.trace,
6161
+ riskProfile: options.riskProfile,
6035
6162
  recentWindowDays: options.recentWindowDays
6036
6163
  },
6037
6164
  logger
@@ -6083,7 +6210,7 @@ var buildGateConfigFromOptions = (options) => {
6083
6210
  failOn: options.failOn
6084
6211
  };
6085
6212
  };
6086
- 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(
6087
6214
  new Option(
6088
6215
  "--author-identity <mode>",
6089
6216
  "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
@@ -6113,6 +6240,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
6113
6240
  {
6114
6241
  ...options.compare === void 0 ? {} : { baselinePath: options.compare },
6115
6242
  includeTrace: options.trace,
6243
+ riskProfile: options.riskProfile,
6116
6244
  recentWindowDays: options.recentWindowDays,
6117
6245
  gateConfig,
6118
6246
  outputFormat: options.format,
@@ -6136,7 +6264,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
6136
6264
  }
6137
6265
  }
6138
6266
  );
6139
- 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(
6140
6268
  new Option(
6141
6269
  "--author-identity <mode>",
6142
6270
  "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
@@ -6185,6 +6313,7 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
6185
6313
  ...options.report === void 0 ? {} : { reportPath: options.report },
6186
6314
  ...options.jsonOutput === void 0 ? {} : { jsonOutputPath: options.jsonOutput },
6187
6315
  includeTrace: options.trace,
6316
+ riskProfile: options.riskProfile,
6188
6317
  recentWindowDays: options.recentWindowDays,
6189
6318
  gateConfig
6190
6319
  },