@getcodesentinel/codesentinel 1.13.0 → 1.15.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/dist/index.js CHANGED
@@ -1656,6 +1656,7 @@ var createReport = (snapshot, diff) => {
1656
1656
  confidence: repositoryConfidence(snapshot),
1657
1657
  dimensionScores: repositoryDimensionScores(snapshot)
1658
1658
  },
1659
+ quality: snapshot.analysis.quality,
1659
1660
  hotspots: hotspotItems(snapshot),
1660
1661
  structural: {
1661
1662
  cycleCount: snapshot.analysis.structural.metrics.cycleCount,
@@ -1727,6 +1728,22 @@ var renderTextReport = (report) => {
1727
1728
  lines.push(` external: ${report.repository.dimensionScores.external ?? "n/a"}`);
1728
1729
  lines.push(` interactions: ${report.repository.dimensionScores.interactions ?? "n/a"}`);
1729
1730
  lines.push("");
1731
+ lines.push("Quality Summary");
1732
+ lines.push(` qualityScore: ${report.quality.qualityScore}`);
1733
+ lines.push(` normalizedScore: ${report.quality.normalizedScore}`);
1734
+ lines.push(` modularity: ${report.quality.dimensions.modularity}`);
1735
+ lines.push(` changeHygiene: ${report.quality.dimensions.changeHygiene}`);
1736
+ lines.push(` testHealth: ${report.quality.dimensions.testHealth}`);
1737
+ lines.push(" topIssues:");
1738
+ for (const issue of report.quality.topIssues.slice(0, 5)) {
1739
+ lines.push(
1740
+ ` - [${issue.severity}] (${issue.dimension}) ${issue.id} @ ${issue.target}: ${issue.message}`
1741
+ );
1742
+ }
1743
+ if (report.quality.topIssues.length === 0) {
1744
+ lines.push(" - none");
1745
+ }
1746
+ lines.push("");
1730
1747
  lines.push("Top Hotspots");
1731
1748
  for (const hotspot of report.hotspots) {
1732
1749
  lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
@@ -1736,8 +1753,7 @@ var renderTextReport = (report) => {
1736
1753
  );
1737
1754
  lines.push(` evidence: ${factor.evidence}`);
1738
1755
  }
1739
- lines.push(` actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1740
- lines.push(` levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
1756
+ lines.push(` priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1741
1757
  }
1742
1758
  lines.push("");
1743
1759
  lines.push("Structural Observations");
@@ -1803,6 +1819,23 @@ var renderMarkdownReport = (report) => {
1803
1819
  lines.push(`- external: \`${report.repository.dimensionScores.external ?? "n/a"}\``);
1804
1820
  lines.push(`- interactions: \`${report.repository.dimensionScores.interactions ?? "n/a"}\``);
1805
1821
  lines.push("");
1822
+ lines.push("## Quality Summary");
1823
+ lines.push(`- qualityScore: \`${report.quality.qualityScore}\``);
1824
+ lines.push(`- normalizedScore: \`${report.quality.normalizedScore}\``);
1825
+ lines.push(`- modularity: \`${report.quality.dimensions.modularity}\``);
1826
+ lines.push(`- changeHygiene: \`${report.quality.dimensions.changeHygiene}\``);
1827
+ lines.push(`- testHealth: \`${report.quality.dimensions.testHealth}\``);
1828
+ if (report.quality.topIssues.length === 0) {
1829
+ lines.push("- top issues: none");
1830
+ } else {
1831
+ lines.push("- top issues:");
1832
+ for (const issue of report.quality.topIssues.slice(0, 5)) {
1833
+ lines.push(
1834
+ ` - [${issue.severity}] \`${issue.id}\` (\`${issue.dimension}\`) @ \`${issue.target}\`: ${issue.message}`
1835
+ );
1836
+ }
1837
+ }
1838
+ lines.push("");
1806
1839
  lines.push("## Top Hotspots");
1807
1840
  for (const hotspot of report.hotspots) {
1808
1841
  lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
@@ -1813,8 +1846,7 @@ var renderMarkdownReport = (report) => {
1813
1846
  );
1814
1847
  lines.push(` - evidence: \`${factor.evidence}\``);
1815
1848
  }
1816
- lines.push(` - Suggested actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1817
- lines.push(` - Biggest levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
1849
+ lines.push(` - Priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1818
1850
  }
1819
1851
  lines.push("");
1820
1852
  lines.push("## Structural Observations");
@@ -2461,6 +2493,7 @@ var resolveAutoBaselineRef = async (input) => {
2461
2493
 
2462
2494
  // src/index.ts
2463
2495
  import { readFileSync as readFileSync2 } from "fs";
2496
+ import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
2464
2497
  import { dirname as dirname2, resolve as resolve5 } from "path";
2465
2498
  import { fileURLToPath } from "url";
2466
2499
 
@@ -2501,6 +2534,12 @@ var createSummaryShape = (summary) => ({
2501
2534
  })),
2502
2535
  fragileClusterCount: summary.risk.fragileClusters.length,
2503
2536
  dependencyAmplificationZoneCount: summary.risk.dependencyAmplificationZones.length
2537
+ },
2538
+ quality: {
2539
+ qualityScore: summary.quality.qualityScore,
2540
+ normalizedScore: summary.quality.normalizedScore,
2541
+ dimensions: summary.quality.dimensions,
2542
+ topIssues: summary.quality.topIssues.slice(0, 5)
2504
2543
  }
2505
2544
  });
2506
2545
  var formatAnalyzeOutput = (summary, mode) => mode === "json" ? JSON.stringify(summary, null, 2) : JSON.stringify(createSummaryShape(summary), null, 2);
@@ -2544,6 +2583,7 @@ var formatFactorLabel = (factorId) => factorLabelById2[factorId] ?? factorId;
2544
2583
  var formatNumber = (value) => value === null || value === void 0 ? "n/a" : `${value}`;
2545
2584
  var formatDimension = (value) => value === null ? "n/a" : `${value}`;
2546
2585
  var formatFactorSummary = (factor) => `${formatFactorLabel(factor.factorId)} (+${factor.contribution}, confidence=${factor.confidence})`;
2586
+ var formatFactorContribution = (factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`;
2547
2587
  var formatFactorEvidence = (factor) => {
2548
2588
  if (factor.factorId === "repository.structural") {
2549
2589
  return `structural dimension=${formatNumber(factor.rawMetrics["structuralDimension"])}`;
@@ -2676,20 +2716,15 @@ var renderText = (payload) => {
2676
2716
  lines.push("");
2677
2717
  lines.push("explanation:");
2678
2718
  lines.push(
2679
- ` why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2680
- );
2681
- lines.push(
2682
- ` what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
2683
- );
2684
- lines.push(
2685
- ` dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
2719
+ ` key drivers: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2686
2720
  );
2687
2721
  lines.push(
2688
- ` intersected signals: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2722
+ ` contributions: ${repositoryTopFactors.map(formatFactorContribution).join(", ") || "insufficient data"}`
2689
2723
  );
2690
2724
  lines.push(
2691
- ` what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
2725
+ ` interaction effects: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2692
2726
  );
2727
+ lines.push(` priority actions: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`);
2693
2728
  lines.push("");
2694
2729
  for (const target of payload.selectedTargets) {
2695
2730
  lines.push(renderTargetText(target));
@@ -2717,25 +2752,19 @@ var renderMarkdown = (payload) => {
2717
2752
  lines.push("");
2718
2753
  lines.push(`## Summary`);
2719
2754
  lines.push(
2720
- `- why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2755
+ `- key drivers: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
2721
2756
  );
2722
2757
  lines.push(
2723
- `- what specifically contributed: ${repositoryTopFactors.map((factor) => `${formatFactorLabel(factor.factorId)}=${factor.contribution}`).join(", ") || "insufficient data"}`
2758
+ `- contributions: ${repositoryTopFactors.map(formatFactorContribution).join(", ") || "insufficient data"}`
2724
2759
  );
2725
2760
  lines.push(
2726
- `- dominant factors: ${repositoryTopFactors.map((factor) => formatFactorLabel(factor.factorId)).join(", ") || "insufficient data"}`
2727
- );
2728
- lines.push(
2729
- `- intersected signals: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2730
- );
2731
- lines.push(
2732
- `- what could reduce risk most: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`
2761
+ `- interaction effects: ${compositeFactors.map((factor) => `${formatFactorLabel(factor.factorId)} [${formatFactorEvidence(factor)}]`).join("; ") || "none"}`
2733
2762
  );
2763
+ lines.push(`- priority actions: ${buildRepositoryActions(payload, repositoryTarget).join(" ")}`);
2734
2764
  lines.push("");
2735
2765
  for (const target of payload.selectedTargets) {
2736
2766
  lines.push(`## ${target.targetType}: \`${target.targetId}\``);
2737
2767
  lines.push(`- score: \`${target.totalScore}\` (\`${target.normalizedScore}\`)`);
2738
- lines.push(`- dominantFactors: \`${target.dominantFactors.join(", ")}\``);
2739
2768
  lines.push(`- Top factors:`);
2740
2769
  for (const factor of [...target.factors].sort(sortFactorByContribution).slice(0, 5)) {
2741
2770
  lines.push(
@@ -3187,7 +3216,8 @@ var checkForCliUpdates = async (input) => {
3187
3216
  };
3188
3217
 
3189
3218
  // src/application/run-analyze-command.ts
3190
- import { resolve as resolve3 } from "path";
3219
+ import { readFile as readFile2 } from "fs/promises";
3220
+ import { join as join4, resolve as resolve3 } from "path";
3191
3221
 
3192
3222
  // ../code-graph/dist/index.js
3193
3223
  import { extname, isAbsolute, relative, resolve as resolve2 } from "path";
@@ -4235,6 +4265,208 @@ var analyzeRepositoryEvolutionFromGit = (input, onProgress) => {
4235
4265
  return analyzeRepositoryEvolution(input, historyProvider, onProgress);
4236
4266
  };
4237
4267
 
4268
+ // ../quality-engine/dist/index.js
4269
+ var clamp01 = (value) => {
4270
+ if (!Number.isFinite(value)) {
4271
+ return 0;
4272
+ }
4273
+ if (value <= 0) {
4274
+ return 0;
4275
+ }
4276
+ if (value >= 1) {
4277
+ return 1;
4278
+ }
4279
+ return value;
4280
+ };
4281
+ var round45 = (value) => Number(value.toFixed(4));
4282
+ var average = (values) => {
4283
+ if (values.length === 0) {
4284
+ return 0;
4285
+ }
4286
+ const total = values.reduce((sum, value) => sum + value, 0);
4287
+ return total / values.length;
4288
+ };
4289
+ var concentration = (rawValues) => {
4290
+ const values = rawValues.filter((value) => value > 0);
4291
+ const count = values.length;
4292
+ if (count <= 1) {
4293
+ return 0;
4294
+ }
4295
+ const total = values.reduce((sum, value) => sum + value, 0);
4296
+ if (total <= 0) {
4297
+ return 0;
4298
+ }
4299
+ const hhi = values.reduce((sum, value) => {
4300
+ const share = value / total;
4301
+ return sum + share * share;
4302
+ }, 0);
4303
+ const minHhi = 1 / count;
4304
+ const normalized = (hhi - minHhi) / (1 - minHhi);
4305
+ return clamp01(normalized);
4306
+ };
4307
+ var DIMENSION_WEIGHTS = {
4308
+ modularity: 0.45,
4309
+ changeHygiene: 0.35,
4310
+ testHealth: 0.2
4311
+ };
4312
+ var TODO_FIXME_MAX_IMPACT = 0.08;
4313
+ var toPercentage = (normalizedQuality) => round45(clamp01(normalizedQuality) * 100);
4314
+ var filePaths = (structural) => structural.files.map((file) => file.relativePath);
4315
+ var isTestPath = (path) => {
4316
+ const normalized = path.toLowerCase();
4317
+ return normalized.includes("/__tests__/") || normalized.includes("\\__tests__\\") || normalized.includes(".test.") || normalized.includes(".spec.");
4318
+ };
4319
+ var isSourcePath = (path) => {
4320
+ if (path.endsWith(".d.ts")) {
4321
+ return false;
4322
+ }
4323
+ return !isTestPath(path);
4324
+ };
4325
+ var pushIssue = (issues, issue) => {
4326
+ issues.push({
4327
+ ...issue,
4328
+ severity: issue.severity ?? "warn"
4329
+ });
4330
+ };
4331
+ var computeRepositoryQualitySummary = (input) => {
4332
+ const issues = [];
4333
+ const sourceFileSet = new Set(input.structural.files.map((file) => file.relativePath));
4334
+ const cycleCount = input.structural.metrics.cycleCount;
4335
+ const cycleSizeAverage = input.structural.cycles.length === 0 ? 0 : average(input.structural.cycles.map((cycle) => cycle.nodes.length));
4336
+ const cyclePenalty = clamp01(cycleCount / 6) * 0.7 + clamp01((cycleSizeAverage - 2) / 8) * 0.3;
4337
+ if (cycleCount > 0) {
4338
+ pushIssue(issues, {
4339
+ id: "quality.modularity.structural_cycles",
4340
+ dimension: "modularity",
4341
+ target: input.structural.cycles[0]?.nodes.slice().sort((a, b) => a.localeCompare(b)).join(" -> ") ?? input.structural.targetPath,
4342
+ message: `${cycleCount} structural cycle(s) increase coupling and refactor cost.`,
4343
+ severity: cycleCount >= 3 ? "error" : "warn",
4344
+ impact: round45(cyclePenalty * 0.6)
4345
+ });
4346
+ }
4347
+ const fanInConcentration = concentration(input.structural.files.map((file) => file.fanIn));
4348
+ const fanOutConcentration = concentration(input.structural.files.map((file) => file.fanOut));
4349
+ const centralityConcentration = average([fanInConcentration, fanOutConcentration]);
4350
+ if (centralityConcentration >= 0.5) {
4351
+ const hottest = [...input.structural.files].map((file) => ({
4352
+ path: file.relativePath,
4353
+ pressure: file.fanIn + file.fanOut
4354
+ })).sort((a, b) => b.pressure - a.pressure || a.path.localeCompare(b.path))[0];
4355
+ pushIssue(issues, {
4356
+ id: "quality.modularity.centrality_concentration",
4357
+ dimension: "modularity",
4358
+ target: hottest?.path ?? input.structural.targetPath,
4359
+ message: "Fan-in/fan-out pressure is concentrated in a small set of files.",
4360
+ impact: round45(centralityConcentration * 0.5)
4361
+ });
4362
+ }
4363
+ let churnConcentration = 0;
4364
+ let volatilityConcentration = 0;
4365
+ let couplingDensity = 0;
4366
+ let couplingIntensity = 0;
4367
+ if (input.evolution.available) {
4368
+ const evolutionSourceFiles = input.evolution.files.filter(
4369
+ (file) => sourceFileSet.has(file.filePath)
4370
+ );
4371
+ churnConcentration = concentration(evolutionSourceFiles.map((file) => file.churnTotal));
4372
+ volatilityConcentration = concentration(
4373
+ evolutionSourceFiles.map((file) => file.recentVolatility)
4374
+ );
4375
+ const fileCount = Math.max(1, evolutionSourceFiles.length);
4376
+ const maxPairs = fileCount * (fileCount - 1) / 2;
4377
+ const sourcePairs = input.evolution.coupling.pairs.filter(
4378
+ (pair) => sourceFileSet.has(pair.fileA) && sourceFileSet.has(pair.fileB)
4379
+ );
4380
+ couplingDensity = maxPairs <= 0 ? 0 : clamp01(sourcePairs.length / maxPairs);
4381
+ couplingIntensity = average(sourcePairs.map((pair) => pair.couplingScore));
4382
+ if (churnConcentration >= 0.45) {
4383
+ const mostChurn = [...evolutionSourceFiles].sort(
4384
+ (a, b) => b.churnTotal - a.churnTotal || a.filePath.localeCompare(b.filePath)
4385
+ )[0];
4386
+ pushIssue(issues, {
4387
+ id: "quality.change_hygiene.churn_concentration",
4388
+ dimension: "changeHygiene",
4389
+ target: mostChurn?.filePath ?? input.structural.targetPath,
4390
+ message: "Churn is concentrated in a narrow part of the codebase.",
4391
+ impact: round45(churnConcentration * 0.45)
4392
+ });
4393
+ }
4394
+ if (volatilityConcentration >= 0.45) {
4395
+ const volatileFile = [...evolutionSourceFiles].sort(
4396
+ (a, b) => b.recentVolatility - a.recentVolatility || a.filePath.localeCompare(b.filePath)
4397
+ )[0];
4398
+ pushIssue(issues, {
4399
+ id: "quality.change_hygiene.volatility_concentration",
4400
+ dimension: "changeHygiene",
4401
+ target: volatileFile?.filePath ?? input.structural.targetPath,
4402
+ message: "Recent volatility is concentrated in files that change frequently.",
4403
+ impact: round45(volatilityConcentration * 0.4)
4404
+ });
4405
+ }
4406
+ if (couplingDensity >= 0.35 || couplingIntensity >= 0.45) {
4407
+ const strongestPair = [...sourcePairs].sort(
4408
+ (a, b) => b.couplingScore - a.couplingScore || `${a.fileA}|${a.fileB}`.localeCompare(`${b.fileA}|${b.fileB}`)
4409
+ )[0];
4410
+ pushIssue(issues, {
4411
+ id: "quality.change_hygiene.coupling_density",
4412
+ dimension: "changeHygiene",
4413
+ target: strongestPair === void 0 ? input.structural.targetPath : `${strongestPair.fileA}<->${strongestPair.fileB}`,
4414
+ message: "Co-change relationships are dense, increasing coordination overhead.",
4415
+ impact: round45(average([couplingDensity, couplingIntensity]) * 0.35)
4416
+ });
4417
+ }
4418
+ }
4419
+ const modularityPenalty = clamp01(cyclePenalty * 0.55 + centralityConcentration * 0.45);
4420
+ const changeHygienePenalty = input.evolution.available ? clamp01(
4421
+ churnConcentration * 0.4 + volatilityConcentration * 0.35 + couplingDensity * 0.15 + couplingIntensity * 0.1
4422
+ ) : 0.25;
4423
+ const paths = filePaths(input.structural);
4424
+ const testFiles = paths.filter((path) => isTestPath(path)).length;
4425
+ const sourceFiles = paths.filter((path) => isSourcePath(path)).length;
4426
+ const testRatio = sourceFiles <= 0 ? 1 : testFiles / sourceFiles;
4427
+ const testPresencePenalty = sourceFiles <= 0 ? 0 : 1 - clamp01(testRatio / 0.3);
4428
+ if (sourceFiles > 0 && testRatio < 0.2) {
4429
+ pushIssue(issues, {
4430
+ id: "quality.test_health.low_test_presence",
4431
+ dimension: "testHealth",
4432
+ target: input.structural.targetPath,
4433
+ message: `Detected ${testFiles} test file(s) for ${sourceFiles} source file(s).`,
4434
+ severity: testRatio === 0 ? "error" : "warn",
4435
+ impact: round45(testPresencePenalty * 0.5)
4436
+ });
4437
+ }
4438
+ const todoFixmeCount = Math.max(0, input.todoFixmeCount ?? 0);
4439
+ const todoFixmePenalty = clamp01(todoFixmeCount / 120) * TODO_FIXME_MAX_IMPACT;
4440
+ if (todoFixmeCount > 0) {
4441
+ pushIssue(issues, {
4442
+ id: "quality.change_hygiene.todo_fixme_load",
4443
+ dimension: "changeHygiene",
4444
+ target: input.structural.targetPath,
4445
+ message: `Found ${todoFixmeCount} TODO/FIXME marker(s); cleanup debt is accumulating.`,
4446
+ impact: round45(todoFixmePenalty * 0.2)
4447
+ });
4448
+ }
4449
+ const modularityQuality = clamp01(1 - modularityPenalty);
4450
+ const changeHygieneQuality = clamp01(1 - clamp01(changeHygienePenalty + todoFixmePenalty));
4451
+ const testHealthQuality = clamp01(1 - testPresencePenalty);
4452
+ const normalizedScore = clamp01(
4453
+ modularityQuality * DIMENSION_WEIGHTS.modularity + changeHygieneQuality * DIMENSION_WEIGHTS.changeHygiene + testHealthQuality * DIMENSION_WEIGHTS.testHealth
4454
+ );
4455
+ const topIssues = [...issues].sort(
4456
+ (a, b) => b.impact - a.impact || a.id.localeCompare(b.id) || a.target.localeCompare(b.target)
4457
+ ).slice(0, 8).map(({ impact: _impact, ...issue }) => issue);
4458
+ return {
4459
+ qualityScore: toPercentage(normalizedScore),
4460
+ normalizedScore: round45(normalizedScore),
4461
+ dimensions: {
4462
+ modularity: toPercentage(modularityQuality),
4463
+ changeHygiene: toPercentage(changeHygieneQuality),
4464
+ testHealth: toPercentage(testHealthQuality)
4465
+ },
4466
+ topIssues
4467
+ };
4468
+ };
4469
+
4238
4470
  // ../risk-engine/dist/index.js
4239
4471
  var DEFAULT_RISK_ENGINE_CONFIG = {
4240
4472
  // Base dimensional influence. Risk is never dominated by a single dimension by default.
@@ -4320,8 +4552,8 @@ var DEFAULT_RISK_ENGINE_CONFIG = {
4320
4552
  }
4321
4553
  };
4322
4554
  var toUnitInterval = (value) => Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
4323
- var round45 = (value) => Number(value.toFixed(4));
4324
- var average = (values) => {
4555
+ var round46 = (value) => Number(value.toFixed(4));
4556
+ var average2 = (values) => {
4325
4557
  if (values.length === 0) {
4326
4558
  return 0;
4327
4559
  }
@@ -4429,14 +4661,14 @@ var computeAggregatorAttenuation = (input) => {
4429
4661
  const fanOutSignal = toUnitInterval((fanOut - config.minFanOut) / Math.max(1, config.minFanOut));
4430
4662
  const lowChurnPerCommitSignal = 1 - toUnitInterval(churnPerCommit / config.maxChurnPerCommit);
4431
4663
  const lowChurnPerDependencySignal = 1 - toUnitInterval(churnPerDependency / config.maxChurnPerDependency);
4432
- const attenuationConfidence = average([
4664
+ const attenuationConfidence = average2([
4433
4665
  fanInSignal,
4434
4666
  fanOutSignal,
4435
4667
  lowChurnPerCommitSignal,
4436
4668
  lowChurnPerDependencySignal
4437
4669
  ]);
4438
4670
  const reduction = toUnitInterval(config.maxStructuralReduction) * attenuationConfidence;
4439
- return round45(toUnitInterval(1 - reduction));
4671
+ return round46(toUnitInterval(1 - reduction));
4440
4672
  };
4441
4673
  var dependencySignalWeights = {
4442
4674
  single_maintainer: 0.3,
@@ -4466,12 +4698,12 @@ var computeDependencySignalScore = (ownSignals, inheritedSignals, inheritedSigna
4466
4698
  }
4467
4699
  return toUnitInterval(weightedTotal / maxWeightedTotal);
4468
4700
  };
4469
- var clampConfidence = (value) => round45(toUnitInterval(value));
4701
+ var clampConfidence = (value) => round46(toUnitInterval(value));
4470
4702
  var computeExternalMetadataConfidence = (external) => {
4471
4703
  if (!external.available) {
4472
4704
  return 0;
4473
4705
  }
4474
- return round45(toUnitInterval(0.35 + external.metrics.metadataCoverage * 0.65));
4706
+ return round46(toUnitInterval(0.35 + external.metrics.metadataCoverage * 0.65));
4475
4707
  };
4476
4708
  var computeEvolutionHistoryConfidence = (structural, evolution, evolutionByFile) => {
4477
4709
  if (!evolution.available) {
@@ -4488,7 +4720,7 @@ var computeEvolutionHistoryConfidence = (structural, evolution, evolutionByFile)
4488
4720
  }
4489
4721
  }
4490
4722
  const coverage = coveredFiles / totalFiles;
4491
- return round45(toUnitInterval(0.3 + coverage * 0.7));
4723
+ return round46(toUnitInterval(0.3 + coverage * 0.7));
4492
4724
  };
4493
4725
  var buildFactorTraces = (totalScore, inputs) => {
4494
4726
  const positiveInputs = inputs.filter((input) => input.strength > 0);
@@ -4526,7 +4758,7 @@ var buildFactorTraces = (totalScore, inputs) => {
4526
4758
  continue;
4527
4759
  }
4528
4760
  if (index === scored.length - 1) {
4529
- const remaining = round45(totalScore - distributed);
4761
+ const remaining = round46(totalScore - distributed);
4530
4762
  traces[traceIndex] = {
4531
4763
  ...existing,
4532
4764
  contribution: Math.max(0, remaining)
@@ -4534,7 +4766,7 @@ var buildFactorTraces = (totalScore, inputs) => {
4534
4766
  distributed += Math.max(0, remaining);
4535
4767
  continue;
4536
4768
  }
4537
- const rounded = round45(current.contribution);
4769
+ const rounded = round46(current.contribution);
4538
4770
  traces[traceIndex] = {
4539
4771
  ...existing,
4540
4772
  contribution: rounded
@@ -4545,15 +4777,15 @@ var buildFactorTraces = (totalScore, inputs) => {
4545
4777
  };
4546
4778
  var buildReductionLevers = (factors) => factors.filter((factor) => factor.contribution > 0).sort((a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)).slice(0, 3).map((factor) => ({
4547
4779
  factorId: factor.factorId,
4548
- estimatedImpact: round45(factor.contribution)
4780
+ estimatedImpact: round46(factor.contribution)
4549
4781
  }));
4550
4782
  var buildTargetTrace = (targetType, targetId, totalScore, normalizedScore, factors) => {
4551
4783
  const dominantFactors = [...factors].filter((factor) => factor.contribution > 0).sort((a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)).slice(0, 3).map((factor) => factor.factorId);
4552
4784
  return {
4553
4785
  targetType,
4554
4786
  targetId,
4555
- totalScore: round45(totalScore),
4556
- normalizedScore: round45(normalizedScore),
4787
+ totalScore: round46(totalScore),
4788
+ normalizedScore: round46(normalizedScore),
4557
4789
  factors,
4558
4790
  dominantFactors,
4559
4791
  reductionLevers: buildReductionLevers(factors)
@@ -4627,14 +4859,14 @@ var computeDependencyScores = (external, config) => {
4627
4859
  ].filter((value) => value !== null).length;
4628
4860
  const confidence = toUnitInterval((0.5 + availableMetricCount * 0.125) * metadataConfidence);
4629
4861
  dependencyContexts.set(dependency.name, {
4630
- signalScore: round45(signalScore),
4631
- stalenessRisk: round45(stalenessRisk),
4632
- maintainerConcentrationRisk: round45(maintainerConcentrationRisk),
4633
- transitiveBurdenRisk: round45(transitiveBurdenRisk),
4634
- centralityRisk: round45(centralityRisk),
4635
- chainDepthRisk: round45(chainDepthRisk),
4636
- busFactorRisk: round45(busFactorRisk),
4637
- popularityDampener: round45(popularityDampener),
4862
+ signalScore: round46(signalScore),
4863
+ stalenessRisk: round46(stalenessRisk),
4864
+ maintainerConcentrationRisk: round46(maintainerConcentrationRisk),
4865
+ transitiveBurdenRisk: round46(transitiveBurdenRisk),
4866
+ centralityRisk: round46(centralityRisk),
4867
+ chainDepthRisk: round46(chainDepthRisk),
4868
+ busFactorRisk: round46(busFactorRisk),
4869
+ popularityDampener: round46(popularityDampener),
4638
4870
  rawMetrics: {
4639
4871
  daysSinceLastRelease: dependency.daysSinceLastRelease,
4640
4872
  maintainerCount: dependency.maintainerCount,
@@ -4644,12 +4876,12 @@ var computeDependencyScores = (external, config) => {
4644
4876
  busFactor: dependency.busFactor,
4645
4877
  weeklyDownloads: dependency.weeklyDownloads
4646
4878
  },
4647
- confidence: round45(confidence)
4879
+ confidence: round46(confidence)
4648
4880
  });
4649
4881
  return {
4650
4882
  dependency: dependency.name,
4651
- score: round45(normalizedScore * 100),
4652
- normalizedScore: round45(normalizedScore),
4883
+ score: round46(normalizedScore * 100),
4884
+ normalizedScore: round46(normalizedScore),
4653
4885
  ownRiskSignals: dependency.ownRiskSignals,
4654
4886
  inheritedRiskSignals: dependency.inheritedRiskSignals
4655
4887
  };
@@ -4658,7 +4890,7 @@ var computeDependencyScores = (external, config) => {
4658
4890
  );
4659
4891
  const normalizedValues = dependencyScores.map((score) => score.normalizedScore);
4660
4892
  const highDependencyRisk = dependencyScores.length === 0 ? 0 : percentile(normalizedValues, config.externalDimension.topDependencyPercentile);
4661
- const averageDependencyRisk = average(normalizedValues);
4893
+ const averageDependencyRisk = average2(normalizedValues);
4662
4894
  const depthRisk = halfLifeRisk(
4663
4895
  external.metrics.dependencyDepth,
4664
4896
  config.externalDimension.dependencyDepthHalfLife
@@ -4668,7 +4900,7 @@ var computeDependencyScores = (external, config) => {
4668
4900
  );
4669
4901
  return {
4670
4902
  dependencyScores,
4671
- repositoryExternalPressure: round45(repositoryExternalPressure),
4903
+ repositoryExternalPressure: round46(repositoryExternalPressure),
4672
4904
  dependencyContexts
4673
4905
  };
4674
4906
  };
@@ -4729,11 +4961,11 @@ var buildFragileClusters = (structural, evolution, fileScoresByFile, config) =>
4729
4961
  continue;
4730
4962
  }
4731
4963
  files.sort((a, b) => a.localeCompare(b));
4732
- const averageRisk = average(
4964
+ const averageRisk = average2(
4733
4965
  files.map((filePath) => fileScoresByFile.get(filePath)?.normalizedScore ?? 0)
4734
4966
  );
4735
4967
  const cycleSizeRisk = toUnitInterval((files.length - 1) / 5);
4736
- const score = round45(toUnitInterval(averageRisk * 0.75 + cycleSizeRisk * 0.25) * 100);
4968
+ const score = round46(toUnitInterval(averageRisk * 0.75 + cycleSizeRisk * 0.25) * 100);
4737
4969
  cycleClusterCount += 1;
4738
4970
  clusters.push({
4739
4971
  id: `cycle:${cycleClusterCount}`,
@@ -4803,11 +5035,11 @@ var buildFragileClusters = (structural, evolution, fileScoresByFile, config) =>
4803
5035
  const componentPairs = selectedPairs.filter(
4804
5036
  (pair) => fileSet.has(pair.fileA) && fileSet.has(pair.fileB)
4805
5037
  );
4806
- const meanFileRisk = average(
5038
+ const meanFileRisk = average2(
4807
5039
  files.map((filePath) => fileScoresByFile.get(filePath)?.normalizedScore ?? 0)
4808
5040
  );
4809
- const meanCoupling = average(componentPairs.map((pair) => pair.couplingScore));
4810
- const score = round45(toUnitInterval(meanFileRisk * 0.65 + meanCoupling * 0.35) * 100);
5041
+ const meanCoupling = average2(componentPairs.map((pair) => pair.couplingScore));
5042
+ const score = round46(toUnitInterval(meanFileRisk * 0.65 + meanCoupling * 0.35) * 100);
4811
5043
  couplingClusterCount += 1;
4812
5044
  clusters.push({
4813
5045
  id: `coupling:${couplingClusterCount}`,
@@ -4918,21 +5150,21 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4918
5150
  const normalizedScore = saturatingComposite(baseline, interactions);
4919
5151
  return {
4920
5152
  file: filePath,
4921
- score: round45(normalizedScore * 100),
4922
- normalizedScore: round45(normalizedScore),
5153
+ score: round46(normalizedScore * 100),
5154
+ normalizedScore: round46(normalizedScore),
4923
5155
  factors: {
4924
- structural: round45(structuralFactor),
4925
- evolution: round45(evolutionFactor),
4926
- external: round45(externalFactor)
5156
+ structural: round46(structuralFactor),
5157
+ evolution: round46(evolutionFactor),
5158
+ external: round46(externalFactor)
4927
5159
  },
4928
- structuralCentrality: round45(structuralCentrality),
5160
+ structuralCentrality: round46(structuralCentrality),
4929
5161
  traceTerms: {
4930
- structuralBase: round45(structuralBase),
4931
- evolutionBase: round45(evolutionBase),
4932
- externalBase: round45(externalBase),
4933
- interactionStructuralEvolution: round45(interactionStructuralEvolution),
4934
- interactionCentralInstability: round45(interactionCentralInstability),
4935
- interactionDependencyAmplification: round45(interactionDependencyAmplification)
5162
+ structuralBase: round46(structuralBase),
5163
+ evolutionBase: round46(evolutionBase),
5164
+ externalBase: round46(externalBase),
5165
+ interactionStructuralEvolution: round46(interactionStructuralEvolution),
5166
+ interactionCentralInstability: round46(interactionCentralInstability),
5167
+ interactionDependencyAmplification: round46(interactionDependencyAmplification)
4936
5168
  },
4937
5169
  rawMetrics: {
4938
5170
  fanIn: file.fanIn,
@@ -4944,19 +5176,19 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4944
5176
  recentVolatility: evolutionMetrics?.recentVolatility ?? null,
4945
5177
  topAuthorShare: evolutionMetrics?.topAuthorShare ?? null,
4946
5178
  busFactor: evolutionMetrics?.busFactor ?? null,
4947
- dependencyAffinity: round45(dependencyAffinity),
4948
- repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure),
4949
- structuralAttenuation: round45(structuralAttenuation)
5179
+ dependencyAffinity: round46(dependencyAffinity),
5180
+ repositoryExternalPressure: round46(dependencyComputation.repositoryExternalPressure),
5181
+ structuralAttenuation: round46(structuralAttenuation)
4950
5182
  },
4951
5183
  normalizedMetrics: {
4952
- fanInRisk: round45(fanInRisk),
4953
- fanOutRisk: round45(fanOutRisk),
4954
- depthRisk: round45(depthRisk),
4955
- frequencyRisk: round45(frequencyRisk),
4956
- churnRisk: round45(churnRisk),
4957
- volatilityRisk: round45(volatilityRisk),
4958
- ownershipConcentrationRisk: round45(ownershipConcentrationRisk),
4959
- busFactorRisk: round45(busFactorRisk)
5184
+ fanInRisk: round46(fanInRisk),
5185
+ fanOutRisk: round46(fanOutRisk),
5186
+ depthRisk: round46(depthRisk),
5187
+ frequencyRisk: round46(frequencyRisk),
5188
+ churnRisk: round46(churnRisk),
5189
+ volatilityRisk: round46(volatilityRisk),
5190
+ ownershipConcentrationRisk: round46(ownershipConcentrationRisk),
5191
+ busFactorRisk: round46(busFactorRisk)
4960
5192
  }
4961
5193
  };
4962
5194
  }).sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
@@ -5082,29 +5314,29 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5082
5314
  moduleFiles.set(moduleName, values);
5083
5315
  }
5084
5316
  const moduleScores = [...moduleFiles.entries()].map(([module, values]) => {
5085
- const averageScore = average(values);
5317
+ const averageScore = average2(values);
5086
5318
  const peakScore = values.reduce((max, value) => Math.max(max, value), 0);
5087
5319
  const normalizedScore = toUnitInterval(averageScore * 0.65 + peakScore * 0.35);
5088
5320
  return {
5089
5321
  module,
5090
- score: round45(normalizedScore * 100),
5091
- normalizedScore: round45(normalizedScore),
5322
+ score: round46(normalizedScore * 100),
5323
+ normalizedScore: round46(normalizedScore),
5092
5324
  fileCount: values.length
5093
5325
  };
5094
5326
  }).sort((a, b) => b.score - a.score || a.module.localeCompare(b.module));
5095
5327
  if (collector !== void 0) {
5096
5328
  for (const [module, values] of moduleFiles.entries()) {
5097
- const averageScore = average(values);
5329
+ const averageScore = average2(values);
5098
5330
  const peakScore = values.reduce((max, value) => Math.max(max, value), 0);
5099
5331
  const normalizedScore = toUnitInterval(averageScore * 0.65 + peakScore * 0.35);
5100
- const totalScore = round45(normalizedScore * 100);
5332
+ const totalScore = round46(normalizedScore * 100);
5101
5333
  const factors = buildFactorTraces(totalScore, [
5102
5334
  {
5103
5335
  factorId: "module.average_file_risk",
5104
5336
  family: "composite",
5105
5337
  strength: averageScore * 0.65,
5106
- rawMetrics: { averageFileRisk: round45(averageScore), fileCount: values.length },
5107
- normalizedMetrics: { normalizedModuleRisk: round45(normalizedScore) },
5338
+ rawMetrics: { averageFileRisk: round46(averageScore), fileCount: values.length },
5339
+ normalizedMetrics: { normalizedModuleRisk: round46(normalizedScore) },
5108
5340
  weight: 0.65,
5109
5341
  amplification: null,
5110
5342
  evidence: [{ kind: "repository_metric", metric: "moduleAggregation.average" }],
@@ -5114,8 +5346,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5114
5346
  factorId: "module.peak_file_risk",
5115
5347
  family: "composite",
5116
5348
  strength: peakScore * 0.35,
5117
- rawMetrics: { peakFileRisk: round45(peakScore), fileCount: values.length },
5118
- normalizedMetrics: { normalizedModuleRisk: round45(normalizedScore) },
5349
+ rawMetrics: { peakFileRisk: round46(peakScore), fileCount: values.length },
5350
+ normalizedMetrics: { normalizedModuleRisk: round46(normalizedScore) },
5119
5351
  weight: 0.35,
5120
5352
  amplification: null,
5121
5353
  evidence: [{ kind: "repository_metric", metric: "moduleAggregation.peak" }],
@@ -5138,12 +5370,12 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5138
5370
  const normalizedZoneScore = toUnitInterval(intensity * 0.7 + fileScore.normalizedScore * 0.3);
5139
5371
  return {
5140
5372
  file: fileScore.file,
5141
- score: round45(normalizedZoneScore * 100),
5373
+ score: round46(normalizedZoneScore * 100),
5142
5374
  externalPressure: fileScore.factors.external
5143
5375
  };
5144
5376
  }).filter((zone) => external.available && zone.externalPressure >= pressureThreshold).sort((a, b) => b.score - a.score || a.file.localeCompare(b.file)).slice(0, config.amplificationZone.maxZones).map((zone) => ({
5145
5377
  ...zone,
5146
- externalPressure: round45(zone.externalPressure)
5378
+ externalPressure: round46(zone.externalPressure)
5147
5379
  }));
5148
5380
  if (collector !== void 0 && external.available) {
5149
5381
  const dependencyByName = new Map(
@@ -5256,16 +5488,16 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5256
5488
  );
5257
5489
  }
5258
5490
  }
5259
- const structuralDimension = average(fileScores.map((fileScore) => fileScore.factors.structural));
5260
- const evolutionDimension = average(fileScores.map((fileScore) => fileScore.factors.evolution));
5491
+ const structuralDimension = average2(fileScores.map((fileScore) => fileScore.factors.structural));
5492
+ const evolutionDimension = average2(fileScores.map((fileScore) => fileScore.factors.evolution));
5261
5493
  const externalDimension = dependencyComputation.repositoryExternalPressure;
5262
5494
  const topCentralSlice = Math.max(1, Math.ceil(fileRiskContexts.length * 0.1));
5263
- const criticalInstability = average(
5495
+ const criticalInstability = average2(
5264
5496
  [...fileRiskContexts].sort(
5265
5497
  (a, b) => b.structuralCentrality * b.factors.evolution - a.structuralCentrality * a.factors.evolution || a.file.localeCompare(b.file)
5266
5498
  ).slice(0, topCentralSlice).map((context) => context.structuralCentrality * context.factors.evolution)
5267
5499
  );
5268
- const dependencyAmplification = average(
5500
+ const dependencyAmplification = average2(
5269
5501
  dependencyAmplificationZones.map(
5270
5502
  (zone) => toUnitInterval(zone.externalPressure * zone.score / 100)
5271
5503
  )
@@ -5276,15 +5508,15 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5276
5508
  criticalInstability * config.interactionWeights.centralInstability,
5277
5509
  dependencyAmplification * config.interactionWeights.dependencyAmplification
5278
5510
  ]);
5279
- const riskScore = round45(repositoryNormalizedScore * 100);
5511
+ const riskScore = round46(repositoryNormalizedScore * 100);
5280
5512
  if (collector !== void 0) {
5281
5513
  const repositoryFactors = buildFactorTraces(riskScore, [
5282
5514
  {
5283
5515
  factorId: "repository.structural",
5284
5516
  family: "structural",
5285
5517
  strength: structuralDimension * dimensionWeights.structural,
5286
- rawMetrics: { structuralDimension: round45(structuralDimension) },
5287
- normalizedMetrics: { dimensionWeight: round45(dimensionWeights.structural) },
5518
+ rawMetrics: { structuralDimension: round46(structuralDimension) },
5519
+ normalizedMetrics: { dimensionWeight: round46(dimensionWeights.structural) },
5288
5520
  weight: dimensionWeights.structural,
5289
5521
  amplification: null,
5290
5522
  evidence: [{ kind: "repository_metric", metric: "structuralDimension" }],
@@ -5294,8 +5526,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5294
5526
  factorId: "repository.evolution",
5295
5527
  family: "evolution",
5296
5528
  strength: evolutionDimension * dimensionWeights.evolution,
5297
- rawMetrics: { evolutionDimension: round45(evolutionDimension) },
5298
- normalizedMetrics: { dimensionWeight: round45(dimensionWeights.evolution) },
5529
+ rawMetrics: { evolutionDimension: round46(evolutionDimension) },
5530
+ normalizedMetrics: { dimensionWeight: round46(dimensionWeights.evolution) },
5299
5531
  weight: dimensionWeights.evolution,
5300
5532
  amplification: null,
5301
5533
  evidence: [{ kind: "repository_metric", metric: "evolutionDimension" }],
@@ -5305,8 +5537,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5305
5537
  factorId: "repository.external",
5306
5538
  family: "external",
5307
5539
  strength: externalDimension * dimensionWeights.external,
5308
- rawMetrics: { externalDimension: round45(externalDimension) },
5309
- normalizedMetrics: { dimensionWeight: round45(dimensionWeights.external) },
5540
+ rawMetrics: { externalDimension: round46(externalDimension) },
5541
+ normalizedMetrics: { dimensionWeight: round46(dimensionWeights.external) },
5310
5542
  weight: dimensionWeights.external,
5311
5543
  amplification: null,
5312
5544
  evidence: [{ kind: "repository_metric", metric: "externalDimension" }],
@@ -5317,19 +5549,19 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5317
5549
  family: "composite",
5318
5550
  strength: structuralDimension * evolutionDimension * config.interactionWeights.structuralEvolution + criticalInstability * config.interactionWeights.centralInstability + dependencyAmplification * config.interactionWeights.dependencyAmplification,
5319
5551
  rawMetrics: {
5320
- structuralEvolution: round45(
5552
+ structuralEvolution: round46(
5321
5553
  structuralDimension * evolutionDimension * config.interactionWeights.structuralEvolution
5322
5554
  ),
5323
- centralInstability: round45(
5555
+ centralInstability: round46(
5324
5556
  criticalInstability * config.interactionWeights.centralInstability
5325
5557
  ),
5326
- dependencyAmplification: round45(
5558
+ dependencyAmplification: round46(
5327
5559
  dependencyAmplification * config.interactionWeights.dependencyAmplification
5328
5560
  )
5329
5561
  },
5330
5562
  normalizedMetrics: {
5331
- criticalInstability: round45(criticalInstability),
5332
- dependencyAmplification: round45(dependencyAmplification)
5563
+ criticalInstability: round46(criticalInstability),
5564
+ dependencyAmplification: round46(dependencyAmplification)
5333
5565
  },
5334
5566
  weight: null,
5335
5567
  amplification: config.interactionWeights.structuralEvolution + config.interactionWeights.centralInstability + config.interactionWeights.dependencyAmplification,
@@ -5349,7 +5581,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
5349
5581
  }
5350
5582
  return {
5351
5583
  riskScore,
5352
- normalizedScore: round45(repositoryNormalizedScore),
5584
+ normalizedScore: round46(repositoryNormalizedScore),
5353
5585
  hotspots,
5354
5586
  fragileClusters,
5355
5587
  dependencyAmplificationZones,
@@ -5472,6 +5704,28 @@ var evaluateRepositoryRisk = (input, options = {}) => {
5472
5704
  };
5473
5705
  };
5474
5706
 
5707
+ // src/application/todo-fixme-counter.ts
5708
+ import * as ts2 from "typescript";
5709
+ var markerRegex = /\b(?:TODO|FIXME)\b/gi;
5710
+ var countMarkers = (text) => text.match(markerRegex)?.length ?? 0;
5711
+ var countTodoFixmeInComments = (content) => {
5712
+ const scanner = ts2.createScanner(
5713
+ ts2.ScriptTarget.Latest,
5714
+ false,
5715
+ ts2.LanguageVariant.Standard,
5716
+ content
5717
+ );
5718
+ let total = 0;
5719
+ let token = scanner.scan();
5720
+ while (token !== ts2.SyntaxKind.EndOfFileToken) {
5721
+ if (token === ts2.SyntaxKind.SingleLineCommentTrivia || token === ts2.SyntaxKind.MultiLineCommentTrivia) {
5722
+ total += countMarkers(scanner.getTokenText());
5723
+ }
5724
+ token = scanner.scan();
5725
+ }
5726
+ return total;
5727
+ };
5728
+
5475
5729
  // src/application/run-analyze-command.ts
5476
5730
  var resolveTargetPath = (inputPath, cwd) => resolve3(cwd, inputPath ?? ".");
5477
5731
  var riskProfileConfig = {
@@ -5600,6 +5854,18 @@ var createEvolutionProgressReporter = (logger) => {
5600
5854
  }
5601
5855
  };
5602
5856
  };
5857
+ var collectTodoFixmeCount = async (targetPath, structural) => {
5858
+ const filePaths2 = [...structural.files].map((file) => file.relativePath).sort((a, b) => a.localeCompare(b));
5859
+ let total = 0;
5860
+ for (const relativePath of filePaths2) {
5861
+ try {
5862
+ const content = await readFile2(join4(targetPath, relativePath), "utf8");
5863
+ total += countTodoFixmeInComments(content);
5864
+ } catch {
5865
+ }
5866
+ }
5867
+ return total;
5868
+ };
5603
5869
  var collectAnalysisInputs = async (inputPath, authorIdentityMode, options = {}, logger = createSilentLogger()) => {
5604
5870
  const invocationCwd = process.env["INIT_CWD"] ?? process.cwd();
5605
5871
  const targetPath = resolveTargetPath(inputPath, invocationCwd);
@@ -5642,10 +5908,14 @@ var collectAnalysisInputs = async (inputPath, authorIdentityMode, options = {},
5642
5908
  } else {
5643
5909
  logger.warn(`external analysis unavailable: ${external.reason}`);
5644
5910
  }
5911
+ logger.info("collecting quality text signals");
5912
+ const todoFixmeCount = await collectTodoFixmeCount(targetPath, structural);
5913
+ logger.debug(`quality text signals: todoFixmeCount=${todoFixmeCount}`);
5645
5914
  return {
5646
5915
  structural,
5647
5916
  evolution,
5648
- external
5917
+ external,
5918
+ todoFixmeCount
5649
5919
  };
5650
5920
  };
5651
5921
  var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logger = createSilentLogger()) => {
@@ -5658,18 +5928,30 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
5658
5928
  logger.info("computing risk summary");
5659
5929
  const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
5660
5930
  const risk = computeRepositoryRiskSummary({
5661
- ...analysisInputs,
5931
+ structural: analysisInputs.structural,
5932
+ evolution: analysisInputs.evolution,
5933
+ external: analysisInputs.external,
5662
5934
  ...riskConfig === void 0 ? {} : { config: riskConfig }
5663
5935
  });
5664
- logger.info(`analysis completed (riskScore=${risk.riskScore})`);
5936
+ const quality = computeRepositoryQualitySummary({
5937
+ structural: analysisInputs.structural,
5938
+ evolution: analysisInputs.evolution,
5939
+ todoFixmeCount: analysisInputs.todoFixmeCount
5940
+ });
5941
+ logger.info(
5942
+ `analysis completed (riskScore=${risk.riskScore}, qualityScore=${quality.qualityScore})`
5943
+ );
5665
5944
  return {
5666
- ...analysisInputs,
5667
- risk
5945
+ structural: analysisInputs.structural,
5946
+ evolution: analysisInputs.evolution,
5947
+ external: analysisInputs.external,
5948
+ risk,
5949
+ quality
5668
5950
  };
5669
5951
  };
5670
5952
 
5671
5953
  // src/application/run-check-command.ts
5672
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5954
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
5673
5955
 
5674
5956
  // src/application/build-analysis-snapshot.ts
5675
5957
  var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logger) => {
@@ -5684,14 +5966,23 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
5684
5966
  const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
5685
5967
  const evaluation = evaluateRepositoryRisk(
5686
5968
  {
5687
- ...analysisInputs,
5969
+ structural: analysisInputs.structural,
5970
+ evolution: analysisInputs.evolution,
5971
+ external: analysisInputs.external,
5688
5972
  ...riskConfig === void 0 ? {} : { config: riskConfig }
5689
5973
  },
5690
5974
  { explain: options.includeTrace }
5691
5975
  );
5692
5976
  const summary = {
5693
- ...analysisInputs,
5694
- risk: evaluation.summary
5977
+ structural: analysisInputs.structural,
5978
+ evolution: analysisInputs.evolution,
5979
+ external: analysisInputs.external,
5980
+ risk: evaluation.summary,
5981
+ quality: computeRepositoryQualitySummary({
5982
+ structural: analysisInputs.structural,
5983
+ evolution: analysisInputs.evolution,
5984
+ todoFixmeCount: analysisInputs.todoFixmeCount
5985
+ })
5695
5986
  };
5696
5987
  return createSnapshot({
5697
5988
  analysis: summary,
@@ -5743,7 +6034,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5743
6034
  let diff;
5744
6035
  if (options.baselinePath !== void 0) {
5745
6036
  logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5746
- const baselineRaw = await readFile2(options.baselinePath, "utf8");
6037
+ const baselineRaw = await readFile3(options.baselinePath, "utf8");
5747
6038
  try {
5748
6039
  baseline = parseSnapshot(baselineRaw);
5749
6040
  } catch (error) {
@@ -5782,7 +6073,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5782
6073
  };
5783
6074
 
5784
6075
  // src/application/run-ci-command.ts
5785
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
6076
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
5786
6077
  import { relative as relative2, resolve as resolve4 } from "path";
5787
6078
  var isPathOutsideBase = (value) => {
5788
6079
  return value === ".." || value.startsWith("../") || value.startsWith("..\\");
@@ -5882,7 +6173,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5882
6173
  diff = compareSnapshots(current, baseline);
5883
6174
  } else if (options.baselinePath !== void 0) {
5884
6175
  logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5885
- const baselineRaw = await readFile3(options.baselinePath, "utf8");
6176
+ const baselineRaw = await readFile4(options.baselinePath, "utf8");
5886
6177
  try {
5887
6178
  baseline = parseSnapshot(baselineRaw);
5888
6179
  } catch (error) {
@@ -5930,7 +6221,7 @@ ${ciMarkdown}`;
5930
6221
  };
5931
6222
 
5932
6223
  // src/application/run-report-command.ts
5933
- import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
6224
+ import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
5934
6225
  var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
5935
6226
  logger.info("building analysis snapshot");
5936
6227
  const current = await buildAnalysisSnapshot(
@@ -5952,7 +6243,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
5952
6243
  report = createReport(current);
5953
6244
  } else {
5954
6245
  logger.info(`loading baseline snapshot: ${options.comparePath}`);
5955
- const baselineRaw = await readFile4(options.comparePath, "utf8");
6246
+ const baselineRaw = await readFile5(options.comparePath, "utf8");
5956
6247
  const baseline = parseSnapshot(baselineRaw);
5957
6248
  const diff = compareSnapshots(current, baseline);
5958
6249
  report = createReport(current, diff);
@@ -5998,7 +6289,9 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
5998
6289
  const riskConfig = resolveRiskConfigForProfile(options.riskProfile);
5999
6290
  const evaluation = evaluateRepositoryRisk(
6000
6291
  {
6001
- ...analysisInputs,
6292
+ structural: analysisInputs.structural,
6293
+ evolution: analysisInputs.evolution,
6294
+ external: analysisInputs.external,
6002
6295
  ...riskConfig === void 0 ? {} : { config: riskConfig }
6003
6296
  },
6004
6297
  { explain: true }
@@ -6007,10 +6300,19 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
6007
6300
  throw new Error("risk trace unavailable");
6008
6301
  }
6009
6302
  const summary = {
6010
- ...analysisInputs,
6011
- risk: evaluation.summary
6303
+ structural: analysisInputs.structural,
6304
+ evolution: analysisInputs.evolution,
6305
+ external: analysisInputs.external,
6306
+ risk: evaluation.summary,
6307
+ quality: computeRepositoryQualitySummary({
6308
+ structural: analysisInputs.structural,
6309
+ evolution: analysisInputs.evolution,
6310
+ todoFixmeCount: analysisInputs.todoFixmeCount
6311
+ })
6012
6312
  };
6013
- logger.info(`explanation completed (riskScore=${summary.risk.riskScore})`);
6313
+ logger.info(
6314
+ `explanation completed (riskScore=${summary.risk.riskScore}, qualityScore=${summary.quality.qualityScore})`
6315
+ );
6014
6316
  return {
6015
6317
  summary,
6016
6318
  trace: evaluation.trace,
@@ -6029,6 +6331,122 @@ var parseRecentWindowDays = (value) => {
6029
6331
  }
6030
6332
  return parsed;
6031
6333
  };
6334
+ var stripLeadingMarkdownHeading = (value, heading) => {
6335
+ const prefix = `${heading}
6336
+ `;
6337
+ if (value.startsWith(prefix)) {
6338
+ return value.slice(prefix.length).trimStart();
6339
+ }
6340
+ return value;
6341
+ };
6342
+ var extractExplainTextSummary = (text) => {
6343
+ const splitIndex = text.search(/\n\n(?:file|module|dependency|repository): /);
6344
+ if (splitIndex < 0) {
6345
+ return text.trim();
6346
+ }
6347
+ return text.slice(0, splitIndex).trim();
6348
+ };
6349
+ var extractExplainMarkdownSummary = (markdown) => {
6350
+ const splitIndex = markdown.search(/\n\n## (?:file|module|dependency|repository): /);
6351
+ if (splitIndex < 0) {
6352
+ return markdown.trim();
6353
+ }
6354
+ return markdown.slice(0, splitIndex).trim();
6355
+ };
6356
+ var extractSummaryValue = (summary, key) => {
6357
+ const prefix = `${key}: `;
6358
+ for (const rawLine of summary.split("\n")) {
6359
+ const line = rawLine.trimStart();
6360
+ if (line.startsWith(prefix)) {
6361
+ return line.slice(prefix.length).trim();
6362
+ }
6363
+ }
6364
+ return void 0;
6365
+ };
6366
+ var renderReportHighlightsText = (report) => {
6367
+ const lines = [];
6368
+ lines.push("Repository Summary");
6369
+ lines.push(` target: ${report.repository.targetPath}`);
6370
+ lines.push(` riskScore: ${report.repository.riskScore}`);
6371
+ lines.push(` qualityScore: ${report.quality.qualityScore}`);
6372
+ lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
6373
+ lines.push(` riskTier: ${report.repository.riskTier}`);
6374
+ lines.push("");
6375
+ lines.push("Top Hotspots");
6376
+ for (const hotspot of report.hotspots.slice(0, 5)) {
6377
+ lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
6378
+ lines.push(` priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
6379
+ }
6380
+ return lines.join("\n");
6381
+ };
6382
+ var renderReportHighlightsMarkdown = (report) => {
6383
+ const lines = [];
6384
+ lines.push("## Repository Summary");
6385
+ lines.push(`- target: \`${report.repository.targetPath}\``);
6386
+ lines.push(`- riskScore: \`${report.repository.riskScore}\``);
6387
+ lines.push(`- qualityScore: \`${report.quality.qualityScore}\``);
6388
+ lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
6389
+ lines.push(`- riskTier: \`${report.repository.riskTier}\``);
6390
+ lines.push("");
6391
+ lines.push("## Top Hotspots");
6392
+ for (const hotspot of report.hotspots.slice(0, 5)) {
6393
+ lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
6394
+ lines.push(` - priority actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
6395
+ }
6396
+ return lines.join("\n");
6397
+ };
6398
+ var renderCompactText = (report, explainSummary) => {
6399
+ const lines = [];
6400
+ lines.push("CodeSentinel Run (compact)");
6401
+ lines.push("");
6402
+ lines.push("Repository");
6403
+ lines.push(` target: ${report.repository.targetPath}`);
6404
+ lines.push(` riskScore: ${report.repository.riskScore}`);
6405
+ lines.push(` qualityScore: ${report.quality.qualityScore}`);
6406
+ lines.push(` riskTier: ${report.repository.riskTier}`);
6407
+ lines.push(
6408
+ ` dimensions: structural=${report.repository.dimensionScores.structural ?? "n/a"}, evolution=${report.repository.dimensionScores.evolution ?? "n/a"}, external=${report.repository.dimensionScores.external ?? "n/a"}, interactions=${report.repository.dimensionScores.interactions ?? "n/a"}`
6409
+ );
6410
+ lines.push("");
6411
+ lines.push(
6412
+ `Key Drivers: ${extractSummaryValue(explainSummary, "key drivers") ?? "insufficient data"}`
6413
+ );
6414
+ lines.push(
6415
+ `Priority Actions: ${extractSummaryValue(explainSummary, "priority actions") ?? "insufficient data"}`
6416
+ );
6417
+ lines.push("");
6418
+ lines.push("Top Hotspots");
6419
+ for (const hotspot of report.hotspots.slice(0, 3)) {
6420
+ lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
6421
+ }
6422
+ return lines.join("\n");
6423
+ };
6424
+ var renderCompactMarkdown = (report, explainSummary) => {
6425
+ const lines = [];
6426
+ lines.push("# CodeSentinel Run (compact)");
6427
+ lines.push("");
6428
+ lines.push("## Repository");
6429
+ lines.push(`- target: \`${report.repository.targetPath}\``);
6430
+ lines.push(`- riskScore: \`${report.repository.riskScore}\``);
6431
+ lines.push(`- qualityScore: \`${report.quality.qualityScore}\``);
6432
+ lines.push(`- riskTier: \`${report.repository.riskTier}\``);
6433
+ lines.push(
6434
+ `- dimensions: structural=\`${report.repository.dimensionScores.structural ?? "n/a"}\`, evolution=\`${report.repository.dimensionScores.evolution ?? "n/a"}\`, external=\`${report.repository.dimensionScores.external ?? "n/a"}\`, interactions=\`${report.repository.dimensionScores.interactions ?? "n/a"}\``
6435
+ );
6436
+ lines.push("");
6437
+ lines.push(
6438
+ `- key drivers: ${extractSummaryValue(explainSummary, "- key drivers") ?? "insufficient data"}`
6439
+ );
6440
+ lines.push(
6441
+ `- priority actions: ${extractSummaryValue(explainSummary, "- priority actions") ?? "insufficient data"}`
6442
+ );
6443
+ lines.push("");
6444
+ lines.push("## Top Hotspots");
6445
+ for (const hotspot of report.hotspots.slice(0, 3)) {
6446
+ lines.push(`- \`${hotspot.target}\` (score: \`${hotspot.score}\`)`);
6447
+ }
6448
+ return lines.join("\n");
6449
+ };
6032
6450
  var riskProfileOption = () => new Option(
6033
6451
  "--risk-profile <profile>",
6034
6452
  "risk profile: default (balanced) or personal (down-weights single-maintainer ownership penalties)"
@@ -6169,6 +6587,220 @@ program.command("report").argument("[path]", "path to the project to analyze").a
6169
6587
  }
6170
6588
  }
6171
6589
  );
6590
+ program.command("run").argument("[path]", "path to the project to analyze").addOption(riskProfileOption()).addOption(
6591
+ new Option(
6592
+ "--author-identity <mode>",
6593
+ "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
6594
+ ).choices(["likely_merge", "strict_email"]).default("likely_merge")
6595
+ ).addOption(
6596
+ new Option(
6597
+ "--log-level <level>",
6598
+ "log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
6599
+ ).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
6600
+ ).addOption(
6601
+ new Option("--format <mode>", "combined output format: text, md, json").choices(["text", "md", "json"]).default("text")
6602
+ ).addOption(
6603
+ new Option("--detail <level>", "run detail level: compact (default), standard, full").choices(["compact", "standard", "full"]).default("compact")
6604
+ ).option("--file <path>", "explain a specific file target").option("--module <name>", "explain a specific module target").option("--top <count>", "number of top hotspots to explain when no target is selected", "5").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
6605
+ new Option(
6606
+ "--recent-window-days <days>",
6607
+ "git recency window in days used for evolution volatility metrics"
6608
+ ).argParser(parseRecentWindowDays).default(30)
6609
+ ).action(
6610
+ async (path, options) => {
6611
+ const logger = createStderrLogger(options.logLevel);
6612
+ const top = Number.parseInt(options.top, 10);
6613
+ const explain = await runExplainCommand(
6614
+ path,
6615
+ options.authorIdentity,
6616
+ {
6617
+ ...options.file === void 0 ? {} : { file: options.file },
6618
+ ...options.module === void 0 ? {} : { module: options.module },
6619
+ top: Number.isFinite(top) ? top : 5,
6620
+ format: options.format,
6621
+ recentWindowDays: options.recentWindowDays,
6622
+ riskProfile: options.riskProfile
6623
+ },
6624
+ logger
6625
+ );
6626
+ const snapshot = createSnapshot({
6627
+ analysis: explain.summary,
6628
+ ...options.trace === true ? { trace: explain.trace } : {}
6629
+ });
6630
+ if (options.snapshot !== void 0) {
6631
+ await writeFile5(options.snapshot, JSON.stringify(snapshot, null, 2), "utf8");
6632
+ logger.info(`snapshot written: ${options.snapshot}`);
6633
+ }
6634
+ const report = options.compare === void 0 ? createReport(snapshot) : createReport(
6635
+ snapshot,
6636
+ compareSnapshots(snapshot, parseSnapshot(await readFile6(options.compare, "utf8")))
6637
+ );
6638
+ if (options.format === "json") {
6639
+ const analyzeSummaryOutput = formatAnalyzeOutput(explain.summary, "summary");
6640
+ if (options.detail === "compact") {
6641
+ process.stdout.write(
6642
+ `${JSON.stringify(
6643
+ {
6644
+ schemaVersion: "codesentinel.run.v1",
6645
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6646
+ repository: report.repository,
6647
+ keyDrivers: extractSummaryValue(
6648
+ extractExplainTextSummary(formatExplainOutput(explain, "text")),
6649
+ "key drivers"
6650
+ ),
6651
+ priorityActions: extractSummaryValue(
6652
+ extractExplainTextSummary(formatExplainOutput(explain, "text")),
6653
+ "priority actions"
6654
+ ),
6655
+ topHotspots: report.hotspots.slice(0, 3).map((hotspot) => ({
6656
+ target: hotspot.target,
6657
+ score: hotspot.score
6658
+ }))
6659
+ },
6660
+ null,
6661
+ 2
6662
+ )}
6663
+ `
6664
+ );
6665
+ return;
6666
+ }
6667
+ if (options.detail === "standard") {
6668
+ const analyzeSummaryPayload = JSON.parse(analyzeSummaryOutput);
6669
+ process.stdout.write(
6670
+ `${JSON.stringify(
6671
+ {
6672
+ schemaVersion: "codesentinel.run.v1",
6673
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6674
+ analyze: analyzeSummaryPayload,
6675
+ explain: {
6676
+ summary: extractExplainTextSummary(formatExplainOutput(explain, "text"))
6677
+ },
6678
+ report: {
6679
+ repository: report.repository,
6680
+ quality: report.quality,
6681
+ hotspots: report.hotspots.slice(0, 5),
6682
+ structural: report.structural,
6683
+ external: report.external
6684
+ }
6685
+ },
6686
+ null,
6687
+ 2
6688
+ )}
6689
+ `
6690
+ );
6691
+ return;
6692
+ }
6693
+ process.stdout.write(
6694
+ `${JSON.stringify(
6695
+ {
6696
+ schemaVersion: "codesentinel.run.v1",
6697
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6698
+ analyze: explain.summary,
6699
+ explain,
6700
+ report
6701
+ },
6702
+ null,
6703
+ 2
6704
+ )}
6705
+ `
6706
+ );
6707
+ return;
6708
+ }
6709
+ const analyzeRendered = formatAnalyzeOutput(explain.summary, "summary");
6710
+ const explainRendered = formatExplainOutput(explain, options.format);
6711
+ if (options.detail === "compact") {
6712
+ if (options.format === "md") {
6713
+ process.stdout.write(
6714
+ `${renderCompactMarkdown(
6715
+ report,
6716
+ extractExplainMarkdownSummary(formatExplainOutput(explain, "md"))
6717
+ )}
6718
+ `
6719
+ );
6720
+ return;
6721
+ }
6722
+ process.stdout.write(
6723
+ `${renderCompactText(report, extractExplainTextSummary(formatExplainOutput(explain, "text")))}
6724
+ `
6725
+ );
6726
+ return;
6727
+ }
6728
+ if (options.detail === "standard") {
6729
+ if (options.format === "md") {
6730
+ process.stdout.write(
6731
+ `# CodeSentinel Run
6732
+
6733
+ ## Analyze
6734
+ \`\`\`json
6735
+ ${analyzeRendered}
6736
+ \`\`\`
6737
+
6738
+ ## Explain
6739
+ ${extractExplainMarkdownSummary(
6740
+ formatExplainOutput(explain, "md")
6741
+ )}
6742
+
6743
+ ${renderReportHighlightsMarkdown(report)}
6744
+ `
6745
+ );
6746
+ return;
6747
+ }
6748
+ process.stdout.write(
6749
+ `CodeSentinel Run
6750
+
6751
+ Analyze
6752
+ ${analyzeRendered}
6753
+
6754
+ Explain
6755
+ ${extractExplainTextSummary(
6756
+ formatExplainOutput(explain, "text")
6757
+ )}
6758
+
6759
+ Report
6760
+ ${renderReportHighlightsText(report)}
6761
+ `
6762
+ );
6763
+ return;
6764
+ }
6765
+ const reportRendered = formatReport(report, options.format);
6766
+ if (options.format === "md") {
6767
+ const explainSection = stripLeadingMarkdownHeading(
6768
+ explainRendered,
6769
+ "# CodeSentinel Explanation"
6770
+ );
6771
+ const reportSection = stripLeadingMarkdownHeading(reportRendered, "# CodeSentinel Report");
6772
+ process.stdout.write(
6773
+ `# CodeSentinel Run
6774
+
6775
+ ## Analyze
6776
+ \`\`\`json
6777
+ ${analyzeRendered}
6778
+ \`\`\`
6779
+
6780
+ ## Explain
6781
+ ${explainSection}
6782
+
6783
+ ## Report
6784
+ ${reportSection}
6785
+ `
6786
+ );
6787
+ return;
6788
+ }
6789
+ process.stdout.write(
6790
+ `CodeSentinel Run
6791
+
6792
+ Analyze
6793
+ ${analyzeRendered}
6794
+
6795
+ Explain
6796
+ ${explainRendered}
6797
+
6798
+ Report
6799
+ ${reportRendered}
6800
+ `
6801
+ );
6802
+ }
6803
+ );
6172
6804
  var parseGateNumber = (value, optionName) => {
6173
6805
  if (value === void 0) {
6174
6806
  return void 0;