@getcodesentinel/codesentinel 1.10.1 → 1.12.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
@@ -127,7 +127,12 @@ var buildExternalAnalysisSummary = (targetPath, extraction, metadataByKey, confi
127
127
  }
128
128
  }
129
129
  const { depthByName, maxDepth } = computeDepths(nodeByName, directNames);
130
- const centralityRanking = rankCentrality(nodes, dependentsByName, directNames, config.centralityTopN);
130
+ const centralityRanking = rankCentrality(
131
+ nodes,
132
+ dependentsByName,
133
+ directNames,
134
+ config.centralityTopN
135
+ );
131
136
  const topCentralNames = new Set(
132
137
  centralityRanking.slice(0, Math.max(1, Math.ceil(centralityRanking.length * 0.25))).map((entry) => entry.name)
133
138
  );
@@ -230,8 +235,12 @@ var buildExternalAnalysisSummary = (targetPath, extraction, metadataByKey, confi
230
235
  metrics: {
231
236
  totalDependencies: allDependencies.length,
232
237
  directDependencies: dependencies.length,
233
- directProductionDependencies: dependencies.filter((dependency) => dependency.dependencyScope === "prod").length,
234
- directDevelopmentDependencies: dependencies.filter((dependency) => dependency.dependencyScope === "dev").length,
238
+ directProductionDependencies: dependencies.filter(
239
+ (dependency) => dependency.dependencyScope === "prod"
240
+ ).length,
241
+ directDevelopmentDependencies: dependencies.filter(
242
+ (dependency) => dependency.dependencyScope === "dev"
243
+ ).length,
235
244
  transitiveDependencies: allDependencies.length - dependencies.length,
236
245
  dependencyDepth: maxDepth,
237
246
  lockfileKind: extraction.kind,
@@ -257,7 +266,8 @@ var DEFAULT_EXTERNAL_ANALYSIS_CONFIG = {
257
266
  var mapWithConcurrency = async (values, limit, handler) => {
258
267
  const effectiveLimit = Math.max(1, limit);
259
268
  const workerCount = Math.min(effectiveLimit, values.length);
260
- const results = new Array(values.length);
269
+ const UNSET = /* @__PURE__ */ Symbol("map_with_concurrency_unset");
270
+ const results = new Array(values.length).fill(UNSET);
261
271
  let index = 0;
262
272
  const workers = Array.from({ length: workerCount }, async () => {
263
273
  while (true) {
@@ -267,12 +277,13 @@ var mapWithConcurrency = async (values, limit, handler) => {
267
277
  return;
268
278
  }
269
279
  const value = values[current];
270
- if (value !== void 0) {
271
- results[current] = await handler(value);
272
- }
280
+ results[current] = await handler(value);
273
281
  }
274
282
  });
275
283
  await Promise.all(workers);
284
+ if (results.some((value) => value === UNSET)) {
285
+ throw new Error("map_with_concurrency_incomplete_results");
286
+ }
276
287
  return results;
277
288
  };
278
289
  var collectDependencyMetadata = async (extraction, metadataProvider, concurrency, onProgress) => {
@@ -473,9 +484,7 @@ var parsePnpmLockfile = (raw, directSpecs) => {
473
484
  version: nodeId.slice(at + 1),
474
485
  dependencies: [...deps].sort((a, b) => a.localeCompare(b))
475
486
  };
476
- }).sort(
477
- (a, b) => a.name.localeCompare(b.name) || a.version.localeCompare(b.version)
478
- );
487
+ }).sort((a, b) => a.name.localeCompare(b.name) || a.version.localeCompare(b.version));
479
488
  return {
480
489
  kind: "pnpm",
481
490
  directDependencies: directSpecs,
@@ -579,7 +588,9 @@ var parseYarnLock = (raw, directSpecs) => {
579
588
  return {
580
589
  kind: "yarn",
581
590
  directDependencies: directSpecs,
582
- nodes: [...deduped.values()].sort((a, b) => a.name.localeCompare(b.name) || a.version.localeCompare(b.version))
591
+ nodes: [...deduped.values()].sort(
592
+ (a, b) => a.name.localeCompare(b.name) || a.version.localeCompare(b.version)
593
+ )
583
594
  };
584
595
  };
585
596
  var parseBunLock = (_raw, _directSpecs) => {
@@ -855,7 +866,9 @@ var resolveRangeVersion = (versions, requested) => {
855
866
  if (clauses.length === 0) {
856
867
  return null;
857
868
  }
858
- const parsedVersions = versions.map((version2) => ({ version: version2, parsed: parseSemver(version2) })).filter((candidate) => candidate.parsed !== null).sort((a, b) => compareSemver(b.parsed, a.parsed));
869
+ const parsedVersions = versions.map((version2) => ({ version: version2, parsed: parseSemver(version2) })).filter(
870
+ (candidate) => candidate.parsed !== null
871
+ ).sort((a, b) => compareSemver(b.parsed, a.parsed));
859
872
  for (const candidate of parsedVersions) {
860
873
  let clauseMatched = false;
861
874
  let clauseUnsupported = false;
@@ -929,7 +942,9 @@ var resolveRequestedVersion = (packument, requested) => {
929
942
  fallbackUsed: requested !== null
930
943
  };
931
944
  }
932
- const semverSorted = versionKeys.map((version2) => ({ version: version2, parsed: parseSemver(version2) })).filter((candidate) => candidate.parsed !== null).sort((a, b) => compareSemver(b.parsed, a.parsed)).map((candidate) => candidate.version);
945
+ const semverSorted = versionKeys.map((version2) => ({ version: version2, parsed: parseSemver(version2) })).filter(
946
+ (candidate) => candidate.parsed !== null
947
+ ).sort((a, b) => compareSemver(b.parsed, a.parsed)).map((candidate) => candidate.version);
933
948
  const fallbackVersion = semverSorted[0] ?? versionKeys.sort((a, b) => b.localeCompare(a))[0];
934
949
  if (fallbackVersion === void 0) {
935
950
  return null;
@@ -1002,11 +1017,15 @@ var resolveRegistryGraphFromDirectSpecs = async (directSpecs, options) => {
1002
1017
  continue;
1003
1018
  }
1004
1019
  const manifest = (packument.versions ?? {})[resolved.version] ?? {};
1005
- const dependencies = Object.entries(manifest.dependencies ?? {}).filter(([dependencyName, dependencyRange]) => dependencyName.length > 0 && dependencyRange.length > 0).sort((a, b) => a[0].localeCompare(b[0]));
1020
+ const dependencies = Object.entries(manifest.dependencies ?? {}).filter(
1021
+ ([dependencyName, dependencyRange]) => dependencyName.length > 0 && dependencyRange.length > 0
1022
+ ).sort((a, b) => a[0].localeCompare(b[0]));
1006
1023
  nodesByKey.set(nodeKey, {
1007
1024
  name: item.name,
1008
1025
  version: resolved.version,
1009
- dependencies: dependencies.map(([dependencyName, dependencyRange]) => `${dependencyName}@${dependencyRange}`)
1026
+ dependencies: dependencies.map(
1027
+ ([dependencyName, dependencyRange]) => `${dependencyName}@${dependencyRange}`
1028
+ )
1010
1029
  });
1011
1030
  if (item.depth >= maxDepth && dependencies.length > 0) {
1012
1031
  truncated = true;
@@ -1183,7 +1202,8 @@ var parseDependencySpec = (value) => {
1183
1202
  var mapWithConcurrency2 = async (values, limit, handler) => {
1184
1203
  const effectiveLimit = Math.max(1, limit);
1185
1204
  const workerCount = Math.min(effectiveLimit, values.length);
1186
- const results = new Array(values.length);
1205
+ const UNSET = /* @__PURE__ */ Symbol("map_with_concurrency_unset");
1206
+ const results = new Array(values.length).fill(UNSET);
1187
1207
  let index = 0;
1188
1208
  const workers = Array.from({ length: workerCount }, async () => {
1189
1209
  while (true) {
@@ -1193,12 +1213,13 @@ var mapWithConcurrency2 = async (values, limit, handler) => {
1193
1213
  return;
1194
1214
  }
1195
1215
  const value = values[current];
1196
- if (value !== void 0) {
1197
- results[current] = await handler(value);
1198
- }
1216
+ results[current] = await handler(value);
1199
1217
  }
1200
1218
  });
1201
1219
  await Promise.all(workers);
1220
+ if (results.some((value) => value === UNSET)) {
1221
+ throw new Error("map_with_concurrency_incomplete_results");
1222
+ }
1202
1223
  return results;
1203
1224
  };
1204
1225
  var analyzeDependencyCandidate = async (input, metadataProvider) => {
@@ -1363,8 +1384,8 @@ var NpmRegistryMetadataProvider = class {
1363
1384
  }
1364
1385
  };
1365
1386
  var NoopMetadataProvider = class {
1366
- async getMetadata(_name, _version, _context) {
1367
- return null;
1387
+ getMetadata(_name, _version, _context) {
1388
+ return Promise.resolve(null);
1368
1389
  }
1369
1390
  };
1370
1391
  var analyzeDependencyExposureFromProject = async (input, onProgress) => {
@@ -1445,7 +1466,9 @@ var diffSets = (current, baseline) => {
1445
1466
  return { added, removed };
1446
1467
  };
1447
1468
  var diffScoreMap = (current, baseline) => {
1448
- const keys = [.../* @__PURE__ */ new Set([...current.keys(), ...baseline.keys()])].sort((a, b) => a.localeCompare(b));
1469
+ const keys = [.../* @__PURE__ */ new Set([...current.keys(), ...baseline.keys()])].sort(
1470
+ (a, b) => a.localeCompare(b)
1471
+ );
1449
1472
  return keys.map((key) => {
1450
1473
  const before = baseline.get(key) ?? 0;
1451
1474
  const after = current.get(key) ?? 0;
@@ -1486,17 +1509,27 @@ var compareSnapshots = (current, baseline) => {
1486
1509
  singleMaintainerDependencies: [],
1487
1510
  abandonedDependencies: []
1488
1511
  };
1489
- const highRisk = diffSets(currentExternal.highRiskDependencies, baselineExternal.highRiskDependencies);
1512
+ const highRisk = diffSets(
1513
+ currentExternal.highRiskDependencies,
1514
+ baselineExternal.highRiskDependencies
1515
+ );
1490
1516
  const singleMaintainer = diffSets(
1491
1517
  currentExternal.singleMaintainerDependencies,
1492
1518
  baselineExternal.singleMaintainerDependencies
1493
1519
  );
1494
- const abandoned = diffSets(currentExternal.abandonedDependencies, baselineExternal.abandonedDependencies);
1520
+ const abandoned = diffSets(
1521
+ currentExternal.abandonedDependencies,
1522
+ baselineExternal.abandonedDependencies
1523
+ );
1495
1524
  const hotspots = diffSets(currentHotspots, baselineHotspots);
1496
1525
  const cycles = diffSets(currentCycles, baselineCycles);
1497
1526
  return {
1498
- repositoryScoreDelta: round43(current.analysis.risk.repositoryScore - baseline.analysis.risk.repositoryScore),
1499
- normalizedScoreDelta: round43(current.analysis.risk.normalizedScore - baseline.analysis.risk.normalizedScore),
1527
+ repositoryScoreDelta: round43(
1528
+ current.analysis.risk.repositoryScore - baseline.analysis.risk.repositoryScore
1529
+ ),
1530
+ normalizedScoreDelta: round43(
1531
+ current.analysis.risk.normalizedScore - baseline.analysis.risk.normalizedScore
1532
+ ),
1500
1533
  fileRiskChanges: diffScoreMap(currentFileScores, baselineFileScores),
1501
1534
  moduleRiskChanges: diffScoreMap(currentModuleScores, baselineModuleScores),
1502
1535
  newHotspots: hotspots.added,
@@ -1586,6 +1619,32 @@ var repositoryConfidence = (snapshot) => {
1586
1619
  );
1587
1620
  return round43(weighted / weight);
1588
1621
  };
1622
+ var repositoryDimensionScores = (snapshot) => {
1623
+ const target = findTraceTarget(snapshot, "repository", snapshot.analysis.structural.targetPath);
1624
+ if (target === void 0) {
1625
+ return {
1626
+ structural: null,
1627
+ evolution: null,
1628
+ external: null,
1629
+ interactions: null
1630
+ };
1631
+ }
1632
+ const structural = target.factors.find((factor) => factor.factorId === "repository.structural");
1633
+ const evolution = target.factors.find((factor) => factor.factorId === "repository.evolution");
1634
+ const external = target.factors.find((factor) => factor.factorId === "repository.external");
1635
+ const interactions = target.factors.find(
1636
+ (factor) => factor.factorId === "repository.composite.interactions"
1637
+ );
1638
+ const interactionScore = interactions === void 0 ? null : round43(
1639
+ ((interactions.rawMetrics["structuralEvolution"] ?? 0) + (interactions.rawMetrics["centralInstability"] ?? 0) + (interactions.rawMetrics["dependencyAmplification"] ?? 0)) * 100
1640
+ );
1641
+ return {
1642
+ structural: structural === void 0 ? null : round43((structural.rawMetrics["structuralDimension"] ?? 0) * 100),
1643
+ evolution: evolution === void 0 ? null : round43((evolution.rawMetrics["evolutionDimension"] ?? 0) * 100),
1644
+ external: external === void 0 ? null : round43((external.rawMetrics["externalDimension"] ?? 0) * 100),
1645
+ interactions: interactionScore
1646
+ };
1647
+ };
1589
1648
  var createReport = (snapshot, diff) => {
1590
1649
  const external = snapshot.analysis.external;
1591
1650
  return {
@@ -1596,7 +1655,8 @@ var createReport = (snapshot, diff) => {
1596
1655
  repositoryScore: snapshot.analysis.risk.repositoryScore,
1597
1656
  normalizedScore: snapshot.analysis.risk.normalizedScore,
1598
1657
  riskTier: toRiskTier(snapshot.analysis.risk.repositoryScore),
1599
- confidence: repositoryConfidence(snapshot)
1658
+ confidence: repositoryConfidence(snapshot),
1659
+ dimensionScores: repositoryDimensionScores(snapshot)
1600
1660
  },
1601
1661
  hotspots: hotspotItems(snapshot),
1602
1662
  structural: {
@@ -1616,10 +1676,18 @@ var createReport = (snapshot, diff) => {
1616
1676
  reason: external.reason
1617
1677
  } : {
1618
1678
  available: true,
1619
- highRiskDependencies: [...external.highRiskDependencies].sort((a, b) => a.localeCompare(b)),
1620
- highRiskDevelopmentDependencies: [...external.highRiskDevelopmentDependencies].sort((a, b) => a.localeCompare(b)),
1621
- singleMaintainerDependencies: [...external.singleMaintainerDependencies].sort((a, b) => a.localeCompare(b)),
1622
- abandonedDependencies: [...external.abandonedDependencies].sort((a, b) => a.localeCompare(b))
1679
+ highRiskDependencies: [...external.highRiskDependencies].sort(
1680
+ (a, b) => a.localeCompare(b)
1681
+ ),
1682
+ highRiskDevelopmentDependencies: [...external.highRiskDevelopmentDependencies].sort(
1683
+ (a, b) => a.localeCompare(b)
1684
+ ),
1685
+ singleMaintainerDependencies: [...external.singleMaintainerDependencies].sort(
1686
+ (a, b) => a.localeCompare(b)
1687
+ ),
1688
+ abandonedDependencies: [...external.abandonedDependencies].sort(
1689
+ (a, b) => a.localeCompare(b)
1690
+ )
1623
1691
  },
1624
1692
  appendix: {
1625
1693
  snapshotSchemaVersion: snapshot.schemaVersion,
@@ -1655,6 +1723,12 @@ var renderTextReport = (report) => {
1655
1723
  lines.push(` riskTier: ${report.repository.riskTier}`);
1656
1724
  lines.push(` confidence: ${report.repository.confidence ?? "n/a"}`);
1657
1725
  lines.push("");
1726
+ lines.push("Dimension Scores (0-100)");
1727
+ lines.push(` structural: ${report.repository.dimensionScores.structural ?? "n/a"}`);
1728
+ lines.push(` evolution: ${report.repository.dimensionScores.evolution ?? "n/a"}`);
1729
+ lines.push(` external: ${report.repository.dimensionScores.external ?? "n/a"}`);
1730
+ lines.push(` interactions: ${report.repository.dimensionScores.interactions ?? "n/a"}`);
1731
+ lines.push("");
1658
1732
  lines.push("Top Hotspots");
1659
1733
  for (const hotspot of report.hotspots) {
1660
1734
  lines.push(` - ${hotspot.target} | score=${hotspot.score}`);
@@ -1677,14 +1751,18 @@ var renderTextReport = (report) => {
1677
1751
  if (!report.external.available) {
1678
1752
  lines.push(` unavailable: ${report.external.reason}`);
1679
1753
  } else {
1680
- lines.push(` highRiskDependencies: ${report.external.highRiskDependencies.join(", ") || "none"}`);
1754
+ lines.push(
1755
+ ` highRiskDependencies: ${report.external.highRiskDependencies.join(", ") || "none"}`
1756
+ );
1681
1757
  lines.push(
1682
1758
  ` highRiskDevelopmentDependencies: ${report.external.highRiskDevelopmentDependencies.join(", ") || "none"}`
1683
1759
  );
1684
1760
  lines.push(
1685
1761
  ` singleMaintainerDependencies: ${report.external.singleMaintainerDependencies.join(", ") || "none"}`
1686
1762
  );
1687
- lines.push(` abandonedDependencies: ${report.external.abandonedDependencies.join(", ") || "none"}`);
1763
+ lines.push(
1764
+ ` abandonedDependencies: ${report.external.abandonedDependencies.join(", ") || "none"}`
1765
+ );
1688
1766
  }
1689
1767
  lines.push("");
1690
1768
  lines.push("Appendix");
@@ -1721,6 +1799,12 @@ var renderMarkdownReport = (report) => {
1721
1799
  lines.push(`- riskTier: \`${report.repository.riskTier}\``);
1722
1800
  lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
1723
1801
  lines.push("");
1802
+ lines.push("## Dimension Scores (0-100)");
1803
+ lines.push(`- structural: \`${report.repository.dimensionScores.structural ?? "n/a"}\``);
1804
+ lines.push(`- evolution: \`${report.repository.dimensionScores.evolution ?? "n/a"}\``);
1805
+ lines.push(`- external: \`${report.repository.dimensionScores.external ?? "n/a"}\``);
1806
+ lines.push(`- interactions: \`${report.repository.dimensionScores.interactions ?? "n/a"}\``);
1807
+ lines.push("");
1724
1808
  lines.push("## Top Hotspots");
1725
1809
  for (const hotspot of report.hotspots) {
1726
1810
  lines.push(`- **${hotspot.target}** (score: \`${hotspot.score}\`)`);
@@ -1737,21 +1821,27 @@ var renderMarkdownReport = (report) => {
1737
1821
  lines.push("");
1738
1822
  lines.push("## Structural Observations");
1739
1823
  lines.push(`- cycles detected: \`${report.structural.cycleCount}\``);
1740
- lines.push(`- cycles: ${report.structural.cycles.map((cycle) => `\`${cycle}\``).join(", ") || "none"}`);
1824
+ lines.push(
1825
+ `- cycles: ${report.structural.cycles.map((cycle) => `\`${cycle}\``).join(", ") || "none"}`
1826
+ );
1741
1827
  lines.push(`- fragile clusters: \`${report.structural.fragileClusters.length}\``);
1742
1828
  lines.push("");
1743
1829
  lines.push("## External Exposure Summary");
1744
1830
  if (!report.external.available) {
1745
1831
  lines.push(`- unavailable: \`${report.external.reason}\``);
1746
1832
  } else {
1747
- lines.push(`- high-risk dependencies: ${report.external.highRiskDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`);
1833
+ lines.push(
1834
+ `- high-risk dependencies: ${report.external.highRiskDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
1835
+ );
1748
1836
  lines.push(
1749
1837
  `- high-risk development dependencies: ${report.external.highRiskDevelopmentDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
1750
1838
  );
1751
1839
  lines.push(
1752
1840
  `- single maintainer dependencies: ${report.external.singleMaintainerDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
1753
1841
  );
1754
- lines.push(`- abandoned dependencies: ${report.external.abandonedDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`);
1842
+ lines.push(
1843
+ `- abandoned dependencies: ${report.external.abandonedDependencies.map((item) => `\`${item}\``).join(", ") || "none"}`
1844
+ );
1755
1845
  }
1756
1846
  lines.push("");
1757
1847
  lines.push("## Appendix");
@@ -1871,7 +1961,9 @@ var validateGateConfig = (input) => {
1871
1961
  throw new GovernanceConfigurationError("max-repo-score must be a number in [0, 100]");
1872
1962
  }
1873
1963
  if (config.newHotspotScoreThreshold !== void 0 && (!Number.isFinite(config.newHotspotScoreThreshold) || config.newHotspotScoreThreshold < 0 || config.newHotspotScoreThreshold > 100)) {
1874
- throw new GovernanceConfigurationError("new-hotspot-score-threshold must be a number in [0, 100]");
1964
+ throw new GovernanceConfigurationError(
1965
+ "new-hotspot-score-threshold must be a number in [0, 100]"
1966
+ );
1875
1967
  }
1876
1968
  };
1877
1969
  var evaluateGates = (input) => {
@@ -2021,7 +2113,9 @@ var renderCheckMarkdown = (snapshot, result) => {
2021
2113
  lines.push("## CodeSentinel CI Summary");
2022
2114
  lines.push(`- target: \`${snapshot.analysis.structural.targetPath}\``);
2023
2115
  lines.push(`- repositoryScore: \`${snapshot.analysis.risk.repositoryScore}\``);
2024
- lines.push(`- evaluatedGates: ${result.evaluatedGates.map((item) => `\`${item}\``).join(", ") || "none"}`);
2116
+ lines.push(
2117
+ `- evaluatedGates: ${result.evaluatedGates.map((item) => `\`${item}\``).join(", ") || "none"}`
2118
+ );
2025
2119
  lines.push(`- violations: \`${result.violations.length}\``);
2026
2120
  lines.push(`- exitCode: \`${result.exitCode}\``);
2027
2121
  const repositoryTrace = snapshot.trace?.targets.find(
@@ -2369,7 +2463,7 @@ var resolveAutoBaselineRef = async (input) => {
2369
2463
 
2370
2464
  // src/index.ts
2371
2465
  import { readFileSync as readFileSync2 } from "fs";
2372
- import { dirname, resolve as resolve5 } from "path";
2466
+ import { dirname as dirname2, resolve as resolve5 } from "path";
2373
2467
  import { fileURLToPath } from "url";
2374
2468
 
2375
2469
  // src/application/format-analyze-output.ts
@@ -2392,7 +2486,10 @@ var createSummaryShape = (summary) => ({
2392
2486
  0,
2393
2487
  10
2394
2488
  ),
2395
- transitiveExposureDependenciesTop: summary.external.transitiveExposureDependencies.slice(0, 10)
2489
+ transitiveExposureDependenciesTop: summary.external.transitiveExposureDependencies.slice(
2490
+ 0,
2491
+ 10
2492
+ )
2396
2493
  } : {
2397
2494
  available: false,
2398
2495
  reason: summary.external.reason
@@ -2413,13 +2510,16 @@ var formatAnalyzeOutput = (summary, mode) => mode === "json" ? JSON.stringify(su
2413
2510
  // src/application/format-explain-output.ts
2414
2511
  var sortFactorByContribution = (left, right) => right.contribution - left.contribution || left.factorId.localeCompare(right.factorId);
2415
2512
  var toRiskBand = (score) => {
2416
- if (score < 25) {
2513
+ if (score < 20) {
2417
2514
  return "low";
2418
2515
  }
2419
- if (score < 50) {
2516
+ if (score < 40) {
2420
2517
  return "moderate";
2421
2518
  }
2422
- if (score < 75) {
2519
+ if (score < 60) {
2520
+ return "elevated";
2521
+ }
2522
+ if (score < 80) {
2423
2523
  return "high";
2424
2524
  }
2425
2525
  return "very_high";
@@ -2444,6 +2544,7 @@ var factorLabelById2 = {
2444
2544
  };
2445
2545
  var formatFactorLabel = (factorId) => factorLabelById2[factorId] ?? factorId;
2446
2546
  var formatNumber = (value) => value === null || value === void 0 ? "n/a" : `${value}`;
2547
+ var formatDimension = (value) => value === null ? "n/a" : `${value}`;
2447
2548
  var formatFactorSummary = (factor) => `${formatFactorLabel(factor.factorId)} (+${factor.contribution}, confidence=${factor.confidence})`;
2448
2549
  var formatFactorEvidence = (factor) => {
2449
2550
  if (factor.factorId === "repository.structural") {
@@ -2473,6 +2574,36 @@ var formatFactorEvidence = (factor) => {
2473
2574
  return "evidence available in trace";
2474
2575
  };
2475
2576
  var findRepositoryTarget = (targets) => targets.find((target) => target.targetType === "repository");
2577
+ var repositoryDimensionScores2 = (repositoryTarget) => {
2578
+ if (repositoryTarget === void 0) {
2579
+ return {
2580
+ structural: null,
2581
+ evolution: null,
2582
+ external: null,
2583
+ interactions: null
2584
+ };
2585
+ }
2586
+ const structural = repositoryTarget.factors.find(
2587
+ (factor) => factor.factorId === "repository.structural"
2588
+ );
2589
+ const evolution = repositoryTarget.factors.find(
2590
+ (factor) => factor.factorId === "repository.evolution"
2591
+ );
2592
+ const external = repositoryTarget.factors.find(
2593
+ (factor) => factor.factorId === "repository.external"
2594
+ );
2595
+ const interactions = repositoryTarget.factors.find(
2596
+ (factor) => factor.factorId === "repository.composite.interactions"
2597
+ );
2598
+ return {
2599
+ structural: structural === void 0 ? null : Number(((structural.rawMetrics["structuralDimension"] ?? 0) * 100).toFixed(4)),
2600
+ evolution: evolution === void 0 ? null : Number(((evolution.rawMetrics["evolutionDimension"] ?? 0) * 100).toFixed(4)),
2601
+ external: external === void 0 ? null : Number(((external.rawMetrics["externalDimension"] ?? 0) * 100).toFixed(4)),
2602
+ interactions: interactions === void 0 ? null : Number(
2603
+ (((interactions.rawMetrics["structuralEvolution"] ?? 0) + (interactions.rawMetrics["centralInstability"] ?? 0) + (interactions.rawMetrics["dependencyAmplification"] ?? 0)) * 100).toFixed(4)
2604
+ )
2605
+ };
2606
+ };
2476
2607
  var buildRepositoryActions = (payload, repositoryTarget) => {
2477
2608
  if (repositoryTarget === void 0) {
2478
2609
  return ["No repository trace available."];
@@ -2518,12 +2649,8 @@ var renderTargetText = (target) => {
2518
2649
  lines.push(" top factors:");
2519
2650
  const topFactors = [...target.factors].sort(sortFactorByContribution).slice(0, 5);
2520
2651
  for (const factor of topFactors) {
2521
- lines.push(
2522
- ` - ${formatFactorSummary(factor)}`
2523
- );
2524
- lines.push(
2525
- ` evidence: ${formatFactorEvidence(factor)}`
2526
- );
2652
+ lines.push(` - ${formatFactorSummary(factor)}`);
2653
+ lines.push(` evidence: ${formatFactorEvidence(factor)}`);
2527
2654
  }
2528
2655
  lines.push(" reduction levers:");
2529
2656
  for (const lever of target.reductionLevers) {
@@ -2537,11 +2664,17 @@ var renderText = (payload) => {
2537
2664
  const lines = [];
2538
2665
  const repositoryTarget = findRepositoryTarget(payload.selectedTargets) ?? findRepositoryTarget(payload.trace.targets);
2539
2666
  const repositoryTopFactors = repositoryTarget === void 0 ? [] : [...repositoryTarget.factors].sort(sortFactorByContribution).slice(0, 3);
2667
+ const dimensionScores = repositoryDimensionScores2(repositoryTarget);
2540
2668
  const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
2541
2669
  lines.push(`target: ${payload.summary.structural.targetPath}`);
2542
2670
  lines.push(`repositoryScore: ${payload.summary.risk.repositoryScore}`);
2543
2671
  lines.push(`riskBand: ${toRiskBand(payload.summary.risk.repositoryScore)}`);
2544
2672
  lines.push(`selectedTargets: ${payload.selectedTargets.length}`);
2673
+ lines.push("dimensionScores:");
2674
+ lines.push(` structural: ${formatDimension(dimensionScores.structural)}`);
2675
+ lines.push(` evolution: ${formatDimension(dimensionScores.evolution)}`);
2676
+ lines.push(` external: ${formatDimension(dimensionScores.external)}`);
2677
+ lines.push(` interactions: ${formatDimension(dimensionScores.interactions)}`);
2545
2678
  lines.push("");
2546
2679
  lines.push("explanation:");
2547
2680
  lines.push(
@@ -2570,6 +2703,7 @@ var renderMarkdown = (payload) => {
2570
2703
  const lines = [];
2571
2704
  const repositoryTarget = findRepositoryTarget(payload.selectedTargets) ?? findRepositoryTarget(payload.trace.targets);
2572
2705
  const repositoryTopFactors = repositoryTarget === void 0 ? [] : [...repositoryTarget.factors].sort(sortFactorByContribution).slice(0, 3);
2706
+ const dimensionScores = repositoryDimensionScores2(repositoryTarget);
2573
2707
  const compositeFactors = repositoryTopFactors.filter((factor) => factor.family === "composite");
2574
2708
  lines.push(`# CodeSentinel Explanation`);
2575
2709
  lines.push(`- target: \`${payload.summary.structural.targetPath}\``);
@@ -2577,6 +2711,12 @@ var renderMarkdown = (payload) => {
2577
2711
  lines.push(`- riskBand: \`${toRiskBand(payload.summary.risk.repositoryScore)}\``);
2578
2712
  lines.push(`- selectedTargets: \`${payload.selectedTargets.length}\``);
2579
2713
  lines.push("");
2714
+ lines.push("## Dimension Scores (0-100)");
2715
+ lines.push(`- structural: \`${formatDimension(dimensionScores.structural)}\``);
2716
+ lines.push(`- evolution: \`${formatDimension(dimensionScores.evolution)}\``);
2717
+ lines.push(`- external: \`${formatDimension(dimensionScores.external)}\``);
2718
+ lines.push(`- interactions: \`${formatDimension(dimensionScores.interactions)}\``);
2719
+ lines.push("");
2580
2720
  lines.push(`## Summary`);
2581
2721
  lines.push(
2582
2722
  `- why risky: ${repositoryTopFactors.map(formatFactorSummary).join("; ") || "insufficient data"}`
@@ -2603,9 +2743,7 @@ var renderMarkdown = (payload) => {
2603
2743
  lines.push(
2604
2744
  ` - \`${formatFactorLabel(factor.factorId)}\` contribution=\`${factor.contribution}\` confidence=\`${factor.confidence}\``
2605
2745
  );
2606
- lines.push(
2607
- ` - evidence: \`${formatFactorEvidence(factor)}\``
2608
- );
2746
+ lines.push(` - evidence: \`${formatFactorEvidence(factor)}\``);
2609
2747
  }
2610
2748
  lines.push(`- Reduction levers:`);
2611
2749
  for (const lever of target.reductionLevers) {
@@ -2719,6 +2857,337 @@ var parseLogLevel = (value) => {
2719
2857
  }
2720
2858
  };
2721
2859
 
2860
+ // src/application/check-for-updates.ts
2861
+ import { spawn } from "child_process";
2862
+ import { mkdir, readFile, writeFile } from "fs/promises";
2863
+ import { homedir } from "os";
2864
+ import { dirname, join as join3 } from "path";
2865
+ import { stderr, stdin } from "process";
2866
+ import { clearScreenDown, cursorTo, emitKeypressEvents, moveCursor } from "readline";
2867
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2868
+ var UPDATE_CACHE_PATH = join3(homedir(), ".cache", "codesentinel", "update-check.json");
2869
+ var SEMVER_PATTERN = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<prerelease>[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
2870
+ var ANSI = {
2871
+ reset: "\x1B[0m",
2872
+ bold: "\x1B[1m",
2873
+ dim: "\x1B[2m",
2874
+ cyan: "\x1B[36m",
2875
+ green: "\x1B[32m",
2876
+ yellow: "\x1B[33m"
2877
+ };
2878
+ var parsePrereleaseIdentifier = (identifier) => {
2879
+ if (/^\d+$/.test(identifier)) {
2880
+ return Number.parseInt(identifier, 10);
2881
+ }
2882
+ return identifier;
2883
+ };
2884
+ var parseSemver2 = (value) => {
2885
+ const match = SEMVER_PATTERN.exec(value.trim());
2886
+ if (match === null) {
2887
+ return null;
2888
+ }
2889
+ const groups = match.groups;
2890
+ if (groups === void 0) {
2891
+ return null;
2892
+ }
2893
+ const majorRaw = groups["major"];
2894
+ const minorRaw = groups["minor"];
2895
+ const patchRaw = groups["patch"];
2896
+ const prereleaseRaw = groups["prerelease"];
2897
+ if (majorRaw === void 0 || minorRaw === void 0 || patchRaw === void 0) {
2898
+ return null;
2899
+ }
2900
+ const prerelease = prereleaseRaw === void 0 || prereleaseRaw.length === 0 ? [] : prereleaseRaw.split(".").map(parsePrereleaseIdentifier);
2901
+ return {
2902
+ major: Number.parseInt(majorRaw, 10),
2903
+ minor: Number.parseInt(minorRaw, 10),
2904
+ patch: Number.parseInt(patchRaw, 10),
2905
+ prerelease
2906
+ };
2907
+ };
2908
+ var comparePrerelease = (left, right) => {
2909
+ if (left.length === 0 && right.length === 0) {
2910
+ return 0;
2911
+ }
2912
+ if (left.length === 0) {
2913
+ return 1;
2914
+ }
2915
+ if (right.length === 0) {
2916
+ return -1;
2917
+ }
2918
+ const length = Math.max(left.length, right.length);
2919
+ for (let index = 0; index < length; index += 1) {
2920
+ const leftValue = left[index];
2921
+ const rightValue = right[index];
2922
+ if (leftValue === void 0) {
2923
+ return -1;
2924
+ }
2925
+ if (rightValue === void 0) {
2926
+ return 1;
2927
+ }
2928
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
2929
+ if (leftValue !== rightValue) {
2930
+ return leftValue > rightValue ? 1 : -1;
2931
+ }
2932
+ continue;
2933
+ }
2934
+ if (typeof leftValue === "number" && typeof rightValue === "string") {
2935
+ return -1;
2936
+ }
2937
+ if (typeof leftValue === "string" && typeof rightValue === "number") {
2938
+ return 1;
2939
+ }
2940
+ if (leftValue !== rightValue) {
2941
+ return leftValue > rightValue ? 1 : -1;
2942
+ }
2943
+ }
2944
+ return 0;
2945
+ };
2946
+ var compareVersions = (left, right) => {
2947
+ const leftParsed = parseSemver2(left);
2948
+ const rightParsed = parseSemver2(right);
2949
+ if (leftParsed === null || rightParsed === null) {
2950
+ return null;
2951
+ }
2952
+ if (leftParsed.major !== rightParsed.major) {
2953
+ return leftParsed.major > rightParsed.major ? 1 : -1;
2954
+ }
2955
+ if (leftParsed.minor !== rightParsed.minor) {
2956
+ return leftParsed.minor > rightParsed.minor ? 1 : -1;
2957
+ }
2958
+ if (leftParsed.patch !== rightParsed.patch) {
2959
+ return leftParsed.patch > rightParsed.patch ? 1 : -1;
2960
+ }
2961
+ return comparePrerelease(leftParsed.prerelease, rightParsed.prerelease);
2962
+ };
2963
+ var isTruthy = (value) => {
2964
+ if (value === void 0) {
2965
+ return false;
2966
+ }
2967
+ return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
2968
+ };
2969
+ var parseNpmViewVersionOutput = (output) => {
2970
+ const trimmed = output.trim();
2971
+ if (trimmed.length === 0) {
2972
+ return null;
2973
+ }
2974
+ try {
2975
+ const parsed = JSON.parse(trimmed);
2976
+ if (typeof parsed === "string" && parsed.trim().length > 0) {
2977
+ return parsed.trim();
2978
+ }
2979
+ if (Array.isArray(parsed) && parsed.length > 0) {
2980
+ const latest = parsed.at(-1);
2981
+ if (typeof latest === "string" && latest.trim().length > 0) {
2982
+ return latest.trim();
2983
+ }
2984
+ }
2985
+ } catch {
2986
+ return trimmed;
2987
+ }
2988
+ return null;
2989
+ };
2990
+ var readCache = async () => {
2991
+ try {
2992
+ const raw = await readFile(UPDATE_CACHE_PATH, "utf8");
2993
+ const parsed = JSON.parse(raw);
2994
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.lastCheckedAt === "string") {
2995
+ return { lastCheckedAt: parsed.lastCheckedAt };
2996
+ }
2997
+ } catch {
2998
+ return null;
2999
+ }
3000
+ return null;
3001
+ };
3002
+ var writeCache = async (cache) => {
3003
+ await mkdir(dirname(UPDATE_CACHE_PATH), { recursive: true });
3004
+ await writeFile(UPDATE_CACHE_PATH, JSON.stringify(cache), "utf8");
3005
+ };
3006
+ var shouldRunUpdateCheck = (input) => {
3007
+ if (!input.isInteractive) {
3008
+ return false;
3009
+ }
3010
+ if (isTruthy(input.env["CI"])) {
3011
+ return false;
3012
+ }
3013
+ if (isTruthy(input.env["CODESENTINEL_NO_UPDATE_NOTIFIER"])) {
3014
+ return false;
3015
+ }
3016
+ if (input.argv.some((argument) => argument === "--help" || argument === "-h")) {
3017
+ return false;
3018
+ }
3019
+ if (input.argv.some((argument) => argument === "--version" || argument === "-V")) {
3020
+ return false;
3021
+ }
3022
+ if (input.lastCheckedAt === null) {
3023
+ return true;
3024
+ }
3025
+ const lastCheckedMs = Date.parse(input.lastCheckedAt);
3026
+ if (!Number.isFinite(lastCheckedMs)) {
3027
+ return true;
3028
+ }
3029
+ return input.nowMs - lastCheckedMs >= UPDATE_CHECK_INTERVAL_MS;
3030
+ };
3031
+ var runCommand = async (command, args, mode) => {
3032
+ return await new Promise((resolvePromise, reject) => {
3033
+ const child = spawn(command, [...args], {
3034
+ stdio: mode === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"]
3035
+ });
3036
+ let stdoutRaw = "";
3037
+ if (mode === "capture" && child.stdout !== null) {
3038
+ child.stdout.setEncoding("utf8");
3039
+ child.stdout.on("data", (chunk) => {
3040
+ stdoutRaw += chunk;
3041
+ });
3042
+ }
3043
+ child.on("error", (error) => {
3044
+ reject(error);
3045
+ });
3046
+ child.on("close", (code) => {
3047
+ resolvePromise({ code: code ?? 1, stdout: stdoutRaw });
3048
+ });
3049
+ });
3050
+ };
3051
+ var fetchLatestVersion = async (packageName) => {
3052
+ const result = await runCommand("npm", ["view", packageName, "version", "--json"], "capture");
3053
+ if (result.code !== 0) {
3054
+ return null;
3055
+ }
3056
+ return parseNpmViewVersionOutput(result.stdout);
3057
+ };
3058
+ var renderUpdatePrompt = (latestVersion, currentVersion, selectedIndex) => {
3059
+ const options = ["Install update now", "Not now (continue current command)"];
3060
+ const lines = [
3061
+ `${ANSI.cyan}${ANSI.bold}CodeSentinel Update Available${ANSI.reset}`,
3062
+ `${ANSI.dim}Current: ${currentVersion} Latest: ${latestVersion}${ANSI.reset}`,
3063
+ "",
3064
+ ...options.map((option, index) => {
3065
+ const selected = index === selectedIndex;
3066
+ const prefix = selected ? `${ANSI.green}>${ANSI.reset}` : " ";
3067
+ const text = selected ? `${ANSI.bold}${option}${ANSI.reset}` : option;
3068
+ return `${prefix} ${text}`;
3069
+ }),
3070
+ "",
3071
+ `${ANSI.dim}Use \u2191/\u2193 to choose, Enter to confirm.${ANSI.reset}`
3072
+ ];
3073
+ stderr.write(lines.join("\n"));
3074
+ return lines.length;
3075
+ };
3076
+ var promptInstall = async (latestVersion, currentVersion) => {
3077
+ if (!stdin.isTTY || !stderr.isTTY || typeof stdin.setRawMode !== "function") {
3078
+ stderr.write(
3079
+ `New version ${latestVersion} is available (current ${currentVersion}). Run: npm install -g @getcodesentinel/codesentinel@latest
3080
+ `
3081
+ );
3082
+ return "skip";
3083
+ }
3084
+ return await new Promise((resolve6) => {
3085
+ emitKeypressEvents(stdin);
3086
+ let selectedIndex = 0;
3087
+ let renderedLines = 0;
3088
+ const previousRawMode = stdin.isRaw;
3089
+ const clearPromptArea = () => {
3090
+ if (renderedLines > 0) {
3091
+ moveCursor(stderr, 0, -(renderedLines - 1));
3092
+ }
3093
+ cursorTo(stderr, 0);
3094
+ clearScreenDown(stderr);
3095
+ };
3096
+ const redraw = () => {
3097
+ clearPromptArea();
3098
+ renderedLines = renderUpdatePrompt(latestVersion, currentVersion, selectedIndex);
3099
+ };
3100
+ const cleanup = (choice) => {
3101
+ stdin.off("keypress", onKeypress);
3102
+ stdin.pause();
3103
+ if (typeof stdin.setRawMode === "function") {
3104
+ stdin.setRawMode(previousRawMode);
3105
+ }
3106
+ clearPromptArea();
3107
+ if (choice === "install") {
3108
+ stderr.write(`${ANSI.yellow}Installing latest CodeSentinel...${ANSI.reset}
3109
+ `);
3110
+ } else if (renderedLines > 0) {
3111
+ stderr.write("\n");
3112
+ }
3113
+ resolve6(choice);
3114
+ };
3115
+ const onKeypress = (_str, key) => {
3116
+ if (key.ctrl === true && key.name === "c") {
3117
+ cleanup("interrupt");
3118
+ return;
3119
+ }
3120
+ if (key.name === "up") {
3121
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : 1;
3122
+ redraw();
3123
+ return;
3124
+ }
3125
+ if (key.name === "down") {
3126
+ selectedIndex = selectedIndex < 1 ? selectedIndex + 1 : 0;
3127
+ redraw();
3128
+ return;
3129
+ }
3130
+ if (key.name === "return" || key.name === "enter") {
3131
+ cleanup(selectedIndex === 0 ? "install" : "skip");
3132
+ }
3133
+ };
3134
+ stdin.on("keypress", onKeypress);
3135
+ if (typeof stdin.setRawMode === "function") {
3136
+ stdin.setRawMode(true);
3137
+ }
3138
+ stdin.resume();
3139
+ redraw();
3140
+ });
3141
+ };
3142
+ var installLatestVersion = async (packageName) => {
3143
+ const result = await runCommand("npm", ["install", "-g", `${packageName}@latest`], "inherit");
3144
+ return result.code === 0;
3145
+ };
3146
+ var checkForCliUpdates = async (input) => {
3147
+ try {
3148
+ const nowMs = Date.now();
3149
+ const cache = await readCache();
3150
+ const shouldCheck = shouldRunUpdateCheck({
3151
+ argv: input.argv,
3152
+ env: input.env,
3153
+ isInteractive: Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY),
3154
+ nowMs,
3155
+ lastCheckedAt: cache?.lastCheckedAt ?? null
3156
+ });
3157
+ if (!shouldCheck) {
3158
+ return;
3159
+ }
3160
+ await writeCache({ lastCheckedAt: new Date(nowMs).toISOString() });
3161
+ const latestVersion = await fetchLatestVersion(input.packageName);
3162
+ if (latestVersion === null) {
3163
+ return;
3164
+ }
3165
+ const comparison = compareVersions(latestVersion, input.currentVersion);
3166
+ if (comparison === null || comparison <= 0) {
3167
+ return;
3168
+ }
3169
+ const choice = await promptInstall(latestVersion, input.currentVersion);
3170
+ if (choice === "interrupt") {
3171
+ process.exit(130);
3172
+ }
3173
+ if (choice !== "install") {
3174
+ return;
3175
+ }
3176
+ const installed = await installLatestVersion(input.packageName);
3177
+ if (installed) {
3178
+ stderr.write(
3179
+ "CodeSentinel updated to latest version. Rerun your command to use the new version.\n"
3180
+ );
3181
+ process.exit(0);
3182
+ } else {
3183
+ stderr.write(
3184
+ "CodeSentinel update failed. You can retry with: npm install -g @getcodesentinel/codesentinel@latest\n"
3185
+ );
3186
+ }
3187
+ } catch {
3188
+ }
3189
+ };
3190
+
2722
3191
  // src/application/run-analyze-command.ts
2723
3192
  import { resolve as resolve3 } from "path";
2724
3193
 
@@ -3031,7 +3500,11 @@ var parseTsConfigFile = (configPath) => {
3031
3500
  return parsedCommandLine;
3032
3501
  };
3033
3502
  var collectFilesFromTsConfigGraph = (projectRoot) => {
3034
- const rootConfigPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
3503
+ const rootConfigPath = ts.findConfigFile(
3504
+ projectRoot,
3505
+ (filePath) => ts.sys.fileExists(filePath),
3506
+ "tsconfig.json"
3507
+ );
3035
3508
  if (rootConfigPath === void 0) {
3036
3509
  return null;
3037
3510
  }
@@ -3053,7 +3526,11 @@ var collectFilesFromTsConfigGraph = (projectRoot) => {
3053
3526
  }
3054
3527
  for (const reference of parsed.projectReferences ?? []) {
3055
3528
  const referencePath = resolve2(reference.path);
3056
- const referenceConfigPath = ts.sys.directoryExists(referencePath) ? ts.findConfigFile(referencePath, ts.sys.fileExists, "tsconfig.json") : referencePath;
3529
+ const referenceConfigPath = ts.sys.directoryExists(referencePath) ? ts.findConfigFile(
3530
+ referencePath,
3531
+ (filePath) => ts.sys.fileExists(filePath),
3532
+ "tsconfig.json"
3533
+ ) : referencePath;
3057
3534
  if (referenceConfigPath !== void 0 && ts.sys.fileExists(referenceConfigPath)) {
3058
3535
  visitConfig(referenceConfigPath);
3059
3536
  }
@@ -3215,7 +3692,12 @@ var parseTypescriptProject = (projectPath, onProgress) => {
3215
3692
  const cacheKey = `${sourcePath}\0${specifier}`;
3216
3693
  let resolvedPath = resolverCache.get(cacheKey);
3217
3694
  if (resolvedPath === void 0 && !resolverCache.has(cacheKey)) {
3218
- const resolved = ts.resolveModuleName(specifier, sourcePath, options, ts.sys).resolvedModule;
3695
+ const resolved = ts.resolveModuleName(
3696
+ specifier,
3697
+ sourcePath,
3698
+ options,
3699
+ ts.sys
3700
+ ).resolvedModule;
3219
3701
  if (resolved !== void 0) {
3220
3702
  resolvedPath = normalizePath(resolve2(resolved.resolvedFileName));
3221
3703
  }
@@ -3299,7 +3781,10 @@ var buildAuthorAliasMap = (commits) => {
3299
3781
  const nameCountsByAuthorId = /* @__PURE__ */ new Map();
3300
3782
  const commitCountByAuthorId = /* @__PURE__ */ new Map();
3301
3783
  for (const commit of commits) {
3302
- commitCountByAuthorId.set(commit.authorId, (commitCountByAuthorId.get(commit.authorId) ?? 0) + 1);
3784
+ commitCountByAuthorId.set(
3785
+ commit.authorId,
3786
+ (commitCountByAuthorId.get(commit.authorId) ?? 0) + 1
3787
+ );
3303
3788
  const normalizedName = normalizeName(commit.authorName);
3304
3789
  const names = nameCountsByAuthorId.get(commit.authorId) ?? /* @__PURE__ */ new Map();
3305
3790
  if (normalizedName.length > 0) {
@@ -3307,19 +3792,21 @@ var buildAuthorAliasMap = (commits) => {
3307
3792
  }
3308
3793
  nameCountsByAuthorId.set(commit.authorId, names);
3309
3794
  }
3310
- const profiles = [...commitCountByAuthorId.entries()].map(([authorId, commitCount]) => {
3311
- const names = nameCountsByAuthorId.get(authorId);
3312
- const primaryName = names === void 0 ? "" : [...names.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? "";
3313
- const normalizedAuthorId = authorId.toLowerCase();
3314
- const isBot = normalizedAuthorId.includes("[bot]");
3315
- return {
3316
- authorId,
3317
- commitCount,
3318
- primaryName,
3319
- emailStem: isBot ? null : extractEmailStem(authorId),
3320
- isBot
3321
- };
3322
- });
3795
+ const profiles = [...commitCountByAuthorId.entries()].map(
3796
+ ([authorId, commitCount]) => {
3797
+ const names = nameCountsByAuthorId.get(authorId);
3798
+ const primaryName = names === void 0 ? "" : [...names.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? "";
3799
+ const normalizedAuthorId = authorId.toLowerCase();
3800
+ const isBot = normalizedAuthorId.includes("[bot]");
3801
+ return {
3802
+ authorId,
3803
+ commitCount,
3804
+ primaryName,
3805
+ emailStem: isBot ? null : extractEmailStem(authorId),
3806
+ isBot
3807
+ };
3808
+ }
3809
+ );
3323
3810
  const groupsByStem = /* @__PURE__ */ new Map();
3324
3811
  for (const profile of profiles) {
3325
3812
  if (profile.emailStem === null || profile.emailStem.length < 4) {
@@ -3425,7 +3912,10 @@ var selectHotspots = (files, config) => {
3425
3912
  const sorted = [...files].sort(
3426
3913
  (a, b) => b.commitCount - a.commitCount || b.churnTotal - a.churnTotal || a.filePath.localeCompare(b.filePath)
3427
3914
  );
3428
- const hotspotCount = Math.max(config.hotspotMinFiles, Math.ceil(sorted.length * config.hotspotTopPercent));
3915
+ const hotspotCount = Math.max(
3916
+ config.hotspotMinFiles,
3917
+ Math.ceil(sorted.length * config.hotspotTopPercent)
3918
+ );
3429
3919
  const selected = sorted.slice(0, hotspotCount);
3430
3920
  const hotspots = selected.map((file, index) => ({
3431
3921
  filePath: file.filePath,
@@ -3734,7 +4224,10 @@ var GitCliHistoryProvider = class {
3734
4224
  "--find-renames"
3735
4225
  ]);
3736
4226
  onProgress?.({ stage: "git_log_received", bytes: Buffer.byteLength(output, "utf8") });
3737
- const commits = parseGitLog(output, (event) => onProgress?.(mapParseProgressToHistoryProgress(event)));
4227
+ const commits = parseGitLog(
4228
+ output,
4229
+ (event) => onProgress?.(mapParseProgressToHistoryProgress(event))
4230
+ );
3738
4231
  onProgress?.({ stage: "git_log_parsed", commits: commits.length });
3739
4232
  return commits;
3740
4233
  }
@@ -3920,7 +4413,10 @@ var dependencySignalWeightBudget = Object.values(dependencySignalWeights).reduce
3920
4413
  0
3921
4414
  );
3922
4415
  var computeDependencySignalScore = (ownSignals, inheritedSignals, inheritedSignalMultiplier) => {
3923
- const ownWeight = ownSignals.reduce((sum, signal) => sum + (dependencySignalWeights[signal] ?? 0), 0);
4416
+ const ownWeight = ownSignals.reduce(
4417
+ (sum, signal) => sum + (dependencySignalWeights[signal] ?? 0),
4418
+ 0
4419
+ );
3924
4420
  const inheritedWeight = inheritedSignals.reduce(
3925
4421
  (sum, signal) => sum + (dependencySignalWeights[signal] ?? 0),
3926
4422
  0
@@ -3986,16 +4482,12 @@ var buildFactorTraces = (totalScore, inputs) => {
3986
4482
  }
3987
4483
  return traces;
3988
4484
  };
3989
- var buildReductionLevers = (factors) => factors.filter((factor) => factor.contribution > 0).sort(
3990
- (a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)
3991
- ).slice(0, 3).map((factor) => ({
4485
+ 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) => ({
3992
4486
  factorId: factor.factorId,
3993
4487
  estimatedImpact: round45(factor.contribution)
3994
4488
  }));
3995
4489
  var buildTargetTrace = (targetType, targetId, totalScore, normalizedScore, factors) => {
3996
- const dominantFactors = [...factors].filter((factor) => factor.contribution > 0).sort(
3997
- (a, b) => b.contribution - a.contribution || a.factorId.localeCompare(b.factorId)
3998
- ).slice(0, 3).map((factor) => factor.factorId);
4490
+ 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);
3999
4491
  return {
4000
4492
  targetType,
4001
4493
  targetId,
@@ -4017,7 +4509,9 @@ var computeDependencyScores = (external, config) => {
4017
4509
  const transitiveCounts = external.dependencies.map(
4018
4510
  (dependency) => logScale(dependency.transitiveDependencies.length)
4019
4511
  );
4020
- const dependentCounts = external.dependencies.map((dependency) => logScale(dependency.dependents));
4512
+ const dependentCounts = external.dependencies.map(
4513
+ (dependency) => logScale(dependency.dependents)
4514
+ );
4021
4515
  const chainDepths = external.dependencies.map((dependency) => dependency.dependencyDepth);
4022
4516
  const transitiveScale = buildQuantileScale(
4023
4517
  transitiveCounts,
@@ -4312,8 +4806,14 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4312
4806
  let busFactorRisk = 0;
4313
4807
  const evolutionMetrics = evolutionByFile.get(filePath);
4314
4808
  if (evolution.available && evolutionMetrics !== void 0) {
4315
- frequencyRisk = normalizeWithScale(logScale(evolutionMetrics.commitCount), evolutionScales.commitCount);
4316
- churnRisk = normalizeWithScale(logScale(evolutionMetrics.churnTotal), evolutionScales.churnTotal);
4809
+ frequencyRisk = normalizeWithScale(
4810
+ logScale(evolutionMetrics.commitCount),
4811
+ evolutionScales.commitCount
4812
+ );
4813
+ churnRisk = normalizeWithScale(
4814
+ logScale(evolutionMetrics.churnTotal),
4815
+ evolutionScales.churnTotal
4816
+ );
4317
4817
  volatilityRisk = toUnitInterval(evolutionMetrics.recentVolatility);
4318
4818
  ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.topAuthorShare);
4319
4819
  busFactorRisk = toUnitInterval(
@@ -4567,7 +5067,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4567
5067
  externalPressure: round45(zone.externalPressure)
4568
5068
  }));
4569
5069
  if (collector !== void 0 && external.available) {
4570
- const dependencyByName = new Map(external.dependencies.map((dependency) => [dependency.name, dependency]));
5070
+ const dependencyByName = new Map(
5071
+ external.dependencies.map((dependency) => [dependency.name, dependency])
5072
+ );
4571
5073
  for (const dependencyScore of dependencyComputation.dependencyScores) {
4572
5074
  const dependency = dependencyByName.get(dependencyScore.dependency);
4573
5075
  const context = dependencyComputation.dependencyContexts.get(dependencyScore.dependency);
@@ -4598,7 +5100,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4598
5100
  normalizedMetrics: { stalenessRisk: context.stalenessRisk },
4599
5101
  weight: config.dependencyFactorWeights.staleness,
4600
5102
  amplification: null,
4601
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "daysSinceLastRelease" }],
5103
+ evidence: [
5104
+ { kind: "dependency_metric", target: dependency.name, metric: "daysSinceLastRelease" }
5105
+ ],
4602
5106
  confidence: hasMetadata ? 0.9 : 0.5
4603
5107
  },
4604
5108
  {
@@ -4611,7 +5115,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4611
5115
  },
4612
5116
  weight: config.dependencyFactorWeights.maintainerConcentration,
4613
5117
  amplification: null,
4614
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "maintainerCount" }],
5118
+ evidence: [
5119
+ { kind: "dependency_metric", target: dependency.name, metric: "maintainerCount" }
5120
+ ],
4615
5121
  confidence: hasMetadata ? 0.9 : 0.5
4616
5122
  },
4617
5123
  {
@@ -4630,7 +5136,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4630
5136
  },
4631
5137
  weight: config.dependencyFactorWeights.transitiveBurden + config.dependencyFactorWeights.centrality + config.dependencyFactorWeights.chainDepth,
4632
5138
  amplification: null,
4633
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "dependencyDepth" }],
5139
+ evidence: [
5140
+ { kind: "dependency_metric", target: dependency.name, metric: "dependencyDepth" }
5141
+ ],
4634
5142
  confidence: 1
4635
5143
  },
4636
5144
  {
@@ -4652,7 +5160,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4652
5160
  normalizedMetrics: { popularityDampener: context.popularityDampener },
4653
5161
  weight: config.dependencySignals.popularityMaxDampening,
4654
5162
  amplification: null,
4655
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "weeklyDownloads" }],
5163
+ evidence: [
5164
+ { kind: "dependency_metric", target: dependency.name, metric: "weeklyDownloads" }
5165
+ ],
4656
5166
  confidence: context.rawMetrics.weeklyDownloads === null ? 0.4 : 0.9
4657
5167
  }
4658
5168
  ]);
@@ -5041,7 +5551,12 @@ var collectAnalysisInputs = async (inputPath, authorIdentityMode, options = {},
5041
5551
  };
5042
5552
  };
5043
5553
  var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logger = createSilentLogger()) => {
5044
- const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, options, logger);
5554
+ const analysisInputs = await collectAnalysisInputs(
5555
+ inputPath,
5556
+ authorIdentityMode,
5557
+ options,
5558
+ logger
5559
+ );
5045
5560
  logger.info("computing risk summary");
5046
5561
  const risk = computeRepositoryRiskSummary(analysisInputs);
5047
5562
  logger.info(`analysis completed (repositoryScore=${risk.repositoryScore})`);
@@ -5052,7 +5567,7 @@ var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logg
5052
5567
  };
5053
5568
 
5054
5569
  // src/application/run-check-command.ts
5055
- import { readFile, writeFile } from "fs/promises";
5570
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5056
5571
 
5057
5572
  // src/application/build-analysis-snapshot.ts
5058
5573
  var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logger) => {
@@ -5117,7 +5632,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5117
5632
  let diff;
5118
5633
  if (options.baselinePath !== void 0) {
5119
5634
  logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5120
- const baselineRaw = await readFile(options.baselinePath, "utf8");
5635
+ const baselineRaw = await readFile2(options.baselinePath, "utf8");
5121
5636
  try {
5122
5637
  baseline = parseSnapshot(baselineRaw);
5123
5638
  } catch (error) {
@@ -5143,7 +5658,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5143
5658
  options.outputFormat
5144
5659
  );
5145
5660
  if (options.outputPath !== void 0) {
5146
- await writeFile(options.outputPath, rendered, "utf8");
5661
+ await writeFile2(options.outputPath, rendered, "utf8");
5147
5662
  logger.info(`check output written: ${options.outputPath}`);
5148
5663
  }
5149
5664
  return {
@@ -5156,7 +5671,7 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
5156
5671
  };
5157
5672
 
5158
5673
  // src/application/run-ci-command.ts
5159
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5674
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
5160
5675
  import { relative as relative2, resolve as resolve4 } from "path";
5161
5676
  var isPathOutsideBase = (value) => {
5162
5677
  return value === ".." || value.startsWith("../") || value.startsWith("..\\");
@@ -5168,9 +5683,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5168
5683
  );
5169
5684
  }
5170
5685
  if (options.baselineSha !== void 0 && options.baselineRef !== "auto") {
5171
- throw new GovernanceConfigurationError(
5172
- "baseline-sha requires --baseline-ref auto"
5173
- );
5686
+ throw new GovernanceConfigurationError("baseline-sha requires --baseline-ref auto");
5174
5687
  }
5175
5688
  const resolvedTargetPath = resolve4(inputPath ?? process.cwd());
5176
5689
  logger.info("building current snapshot");
@@ -5184,7 +5697,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5184
5697
  logger
5185
5698
  );
5186
5699
  if (options.snapshotPath !== void 0) {
5187
- await writeFile2(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5700
+ await writeFile3(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5188
5701
  logger.info(`snapshot written: ${options.snapshotPath}`);
5189
5702
  }
5190
5703
  let baseline;
@@ -5256,7 +5769,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5256
5769
  diff = compareSnapshots(current, baseline);
5257
5770
  } else if (options.baselinePath !== void 0) {
5258
5771
  logger.info(`loading baseline snapshot: ${options.baselinePath}`);
5259
- const baselineRaw = await readFile2(options.baselinePath, "utf8");
5772
+ const baselineRaw = await readFile3(options.baselinePath, "utf8");
5260
5773
  try {
5261
5774
  baseline = parseSnapshot(baselineRaw);
5262
5775
  } catch (error) {
@@ -5278,7 +5791,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5278
5791
 
5279
5792
  ${ciMarkdown}`;
5280
5793
  if (options.reportPath !== void 0) {
5281
- await writeFile2(options.reportPath, markdownSummary, "utf8");
5794
+ await writeFile3(options.reportPath, markdownSummary, "utf8");
5282
5795
  logger.info(`report written: ${options.reportPath}`);
5283
5796
  }
5284
5797
  const machineReadable = {
@@ -5290,7 +5803,7 @@ ${ciMarkdown}`;
5290
5803
  exitCode: gateResult.exitCode
5291
5804
  };
5292
5805
  if (options.jsonOutputPath !== void 0) {
5293
- await writeFile2(options.jsonOutputPath, JSON.stringify(machineReadable, null, 2), "utf8");
5806
+ await writeFile3(options.jsonOutputPath, JSON.stringify(machineReadable, null, 2), "utf8");
5294
5807
  logger.info(`ci machine output written: ${options.jsonOutputPath}`);
5295
5808
  }
5296
5809
  return {
@@ -5304,7 +5817,7 @@ ${ciMarkdown}`;
5304
5817
  };
5305
5818
 
5306
5819
  // src/application/run-report-command.ts
5307
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
5820
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
5308
5821
  var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
5309
5822
  logger.info("building analysis snapshot");
5310
5823
  const current = await buildAnalysisSnapshot(
@@ -5317,7 +5830,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
5317
5830
  logger
5318
5831
  );
5319
5832
  if (options.snapshotPath !== void 0) {
5320
- await writeFile3(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5833
+ await writeFile4(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
5321
5834
  logger.info(`snapshot written: ${options.snapshotPath}`);
5322
5835
  }
5323
5836
  let report;
@@ -5325,14 +5838,14 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
5325
5838
  report = createReport(current);
5326
5839
  } else {
5327
5840
  logger.info(`loading baseline snapshot: ${options.comparePath}`);
5328
- const baselineRaw = await readFile3(options.comparePath, "utf8");
5841
+ const baselineRaw = await readFile4(options.comparePath, "utf8");
5329
5842
  const baseline = parseSnapshot(baselineRaw);
5330
5843
  const diff = compareSnapshots(current, baseline);
5331
5844
  report = createReport(current, diff);
5332
5845
  }
5333
5846
  const rendered = formatReport(report, options.format);
5334
5847
  if (options.outputPath !== void 0) {
5335
- await writeFile3(options.outputPath, rendered, "utf8");
5848
+ await writeFile4(options.outputPath, rendered, "utf8");
5336
5849
  logger.info(`report written: ${options.outputPath}`);
5337
5850
  }
5338
5851
  return { report, rendered };
@@ -5386,7 +5899,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
5386
5899
 
5387
5900
  // src/index.ts
5388
5901
  var program = new Command();
5389
- var packageJsonPath = resolve5(dirname(fileURLToPath(import.meta.url)), "../package.json");
5902
+ var packageJsonPath = resolve5(dirname2(fileURLToPath(import.meta.url)), "../package.json");
5390
5903
  var { version } = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
5391
5904
  var parseRecentWindowDays = (value) => {
5392
5905
  const parsed = Number.parseInt(value, 10);
@@ -5704,5 +6217,11 @@ if (argv.length <= 2) {
5704
6217
  program.outputHelp();
5705
6218
  process.exit(0);
5706
6219
  }
6220
+ await checkForCliUpdates({
6221
+ packageName: "@getcodesentinel/codesentinel",
6222
+ currentVersion: version,
6223
+ argv: process.argv,
6224
+ env: process.env
6225
+ });
5707
6226
  await program.parseAsync(argv);
5708
6227
  //# sourceMappingURL=index.js.map