@getcodesentinel/codesentinel 1.10.1 → 1.11.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(
@@ -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) {
@@ -3031,7 +3169,11 @@ var parseTsConfigFile = (configPath) => {
3031
3169
  return parsedCommandLine;
3032
3170
  };
3033
3171
  var collectFilesFromTsConfigGraph = (projectRoot) => {
3034
- const rootConfigPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
3172
+ const rootConfigPath = ts.findConfigFile(
3173
+ projectRoot,
3174
+ (filePath) => ts.sys.fileExists(filePath),
3175
+ "tsconfig.json"
3176
+ );
3035
3177
  if (rootConfigPath === void 0) {
3036
3178
  return null;
3037
3179
  }
@@ -3053,7 +3195,11 @@ var collectFilesFromTsConfigGraph = (projectRoot) => {
3053
3195
  }
3054
3196
  for (const reference of parsed.projectReferences ?? []) {
3055
3197
  const referencePath = resolve2(reference.path);
3056
- const referenceConfigPath = ts.sys.directoryExists(referencePath) ? ts.findConfigFile(referencePath, ts.sys.fileExists, "tsconfig.json") : referencePath;
3198
+ const referenceConfigPath = ts.sys.directoryExists(referencePath) ? ts.findConfigFile(
3199
+ referencePath,
3200
+ (filePath) => ts.sys.fileExists(filePath),
3201
+ "tsconfig.json"
3202
+ ) : referencePath;
3057
3203
  if (referenceConfigPath !== void 0 && ts.sys.fileExists(referenceConfigPath)) {
3058
3204
  visitConfig(referenceConfigPath);
3059
3205
  }
@@ -3215,7 +3361,12 @@ var parseTypescriptProject = (projectPath, onProgress) => {
3215
3361
  const cacheKey = `${sourcePath}\0${specifier}`;
3216
3362
  let resolvedPath = resolverCache.get(cacheKey);
3217
3363
  if (resolvedPath === void 0 && !resolverCache.has(cacheKey)) {
3218
- const resolved = ts.resolveModuleName(specifier, sourcePath, options, ts.sys).resolvedModule;
3364
+ const resolved = ts.resolveModuleName(
3365
+ specifier,
3366
+ sourcePath,
3367
+ options,
3368
+ ts.sys
3369
+ ).resolvedModule;
3219
3370
  if (resolved !== void 0) {
3220
3371
  resolvedPath = normalizePath(resolve2(resolved.resolvedFileName));
3221
3372
  }
@@ -3299,7 +3450,10 @@ var buildAuthorAliasMap = (commits) => {
3299
3450
  const nameCountsByAuthorId = /* @__PURE__ */ new Map();
3300
3451
  const commitCountByAuthorId = /* @__PURE__ */ new Map();
3301
3452
  for (const commit of commits) {
3302
- commitCountByAuthorId.set(commit.authorId, (commitCountByAuthorId.get(commit.authorId) ?? 0) + 1);
3453
+ commitCountByAuthorId.set(
3454
+ commit.authorId,
3455
+ (commitCountByAuthorId.get(commit.authorId) ?? 0) + 1
3456
+ );
3303
3457
  const normalizedName = normalizeName(commit.authorName);
3304
3458
  const names = nameCountsByAuthorId.get(commit.authorId) ?? /* @__PURE__ */ new Map();
3305
3459
  if (normalizedName.length > 0) {
@@ -3307,19 +3461,21 @@ var buildAuthorAliasMap = (commits) => {
3307
3461
  }
3308
3462
  nameCountsByAuthorId.set(commit.authorId, names);
3309
3463
  }
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
- });
3464
+ const profiles = [...commitCountByAuthorId.entries()].map(
3465
+ ([authorId, commitCount]) => {
3466
+ const names = nameCountsByAuthorId.get(authorId);
3467
+ const primaryName = names === void 0 ? "" : [...names.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? "";
3468
+ const normalizedAuthorId = authorId.toLowerCase();
3469
+ const isBot = normalizedAuthorId.includes("[bot]");
3470
+ return {
3471
+ authorId,
3472
+ commitCount,
3473
+ primaryName,
3474
+ emailStem: isBot ? null : extractEmailStem(authorId),
3475
+ isBot
3476
+ };
3477
+ }
3478
+ );
3323
3479
  const groupsByStem = /* @__PURE__ */ new Map();
3324
3480
  for (const profile of profiles) {
3325
3481
  if (profile.emailStem === null || profile.emailStem.length < 4) {
@@ -3425,7 +3581,10 @@ var selectHotspots = (files, config) => {
3425
3581
  const sorted = [...files].sort(
3426
3582
  (a, b) => b.commitCount - a.commitCount || b.churnTotal - a.churnTotal || a.filePath.localeCompare(b.filePath)
3427
3583
  );
3428
- const hotspotCount = Math.max(config.hotspotMinFiles, Math.ceil(sorted.length * config.hotspotTopPercent));
3584
+ const hotspotCount = Math.max(
3585
+ config.hotspotMinFiles,
3586
+ Math.ceil(sorted.length * config.hotspotTopPercent)
3587
+ );
3429
3588
  const selected = sorted.slice(0, hotspotCount);
3430
3589
  const hotspots = selected.map((file, index) => ({
3431
3590
  filePath: file.filePath,
@@ -3734,7 +3893,10 @@ var GitCliHistoryProvider = class {
3734
3893
  "--find-renames"
3735
3894
  ]);
3736
3895
  onProgress?.({ stage: "git_log_received", bytes: Buffer.byteLength(output, "utf8") });
3737
- const commits = parseGitLog(output, (event) => onProgress?.(mapParseProgressToHistoryProgress(event)));
3896
+ const commits = parseGitLog(
3897
+ output,
3898
+ (event) => onProgress?.(mapParseProgressToHistoryProgress(event))
3899
+ );
3738
3900
  onProgress?.({ stage: "git_log_parsed", commits: commits.length });
3739
3901
  return commits;
3740
3902
  }
@@ -3920,7 +4082,10 @@ var dependencySignalWeightBudget = Object.values(dependencySignalWeights).reduce
3920
4082
  0
3921
4083
  );
3922
4084
  var computeDependencySignalScore = (ownSignals, inheritedSignals, inheritedSignalMultiplier) => {
3923
- const ownWeight = ownSignals.reduce((sum, signal) => sum + (dependencySignalWeights[signal] ?? 0), 0);
4085
+ const ownWeight = ownSignals.reduce(
4086
+ (sum, signal) => sum + (dependencySignalWeights[signal] ?? 0),
4087
+ 0
4088
+ );
3924
4089
  const inheritedWeight = inheritedSignals.reduce(
3925
4090
  (sum, signal) => sum + (dependencySignalWeights[signal] ?? 0),
3926
4091
  0
@@ -3986,16 +4151,12 @@ var buildFactorTraces = (totalScore, inputs) => {
3986
4151
  }
3987
4152
  return traces;
3988
4153
  };
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) => ({
4154
+ 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
4155
  factorId: factor.factorId,
3993
4156
  estimatedImpact: round45(factor.contribution)
3994
4157
  }));
3995
4158
  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);
4159
+ 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
4160
  return {
4000
4161
  targetType,
4001
4162
  targetId,
@@ -4017,7 +4178,9 @@ var computeDependencyScores = (external, config) => {
4017
4178
  const transitiveCounts = external.dependencies.map(
4018
4179
  (dependency) => logScale(dependency.transitiveDependencies.length)
4019
4180
  );
4020
- const dependentCounts = external.dependencies.map((dependency) => logScale(dependency.dependents));
4181
+ const dependentCounts = external.dependencies.map(
4182
+ (dependency) => logScale(dependency.dependents)
4183
+ );
4021
4184
  const chainDepths = external.dependencies.map((dependency) => dependency.dependencyDepth);
4022
4185
  const transitiveScale = buildQuantileScale(
4023
4186
  transitiveCounts,
@@ -4312,8 +4475,14 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4312
4475
  let busFactorRisk = 0;
4313
4476
  const evolutionMetrics = evolutionByFile.get(filePath);
4314
4477
  if (evolution.available && evolutionMetrics !== void 0) {
4315
- frequencyRisk = normalizeWithScale(logScale(evolutionMetrics.commitCount), evolutionScales.commitCount);
4316
- churnRisk = normalizeWithScale(logScale(evolutionMetrics.churnTotal), evolutionScales.churnTotal);
4478
+ frequencyRisk = normalizeWithScale(
4479
+ logScale(evolutionMetrics.commitCount),
4480
+ evolutionScales.commitCount
4481
+ );
4482
+ churnRisk = normalizeWithScale(
4483
+ logScale(evolutionMetrics.churnTotal),
4484
+ evolutionScales.churnTotal
4485
+ );
4317
4486
  volatilityRisk = toUnitInterval(evolutionMetrics.recentVolatility);
4318
4487
  ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.topAuthorShare);
4319
4488
  busFactorRisk = toUnitInterval(
@@ -4567,7 +4736,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4567
4736
  externalPressure: round45(zone.externalPressure)
4568
4737
  }));
4569
4738
  if (collector !== void 0 && external.available) {
4570
- const dependencyByName = new Map(external.dependencies.map((dependency) => [dependency.name, dependency]));
4739
+ const dependencyByName = new Map(
4740
+ external.dependencies.map((dependency) => [dependency.name, dependency])
4741
+ );
4571
4742
  for (const dependencyScore of dependencyComputation.dependencyScores) {
4572
4743
  const dependency = dependencyByName.get(dependencyScore.dependency);
4573
4744
  const context = dependencyComputation.dependencyContexts.get(dependencyScore.dependency);
@@ -4598,7 +4769,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4598
4769
  normalizedMetrics: { stalenessRisk: context.stalenessRisk },
4599
4770
  weight: config.dependencyFactorWeights.staleness,
4600
4771
  amplification: null,
4601
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "daysSinceLastRelease" }],
4772
+ evidence: [
4773
+ { kind: "dependency_metric", target: dependency.name, metric: "daysSinceLastRelease" }
4774
+ ],
4602
4775
  confidence: hasMetadata ? 0.9 : 0.5
4603
4776
  },
4604
4777
  {
@@ -4611,7 +4784,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4611
4784
  },
4612
4785
  weight: config.dependencyFactorWeights.maintainerConcentration,
4613
4786
  amplification: null,
4614
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "maintainerCount" }],
4787
+ evidence: [
4788
+ { kind: "dependency_metric", target: dependency.name, metric: "maintainerCount" }
4789
+ ],
4615
4790
  confidence: hasMetadata ? 0.9 : 0.5
4616
4791
  },
4617
4792
  {
@@ -4630,7 +4805,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4630
4805
  },
4631
4806
  weight: config.dependencyFactorWeights.transitiveBurden + config.dependencyFactorWeights.centrality + config.dependencyFactorWeights.chainDepth,
4632
4807
  amplification: null,
4633
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "dependencyDepth" }],
4808
+ evidence: [
4809
+ { kind: "dependency_metric", target: dependency.name, metric: "dependencyDepth" }
4810
+ ],
4634
4811
  confidence: 1
4635
4812
  },
4636
4813
  {
@@ -4652,7 +4829,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
4652
4829
  normalizedMetrics: { popularityDampener: context.popularityDampener },
4653
4830
  weight: config.dependencySignals.popularityMaxDampening,
4654
4831
  amplification: null,
4655
- evidence: [{ kind: "dependency_metric", target: dependency.name, metric: "weeklyDownloads" }],
4832
+ evidence: [
4833
+ { kind: "dependency_metric", target: dependency.name, metric: "weeklyDownloads" }
4834
+ ],
4656
4835
  confidence: context.rawMetrics.weeklyDownloads === null ? 0.4 : 0.9
4657
4836
  }
4658
4837
  ]);
@@ -5041,7 +5220,12 @@ var collectAnalysisInputs = async (inputPath, authorIdentityMode, options = {},
5041
5220
  };
5042
5221
  };
5043
5222
  var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logger = createSilentLogger()) => {
5044
- const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, options, logger);
5223
+ const analysisInputs = await collectAnalysisInputs(
5224
+ inputPath,
5225
+ authorIdentityMode,
5226
+ options,
5227
+ logger
5228
+ );
5045
5229
  logger.info("computing risk summary");
5046
5230
  const risk = computeRepositoryRiskSummary(analysisInputs);
5047
5231
  logger.info(`analysis completed (repositoryScore=${risk.repositoryScore})`);
@@ -5168,9 +5352,7 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
5168
5352
  );
5169
5353
  }
5170
5354
  if (options.baselineSha !== void 0 && options.baselineRef !== "auto") {
5171
- throw new GovernanceConfigurationError(
5172
- "baseline-sha requires --baseline-ref auto"
5173
- );
5355
+ throw new GovernanceConfigurationError("baseline-sha requires --baseline-ref auto");
5174
5356
  }
5175
5357
  const resolvedTargetPath = resolve4(inputPath ?? process.cwd());
5176
5358
  logger.info("building current snapshot");