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