@getcodesentinel/codesentinel 1.7.0 → 1.9.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
@@ -1349,9 +1349,1000 @@ var analyzeDependencyCandidateFromRegistry = async (input) => {
1349
1349
  return analyzeDependencyCandidate(input, metadataProvider);
1350
1350
  };
1351
1351
 
1352
+ // ../reporter/dist/index.js
1353
+ var SNAPSHOT_SCHEMA_VERSION = "codesentinel.snapshot.v1";
1354
+ var REPORT_SCHEMA_VERSION = "codesentinel.report.v1";
1355
+ var RISK_MODEL_VERSION = "deterministic-v1";
1356
+ var round43 = (value) => Number(value.toFixed(4));
1357
+ var toRiskTier = (score) => {
1358
+ if (score < 20) {
1359
+ return "low";
1360
+ }
1361
+ if (score < 40) {
1362
+ return "moderate";
1363
+ }
1364
+ if (score < 60) {
1365
+ return "elevated";
1366
+ }
1367
+ if (score < 80) {
1368
+ return "high";
1369
+ }
1370
+ return "very_high";
1371
+ };
1372
+ var factorLabelById = {
1373
+ "repository.structural": "Structural complexity",
1374
+ "repository.evolution": "Change volatility",
1375
+ "repository.external": "External dependency pressure",
1376
+ "repository.composite.interactions": "Intersection amplification",
1377
+ "file.structural": "File structural complexity",
1378
+ "file.evolution": "File change volatility",
1379
+ "file.external": "File external pressure",
1380
+ "file.composite.interactions": "File interaction amplification",
1381
+ "module.average_file_risk": "Average file risk",
1382
+ "module.peak_file_risk": "Peak file risk",
1383
+ "dependency.signals": "Dependency risk signals",
1384
+ "dependency.staleness": "Dependency staleness",
1385
+ "dependency.maintainer_concentration": "Maintainer concentration",
1386
+ "dependency.topology": "Dependency topology pressure",
1387
+ "dependency.bus_factor": "Dependency bus factor",
1388
+ "dependency.popularity_dampening": "Popularity dampening"
1389
+ };
1390
+ var factorLabel = (factorId) => factorLabelById[factorId] ?? factorId;
1391
+ var summarizeEvidence = (factor) => {
1392
+ const entries = Object.entries(factor.rawMetrics).filter(([, value]) => value !== null).sort((a, b) => a[0].localeCompare(b[0])).slice(0, 3).map(([key, value]) => `${key}=${value}`);
1393
+ if (entries.length > 0) {
1394
+ return entries.join(", ");
1395
+ }
1396
+ const evidence = [...factor.evidence].map((entry) => {
1397
+ if (entry.kind === "file_metric") {
1398
+ return `${entry.target}:${entry.metric}`;
1399
+ }
1400
+ if (entry.kind === "dependency_metric") {
1401
+ return `${entry.target}:${entry.metric}`;
1402
+ }
1403
+ if (entry.kind === "repository_metric") {
1404
+ return entry.metric;
1405
+ }
1406
+ if (entry.kind === "graph_cycle") {
1407
+ return `cycle:${entry.cycleId}`;
1408
+ }
1409
+ return `${entry.fileA}<->${entry.fileB}`;
1410
+ }).sort((a, b) => a.localeCompare(b));
1411
+ return evidence.join(", ");
1412
+ };
1413
+ var diffSets = (current, baseline) => {
1414
+ const currentSet = new Set(current);
1415
+ const baselineSet = new Set(baseline);
1416
+ const added = [...currentSet].filter((item) => !baselineSet.has(item)).sort((a, b) => a.localeCompare(b));
1417
+ const removed = [...baselineSet].filter((item) => !currentSet.has(item)).sort((a, b) => a.localeCompare(b));
1418
+ return { added, removed };
1419
+ };
1420
+ var diffScoreMap = (current, baseline) => {
1421
+ const keys = [.../* @__PURE__ */ new Set([...current.keys(), ...baseline.keys()])].sort((a, b) => a.localeCompare(b));
1422
+ return keys.map((key) => {
1423
+ const before = baseline.get(key) ?? 0;
1424
+ const after = current.get(key) ?? 0;
1425
+ const delta = round43(after - before);
1426
+ return {
1427
+ target: key,
1428
+ before: round43(before),
1429
+ after: round43(after),
1430
+ delta
1431
+ };
1432
+ }).filter((entry) => entry.delta !== 0).sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta) || a.target.localeCompare(b.target));
1433
+ };
1434
+ var cycleKey = (nodes) => [...nodes].sort((a, b) => a.localeCompare(b)).join(" -> ");
1435
+ var compareSnapshots = (current, baseline) => {
1436
+ const currentFileScores = new Map(
1437
+ current.analysis.risk.fileScores.map((item) => [item.file, item.score])
1438
+ );
1439
+ const baselineFileScores = new Map(
1440
+ baseline.analysis.risk.fileScores.map((item) => [item.file, item.score])
1441
+ );
1442
+ const currentModuleScores = new Map(
1443
+ current.analysis.risk.moduleScores.map((item) => [item.module, item.score])
1444
+ );
1445
+ const baselineModuleScores = new Map(
1446
+ baseline.analysis.risk.moduleScores.map((item) => [item.module, item.score])
1447
+ );
1448
+ const currentHotspots = current.analysis.risk.hotspots.slice(0, 10).map((item) => item.file);
1449
+ const baselineHotspots = baseline.analysis.risk.hotspots.slice(0, 10).map((item) => item.file);
1450
+ const currentCycles = current.analysis.structural.cycles.map((cycle) => cycleKey(cycle.nodes));
1451
+ const baselineCycles = baseline.analysis.structural.cycles.map((cycle) => cycleKey(cycle.nodes));
1452
+ const currentExternal = current.analysis.external.available ? current.analysis.external : {
1453
+ highRiskDependencies: [],
1454
+ singleMaintainerDependencies: [],
1455
+ abandonedDependencies: []
1456
+ };
1457
+ const baselineExternal = baseline.analysis.external.available ? baseline.analysis.external : {
1458
+ highRiskDependencies: [],
1459
+ singleMaintainerDependencies: [],
1460
+ abandonedDependencies: []
1461
+ };
1462
+ const highRisk = diffSets(currentExternal.highRiskDependencies, baselineExternal.highRiskDependencies);
1463
+ const singleMaintainer = diffSets(
1464
+ currentExternal.singleMaintainerDependencies,
1465
+ baselineExternal.singleMaintainerDependencies
1466
+ );
1467
+ const abandoned = diffSets(currentExternal.abandonedDependencies, baselineExternal.abandonedDependencies);
1468
+ const hotspots = diffSets(currentHotspots, baselineHotspots);
1469
+ const cycles = diffSets(currentCycles, baselineCycles);
1470
+ return {
1471
+ repositoryScoreDelta: round43(current.analysis.risk.repositoryScore - baseline.analysis.risk.repositoryScore),
1472
+ normalizedScoreDelta: round43(current.analysis.risk.normalizedScore - baseline.analysis.risk.normalizedScore),
1473
+ fileRiskChanges: diffScoreMap(currentFileScores, baselineFileScores),
1474
+ moduleRiskChanges: diffScoreMap(currentModuleScores, baselineModuleScores),
1475
+ newHotspots: hotspots.added,
1476
+ resolvedHotspots: hotspots.removed,
1477
+ newCycles: cycles.added,
1478
+ resolvedCycles: cycles.removed,
1479
+ externalChanges: {
1480
+ highRiskAdded: highRisk.added,
1481
+ highRiskRemoved: highRisk.removed,
1482
+ singleMaintainerAdded: singleMaintainer.added,
1483
+ singleMaintainerRemoved: singleMaintainer.removed,
1484
+ abandonedAdded: abandoned.added,
1485
+ abandonedRemoved: abandoned.removed
1486
+ }
1487
+ };
1488
+ };
1489
+ var findTraceTarget = (snapshot, targetType, targetId) => snapshot.trace?.targets.find(
1490
+ (target) => target.targetType === targetType && target.targetId === targetId
1491
+ );
1492
+ var toRenderedFactors = (target) => {
1493
+ if (target === void 0) {
1494
+ return [];
1495
+ }
1496
+ return [...target.factors].sort((a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)).slice(0, 4).map((factor) => ({
1497
+ id: factor.factorId,
1498
+ label: factorLabel(factor.factorId),
1499
+ contribution: round43(factor.contribution),
1500
+ confidence: round43(factor.confidence),
1501
+ evidence: summarizeEvidence(factor)
1502
+ }));
1503
+ };
1504
+ var suggestedActions = (target) => {
1505
+ if (target === void 0) {
1506
+ return [];
1507
+ }
1508
+ const actions = [];
1509
+ for (const lever of target.reductionLevers) {
1510
+ switch (lever.factorId) {
1511
+ case "file.evolution":
1512
+ case "repository.evolution":
1513
+ actions.push("Reduce recent churn and volatile edit frequency in this area.");
1514
+ break;
1515
+ case "file.structural":
1516
+ case "repository.structural":
1517
+ actions.push("Reduce fan-in/fan-out concentration and simplify deep dependency paths.");
1518
+ break;
1519
+ case "file.composite.interactions":
1520
+ case "repository.composite.interactions":
1521
+ actions.push("Stabilize central files before concurrent structural changes.");
1522
+ break;
1523
+ case "file.external":
1524
+ case "repository.external":
1525
+ actions.push("Review external dependency pressure for this hotspot.");
1526
+ break;
1527
+ default:
1528
+ actions.push(`Reduce ${factorLabel(lever.factorId).toLowerCase()} influence.`);
1529
+ break;
1530
+ }
1531
+ }
1532
+ return [...new Set(actions)].slice(0, 3);
1533
+ };
1534
+ var hotspotItems = (snapshot) => snapshot.analysis.risk.hotspots.slice(0, 10).map((hotspot) => {
1535
+ const fileScore = snapshot.analysis.risk.fileScores.find((item) => item.file === hotspot.file);
1536
+ const traceTarget = findTraceTarget(snapshot, "file", hotspot.file);
1537
+ const factors = toRenderedFactors(traceTarget);
1538
+ return {
1539
+ target: hotspot.file,
1540
+ score: hotspot.score,
1541
+ normalizedScore: fileScore?.normalizedScore ?? round43(hotspot.score / 100),
1542
+ topFactors: factors,
1543
+ suggestedActions: suggestedActions(traceTarget),
1544
+ biggestLevers: (traceTarget?.reductionLevers ?? []).slice(0, 3).map((lever) => `${factorLabel(lever.factorId)} (${lever.estimatedImpact})`)
1545
+ };
1546
+ });
1547
+ var repositoryConfidence = (snapshot) => {
1548
+ const target = findTraceTarget(snapshot, "repository", snapshot.analysis.structural.targetPath);
1549
+ if (target === void 0 || target.factors.length === 0) {
1550
+ return null;
1551
+ }
1552
+ const weight = target.factors.reduce((sum, factor) => sum + factor.contribution, 0);
1553
+ if (weight <= 0) {
1554
+ return null;
1555
+ }
1556
+ const weighted = target.factors.reduce(
1557
+ (sum, factor) => sum + factor.confidence * factor.contribution,
1558
+ 0
1559
+ );
1560
+ return round43(weighted / weight);
1561
+ };
1562
+ var createReport = (snapshot, diff) => {
1563
+ const external = snapshot.analysis.external;
1564
+ return {
1565
+ schemaVersion: REPORT_SCHEMA_VERSION,
1566
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1567
+ repository: {
1568
+ targetPath: snapshot.analysis.structural.targetPath,
1569
+ repositoryScore: snapshot.analysis.risk.repositoryScore,
1570
+ normalizedScore: snapshot.analysis.risk.normalizedScore,
1571
+ riskTier: toRiskTier(snapshot.analysis.risk.repositoryScore),
1572
+ confidence: repositoryConfidence(snapshot)
1573
+ },
1574
+ hotspots: hotspotItems(snapshot),
1575
+ structural: {
1576
+ cycleCount: snapshot.analysis.structural.metrics.cycleCount,
1577
+ cycles: snapshot.analysis.structural.cycles.map(
1578
+ (cycle) => [...cycle.nodes].sort((a, b) => a.localeCompare(b)).join(" -> ")
1579
+ ),
1580
+ fragileClusters: snapshot.analysis.risk.fragileClusters.map((cluster) => ({
1581
+ id: cluster.id,
1582
+ kind: cluster.kind,
1583
+ score: cluster.score,
1584
+ files: [...cluster.files].sort((a, b) => a.localeCompare(b))
1585
+ }))
1586
+ },
1587
+ external: !external.available ? {
1588
+ available: false,
1589
+ reason: external.reason
1590
+ } : {
1591
+ available: true,
1592
+ highRiskDependencies: [...external.highRiskDependencies].sort((a, b) => a.localeCompare(b)),
1593
+ highRiskDevelopmentDependencies: [...external.highRiskDevelopmentDependencies].sort((a, b) => a.localeCompare(b)),
1594
+ singleMaintainerDependencies: [...external.singleMaintainerDependencies].sort((a, b) => a.localeCompare(b)),
1595
+ abandonedDependencies: [...external.abandonedDependencies].sort((a, b) => a.localeCompare(b))
1596
+ },
1597
+ appendix: {
1598
+ snapshotSchemaVersion: snapshot.schemaVersion,
1599
+ riskModelVersion: snapshot.riskModelVersion,
1600
+ timestamp: snapshot.generatedAt,
1601
+ normalization: "Scores are deterministic 0-100 outputs from risk-engine normalized factors and interaction terms.",
1602
+ ...snapshot.analysisConfig === void 0 ? {} : { analysisConfig: snapshot.analysisConfig }
1603
+ },
1604
+ ...diff === void 0 ? {} : { diff }
1605
+ };
1606
+ };
1607
+ var renderTextDiff = (report) => {
1608
+ if (report.diff === void 0) {
1609
+ return [];
1610
+ }
1611
+ return [
1612
+ "",
1613
+ "Diff",
1614
+ ` repositoryScoreDelta: ${report.diff.repositoryScoreDelta}`,
1615
+ ` normalizedScoreDelta: ${report.diff.normalizedScoreDelta}`,
1616
+ ` newHotspots: ${report.diff.newHotspots.join(", ") || "none"}`,
1617
+ ` resolvedHotspots: ${report.diff.resolvedHotspots.join(", ") || "none"}`,
1618
+ ` newCycles: ${report.diff.newCycles.join(", ") || "none"}`,
1619
+ ` resolvedCycles: ${report.diff.resolvedCycles.join(", ") || "none"}`
1620
+ ];
1621
+ };
1622
+ var renderTextReport = (report) => {
1623
+ const lines = [];
1624
+ lines.push("Repository Summary");
1625
+ lines.push(` target: ${report.repository.targetPath}`);
1626
+ lines.push(` repositoryScore: ${report.repository.repositoryScore}`);
1627
+ lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
1628
+ lines.push(` riskTier: ${report.repository.riskTier}`);
1629
+ lines.push(` confidence: ${report.repository.confidence ?? "n/a"}`);
1630
+ lines.push("");
1631
+ lines.push("Top Hotspots");
1632
+ for (const hotspot of report.hotspots) {
1633
+ lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
1634
+ for (const factor of hotspot.topFactors) {
1635
+ lines.push(
1636
+ ` factor: ${factor.label} contribution=${factor.contribution} confidence=${factor.confidence}`
1637
+ );
1638
+ lines.push(` evidence: ${factor.evidence}`);
1639
+ }
1640
+ lines.push(` actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1641
+ lines.push(` levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
1642
+ }
1643
+ lines.push("");
1644
+ lines.push("Structural Observations");
1645
+ lines.push(` cycleCount: ${report.structural.cycleCount}`);
1646
+ lines.push(` cycles: ${report.structural.cycles.join(" ; ") || "none"}`);
1647
+ lines.push(` fragileClusters: ${report.structural.fragileClusters.length}`);
1648
+ lines.push("");
1649
+ lines.push("External Exposure");
1650
+ if (!report.external.available) {
1651
+ lines.push(` unavailable: ${report.external.reason}`);
1652
+ } else {
1653
+ lines.push(` highRiskDependencies: ${report.external.highRiskDependencies.join(", ") || "none"}`);
1654
+ lines.push(
1655
+ ` highRiskDevelopmentDependencies: ${report.external.highRiskDevelopmentDependencies.join(", ") || "none"}`
1656
+ );
1657
+ lines.push(
1658
+ ` singleMaintainerDependencies: ${report.external.singleMaintainerDependencies.join(", ") || "none"}`
1659
+ );
1660
+ lines.push(` abandonedDependencies: ${report.external.abandonedDependencies.join(", ") || "none"}`);
1661
+ }
1662
+ lines.push("");
1663
+ lines.push("Appendix");
1664
+ lines.push(` snapshotSchemaVersion: ${report.appendix.snapshotSchemaVersion}`);
1665
+ lines.push(` riskModelVersion: ${report.appendix.riskModelVersion}`);
1666
+ lines.push(` timestamp: ${report.appendix.timestamp}`);
1667
+ lines.push(` normalization: ${report.appendix.normalization}`);
1668
+ lines.push(...renderTextDiff(report));
1669
+ return lines.join("\n");
1670
+ };
1671
+ var renderMarkdownDiff = (report) => {
1672
+ if (report.diff === void 0) {
1673
+ return [];
1674
+ }
1675
+ return [
1676
+ "",
1677
+ "## Diff",
1678
+ `- repositoryScoreDelta: \`${report.diff.repositoryScoreDelta}\``,
1679
+ `- normalizedScoreDelta: \`${report.diff.normalizedScoreDelta}\``,
1680
+ `- newHotspots: ${report.diff.newHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
1681
+ `- resolvedHotspots: ${report.diff.resolvedHotspots.map((item) => `\`${item}\``).join(", ") || "none"}`,
1682
+ `- newCycles: ${report.diff.newCycles.map((item) => `\`${item}\``).join(", ") || "none"}`,
1683
+ `- resolvedCycles: ${report.diff.resolvedCycles.map((item) => `\`${item}\``).join(", ") || "none"}`
1684
+ ];
1685
+ };
1686
+ var renderMarkdownReport = (report) => {
1687
+ const lines = [];
1688
+ lines.push("# CodeSentinel Report");
1689
+ lines.push("");
1690
+ lines.push("## Repository Summary");
1691
+ lines.push(`- target: \`${report.repository.targetPath}\``);
1692
+ lines.push(`- repositoryScore: \`${report.repository.repositoryScore}\``);
1693
+ lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
1694
+ lines.push(`- riskTier: \`${report.repository.riskTier}\``);
1695
+ lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
1696
+ lines.push("");
1697
+ lines.push("## Top Hotspots");
1698
+ for (const hotspot of report.hotspots) {
1699
+ lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
1700
+ lines.push(` - Top factors:`);
1701
+ for (const factor of hotspot.topFactors) {
1702
+ lines.push(
1703
+ ` - ${factor.label}: contribution=\`${factor.contribution}\`, confidence=\`${factor.confidence}\``
1704
+ );
1705
+ lines.push(` - evidence: \`${factor.evidence}\``);
1706
+ }
1707
+ lines.push(` - Suggested actions: ${hotspot.suggestedActions.join(" | ") || "none"}`);
1708
+ lines.push(` - Biggest levers: ${hotspot.biggestLevers.join(" | ") || "none"}`);
1709
+ }
1710
+ lines.push("");
1711
+ lines.push("## Structural Observations");
1712
+ lines.push(`- cycles detected: \`${report.structural.cycleCount}\``);
1713
+ lines.push(`- cycles: ${report.structural.cycles.map((cycle) => `\`${cycle}\``).join(", ") || "none"}`);
1714
+ lines.push(`- fragile clusters: \`${report.structural.fragileClusters.length}\``);
1715
+ lines.push("");
1716
+ lines.push("## External Exposure Summary");
1717
+ if (!report.external.available) {
1718
+ lines.push(`- unavailable: \`${report.external.reason}\``);
1719
+ } else {
1720
+ lines.push(`- high-risk dependencies: ${report.external.highRiskDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`);
1721
+ lines.push(
1722
+ `- high-risk development dependencies: ${report.external.highRiskDevelopmentDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
1723
+ );
1724
+ lines.push(
1725
+ `- single maintainer dependencies: ${report.external.singleMaintainerDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
1726
+ );
1727
+ lines.push(`- abandoned dependencies: ${report.external.abandonedDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`);
1728
+ }
1729
+ lines.push("");
1730
+ lines.push("## Appendix");
1731
+ lines.push(`- snapshot schema: \`${report.appendix.snapshotSchemaVersion}\``);
1732
+ lines.push(`- risk model version: \`${report.appendix.riskModelVersion}\``);
1733
+ lines.push(`- timestamp: \`${report.appendix.timestamp}\``);
1734
+ lines.push(`- normalization: ${report.appendix.normalization}`);
1735
+ lines.push(...renderMarkdownDiff(report));
1736
+ return lines.join("\n");
1737
+ };
1738
+ var createSnapshot = (input) => ({
1739
+ schemaVersion: SNAPSHOT_SCHEMA_VERSION,
1740
+ generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1741
+ riskModelVersion: RISK_MODEL_VERSION,
1742
+ source: {
1743
+ targetPath: input.analysis.structural.targetPath
1744
+ },
1745
+ analysis: input.analysis,
1746
+ ...input.trace === void 0 ? {} : { trace: input.trace },
1747
+ ...input.analysisConfig === void 0 ? {} : { analysisConfig: input.analysisConfig }
1748
+ });
1749
+ var parseSnapshot = (raw) => {
1750
+ const parsed = JSON.parse(raw);
1751
+ if (parsed.schemaVersion !== SNAPSHOT_SCHEMA_VERSION) {
1752
+ throw new Error("unsupported_snapshot_schema");
1753
+ }
1754
+ if (typeof parsed.generatedAt !== "string") {
1755
+ throw new Error("invalid_snapshot_generated_at");
1756
+ }
1757
+ if (parsed.analysis === void 0 || parsed.analysis === null) {
1758
+ throw new Error("invalid_snapshot_analysis");
1759
+ }
1760
+ if (parsed.source === void 0 || typeof parsed.source.targetPath !== "string") {
1761
+ throw new Error("invalid_snapshot_source");
1762
+ }
1763
+ return parsed;
1764
+ };
1765
+ var formatReport = (report, format) => {
1766
+ if (format === "json") {
1767
+ return JSON.stringify(report, null, 2);
1768
+ }
1769
+ if (format === "md") {
1770
+ return renderMarkdownReport(report);
1771
+ }
1772
+ return renderTextReport(report);
1773
+ };
1774
+
1775
+ // ../governance/dist/index.js
1776
+ import { mkdirSync, rmSync } from "fs";
1777
+ import { basename, join as join2, resolve } from "path";
1778
+ import { execFile } from "child_process";
1779
+ import { promisify } from "util";
1780
+ var EXIT_CODES = {
1781
+ ok: 0,
1782
+ errorViolation: 1,
1783
+ warnViolation: 2,
1784
+ invalidConfiguration: 3,
1785
+ internalError: 4
1786
+ };
1787
+ var GovernanceConfigurationError = class extends Error {
1788
+ constructor(message) {
1789
+ super(message);
1790
+ this.name = "GovernanceConfigurationError";
1791
+ }
1792
+ };
1793
+ var DEFAULT_NEW_HOTSPOT_SCORE_THRESHOLD = 60;
1794
+ var severityRank = {
1795
+ info: 0,
1796
+ warn: 1,
1797
+ error: 2
1798
+ };
1799
+ var compareSeverity = (left, right) => {
1800
+ if (left === null) {
1801
+ return right;
1802
+ }
1803
+ if (right === null) {
1804
+ return left;
1805
+ }
1806
+ return severityRank[left] >= severityRank[right] ? left : right;
1807
+ };
1808
+ var stableSortViolations = (violations) => [...violations].sort((a, b) => {
1809
+ const severity = severityRank[b.severity] - severityRank[a.severity];
1810
+ if (severity !== 0) {
1811
+ return severity;
1812
+ }
1813
+ if (a.id !== b.id) {
1814
+ return a.id.localeCompare(b.id);
1815
+ }
1816
+ const aTarget = a.targets[0] ?? "";
1817
+ const bTarget = b.targets[0] ?? "";
1818
+ if (aTarget !== bTarget) {
1819
+ return aTarget.localeCompare(bTarget);
1820
+ }
1821
+ return a.message.localeCompare(b.message);
1822
+ });
1823
+ var makeViolation = (id, severity, message, targets, evidenceRefs) => ({
1824
+ id,
1825
+ severity,
1826
+ message,
1827
+ targets: [...targets].sort((a, b) => a.localeCompare(b)),
1828
+ evidenceRefs
1829
+ });
1830
+ var requireDiff = (input, gateId) => {
1831
+ if (input.baseline === void 0 || input.diff === void 0) {
1832
+ throw new GovernanceConfigurationError(`${gateId} requires --compare <baseline.json>`);
1833
+ }
1834
+ };
1835
+ var validateGateConfig = (input) => {
1836
+ const config = input.gateConfig;
1837
+ if (config.maxRepoDelta !== void 0 && (!Number.isFinite(config.maxRepoDelta) || config.maxRepoDelta < 0)) {
1838
+ throw new GovernanceConfigurationError("max-repo-delta must be a finite number >= 0");
1839
+ }
1840
+ if (config.maxNewHotspots !== void 0 && (!Number.isInteger(config.maxNewHotspots) || config.maxNewHotspots < 0)) {
1841
+ throw new GovernanceConfigurationError("max-new-hotspots must be an integer >= 0");
1842
+ }
1843
+ if (config.maxRepoScore !== void 0 && (!Number.isFinite(config.maxRepoScore) || config.maxRepoScore < 0 || config.maxRepoScore > 100)) {
1844
+ throw new GovernanceConfigurationError("max-repo-score must be a number in [0, 100]");
1845
+ }
1846
+ if (config.newHotspotScoreThreshold !== void 0 && (!Number.isFinite(config.newHotspotScoreThreshold) || config.newHotspotScoreThreshold < 0 || config.newHotspotScoreThreshold > 100)) {
1847
+ throw new GovernanceConfigurationError("new-hotspot-score-threshold must be a number in [0, 100]");
1848
+ }
1849
+ };
1850
+ var evaluateGates = (input) => {
1851
+ validateGateConfig(input);
1852
+ const config = input.gateConfig;
1853
+ const violations = [];
1854
+ const evaluatedGates = [];
1855
+ if (config.maxRepoScore !== void 0) {
1856
+ evaluatedGates.push("max-repo-score");
1857
+ const current = input.current.analysis.risk.repositoryScore;
1858
+ if (current > config.maxRepoScore) {
1859
+ violations.push(
1860
+ makeViolation(
1861
+ "max-repo-score",
1862
+ "error",
1863
+ `Repository score ${current} exceeds configured max ${config.maxRepoScore}.`,
1864
+ [input.current.analysis.structural.targetPath],
1865
+ [{ kind: "repository_metric", metric: "repositoryScore" }]
1866
+ )
1867
+ );
1868
+ }
1869
+ }
1870
+ if (config.maxRepoDelta !== void 0) {
1871
+ evaluatedGates.push("max-repo-delta");
1872
+ requireDiff(input, "max-repo-delta");
1873
+ const baseline = input.baseline;
1874
+ if (baseline === void 0) {
1875
+ throw new GovernanceConfigurationError("max-repo-delta requires baseline snapshot");
1876
+ }
1877
+ const delta = input.current.analysis.risk.normalizedScore - baseline.analysis.risk.normalizedScore;
1878
+ if (delta > config.maxRepoDelta) {
1879
+ violations.push(
1880
+ makeViolation(
1881
+ "max-repo-delta",
1882
+ "error",
1883
+ `Repository normalized score delta ${delta.toFixed(4)} exceeds allowed ${config.maxRepoDelta}.`,
1884
+ [input.current.analysis.structural.targetPath],
1885
+ [{ kind: "repository_metric", metric: "normalizedScore" }]
1886
+ )
1887
+ );
1888
+ }
1889
+ }
1890
+ if (config.noNewCycles === true) {
1891
+ evaluatedGates.push("no-new-cycles");
1892
+ requireDiff(input, "no-new-cycles");
1893
+ const diff = input.diff;
1894
+ if (diff === void 0) {
1895
+ throw new GovernanceConfigurationError("no-new-cycles requires diff");
1896
+ }
1897
+ if (diff.newCycles.length > 0) {
1898
+ violations.push(
1899
+ makeViolation(
1900
+ "no-new-cycles",
1901
+ "error",
1902
+ `Detected ${diff.newCycles.length} new structural cycle(s).`,
1903
+ diff.newCycles,
1904
+ [{ kind: "repository_metric", metric: "cycleCount" }]
1905
+ )
1906
+ );
1907
+ }
1908
+ }
1909
+ if (config.noNewHighRiskDeps === true) {
1910
+ evaluatedGates.push("no-new-high-risk-deps");
1911
+ requireDiff(input, "no-new-high-risk-deps");
1912
+ const diff = input.diff;
1913
+ if (diff === void 0) {
1914
+ throw new GovernanceConfigurationError("no-new-high-risk-deps requires diff");
1915
+ }
1916
+ if (diff.externalChanges.highRiskAdded.length > 0) {
1917
+ violations.push(
1918
+ makeViolation(
1919
+ "no-new-high-risk-deps",
1920
+ "error",
1921
+ `Detected ${diff.externalChanges.highRiskAdded.length} new high-risk dependency(ies).`,
1922
+ diff.externalChanges.highRiskAdded,
1923
+ diff.externalChanges.highRiskAdded.map((name) => ({
1924
+ kind: "dependency_metric",
1925
+ target: name,
1926
+ metric: "highRiskDependencies"
1927
+ }))
1928
+ )
1929
+ );
1930
+ }
1931
+ }
1932
+ if (config.maxNewHotspots !== void 0) {
1933
+ evaluatedGates.push("max-new-hotspots");
1934
+ requireDiff(input, "max-new-hotspots");
1935
+ const diff = input.diff;
1936
+ if (diff === void 0) {
1937
+ throw new GovernanceConfigurationError("max-new-hotspots requires diff");
1938
+ }
1939
+ const scoreByFile = new Map(
1940
+ input.current.analysis.risk.fileScores.map((item) => [item.file, item.score])
1941
+ );
1942
+ const threshold = config.newHotspotScoreThreshold ?? DEFAULT_NEW_HOTSPOT_SCORE_THRESHOLD;
1943
+ const counted = diff.newHotspots.filter((file) => (scoreByFile.get(file) ?? 0) >= threshold);
1944
+ if (counted.length > config.maxNewHotspots) {
1945
+ violations.push(
1946
+ makeViolation(
1947
+ "max-new-hotspots",
1948
+ "warn",
1949
+ `Detected ${counted.length} new hotspot(s) above score ${threshold}; allowed max is ${config.maxNewHotspots}.`,
1950
+ counted,
1951
+ counted.map((file) => ({ kind: "file_metric", target: file, metric: "score" }))
1952
+ )
1953
+ );
1954
+ }
1955
+ }
1956
+ const ordered = stableSortViolations(violations);
1957
+ const highestSeverity = ordered.reduce(
1958
+ (current, violation) => compareSeverity(current, violation.severity),
1959
+ null
1960
+ );
1961
+ const exitCode = highestSeverity === "error" ? 1 : highestSeverity === "warn" && config.failOn === "warn" ? 2 : 0;
1962
+ return {
1963
+ violations: ordered,
1964
+ highestSeverity,
1965
+ exitCode,
1966
+ evaluatedGates: [...evaluatedGates].sort((a, b) => a.localeCompare(b))
1967
+ };
1968
+ };
1969
+ var renderViolationText = (violation) => {
1970
+ const targets = violation.targets.join(", ") || "n/a";
1971
+ return `- [${violation.severity}] ${violation.id}: ${violation.message} (targets: ${targets})`;
1972
+ };
1973
+ var renderCheckText = (snapshot, result) => {
1974
+ const lines = [];
1975
+ lines.push("CodeSentinel Check");
1976
+ lines.push(`target: ${snapshot.analysis.structural.targetPath}`);
1977
+ lines.push(`repositoryScore: ${snapshot.analysis.risk.repositoryScore}`);
1978
+ lines.push(`evaluatedGates: ${result.evaluatedGates.join(", ") || "none"}`);
1979
+ lines.push(`violations: ${result.violations.length}`);
1980
+ lines.push(`exitCode: ${result.exitCode}`);
1981
+ lines.push("");
1982
+ lines.push("Violations");
1983
+ if (result.violations.length === 0) {
1984
+ lines.push("- none");
1985
+ } else {
1986
+ for (const violation of result.violations) {
1987
+ lines.push(renderViolationText(violation));
1988
+ }
1989
+ }
1990
+ return lines.join("\n");
1991
+ };
1992
+ var renderCheckMarkdown = (snapshot, result) => {
1993
+ const lines = [];
1994
+ lines.push("## CodeSentinel CI Summary");
1995
+ lines.push(`- target: \`${snapshot.analysis.structural.targetPath}\``);
1996
+ lines.push(`- repositoryScore: \`${snapshot.analysis.risk.repositoryScore}\``);
1997
+ lines.push(`- evaluatedGates: ${result.evaluatedGates.map((item) => `\`${item}\``).join(", ") || "none"}`);
1998
+ lines.push(`- violations: \`${result.violations.length}\``);
1999
+ lines.push(`- exitCode: \`${result.exitCode}\``);
2000
+ const repositoryTrace = snapshot.trace?.targets.find(
2001
+ (target) => target.targetType === "repository" && target.targetId === snapshot.analysis.structural.targetPath
2002
+ );
2003
+ if (repositoryTrace !== void 0) {
2004
+ lines.push("");
2005
+ lines.push("### Why");
2006
+ const topFactors = [...repositoryTrace.factors].sort((a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)).slice(0, 3);
2007
+ for (const factor of topFactors) {
2008
+ lines.push(
2009
+ `- ${factorLabel(factor.factorId)}: contribution=\`${factor.contribution}\`, evidence=\`${summarizeEvidence(factor)}\``
2010
+ );
2011
+ }
2012
+ }
2013
+ lines.push("");
2014
+ lines.push("### Violations");
2015
+ if (result.violations.length === 0) {
2016
+ lines.push("- none");
2017
+ } else {
2018
+ for (const violation of result.violations) {
2019
+ lines.push(`- [${violation.severity}] **${violation.id}**: ${violation.message}`);
2020
+ if (violation.targets.length > 0) {
2021
+ lines.push(` - targets: ${violation.targets.map((target) => `\`${target}\``).join(", ")}`);
2022
+ }
2023
+ }
2024
+ }
2025
+ return lines.join("\n");
2026
+ };
2027
+ var BaselineAutoResolutionError = class extends Error {
2028
+ constructor(message) {
2029
+ super(message);
2030
+ this.name = "BaselineAutoResolutionError";
2031
+ }
2032
+ };
2033
+ var DEFAULT_MAIN_BRANCH_CANDIDATES = ["main", "master"];
2034
+ var providerBaseBranchKeys = [
2035
+ "GITHUB_BASE_REF",
2036
+ "CI_MERGE_REQUEST_TARGET_BRANCH_NAME",
2037
+ "BITBUCKET_PR_DESTINATION_BRANCH"
2038
+ ];
2039
+ var normalizeMainBranches = (input) => {
2040
+ const source = input === void 0 || input.length === 0 ? DEFAULT_MAIN_BRANCH_CANDIDATES : input;
2041
+ const seen = /* @__PURE__ */ new Set();
2042
+ const values = [];
2043
+ for (const candidate of source) {
2044
+ const trimmed = candidate.trim();
2045
+ if (trimmed.length === 0 || seen.has(trimmed)) {
2046
+ continue;
2047
+ }
2048
+ seen.add(trimmed);
2049
+ values.push(trimmed);
2050
+ }
2051
+ return values.length > 0 ? values : DEFAULT_MAIN_BRANCH_CANDIDATES;
2052
+ };
2053
+ var firstNonEmptyEnv = (environment) => {
2054
+ for (const key of providerBaseBranchKeys) {
2055
+ const value = environment[key]?.trim();
2056
+ if (value !== void 0 && value.length > 0) {
2057
+ return { key, value };
2058
+ }
2059
+ }
2060
+ return void 0;
2061
+ };
2062
+ var asBoolean = (value) => {
2063
+ return value.trim().toLowerCase() === "true";
2064
+ };
2065
+ var buildNoBaselineMessage = () => {
2066
+ return "unable to resolve auto baseline; set --baseline-ref <ref> explicitly or provide --baseline <snapshot.json>";
2067
+ };
2068
+ var resolveAutoBaseline = async (input) => {
2069
+ const attempts = [];
2070
+ const mainBranches = normalizeMainBranches(input.mainBranchCandidates);
2071
+ const environment = input.environment ?? {};
2072
+ const baselineSha = input.baselineSha?.trim();
2073
+ if (baselineSha !== void 0 && baselineSha.length > 0) {
2074
+ const result = await input.git.resolveCommit(`${baselineSha}^{commit}`);
2075
+ if (result.ok) {
2076
+ attempts.push({ step: "explicit-sha", candidate: baselineSha, outcome: "resolved" });
2077
+ return {
2078
+ strategy: "explicit_sha",
2079
+ resolvedRef: baselineSha,
2080
+ resolvedSha: result.stdout,
2081
+ attempts
2082
+ };
2083
+ }
2084
+ attempts.push({
2085
+ step: "explicit-sha",
2086
+ candidate: baselineSha,
2087
+ outcome: "failed",
2088
+ detail: result.message
2089
+ });
2090
+ throw new BaselineAutoResolutionError(
2091
+ `invalid --baseline-sha '${baselineSha}': ${result.message}`
2092
+ );
2093
+ }
2094
+ const providerBaseBranch = firstNonEmptyEnv(environment);
2095
+ if (providerBaseBranch !== void 0) {
2096
+ const originRef = `origin/${providerBaseBranch.value}`;
2097
+ const originResult = await input.git.resolveCommit(`${originRef}^{commit}`);
2098
+ if (originResult.ok) {
2099
+ attempts.push({
2100
+ step: `ci-base-branch:${providerBaseBranch.key}`,
2101
+ candidate: originRef,
2102
+ outcome: "resolved"
2103
+ });
2104
+ return {
2105
+ strategy: "ci_base_branch",
2106
+ resolvedRef: originRef,
2107
+ resolvedSha: originResult.stdout,
2108
+ attempts,
2109
+ baseBranch: providerBaseBranch.value
2110
+ };
2111
+ }
2112
+ attempts.push({
2113
+ step: `ci-base-branch:${providerBaseBranch.key}`,
2114
+ candidate: originRef,
2115
+ outcome: "failed",
2116
+ detail: originResult.message
2117
+ });
2118
+ const localRef = providerBaseBranch.value;
2119
+ const localResult = await input.git.resolveCommit(`${localRef}^{commit}`);
2120
+ if (localResult.ok) {
2121
+ attempts.push({
2122
+ step: `ci-base-branch-local:${providerBaseBranch.key}`,
2123
+ candidate: localRef,
2124
+ outcome: "resolved"
2125
+ });
2126
+ return {
2127
+ strategy: "ci_base_branch",
2128
+ resolvedRef: localRef,
2129
+ resolvedSha: localResult.stdout,
2130
+ attempts,
2131
+ baseBranch: providerBaseBranch.value
2132
+ };
2133
+ }
2134
+ attempts.push({
2135
+ step: `ci-base-branch-local:${providerBaseBranch.key}`,
2136
+ candidate: localRef,
2137
+ outcome: "failed",
2138
+ detail: localResult.message
2139
+ });
2140
+ } else {
2141
+ attempts.push({
2142
+ step: "ci-base-branch",
2143
+ candidate: providerBaseBranchKeys.join(","),
2144
+ outcome: "skipped",
2145
+ detail: "no CI base branch environment variable found"
2146
+ });
2147
+ }
2148
+ const branchResult = await input.git.currentBranch();
2149
+ const branchName = branchResult.ok ? branchResult.stdout.trim() : void 0;
2150
+ if (branchName !== void 0 && mainBranches.includes(branchName)) {
2151
+ const headPrevious = await input.git.resolveCommit("HEAD~1^{commit}");
2152
+ if (headPrevious.ok) {
2153
+ attempts.push({
2154
+ step: "main-branch-head-previous",
2155
+ candidate: "HEAD~1",
2156
+ outcome: "resolved"
2157
+ });
2158
+ return {
2159
+ strategy: "main_branch_previous_commit",
2160
+ resolvedRef: "HEAD~1",
2161
+ resolvedSha: headPrevious.stdout,
2162
+ attempts
2163
+ };
2164
+ }
2165
+ attempts.push({
2166
+ step: "main-branch-head-previous",
2167
+ candidate: "HEAD~1",
2168
+ outcome: "failed",
2169
+ detail: headPrevious.message
2170
+ });
2171
+ throw new BaselineAutoResolutionError(
2172
+ `unable to resolve baseline from HEAD~1 on branch '${branchName}': ${headPrevious.message}`
2173
+ );
2174
+ }
2175
+ if (branchName === void 0) {
2176
+ attempts.push({
2177
+ step: "current-branch",
2178
+ candidate: "HEAD",
2179
+ outcome: "skipped",
2180
+ detail: "detached HEAD or symbolic-ref unavailable"
2181
+ });
2182
+ } else {
2183
+ attempts.push({
2184
+ step: "current-branch",
2185
+ candidate: branchName,
2186
+ outcome: "resolved",
2187
+ detail: "feature branch detected"
2188
+ });
2189
+ }
2190
+ const mergeBaseCandidates = [
2191
+ ...mainBranches.map((candidate) => `origin/${candidate}`),
2192
+ ...mainBranches
2193
+ ];
2194
+ for (const candidate of mergeBaseCandidates) {
2195
+ const mergeBase = await input.git.mergeBase("HEAD", candidate);
2196
+ if (mergeBase.ok) {
2197
+ attempts.push({
2198
+ step: "merge-base",
2199
+ candidate: `HEAD..${candidate}`,
2200
+ outcome: "resolved"
2201
+ });
2202
+ return {
2203
+ strategy: "feature_branch_merge_base",
2204
+ resolvedRef: mergeBase.stdout,
2205
+ resolvedSha: mergeBase.stdout,
2206
+ attempts
2207
+ };
2208
+ }
2209
+ attempts.push({
2210
+ step: "merge-base",
2211
+ candidate: `HEAD..${candidate}`,
2212
+ outcome: "failed",
2213
+ detail: mergeBase.message
2214
+ });
2215
+ }
2216
+ const shallowResult = await input.git.isShallowRepository();
2217
+ const shallowRepository = shallowResult.ok && asBoolean(shallowResult.stdout);
2218
+ if (shallowRepository) {
2219
+ throw new BaselineAutoResolutionError(
2220
+ `${buildNoBaselineMessage()}; repository appears shallow. Fetch full history (for example: git fetch --unshallow or fetch-depth: 0).`
2221
+ );
2222
+ }
2223
+ throw new BaselineAutoResolutionError(buildNoBaselineMessage());
2224
+ };
2225
+ var execFileAsync = promisify(execFile);
2226
+ var SENTINEL_TMP_DIR = ".codesentinel-tmp";
2227
+ var WORKTREE_DIR = "worktrees";
2228
+ var BaselineRefResolutionError = class extends Error {
2229
+ constructor(message) {
2230
+ super(message);
2231
+ this.name = "BaselineRefResolutionError";
2232
+ }
2233
+ };
2234
+ var runGit = async (repositoryPath, args) => {
2235
+ const result = await tryRunGit(repositoryPath, args);
2236
+ if (result.ok) {
2237
+ return result.stdout;
2238
+ }
2239
+ throw new BaselineRefResolutionError(result.message);
2240
+ };
2241
+ var tryRunGit = async (repositoryPath, args) => {
2242
+ try {
2243
+ const { stdout } = await execFileAsync("git", ["-C", repositoryPath, ...args], {
2244
+ encoding: "utf8"
2245
+ });
2246
+ return { ok: true, stdout: stdout.trim() };
2247
+ } catch (error) {
2248
+ const message = error instanceof Error ? error.message : "unknown git error";
2249
+ return { ok: false, message };
2250
+ }
2251
+ };
2252
+ var buildWorktreePath = (repoRoot, sha) => {
2253
+ const tmpRoot = join2(repoRoot, SENTINEL_TMP_DIR, WORKTREE_DIR);
2254
+ mkdirSync(tmpRoot, { recursive: true });
2255
+ const baseName = `baseline-${sha.slice(0, 12)}-${process.pid}`;
2256
+ const candidate = resolve(tmpRoot, baseName);
2257
+ return candidate;
2258
+ };
2259
+ var sanitizeSnapshotForWorktree = (snapshot, worktreePath, canonicalPath) => {
2260
+ const replacePrefix = (value) => value.startsWith(worktreePath) ? `${canonicalPath}${value.slice(worktreePath.length)}` : value;
2261
+ const structural = snapshot.analysis.structural;
2262
+ return {
2263
+ ...snapshot,
2264
+ source: {
2265
+ targetPath: replacePrefix(snapshot.source.targetPath)
2266
+ },
2267
+ analysis: {
2268
+ ...snapshot.analysis,
2269
+ structural: {
2270
+ ...structural,
2271
+ targetPath: replacePrefix(structural.targetPath),
2272
+ nodes: structural.nodes.map((node) => ({
2273
+ ...node,
2274
+ absolutePath: replacePrefix(node.absolutePath)
2275
+ }))
2276
+ },
2277
+ evolution: {
2278
+ ...snapshot.analysis.evolution,
2279
+ targetPath: replacePrefix(snapshot.analysis.evolution.targetPath)
2280
+ },
2281
+ external: {
2282
+ ...snapshot.analysis.external,
2283
+ targetPath: replacePrefix(snapshot.analysis.external.targetPath)
2284
+ }
2285
+ }
2286
+ };
2287
+ };
2288
+ var resolveBaselineSnapshotFromRef = async (input) => {
2289
+ const repositoryPath = resolve(input.repositoryPath);
2290
+ const ref = input.baselineRef.trim();
2291
+ if (ref.length === 0) {
2292
+ throw new BaselineRefResolutionError("baseline-ref cannot be empty");
2293
+ }
2294
+ const repoRoot = await runGit(repositoryPath, ["rev-parse", "--show-toplevel"]);
2295
+ const sha = await runGit(repositoryPath, ["rev-parse", "--verify", `${ref}^{commit}`]);
2296
+ const worktreePath = buildWorktreePath(repoRoot, sha);
2297
+ const cleanup = () => {
2298
+ try {
2299
+ rmSync(worktreePath, { recursive: true, force: true });
2300
+ } catch {
2301
+ }
2302
+ };
2303
+ try {
2304
+ await runGit(repoRoot, ["worktree", "add", "--detach", worktreePath, sha]);
2305
+ const snapshot = await input.analyzeWorktree(worktreePath, repoRoot);
2306
+ const sanitized = sanitizeSnapshotForWorktree(snapshot, worktreePath, repoRoot);
2307
+ return {
2308
+ baselineSnapshot: sanitized,
2309
+ resolvedRef: ref,
2310
+ resolvedSha: sha
2311
+ };
2312
+ } finally {
2313
+ try {
2314
+ await runGit(repoRoot, ["worktree", "remove", "--force", worktreePath]);
2315
+ } catch {
2316
+ cleanup();
2317
+ }
2318
+ }
2319
+ };
2320
+ var resolveAutoBaselineRef = async (input) => {
2321
+ const repositoryPath = resolve(input.repositoryPath);
2322
+ const repoRoot = await runGit(repositoryPath, ["rev-parse", "--show-toplevel"]);
2323
+ try {
2324
+ return await resolveAutoBaseline({
2325
+ ...input.baselineSha === void 0 ? {} : { baselineSha: input.baselineSha },
2326
+ ...input.environment === void 0 ? {} : { environment: input.environment },
2327
+ ...input.mainBranchCandidates === void 0 ? {} : { mainBranchCandidates: input.mainBranchCandidates },
2328
+ git: {
2329
+ resolveCommit: async (ref) => tryRunGit(repoRoot, ["rev-parse", "--verify", ref]),
2330
+ mergeBase: async (leftRef, rightRef) => tryRunGit(repoRoot, ["merge-base", leftRef, rightRef]),
2331
+ currentBranch: async () => tryRunGit(repoRoot, ["symbolic-ref", "--quiet", "--short", "HEAD"]),
2332
+ isShallowRepository: async () => tryRunGit(repoRoot, ["rev-parse", "--is-shallow-repository"])
2333
+ }
2334
+ });
2335
+ } catch (error) {
2336
+ if (error instanceof BaselineAutoResolutionError) {
2337
+ throw new BaselineRefResolutionError(error.message);
2338
+ }
2339
+ throw error;
2340
+ }
2341
+ };
2342
+
1352
2343
  // src/index.ts
1353
2344
  import { readFileSync as readFileSync2 } from "fs";
1354
- import { dirname, resolve as resolve3 } from "path";
2345
+ import { dirname, resolve as resolve5 } from "path";
1355
2346
  import { fileURLToPath } from "url";
1356
2347
 
1357
2348
  // src/application/format-analyze-output.ts
@@ -1406,7 +2397,7 @@ var toRiskBand = (score) => {
1406
2397
  }
1407
2398
  return "very_high";
1408
2399
  };
1409
- var factorLabelById = {
2400
+ var factorLabelById2 = {
1410
2401
  "repository.structural": "Structural complexity",
1411
2402
  "repository.evolution": "Change volatility",
1412
2403
  "repository.external": "External dependency pressure",
@@ -1424,7 +2415,7 @@ var factorLabelById = {
1424
2415
  "dependency.bus_factor": "Dependency bus factor",
1425
2416
  "dependency.popularity_dampening": "Popularity dampening"
1426
2417
  };
1427
- var formatFactorLabel = (factorId) => factorLabelById[factorId] ?? factorId;
2418
+ var formatFactorLabel = (factorId) => factorLabelById2[factorId] ?? factorId;
1428
2419
  var formatNumber = (value) => value === null || value === void 0 ? "n/a" : `${value}`;
1429
2420
  var formatFactorSummary = (factor) => `${formatFactorLabel(factor.factorId)} (+${factor.contribution}, confidence=${factor.confidence})`;
1430
2421
  var formatFactorEvidence = (factor) => {
@@ -1702,10 +2693,10 @@ var parseLogLevel = (value) => {
1702
2693
  };
1703
2694
 
1704
2695
  // src/application/run-analyze-command.ts
1705
- import { resolve as resolve2 } from "path";
2696
+ import { resolve as resolve3 } from "path";
1706
2697
 
1707
2698
  // ../code-graph/dist/index.js
1708
- import { extname, isAbsolute, relative, resolve } from "path";
2699
+ import { extname, isAbsolute, relative, resolve as resolve2 } from "path";
1709
2700
  import * as ts from "typescript";
1710
2701
  var edgeKey = (from, to) => `${from}\0${to}`;
1711
2702
  var createGraphData = (nodes, rawEdges) => {
@@ -1994,7 +2985,7 @@ var discoverSourceFilesByScan = (projectRoot) => {
1994
2985
  SCAN_EXCLUDES,
1995
2986
  SCAN_INCLUDES
1996
2987
  );
1997
- return files.map((filePath) => resolve(filePath));
2988
+ return files.map((filePath) => resolve2(filePath));
1998
2989
  };
1999
2990
  var parseTsConfigFile = (configPath) => {
2000
2991
  const parsedCommandLine = ts.getParsedCommandLineOfConfigFile(
@@ -2021,7 +3012,7 @@ var collectFilesFromTsConfigGraph = (projectRoot) => {
2021
3012
  const collectedFiles = /* @__PURE__ */ new Set();
2022
3013
  let rootOptions = null;
2023
3014
  const visitConfig = (configPath) => {
2024
- const absoluteConfigPath = resolve(configPath);
3015
+ const absoluteConfigPath = resolve2(configPath);
2025
3016
  if (visitedConfigPaths.has(absoluteConfigPath)) {
2026
3017
  return;
2027
3018
  }
@@ -2031,10 +3022,10 @@ var collectFilesFromTsConfigGraph = (projectRoot) => {
2031
3022
  rootOptions = parsed.options;
2032
3023
  }
2033
3024
  for (const filePath of parsed.fileNames) {
2034
- collectedFiles.add(resolve(filePath));
3025
+ collectedFiles.add(resolve2(filePath));
2035
3026
  }
2036
3027
  for (const reference of parsed.projectReferences ?? []) {
2037
- const referencePath = resolve(reference.path);
3028
+ const referencePath = resolve2(reference.path);
2038
3029
  const referenceConfigPath = ts.sys.directoryExists(referencePath) ? ts.findConfigFile(referencePath, ts.sys.fileExists, "tsconfig.json") : referencePath;
2039
3030
  if (referenceConfigPath !== void 0 && ts.sys.fileExists(referenceConfigPath)) {
2040
3031
  visitConfig(referenceConfigPath);
@@ -2159,10 +3150,10 @@ var extractModuleSpecifiers = (sourceFile) => {
2159
3150
  return [...specifiers];
2160
3151
  };
2161
3152
  var parseTypescriptProject = (projectPath, onProgress) => {
2162
- const projectRoot = isAbsolute(projectPath) ? projectPath : resolve(projectPath);
3153
+ const projectRoot = isAbsolute(projectPath) ? projectPath : resolve2(projectPath);
2163
3154
  const { fileNames, options, tsconfigCount, usedFallbackScan } = parseTsConfig(projectRoot);
2164
3155
  onProgress?.({ stage: "config_resolved", tsconfigCount, usedFallbackScan });
2165
- const sourceFilePaths = fileNames.filter((filePath) => isProjectSourceFile(filePath, projectRoot)).map((filePath) => normalizePath(resolve(filePath)));
3156
+ const sourceFilePaths = fileNames.filter((filePath) => isProjectSourceFile(filePath, projectRoot)).map((filePath) => normalizePath(resolve2(filePath)));
2166
3157
  const uniqueSourceFilePaths = [...new Set(sourceFilePaths)].sort((a, b) => a.localeCompare(b));
2167
3158
  const sourceFilePathSet = new Set(uniqueSourceFilePaths);
2168
3159
  onProgress?.({ stage: "files_discovered", totalSourceFiles: uniqueSourceFilePaths.length });
@@ -2199,7 +3190,7 @@ var parseTypescriptProject = (projectPath, onProgress) => {
2199
3190
  if (resolvedPath === void 0 && !resolverCache.has(cacheKey)) {
2200
3191
  const resolved = ts.resolveModuleName(specifier, sourcePath, options, ts.sys).resolvedModule;
2201
3192
  if (resolved !== void 0) {
2202
- resolvedPath = normalizePath(resolve(resolved.resolvedFileName));
3193
+ resolvedPath = normalizePath(resolve2(resolved.resolvedFileName));
2203
3194
  }
2204
3195
  resolverCache.set(cacheKey, resolvedPath);
2205
3196
  }
@@ -2237,7 +3228,7 @@ var buildProjectGraphSummary = (input) => {
2237
3228
  // ../git-analyzer/dist/index.js
2238
3229
  import { execFileSync } from "child_process";
2239
3230
  var pairKey = (a, b) => `${a}\0${b}`;
2240
- var round43 = (value) => Number(value.toFixed(4));
3231
+ var round44 = (value) => Number(value.toFixed(4));
2241
3232
  var normalizeName = (value) => value.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
2242
3233
  var extractEmailStem = (authorId) => {
2243
3234
  const normalized = authorId.trim().toLowerCase();
@@ -2367,7 +3358,7 @@ var finalizeAuthorDistribution = (authorCommits) => {
2367
3358
  return [...authorCommits.entries()].map(([authorId, commits]) => ({
2368
3359
  authorId,
2369
3360
  commits,
2370
- share: round43(commits / totalCommits)
3361
+ share: round44(commits / totalCommits)
2371
3362
  })).sort((a, b) => b.commits - a.commits || a.authorId.localeCompare(b.authorId));
2372
3363
  };
2373
3364
  var buildCouplingMatrix = (coChangeByPair, fileCommitCount, consideredCommits, skippedLargeCommits, maxCouplingPairs) => {
@@ -2380,7 +3371,7 @@ var buildCouplingMatrix = (coChangeByPair, fileCommitCount, consideredCommits, s
2380
3371
  const fileACommits = fileCommitCount.get(fileA) ?? 0;
2381
3372
  const fileBCommits = fileCommitCount.get(fileB) ?? 0;
2382
3373
  const denominator = fileACommits + fileBCommits - coChangeCommits;
2383
- const couplingScore = denominator === 0 ? 0 : round43(coChangeCommits / denominator);
3374
+ const couplingScore = denominator === 0 ? 0 : round44(coChangeCommits / denominator);
2384
3375
  allPairs.push({
2385
3376
  fileA,
2386
3377
  fileB,
@@ -2479,12 +3470,12 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
2479
3470
  return {
2480
3471
  filePath,
2481
3472
  commitCount: stats.commitCount,
2482
- frequencyPer100Commits: commits.length === 0 ? 0 : round43(stats.commitCount / commits.length * 100),
3473
+ frequencyPer100Commits: commits.length === 0 ? 0 : round44(stats.commitCount / commits.length * 100),
2483
3474
  churnAdded: stats.churnAdded,
2484
3475
  churnDeleted: stats.churnDeleted,
2485
3476
  churnTotal: stats.churnAdded + stats.churnDeleted,
2486
3477
  recentCommitCount: stats.recentCommitCount,
2487
- recentVolatility: stats.commitCount === 0 ? 0 : round43(stats.recentCommitCount / stats.commitCount),
3478
+ recentVolatility: stats.commitCount === 0 ? 0 : round44(stats.recentCommitCount / stats.commitCount),
2488
3479
  topAuthorShare,
2489
3480
  busFactor: computeBusFactor(authorDistribution, config.busFactorCoverageThreshold),
2490
3481
  authorDistribution
@@ -2800,7 +3791,7 @@ var DEFAULT_RISK_ENGINE_CONFIG = {
2800
3791
  }
2801
3792
  };
2802
3793
  var toUnitInterval = (value) => Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
2803
- var round44 = (value) => Number(value.toFixed(4));
3794
+ var round45 = (value) => Number(value.toFixed(4));
2804
3795
  var average = (values) => {
2805
3796
  if (values.length === 0) {
2806
3797
  return 0;
@@ -2914,7 +3905,7 @@ var computeDependencySignalScore = (ownSignals, inheritedSignals, inheritedSigna
2914
3905
  }
2915
3906
  return toUnitInterval(weightedTotal / maxWeightedTotal);
2916
3907
  };
2917
- var clampConfidence = (value) => round44(toUnitInterval(value));
3908
+ var clampConfidence = (value) => round45(toUnitInterval(value));
2918
3909
  var buildFactorTraces = (totalScore, inputs) => {
2919
3910
  const positiveInputs = inputs.filter((input) => input.strength > 0);
2920
3911
  const strengthTotal = positiveInputs.reduce((sum, input) => sum + input.strength, 0);
@@ -2951,7 +3942,7 @@ var buildFactorTraces = (totalScore, inputs) => {
2951
3942
  continue;
2952
3943
  }
2953
3944
  if (index === scored.length - 1) {
2954
- const remaining = round44(totalScore - distributed);
3945
+ const remaining = round45(totalScore - distributed);
2955
3946
  traces[traceIndex] = {
2956
3947
  ...existing,
2957
3948
  contribution: Math.max(0, remaining)
@@ -2959,7 +3950,7 @@ var buildFactorTraces = (totalScore, inputs) => {
2959
3950
  distributed += Math.max(0, remaining);
2960
3951
  continue;
2961
3952
  }
2962
- const rounded = round44(current.contribution);
3953
+ const rounded = round45(current.contribution);
2963
3954
  traces[traceIndex] = {
2964
3955
  ...existing,
2965
3956
  contribution: rounded
@@ -2972,7 +3963,7 @@ var buildReductionLevers = (factors) => factors.filter((factor) => factor.contri
2972
3963
  (a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)
2973
3964
  ).slice(0, 3).map((factor) => ({
2974
3965
  factorId: factor.factorId,
2975
- estimatedImpact: round44(factor.contribution)
3966
+ estimatedImpact: round45(factor.contribution)
2976
3967
  }));
2977
3968
  var buildTargetTrace = (targetType, targetId, totalScore, normalizedScore, factors) => {
2978
3969
  const dominantFactors = [...factors].filter((factor) => factor.contribution > 0).sort(
@@ -2981,8 +3972,8 @@ var buildTargetTrace = (targetType, targetId, totalScore, normalizedScore, facto
2981
3972
  return {
2982
3973
  targetType,
2983
3974
  targetId,
2984
- totalScore: round44(totalScore),
2985
- normalizedScore: round44(normalizedScore),
3975
+ totalScore: round45(totalScore),
3976
+ normalizedScore: round45(normalizedScore),
2986
3977
  factors,
2987
3978
  dominantFactors,
2988
3979
  reductionLevers: buildReductionLevers(factors)
@@ -3053,14 +4044,14 @@ var computeDependencyScores = (external, config) => {
3053
4044
  ].filter((value) => value !== null).length;
3054
4045
  const confidence = toUnitInterval(0.5 + availableMetricCount * 0.125);
3055
4046
  dependencyContexts.set(dependency.name, {
3056
- signalScore: round44(signalScore),
3057
- stalenessRisk: round44(stalenessRisk),
3058
- maintainerConcentrationRisk: round44(maintainerConcentrationRisk),
3059
- transitiveBurdenRisk: round44(transitiveBurdenRisk),
3060
- centralityRisk: round44(centralityRisk),
3061
- chainDepthRisk: round44(chainDepthRisk),
3062
- busFactorRisk: round44(busFactorRisk),
3063
- popularityDampener: round44(popularityDampener),
4047
+ signalScore: round45(signalScore),
4048
+ stalenessRisk: round45(stalenessRisk),
4049
+ maintainerConcentrationRisk: round45(maintainerConcentrationRisk),
4050
+ transitiveBurdenRisk: round45(transitiveBurdenRisk),
4051
+ centralityRisk: round45(centralityRisk),
4052
+ chainDepthRisk: round45(chainDepthRisk),
4053
+ busFactorRisk: round45(busFactorRisk),
4054
+ popularityDampener: round45(popularityDampener),
3064
4055
  rawMetrics: {
3065
4056
  daysSinceLastRelease: dependency.daysSinceLastRelease,
3066
4057
  maintainerCount: dependency.maintainerCount,
@@ -3070,12 +4061,12 @@ var computeDependencyScores = (external, config) => {
3070
4061
  busFactor: dependency.busFactor,
3071
4062
  weeklyDownloads: dependency.weeklyDownloads
3072
4063
  },
3073
- confidence: round44(confidence)
4064
+ confidence: round45(confidence)
3074
4065
  });
3075
4066
  return {
3076
4067
  dependency: dependency.name,
3077
- score: round44(normalizedScore * 100),
3078
- normalizedScore: round44(normalizedScore),
4068
+ score: round45(normalizedScore * 100),
4069
+ normalizedScore: round45(normalizedScore),
3079
4070
  ownRiskSignals: dependency.ownRiskSignals,
3080
4071
  inheritedRiskSignals: dependency.inheritedRiskSignals
3081
4072
  };
@@ -3094,7 +4085,7 @@ var computeDependencyScores = (external, config) => {
3094
4085
  );
3095
4086
  return {
3096
4087
  dependencyScores,
3097
- repositoryExternalPressure: round44(repositoryExternalPressure),
4088
+ repositoryExternalPressure: round45(repositoryExternalPressure),
3098
4089
  dependencyContexts
3099
4090
  };
3100
4091
  };
@@ -3159,7 +4150,7 @@ var buildFragileClusters = (structural, evolution, fileScoresByFile, config) =>
3159
4150
  files.map((filePath) => fileScoresByFile.get(filePath)?.normalizedScore ?? 0)
3160
4151
  );
3161
4152
  const cycleSizeRisk = toUnitInterval((files.length - 1) / 5);
3162
- const score = round44(toUnitInterval(averageRisk * 0.75 + cycleSizeRisk * 0.25) * 100);
4153
+ const score = round45(toUnitInterval(averageRisk * 0.75 + cycleSizeRisk * 0.25) * 100);
3163
4154
  cycleClusterCount += 1;
3164
4155
  clusters.push({
3165
4156
  id: `cycle:${cycleClusterCount}`,
@@ -3233,7 +4224,7 @@ var buildFragileClusters = (structural, evolution, fileScoresByFile, config) =>
3233
4224
  files.map((filePath) => fileScoresByFile.get(filePath)?.normalizedScore ?? 0)
3234
4225
  );
3235
4226
  const meanCoupling = average(componentPairs.map((pair) => pair.couplingScore));
3236
- const score = round44(toUnitInterval(meanFileRisk * 0.65 + meanCoupling * 0.35) * 100);
4227
+ const score = round45(toUnitInterval(meanFileRisk * 0.65 + meanCoupling * 0.35) * 100);
3237
4228
  couplingClusterCount += 1;
3238
4229
  clusters.push({
3239
4230
  id: `coupling:${couplingClusterCount}`,
@@ -3323,21 +4314,21 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3323
4314
  const normalizedScore = saturatingComposite(baseline, interactions);
3324
4315
  return {
3325
4316
  file: filePath,
3326
- score: round44(normalizedScore * 100),
3327
- normalizedScore: round44(normalizedScore),
4317
+ score: round45(normalizedScore * 100),
4318
+ normalizedScore: round45(normalizedScore),
3328
4319
  factors: {
3329
- structural: round44(structuralFactor),
3330
- evolution: round44(evolutionFactor),
3331
- external: round44(externalFactor)
4320
+ structural: round45(structuralFactor),
4321
+ evolution: round45(evolutionFactor),
4322
+ external: round45(externalFactor)
3332
4323
  },
3333
- structuralCentrality: round44(structuralCentrality),
4324
+ structuralCentrality: round45(structuralCentrality),
3334
4325
  traceTerms: {
3335
- structuralBase: round44(structuralBase),
3336
- evolutionBase: round44(evolutionBase),
3337
- externalBase: round44(externalBase),
3338
- interactionStructuralEvolution: round44(interactionStructuralEvolution),
3339
- interactionCentralInstability: round44(interactionCentralInstability),
3340
- interactionDependencyAmplification: round44(interactionDependencyAmplification)
4326
+ structuralBase: round45(structuralBase),
4327
+ evolutionBase: round45(evolutionBase),
4328
+ externalBase: round45(externalBase),
4329
+ interactionStructuralEvolution: round45(interactionStructuralEvolution),
4330
+ interactionCentralInstability: round45(interactionCentralInstability),
4331
+ interactionDependencyAmplification: round45(interactionDependencyAmplification)
3341
4332
  },
3342
4333
  rawMetrics: {
3343
4334
  fanIn: file.fanIn,
@@ -3349,18 +4340,18 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3349
4340
  recentVolatility: evolutionMetrics?.recentVolatility ?? null,
3350
4341
  topAuthorShare: evolutionMetrics?.topAuthorShare ?? null,
3351
4342
  busFactor: evolutionMetrics?.busFactor ?? null,
3352
- dependencyAffinity: round44(dependencyAffinity),
3353
- repositoryExternalPressure: round44(dependencyComputation.repositoryExternalPressure)
4343
+ dependencyAffinity: round45(dependencyAffinity),
4344
+ repositoryExternalPressure: round45(dependencyComputation.repositoryExternalPressure)
3354
4345
  },
3355
4346
  normalizedMetrics: {
3356
- fanInRisk: round44(fanInRisk),
3357
- fanOutRisk: round44(fanOutRisk),
3358
- depthRisk: round44(depthRisk),
3359
- frequencyRisk: round44(frequencyRisk),
3360
- churnRisk: round44(churnRisk),
3361
- volatilityRisk: round44(volatilityRisk),
3362
- ownershipConcentrationRisk: round44(ownershipConcentrationRisk),
3363
- busFactorRisk: round44(busFactorRisk)
4347
+ fanInRisk: round45(fanInRisk),
4348
+ fanOutRisk: round45(fanOutRisk),
4349
+ depthRisk: round45(depthRisk),
4350
+ frequencyRisk: round45(frequencyRisk),
4351
+ churnRisk: round45(churnRisk),
4352
+ volatilityRisk: round45(volatilityRisk),
4353
+ ownershipConcentrationRisk: round45(ownershipConcentrationRisk),
4354
+ busFactorRisk: round45(busFactorRisk)
3364
4355
  }
3365
4356
  };
3366
4357
  }).sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
@@ -3490,8 +4481,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3490
4481
  const normalizedScore = toUnitInterval(averageScore * 0.65 + peakScore * 0.35);
3491
4482
  return {
3492
4483
  module,
3493
- score: round44(normalizedScore * 100),
3494
- normalizedScore: round44(normalizedScore),
4484
+ score: round45(normalizedScore * 100),
4485
+ normalizedScore: round45(normalizedScore),
3495
4486
  fileCount: values.length
3496
4487
  };
3497
4488
  }).sort((a, b) => b.score - a.score || a.module.localeCompare(b.module));
@@ -3500,14 +4491,14 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3500
4491
  const averageScore = average(values);
3501
4492
  const peakScore = values.reduce((max, value) => Math.max(max, value), 0);
3502
4493
  const normalizedScore = toUnitInterval(averageScore * 0.65 + peakScore * 0.35);
3503
- const totalScore = round44(normalizedScore * 100);
4494
+ const totalScore = round45(normalizedScore * 100);
3504
4495
  const factors = buildFactorTraces(totalScore, [
3505
4496
  {
3506
4497
  factorId: "module.average_file_risk",
3507
4498
  family: "composite",
3508
4499
  strength: averageScore * 0.65,
3509
- rawMetrics: { averageFileRisk: round44(averageScore), fileCount: values.length },
3510
- normalizedMetrics: { normalizedModuleRisk: round44(normalizedScore) },
4500
+ rawMetrics: { averageFileRisk: round45(averageScore), fileCount: values.length },
4501
+ normalizedMetrics: { normalizedModuleRisk: round45(normalizedScore) },
3511
4502
  weight: 0.65,
3512
4503
  amplification: null,
3513
4504
  evidence: [{ kind: "repository_metric", metric: "moduleAggregation.average" }],
@@ -3517,8 +4508,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3517
4508
  factorId: "module.peak_file_risk",
3518
4509
  family: "composite",
3519
4510
  strength: peakScore * 0.35,
3520
- rawMetrics: { peakFileRisk: round44(peakScore), fileCount: values.length },
3521
- normalizedMetrics: { normalizedModuleRisk: round44(normalizedScore) },
4511
+ rawMetrics: { peakFileRisk: round45(peakScore), fileCount: values.length },
4512
+ normalizedMetrics: { normalizedModuleRisk: round45(normalizedScore) },
3522
4513
  weight: 0.35,
3523
4514
  amplification: null,
3524
4515
  evidence: [{ kind: "repository_metric", metric: "moduleAggregation.peak" }],
@@ -3541,12 +4532,12 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3541
4532
  const normalizedZoneScore = toUnitInterval(intensity * 0.7 + fileScore.normalizedScore * 0.3);
3542
4533
  return {
3543
4534
  file: fileScore.file,
3544
- score: round44(normalizedZoneScore * 100),
4535
+ score: round45(normalizedZoneScore * 100),
3545
4536
  externalPressure: fileScore.factors.external
3546
4537
  };
3547
4538
  }).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) => ({
3548
4539
  ...zone,
3549
- externalPressure: round44(zone.externalPressure)
4540
+ externalPressure: round45(zone.externalPressure)
3550
4541
  }));
3551
4542
  if (collector !== void 0 && external.available) {
3552
4543
  const dependencyByName = new Map(external.dependencies.map((dependency) => [dependency.name, dependency]));
@@ -3669,15 +4660,15 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3669
4660
  criticalInstability * config.interactionWeights.centralInstability,
3670
4661
  dependencyAmplification * config.interactionWeights.dependencyAmplification
3671
4662
  ]);
3672
- const repositoryScore = round44(repositoryNormalizedScore * 100);
4663
+ const repositoryScore = round45(repositoryNormalizedScore * 100);
3673
4664
  if (collector !== void 0) {
3674
4665
  const repositoryFactors = buildFactorTraces(repositoryScore, [
3675
4666
  {
3676
4667
  factorId: "repository.structural",
3677
4668
  family: "structural",
3678
4669
  strength: structuralDimension * dimensionWeights.structural,
3679
- rawMetrics: { structuralDimension: round44(structuralDimension) },
3680
- normalizedMetrics: { dimensionWeight: round44(dimensionWeights.structural) },
4670
+ rawMetrics: { structuralDimension: round45(structuralDimension) },
4671
+ normalizedMetrics: { dimensionWeight: round45(dimensionWeights.structural) },
3681
4672
  weight: dimensionWeights.structural,
3682
4673
  amplification: null,
3683
4674
  evidence: [{ kind: "repository_metric", metric: "structuralDimension" }],
@@ -3687,8 +4678,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3687
4678
  factorId: "repository.evolution",
3688
4679
  family: "evolution",
3689
4680
  strength: evolutionDimension * dimensionWeights.evolution,
3690
- rawMetrics: { evolutionDimension: round44(evolutionDimension) },
3691
- normalizedMetrics: { dimensionWeight: round44(dimensionWeights.evolution) },
4681
+ rawMetrics: { evolutionDimension: round45(evolutionDimension) },
4682
+ normalizedMetrics: { dimensionWeight: round45(dimensionWeights.evolution) },
3692
4683
  weight: dimensionWeights.evolution,
3693
4684
  amplification: null,
3694
4685
  evidence: [{ kind: "repository_metric", metric: "evolutionDimension" }],
@@ -3698,8 +4689,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3698
4689
  factorId: "repository.external",
3699
4690
  family: "external",
3700
4691
  strength: externalDimension * dimensionWeights.external,
3701
- rawMetrics: { externalDimension: round44(externalDimension) },
3702
- normalizedMetrics: { dimensionWeight: round44(dimensionWeights.external) },
4692
+ rawMetrics: { externalDimension: round45(externalDimension) },
4693
+ normalizedMetrics: { dimensionWeight: round45(dimensionWeights.external) },
3703
4694
  weight: dimensionWeights.external,
3704
4695
  amplification: null,
3705
4696
  evidence: [{ kind: "repository_metric", metric: "externalDimension" }],
@@ -3710,19 +4701,19 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3710
4701
  family: "composite",
3711
4702
  strength: structuralDimension * evolutionDimension * config.interactionWeights.structuralEvolution + criticalInstability * config.interactionWeights.centralInstability + dependencyAmplification * config.interactionWeights.dependencyAmplification,
3712
4703
  rawMetrics: {
3713
- structuralEvolution: round44(
4704
+ structuralEvolution: round45(
3714
4705
  structuralDimension * evolutionDimension * config.interactionWeights.structuralEvolution
3715
4706
  ),
3716
- centralInstability: round44(
4707
+ centralInstability: round45(
3717
4708
  criticalInstability * config.interactionWeights.centralInstability
3718
4709
  ),
3719
- dependencyAmplification: round44(
4710
+ dependencyAmplification: round45(
3720
4711
  dependencyAmplification * config.interactionWeights.dependencyAmplification
3721
4712
  )
3722
4713
  },
3723
4714
  normalizedMetrics: {
3724
- criticalInstability: round44(criticalInstability),
3725
- dependencyAmplification: round44(dependencyAmplification)
4715
+ criticalInstability: round45(criticalInstability),
4716
+ dependencyAmplification: round45(dependencyAmplification)
3726
4717
  },
3727
4718
  weight: null,
3728
4719
  amplification: config.interactionWeights.structuralEvolution + config.interactionWeights.centralInstability + config.interactionWeights.dependencyAmplification,
@@ -3742,7 +4733,7 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
3742
4733
  }
3743
4734
  return {
3744
4735
  repositoryScore,
3745
- normalizedScore: round44(repositoryNormalizedScore),
4736
+ normalizedScore: round45(repositoryNormalizedScore),
3746
4737
  hotspots,
3747
4738
  fragileClusters,
3748
4739
  dependencyAmplificationZones,
@@ -3862,7 +4853,7 @@ var evaluateRepositoryRisk = (input, options = {}) => {
3862
4853
  };
3863
4854
 
3864
4855
  // src/application/run-analyze-command.ts
3865
- var resolveTargetPath = (inputPath, cwd) => resolve2(cwd, inputPath ?? ".");
4856
+ var resolveTargetPath = (inputPath, cwd) => resolve3(cwd, inputPath ?? ".");
3866
4857
  var createExternalProgressReporter = (logger) => {
3867
4858
  let lastLoggedProgress = 0;
3868
4859
  return (event) => {
@@ -4030,6 +5021,273 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, logger = createSil
4030
5021
  };
4031
5022
  };
4032
5023
 
5024
+ // src/application/run-check-command.ts
5025
+ import { readFile, writeFile } from "fs/promises";
5026
+
5027
+ // src/application/build-analysis-snapshot.ts
5028
+ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logger) => {
5029
+ const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, logger);
5030
+ const evaluation = evaluateRepositoryRisk(analysisInputs, { explain: options.includeTrace });
5031
+ const summary = {
5032
+ ...analysisInputs,
5033
+ risk: evaluation.summary
5034
+ };
5035
+ return createSnapshot({
5036
+ analysis: summary,
5037
+ ...evaluation.trace === void 0 ? {} : { trace: evaluation.trace },
5038
+ analysisConfig: {
5039
+ authorIdentityMode,
5040
+ includeTrace: options.includeTrace
5041
+ }
5042
+ });
5043
+ };
5044
+
5045
+ // src/application/run-check-command.ts
5046
+ var formatCheckResult = (result, format) => {
5047
+ if (format === "json") {
5048
+ return JSON.stringify(
5049
+ {
5050
+ current: result.current,
5051
+ ...result.baseline === void 0 ? {} : { baseline: result.baseline },
5052
+ ...result.diff === void 0 ? {} : { diff: result.diff },
5053
+ violations: result.gateResult.violations,
5054
+ evaluatedGates: result.gateResult.evaluatedGates,
5055
+ highestSeverity: result.gateResult.highestSeverity,
5056
+ exitCode: result.gateResult.exitCode
5057
+ },
5058
+ null,
5059
+ 2
5060
+ );
5061
+ }
5062
+ if (format === "md") {
5063
+ return renderCheckMarkdown(result.current, result.gateResult);
5064
+ }
5065
+ return renderCheckText(result.current, result.gateResult);
5066
+ };
5067
+ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
5068
+ logger.info("building current snapshot for check");
5069
+ const current = await buildAnalysisSnapshot(
5070
+ inputPath,
5071
+ authorIdentityMode,
5072
+ { includeTrace: options.includeTrace },
5073
+ logger
5074
+ );
5075
+ let baseline;
5076
+ let diff;
5077
+ if (options.baselinePath !== void 0) {
5078
+ logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5079
+ const baselineRaw = await readFile(options.baselinePath, "utf8");
5080
+ try {
5081
+ baseline = parseSnapshot(baselineRaw);
5082
+ } catch (error) {
5083
+ const message = error instanceof Error ? error.message : "invalid baseline snapshot";
5084
+ throw new GovernanceConfigurationError(`invalid baseline snapshot: ${message}`);
5085
+ }
5086
+ diff = compareSnapshots(current, baseline);
5087
+ }
5088
+ const gateResult = evaluateGates({
5089
+ current,
5090
+ ...baseline === void 0 ? {} : { baseline },
5091
+ ...diff === void 0 ? {} : { diff },
5092
+ gateConfig: options.gateConfig
5093
+ });
5094
+ const rendered = formatCheckResult(
5095
+ {
5096
+ current,
5097
+ ...baseline === void 0 ? {} : { baseline },
5098
+ ...diff === void 0 ? {} : { diff },
5099
+ gateResult,
5100
+ rendered: ""
5101
+ },
5102
+ options.outputFormat
5103
+ );
5104
+ if (options.outputPath !== void 0) {
5105
+ await writeFile(options.outputPath, rendered, "utf8");
5106
+ logger.info(`check output written: ${options.outputPath}`);
5107
+ }
5108
+ return {
5109
+ current,
5110
+ ...baseline === void 0 ? {} : { baseline },
5111
+ ...diff === void 0 ? {} : { diff },
5112
+ gateResult,
5113
+ rendered
5114
+ };
5115
+ };
5116
+
5117
+ // src/application/run-ci-command.ts
5118
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5119
+ import { relative as relative2, resolve as resolve4 } from "path";
5120
+ var isPathOutsideBase = (value) => {
5121
+ return value === ".." || value.startsWith("../") || value.startsWith("..\\");
5122
+ };
5123
+ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
5124
+ if (options.baselinePath !== void 0 && options.baselineRef !== void 0) {
5125
+ throw new GovernanceConfigurationError(
5126
+ "baseline configuration is ambiguous: use either --baseline or --baseline-ref"
5127
+ );
5128
+ }
5129
+ if (options.baselineSha !== void 0 && options.baselineRef !== "auto") {
5130
+ throw new GovernanceConfigurationError(
5131
+ "baseline-sha requires --baseline-ref auto"
5132
+ );
5133
+ }
5134
+ const resolvedTargetPath = resolve4(inputPath ?? process.cwd());
5135
+ logger.info("building current snapshot");
5136
+ const current = await buildAnalysisSnapshot(
5137
+ inputPath,
5138
+ authorIdentityMode,
5139
+ { includeTrace: options.includeTrace },
5140
+ logger
5141
+ );
5142
+ if (options.snapshotPath !== void 0) {
5143
+ await writeFile2(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5144
+ logger.info(`snapshot written: ${options.snapshotPath}`);
5145
+ }
5146
+ let baseline;
5147
+ let diff;
5148
+ if (options.baselineRef !== void 0) {
5149
+ let baselineRef = options.baselineRef;
5150
+ if (options.baselineRef === "auto") {
5151
+ logger.info("resolving baseline ref using auto strategy");
5152
+ try {
5153
+ const autoResolved = await resolveAutoBaselineRef({
5154
+ repositoryPath: resolvedTargetPath,
5155
+ ...options.baselineSha === void 0 ? {} : { baselineSha: options.baselineSha },
5156
+ ...options.mainBranchCandidates === void 0 ? {} : { mainBranchCandidates: options.mainBranchCandidates },
5157
+ environment: process.env
5158
+ });
5159
+ logger.info(
5160
+ `baseline auto strategy selected: ${autoResolved.strategy} (${autoResolved.resolvedRef} -> ${autoResolved.resolvedSha})`
5161
+ );
5162
+ for (const attempt of autoResolved.attempts) {
5163
+ const detail = attempt.detail === void 0 ? "" : ` (${attempt.detail})`;
5164
+ logger.debug(
5165
+ `baseline auto attempt: ${attempt.step} ${attempt.candidate} => ${attempt.outcome}${detail}`
5166
+ );
5167
+ }
5168
+ baselineRef = autoResolved.resolvedRef;
5169
+ } catch (error) {
5170
+ if (error instanceof BaselineRefResolutionError) {
5171
+ throw new GovernanceConfigurationError(
5172
+ `unable to resolve baseline ref 'auto': ${error.message}`
5173
+ );
5174
+ }
5175
+ throw error;
5176
+ }
5177
+ }
5178
+ logger.info(`resolving baseline from git ref: ${baselineRef}`);
5179
+ try {
5180
+ const resolved = await resolveBaselineSnapshotFromRef({
5181
+ repositoryPath: resolvedTargetPath,
5182
+ baselineRef,
5183
+ analyzeWorktree: async (worktreePath, repositoryRoot) => {
5184
+ const relativeTargetPath = relative2(repositoryRoot, resolvedTargetPath);
5185
+ if (isPathOutsideBase(relativeTargetPath)) {
5186
+ throw new GovernanceConfigurationError(
5187
+ `target path is outside git repository root: ${resolvedTargetPath}`
5188
+ );
5189
+ }
5190
+ const baselineTargetPath = relativeTargetPath.length === 0 || relativeTargetPath === "." ? worktreePath : resolve4(worktreePath, relativeTargetPath);
5191
+ return buildAnalysisSnapshot(
5192
+ baselineTargetPath,
5193
+ authorIdentityMode,
5194
+ { includeTrace: options.includeTrace },
5195
+ logger
5196
+ );
5197
+ }
5198
+ });
5199
+ baseline = resolved.baselineSnapshot;
5200
+ logger.info(`baseline ref resolved to ${resolved.resolvedSha}`);
5201
+ } catch (error) {
5202
+ if (error instanceof BaselineRefResolutionError) {
5203
+ throw new GovernanceConfigurationError(
5204
+ `unable to resolve baseline ref '${baselineRef}': ${error.message}`
5205
+ );
5206
+ }
5207
+ throw error;
5208
+ }
5209
+ diff = compareSnapshots(current, baseline);
5210
+ } else if (options.baselinePath !== void 0) {
5211
+ logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5212
+ const baselineRaw = await readFile2(options.baselinePath, "utf8");
5213
+ try {
5214
+ baseline = parseSnapshot(baselineRaw);
5215
+ } catch (error) {
5216
+ const message = error instanceof Error ? error.message : "invalid baseline snapshot";
5217
+ throw new GovernanceConfigurationError(`invalid baseline snapshot: ${message}`);
5218
+ }
5219
+ diff = compareSnapshots(current, baseline);
5220
+ }
5221
+ const gateResult = evaluateGates({
5222
+ current,
5223
+ ...baseline === void 0 ? {} : { baseline },
5224
+ ...diff === void 0 ? {} : { diff },
5225
+ gateConfig: options.gateConfig
5226
+ });
5227
+ const report = createReport(current, diff);
5228
+ const reportMarkdown = formatReport(report, "md");
5229
+ const ciMarkdown = renderCheckMarkdown(current, gateResult);
5230
+ const markdownSummary = `${reportMarkdown}
5231
+
5232
+ ${ciMarkdown}`;
5233
+ if (options.reportPath !== void 0) {
5234
+ await writeFile2(options.reportPath, markdownSummary, "utf8");
5235
+ logger.info(`report written: ${options.reportPath}`);
5236
+ }
5237
+ const machineReadable = {
5238
+ current,
5239
+ ...baseline === void 0 ? {} : { baseline },
5240
+ ...diff === void 0 ? {} : { diff },
5241
+ violations: gateResult.violations,
5242
+ highestSeverity: gateResult.highestSeverity,
5243
+ exitCode: gateResult.exitCode
5244
+ };
5245
+ if (options.jsonOutputPath !== void 0) {
5246
+ await writeFile2(options.jsonOutputPath, JSON.stringify(machineReadable, null, 2), "utf8");
5247
+ logger.info(`ci machine output written: ${options.jsonOutputPath}`);
5248
+ }
5249
+ return {
5250
+ current,
5251
+ ...baseline === void 0 ? {} : { baseline },
5252
+ ...diff === void 0 ? {} : { diff },
5253
+ gateResult,
5254
+ markdownSummary,
5255
+ machineReadable
5256
+ };
5257
+ };
5258
+
5259
+ // src/application/run-report-command.ts
5260
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
5261
+ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
5262
+ logger.info("building analysis snapshot");
5263
+ const current = await buildAnalysisSnapshot(
5264
+ inputPath,
5265
+ authorIdentityMode,
5266
+ { includeTrace: options.includeTrace },
5267
+ logger
5268
+ );
5269
+ if (options.snapshotPath !== void 0) {
5270
+ await writeFile3(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5271
+ logger.info(`snapshot written: ${options.snapshotPath}`);
5272
+ }
5273
+ let report;
5274
+ if (options.comparePath === void 0) {
5275
+ report = createReport(current);
5276
+ } else {
5277
+ logger.info(`loading baseline snapshot: ${options.comparePath}`);
5278
+ const baselineRaw = await readFile3(options.comparePath, "utf8");
5279
+ const baseline = parseSnapshot(baselineRaw);
5280
+ const diff = compareSnapshots(current, baseline);
5281
+ report = createReport(current, diff);
5282
+ }
5283
+ const rendered = formatReport(report, options.format);
5284
+ if (options.outputPath !== void 0) {
5285
+ await writeFile3(options.outputPath, rendered, "utf8");
5286
+ logger.info(`report written: ${options.outputPath}`);
5287
+ }
5288
+ return { report, rendered };
5289
+ };
5290
+
4033
5291
  // src/application/run-explain-command.ts
4034
5292
  var selectTargets = (trace, summary, options) => {
4035
5293
  if (options.file !== void 0) {
@@ -4071,7 +5329,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
4071
5329
 
4072
5330
  // src/index.ts
4073
5331
  var program = new Command();
4074
- var packageJsonPath = resolve3(dirname(fileURLToPath(import.meta.url)), "../package.json");
5332
+ var packageJsonPath = resolve5(dirname(fileURLToPath(import.meta.url)), "../package.json");
4075
5333
  var { version } = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
4076
5334
  program.name("codesentinel").description("Structural and evolutionary risk analysis for TypeScript/JavaScript codebases").version(version);
4077
5335
  program.command("analyze").argument("[path]", "path to the project to analyze").addOption(
@@ -4163,6 +5421,183 @@ program.command("dependency-risk").argument("<dependency>", "dependency spec to
4163
5421
  `);
4164
5422
  }
4165
5423
  );
5424
+ program.command("report").argument("[path]", "path to the project to analyze").addOption(
5425
+ new Option(
5426
+ "--author-identity <mode>",
5427
+ "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
5428
+ ).choices(["likely_merge", "strict_email"]).default("likely_merge")
5429
+ ).addOption(
5430
+ new Option(
5431
+ "--log-level <level>",
5432
+ "log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
5433
+ ).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
5434
+ ).addOption(
5435
+ new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")
5436
+ ).option("--output <path>", "write rendered report to a file path").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").action(
5437
+ async (path, options) => {
5438
+ const logger = createStderrLogger(options.logLevel);
5439
+ const result = await runReportCommand(
5440
+ path,
5441
+ options.authorIdentity,
5442
+ {
5443
+ format: options.format,
5444
+ ...options.output === void 0 ? {} : { outputPath: options.output },
5445
+ ...options.compare === void 0 ? {} : { comparePath: options.compare },
5446
+ ...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
5447
+ includeTrace: options.trace
5448
+ },
5449
+ logger
5450
+ );
5451
+ if (options.output === void 0) {
5452
+ process.stdout.write(`${result.rendered}
5453
+ `);
5454
+ }
5455
+ }
5456
+ );
5457
+ var parseGateNumber = (value, optionName) => {
5458
+ if (value === void 0) {
5459
+ return void 0;
5460
+ }
5461
+ const parsed = Number.parseFloat(value);
5462
+ if (!Number.isFinite(parsed)) {
5463
+ throw new GovernanceConfigurationError(`${optionName} must be numeric`);
5464
+ }
5465
+ return parsed;
5466
+ };
5467
+ var collectOptionValues = (value, previous = []) => {
5468
+ return [...previous, value];
5469
+ };
5470
+ var parseMainBranches = (options) => {
5471
+ const fromRepeated = options.mainBranch ?? [];
5472
+ const fromCsv = options.mainBranches === void 0 ? [] : options.mainBranches.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
5473
+ const merged = [...fromRepeated, ...fromCsv];
5474
+ if (merged.length === 0) {
5475
+ return void 0;
5476
+ }
5477
+ const unique = Array.from(new Set(merged));
5478
+ return unique.length > 0 ? unique : void 0;
5479
+ };
5480
+ var buildGateConfigFromOptions = (options) => {
5481
+ const maxRepoDelta = parseGateNumber(options.maxRepoDelta, "--max-repo-delta");
5482
+ const maxNewHotspots = parseGateNumber(options.maxNewHotspots, "--max-new-hotspots");
5483
+ const maxRepoScore = parseGateNumber(options.maxRepoScore, "--max-repo-score");
5484
+ const newHotspotScoreThreshold = parseGateNumber(
5485
+ options.newHotspotScoreThreshold,
5486
+ "--new-hotspot-score-threshold"
5487
+ );
5488
+ return {
5489
+ ...maxRepoDelta === void 0 ? {} : { maxRepoDelta },
5490
+ ...options.noNewCycles === true ? { noNewCycles: true } : {},
5491
+ ...options.noNewHighRiskDeps === true ? { noNewHighRiskDeps: true } : {},
5492
+ ...maxNewHotspots === void 0 ? {} : { maxNewHotspots },
5493
+ ...maxRepoScore === void 0 ? {} : { maxRepoScore },
5494
+ ...newHotspotScoreThreshold === void 0 ? {} : { newHotspotScoreThreshold },
5495
+ failOn: options.failOn
5496
+ };
5497
+ };
5498
+ program.command("check").argument("[path]", "path to the project to analyze").addOption(
5499
+ new Option(
5500
+ "--author-identity <mode>",
5501
+ "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
5502
+ ).choices(["likely_merge", "strict_email"]).default("likely_merge")
5503
+ ).addOption(
5504
+ new Option(
5505
+ "--log-level <level>",
5506
+ "log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
5507
+ ).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
5508
+ ).option("--compare <baseline>", "baseline snapshot path").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")).addOption(new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")).option("--output <path>", "write check output to a file path").option("--no-trace", "disable trace embedding in generated snapshot").action(
5509
+ async (path, options) => {
5510
+ const logger = createStderrLogger(options.logLevel);
5511
+ try {
5512
+ const gateConfig = buildGateConfigFromOptions(options);
5513
+ const result = await runCheckCommand(
5514
+ path,
5515
+ options.authorIdentity,
5516
+ {
5517
+ ...options.compare === void 0 ? {} : { baselinePath: options.compare },
5518
+ includeTrace: options.trace,
5519
+ gateConfig,
5520
+ outputFormat: options.format,
5521
+ ...options.output === void 0 ? {} : { outputPath: options.output }
5522
+ },
5523
+ logger
5524
+ );
5525
+ if (options.output === void 0) {
5526
+ process.stdout.write(`${result.rendered}
5527
+ `);
5528
+ }
5529
+ process.exitCode = result.gateResult.exitCode;
5530
+ } catch (error) {
5531
+ if (error instanceof GovernanceConfigurationError) {
5532
+ logger.error(error.message);
5533
+ process.exitCode = EXIT_CODES.invalidConfiguration;
5534
+ return;
5535
+ }
5536
+ logger.error(error instanceof Error ? error.message : "internal error");
5537
+ process.exitCode = EXIT_CODES.internalError;
5538
+ }
5539
+ }
5540
+ );
5541
+ program.command("ci").argument("[path]", "path to the project to analyze").addOption(
5542
+ new Option(
5543
+ "--author-identity <mode>",
5544
+ "author identity mode: likely_merge (heuristic) or strict_email (deterministic)"
5545
+ ).choices(["likely_merge", "strict_email"]).default("likely_merge")
5546
+ ).addOption(
5547
+ new Option(
5548
+ "--log-level <level>",
5549
+ "log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
5550
+ ).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
5551
+ ).option("--baseline <path>", "baseline snapshot path").option("--baseline-ref <gitRef>", "resolve baseline snapshot from a git reference (for example origin/main)").option("--baseline-sha <sha>", "explicit baseline commit SHA (only valid with --baseline-ref auto)").addOption(
5552
+ new Option(
5553
+ "--main-branch <name>",
5554
+ "add a default branch candidate for auto baseline resolution (repeatable)"
5555
+ ).argParser(collectOptionValues)
5556
+ ).option(
5557
+ "--main-branches <names>",
5558
+ "comma-separated default branch candidates for auto baseline resolution (for example: main,master)"
5559
+ ).option("--snapshot <path>", "write current snapshot JSON to path").option("--report <path>", "write markdown CI summary report").option("--json-output <path>", "write machine-readable CI JSON output").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")).option("--no-trace", "disable trace embedding in generated snapshot").action(
5560
+ async (path, options) => {
5561
+ const logger = createStderrLogger(options.logLevel);
5562
+ try {
5563
+ const gateConfig = buildGateConfigFromOptions(options);
5564
+ const mainBranchCandidates = parseMainBranches(options);
5565
+ const result = await runCiCommand(
5566
+ path,
5567
+ options.authorIdentity,
5568
+ {
5569
+ ...options.baseline === void 0 ? {} : { baselinePath: options.baseline },
5570
+ ...options.baselineRef === void 0 ? {} : { baselineRef: options.baselineRef },
5571
+ ...options.baselineSha === void 0 ? {} : { baselineSha: options.baselineSha },
5572
+ ...mainBranchCandidates === void 0 ? {} : { mainBranchCandidates },
5573
+ ...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
5574
+ ...options.report === void 0 ? {} : { reportPath: options.report },
5575
+ ...options.jsonOutput === void 0 ? {} : { jsonOutputPath: options.jsonOutput },
5576
+ includeTrace: options.trace,
5577
+ gateConfig
5578
+ },
5579
+ logger
5580
+ );
5581
+ if (options.report === void 0) {
5582
+ process.stdout.write(`${result.markdownSummary}
5583
+ `);
5584
+ }
5585
+ if (options.jsonOutput === void 0) {
5586
+ process.stdout.write(`${JSON.stringify(result.machineReadable, null, 2)}
5587
+ `);
5588
+ }
5589
+ process.exitCode = result.gateResult.exitCode;
5590
+ } catch (error) {
5591
+ if (error instanceof GovernanceConfigurationError) {
5592
+ logger.error(error.message);
5593
+ process.exitCode = EXIT_CODES.invalidConfiguration;
5594
+ return;
5595
+ }
5596
+ logger.error(error instanceof Error ? error.message : "internal error");
5597
+ process.exitCode = EXIT_CODES.internalError;
5598
+ }
5599
+ }
5600
+ );
4166
5601
  if (process.argv.length <= 2) {
4167
5602
  program.outputHelp();
4168
5603
  process.exit(0);