autoremediator 0.17.0 → 0.18.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.
Files changed (62) hide show
  1. package/README.md +2 -1
  2. package/dist/api/change-request/index.d.ts +2 -1
  3. package/dist/api/change-request/index.d.ts.map +1 -1
  4. package/dist/api/index.d.ts +2 -2
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api/patches/index.d.ts +1 -0
  7. package/dist/api/patches/index.d.ts.map +1 -1
  8. package/dist/api/patches/reconcile.d.ts +7 -0
  9. package/dist/api/patches/reconcile.d.ts.map +1 -0
  10. package/dist/api/patches/reconcile.test.d.ts +2 -0
  11. package/dist/api/patches/reconcile.test.d.ts.map +1 -0
  12. package/dist/api/remediate/validators.d.ts +3 -0
  13. package/dist/api/remediate/validators.d.ts.map +1 -1
  14. package/dist/{chunk-U4LIJIGD.js → chunk-FXOUJDQ3.js} +1712 -856
  15. package/dist/cli/program.d.ts.map +1 -1
  16. package/dist/cli/runners.d.ts +5 -0
  17. package/dist/cli/runners.d.ts.map +1 -1
  18. package/dist/cli/triage.d.ts +7 -0
  19. package/dist/cli/triage.d.ts.map +1 -0
  20. package/dist/cli/types.d.ts +2 -0
  21. package/dist/cli/types.d.ts.map +1 -1
  22. package/dist/cli.js +220 -3
  23. package/dist/index.js +1707 -852
  24. package/dist/index.js.map +1 -1
  25. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  26. package/dist/mcp/server.js +1 -1
  27. package/dist/openapi/server.js +1 -1
  28. package/dist/platform/package-manager/index.d.ts +1 -0
  29. package/dist/platform/package-manager/index.d.ts.map +1 -1
  30. package/dist/platform/supply-chain.d.ts +10 -0
  31. package/dist/platform/supply-chain.d.ts.map +1 -0
  32. package/dist/platform/supply-chain.test.d.ts +2 -0
  33. package/dist/platform/supply-chain.test.d.ts.map +1 -0
  34. package/dist/platform/types.d.ts +38 -4
  35. package/dist/platform/types.d.ts.map +1 -1
  36. package/dist/platform/workspace.d.ts +28 -0
  37. package/dist/platform/workspace.d.ts.map +1 -0
  38. package/dist/platform/workspace.test.d.ts +2 -0
  39. package/dist/platform/workspace.test.d.ts.map +1 -0
  40. package/dist/remediation/local/options.d.ts.map +1 -1
  41. package/dist/remediation/local/primary-strategy.d.ts +3 -1
  42. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  43. package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
  44. package/dist/remediation/tools/apply-patch-file/index.d.ts +1 -0
  45. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  46. package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
  47. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  48. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  49. package/dist/remediation/tools/check-reachability.d.ts +6 -1
  50. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  51. package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
  52. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  53. package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
  54. package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
  55. package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
  56. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  57. package/dist/scanner/delta-scan.d.ts.map +1 -1
  58. package/dist/scanner/lockfile-diff.d.ts +10 -0
  59. package/dist/scanner/lockfile-diff.d.ts.map +1 -0
  60. package/dist/scanner/lockfile-diff.test.d.ts +2 -0
  61. package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
  62. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -887,12 +887,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
887
887
  import { join as join5 } from "path";
888
888
 
889
889
  // src/remediation/strategies/patch-utils.ts
890
- import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
890
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
891
891
  import { join as join4 } from "path";
892
892
  import { execa as execa2 } from "execa";
893
893
 
894
894
  // src/platform/package-manager/index.ts
895
- import { existsSync as existsSync2 } from "fs";
895
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
896
896
  import { join as join3 } from "path";
897
897
  import { execa } from "execa";
898
898
 
@@ -1085,6 +1085,32 @@ function resolveTestCommand(pm, constraints) {
1085
1085
  const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
1086
1086
  return withWorkspace(base, pm, constraints?.workspace);
1087
1087
  }
1088
+ function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
1089
+ const pkgJsonPath = join3(cwd, "package.json");
1090
+ let hasTypecheckScript = false;
1091
+ const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
1092
+ if (existsSync2(pkgJsonPath)) {
1093
+ try {
1094
+ const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
1095
+ if (pkg.scripts?.typecheck) {
1096
+ hasTypecheckScript = true;
1097
+ }
1098
+ } catch {
1099
+ }
1100
+ }
1101
+ if (hasTypecheckScript) {
1102
+ const base = [pm, "run", "typecheck"];
1103
+ return withWorkspace(base, pm, constraints?.workspace);
1104
+ }
1105
+ if (hasTsConfig) {
1106
+ if (pm === "deno") {
1107
+ return withWorkspace(["deno", "check"], pm, constraints?.workspace);
1108
+ }
1109
+ const base = ["npx", "tsc", "--noEmit"];
1110
+ return withWorkspace(base, pm, constraints?.workspace);
1111
+ }
1112
+ return [];
1113
+ }
1088
1114
  function resolveAuditCommand(pm, constraints) {
1089
1115
  if (pm === "deno") {
1090
1116
  throw new Error(
@@ -1329,12 +1355,12 @@ function defineTool(config) {
1329
1355
 
1330
1356
  // src/remediation/tools/check-inventory.ts
1331
1357
  import { z } from "zod";
1332
- import { readFileSync as readFileSync4 } from "fs";
1333
- import { join as join7 } from "path";
1358
+ import { readFileSync as readFileSync6 } from "fs";
1359
+ import { join as join8 } from "path";
1334
1360
  import { execa as execa3 } from "execa";
1335
1361
 
1336
1362
  // src/platform/policy.ts
1337
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1363
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1338
1364
  import { join as join6 } from "path";
1339
1365
  import { parse as yamlParse } from "yaml";
1340
1366
  var DEFAULT_POLICY = {
@@ -1369,7 +1395,7 @@ function loadPolicy(cwd, explicitPath) {
1369
1395
  const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
1370
1396
  if (!existsSync6(candidate)) return DEFAULT_POLICY;
1371
1397
  try {
1372
- const parsed = yamlParse(readFileSync3(candidate, "utf8"));
1398
+ const parsed = yamlParse(readFileSync4(candidate, "utf8"));
1373
1399
  return {
1374
1400
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
1375
1401
  denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
@@ -1430,7 +1456,7 @@ function isActiveSuppression(suppression) {
1430
1456
  }
1431
1457
  function loadSuppressionsFile(filePath) {
1432
1458
  try {
1433
- const content = readFileSync3(filePath, "utf8");
1459
+ const content = readFileSync4(filePath, "utf8");
1434
1460
  const parsed = yamlParse(content);
1435
1461
  return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
1436
1462
  } catch {
@@ -1456,6 +1482,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1456
1482
  };
1457
1483
  }
1458
1484
 
1485
+ // src/platform/workspace.ts
1486
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1487
+ import { join as join7, resolve as resolve2, relative } from "path";
1488
+ import yaml from "yaml";
1489
+ function findWorkspaceRoot(startDir) {
1490
+ let curr = resolve2(startDir);
1491
+ while (true) {
1492
+ if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
1493
+ const pkgPath = join7(curr, "package.json");
1494
+ if (existsSync7(pkgPath)) {
1495
+ try {
1496
+ const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
1497
+ if (parsed.workspaces) return curr;
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ const parent = resolve2(curr, "..");
1502
+ if (parent === curr) break;
1503
+ curr = parent;
1504
+ }
1505
+ return null;
1506
+ }
1507
+ function findWorkspacePackages(cwd) {
1508
+ const rootCwd = findWorkspaceRoot(cwd) ?? resolve2(cwd);
1509
+ const packages = [];
1510
+ const packageGlobs = [];
1511
+ const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
1512
+ if (existsSync7(pnpmWorkspaceFile)) {
1513
+ try {
1514
+ const content = readFileSync5(pnpmWorkspaceFile, "utf8");
1515
+ const parsed = yaml.parse(content);
1516
+ if (Array.isArray(parsed?.packages)) {
1517
+ packageGlobs.push(...parsed.packages);
1518
+ }
1519
+ } catch {
1520
+ }
1521
+ }
1522
+ const rootPkgFile = join7(rootCwd, "package.json");
1523
+ if (existsSync7(rootPkgFile)) {
1524
+ try {
1525
+ const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
1526
+ if (Array.isArray(rootPkg.workspaces)) {
1527
+ packageGlobs.push(...rootPkg.workspaces);
1528
+ } else if (Array.isArray(rootPkg.workspaces?.packages)) {
1529
+ packageGlobs.push(...rootPkg.workspaces.packages);
1530
+ }
1531
+ } catch {
1532
+ }
1533
+ }
1534
+ const denoJsonFile = join7(rootCwd, "deno.json");
1535
+ if (existsSync7(denoJsonFile)) {
1536
+ try {
1537
+ const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
1538
+ if (Array.isArray(denoJson.workspace)) {
1539
+ packageGlobs.push(...denoJson.workspace);
1540
+ }
1541
+ } catch {
1542
+ }
1543
+ }
1544
+ if (packageGlobs.length === 0) {
1545
+ return packages;
1546
+ }
1547
+ const seenDirs = /* @__PURE__ */ new Set();
1548
+ for (const glob of packageGlobs) {
1549
+ const cleanGlob = glob.trim().replace(/\/+$/, "");
1550
+ if (!cleanGlob || cleanGlob.startsWith("!")) continue;
1551
+ if (cleanGlob.endsWith("/*")) {
1552
+ const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
1553
+ if (!existsSync7(parentDir)) continue;
1554
+ try {
1555
+ const entries = readdirSync(parentDir);
1556
+ for (const entry of entries) {
1557
+ if (entry.startsWith(".") || entry === "node_modules") continue;
1558
+ const subDir = join7(parentDir, entry);
1559
+ try {
1560
+ if (statSync(subDir).isDirectory()) {
1561
+ addWorkspacePackage(rootCwd, subDir, packages, seenDirs);
1562
+ }
1563
+ } catch {
1564
+ }
1565
+ }
1566
+ } catch {
1567
+ }
1568
+ } else {
1569
+ const directDir = join7(rootCwd, cleanGlob);
1570
+ if (existsSync7(directDir)) {
1571
+ try {
1572
+ if (statSync(directDir).isDirectory()) {
1573
+ addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
1574
+ }
1575
+ } catch {
1576
+ }
1577
+ }
1578
+ }
1579
+ }
1580
+ return packages;
1581
+ }
1582
+ function addWorkspacePackage(rootCwd, dir, out, seen) {
1583
+ const norm = resolve2(dir);
1584
+ if (seen.has(norm)) return;
1585
+ seen.add(norm);
1586
+ const manifestPath = join7(dir, "package.json");
1587
+ if (!existsSync7(manifestPath)) return;
1588
+ try {
1589
+ const content = readFileSync5(manifestPath, "utf8");
1590
+ const manifest = JSON.parse(content);
1591
+ out.push({
1592
+ name: manifest.name ?? relative(rootCwd, dir),
1593
+ relativeDir: relative(rootCwd, dir),
1594
+ absoluteDir: norm,
1595
+ manifestPath,
1596
+ manifest
1597
+ });
1598
+ } catch {
1599
+ }
1600
+ }
1601
+ function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
1602
+ const workspaces = findWorkspacePackages(cwd);
1603
+ if (workspaces.length === 0) return null;
1604
+ const matchesFilter = (wp) => {
1605
+ if (!filterWorkspace) return true;
1606
+ const filter = filterWorkspace.trim();
1607
+ return wp.name === filter || wp.relativeDir === filter || wp.relativeDir.replace(/\\/g, "/") === filter.replace(/\\/g, "/");
1608
+ };
1609
+ for (const wp of workspaces) {
1610
+ if (!matchesFilter(wp)) continue;
1611
+ const isDeclared = Boolean(wp.manifest.dependencies?.[dependencyName]) || Boolean(wp.manifest.devDependencies?.[dependencyName]) || Boolean(wp.manifest.peerDependencies?.[dependencyName]);
1612
+ if (isDeclared) {
1613
+ return wp;
1614
+ }
1615
+ }
1616
+ return null;
1617
+ }
1618
+
1459
1619
  // src/remediation/tools/check-inventory.ts
1460
1620
  var checkInventoryTool = defineTool({
1461
1621
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
@@ -1473,7 +1633,7 @@ var checkInventoryTool = defineTool({
1473
1633
  }) => {
1474
1634
  let pkgJson;
1475
1635
  try {
1476
- pkgJson = JSON.parse(readFileSync4(join7(cwd, "package.json"), "utf8"));
1636
+ pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1477
1637
  } catch {
1478
1638
  return {
1479
1639
  packages: [],
@@ -1505,7 +1665,8 @@ var checkInventoryTool = defineTool({
1505
1665
  }
1506
1666
  const packages = [];
1507
1667
  for (const [name, version] of installedVersions.entries()) {
1508
- const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]);
1668
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, name, workspace);
1669
+ const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]) || Boolean(declaringWorkspace);
1509
1670
  packages.push({
1510
1671
  name,
1511
1672
  version,
@@ -1628,173 +1789,14 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
1628
1789
  };
1629
1790
  }
1630
1791
 
1631
- // src/platform/config.ts
1632
- function resolveProvider(options = {}) {
1633
- const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1634
- if (raw !== "remote" && raw !== "local") {
1635
- throw new Error(
1636
- `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1637
- );
1638
- }
1639
- return raw;
1640
- }
1641
- function resolveModelName(provider, options = {}, routing = {}) {
1642
- if (options.model) return options.model;
1643
- if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1644
- const cwd = options.cwd ?? process.cwd();
1645
- const policy = loadPolicy(cwd, options.policy);
1646
- const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1647
- if (providerEnvModel) return providerEnvModel;
1648
- const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1649
- if (policyPinnedModel) return policyPinnedModel;
1650
- const defaults = {
1651
- remote: "remote-default",
1652
- local: "local"
1653
- };
1654
- const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
1655
- const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
1656
- if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
1657
- return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
1658
- }
1659
- return defaults[provider];
1660
- }
1661
- async function loadRemoteFactory() {
1662
- const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
1663
- const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
1664
- if (!moduleName) {
1665
- throw new Error(
1666
- "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
1667
- );
1668
- }
1669
- if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
1670
- throw new Error(
1671
- `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
1672
- );
1673
- }
1674
- const loaded = await import(moduleName);
1675
- const factory = loaded[exportName];
1676
- if (typeof factory !== "function") {
1677
- throw new Error(
1678
- `Remote client factory "${exportName}" was not found in module "${moduleName}".`
1679
- );
1680
- }
1681
- return factory;
1682
- }
1683
- async function createModel(options = {}, routing = {}) {
1684
- const provider = resolveProvider(options);
1685
- if (provider === "local") {
1686
- throw new Error(
1687
- "Local provider does not create a language model. Use the deterministic pipeline path instead."
1688
- );
1689
- }
1690
- const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
1691
- if (!apiKey) {
1692
- throw new Error(
1693
- "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
1694
- );
1695
- }
1696
- const modelName = resolveModelName(provider, options, routing);
1697
- const createRemoteClient = await loadRemoteFactory();
1698
- const remoteClient = createRemoteClient({ apiKey });
1699
- return remoteClient(modelName);
1700
- }
1701
- function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
1702
- const relaxed = {
1703
- remote: { low: 0.65, medium: 0.7, high: 0.8 },
1704
- local: { low: 0.65, medium: 0.7, high: 0.8 }
1705
- };
1706
- const strict = {
1707
- remote: { low: 0.8, medium: 0.85, high: 0.9 },
1708
- local: { low: 0.8, medium: 0.85, high: 0.9 }
1709
- };
1710
- const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
1711
- const override = overrides?.[riskLevel];
1712
- if (typeof override === "number" && Number.isFinite(override)) {
1713
- return Math.max(0, Math.min(1, override));
1714
- }
1715
- return baseThreshold;
1716
- }
1717
- function estimateModelCostUsd(params) {
1718
- const inputTokens = params.promptChars / 4;
1719
- const outputTokens = params.completionChars / 4;
1720
- const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
1721
- return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
1722
- }
1723
- function getNvdConfig() {
1724
- return {
1725
- apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
1726
- };
1727
- }
1728
- function getGitHubToken() {
1729
- return process.env.GITHUB_TOKEN;
1730
- }
1731
- function getIntelligenceSourceConfig() {
1732
- return {
1733
- gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
1734
- certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
1735
- epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
1736
- cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
1737
- depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
1738
- scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
1739
- vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1740
- commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1741
- commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
1742
- };
1743
- }
1744
-
1745
- // src/platform/idempotency.ts
1746
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1747
- import { join as join8 } from "path";
1748
- var DEFAULT_INDEX = {
1749
- schemaVersion: "1.0",
1750
- entries: {}
1751
- };
1752
- function indexFilePath(cwd) {
1753
- return join8(cwd, ".autoremediator", "state", "idempotency.json");
1754
- }
1755
- function entryKey(idempotencyKey, cveId) {
1756
- return `${idempotencyKey}::${cveId.toUpperCase()}`;
1757
- }
1758
- function loadIndex(cwd) {
1759
- const filePath = indexFilePath(cwd);
1760
- if (!existsSync7(filePath)) return DEFAULT_INDEX;
1761
- try {
1762
- const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
1763
- if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
1764
- return parsed;
1765
- }
1766
- return DEFAULT_INDEX;
1767
- } catch {
1768
- return DEFAULT_INDEX;
1769
- }
1770
- }
1771
- function saveIndex(cwd, index) {
1772
- const filePath = indexFilePath(cwd);
1773
- mkdirSync2(join8(cwd, ".autoremediator", "state"), { recursive: true });
1774
- writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
1775
- }
1776
- function readIdempotentReport(cwd, idempotencyKey, cveId) {
1777
- const index = loadIndex(cwd);
1778
- const key = entryKey(idempotencyKey, cveId);
1779
- return index.entries[key]?.report;
1780
- }
1781
- function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
1782
- const index = loadIndex(cwd);
1783
- const key = entryKey(idempotencyKey, cveId);
1784
- index.entries[key] = {
1785
- key: idempotencyKey,
1786
- cveId: cveId.toUpperCase(),
1787
- report,
1788
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
1789
- };
1790
- saveIndex(cwd, index);
1791
- }
1792
-
1793
- // src/remediation/pipeline.ts
1794
- import { generateText as generateText2 } from "ai";
1792
+ // src/api/patches/reconcile.ts
1793
+ import { existsSync as existsSync8 } from "fs";
1794
+ import { readFile as readFile3, rm, writeFile } from "fs/promises";
1795
+ import { join as join10 } from "path";
1796
+ import { execa as execa5 } from "execa";
1795
1797
 
1796
- // src/remediation/local/run.ts
1797
- import semver5 from "semver";
1798
+ // src/remediation/evaluate-package.ts
1799
+ import semver2 from "semver";
1798
1800
 
1799
1801
  // src/platform/http-client.ts
1800
1802
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -1976,26 +1978,750 @@ async function lookupCveOsv(cveId) {
1976
1978
  return parseOsvVuln(vuln);
1977
1979
  }
1978
1980
 
1979
- // src/intelligence/sources/github-advisory.ts
1980
- var GITHUB_API_BASE = "https://api.github.com/advisories";
1981
- async function fetchGhAdvisories(cveId) {
1982
- const token = getGitHubToken();
1983
- const headers = {
1984
- "X-GitHub-Api-Version": "2022-11-28",
1985
- "User-Agent": "autoremediator"
1986
- };
1987
- if (token) {
1988
- headers["Authorization"] = `token ${token}`;
1981
+ // src/platform/config.ts
1982
+ function resolveProvider(options = {}) {
1983
+ const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1984
+ if (raw !== "remote" && raw !== "local") {
1985
+ throw new Error(
1986
+ `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1987
+ );
1989
1988
  }
1990
- const url = new URL(GITHUB_API_BASE);
1991
- url.searchParams.set("cve_id", cveId);
1992
- url.searchParams.set("ecosystem", "npm");
1993
- url.searchParams.set("type", "reviewed");
1994
- url.searchParams.set("per_page", "10");
1995
- try {
1996
- const res = await httpClient({
1997
- url: url.toString(),
1998
- headers
1989
+ return raw;
1990
+ }
1991
+ function resolveModelName(provider, options = {}, routing = {}) {
1992
+ if (options.model) return options.model;
1993
+ if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1994
+ const cwd = options.cwd ?? process.cwd();
1995
+ const policy = loadPolicy(cwd, options.policy);
1996
+ const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1997
+ if (providerEnvModel) return providerEnvModel;
1998
+ const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1999
+ if (policyPinnedModel) return policyPinnedModel;
2000
+ const defaults = {
2001
+ remote: "remote-default",
2002
+ local: "local"
2003
+ };
2004
+ const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
2005
+ const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
2006
+ if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
2007
+ return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
2008
+ }
2009
+ return defaults[provider];
2010
+ }
2011
+ async function loadRemoteFactory() {
2012
+ const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
2013
+ const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
2014
+ if (!moduleName) {
2015
+ throw new Error(
2016
+ "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
2017
+ );
2018
+ }
2019
+ if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
2020
+ throw new Error(
2021
+ `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
2022
+ );
2023
+ }
2024
+ const loaded = await import(moduleName);
2025
+ const factory = loaded[exportName];
2026
+ if (typeof factory !== "function") {
2027
+ throw new Error(
2028
+ `Remote client factory "${exportName}" was not found in module "${moduleName}".`
2029
+ );
2030
+ }
2031
+ return factory;
2032
+ }
2033
+ async function createModel(options = {}, routing = {}) {
2034
+ const provider = resolveProvider(options);
2035
+ if (provider === "local") {
2036
+ throw new Error(
2037
+ "Local provider does not create a language model. Use the deterministic pipeline path instead."
2038
+ );
2039
+ }
2040
+ const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
2041
+ if (!apiKey) {
2042
+ throw new Error(
2043
+ "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
2044
+ );
2045
+ }
2046
+ const modelName = resolveModelName(provider, options, routing);
2047
+ const createRemoteClient = await loadRemoteFactory();
2048
+ const remoteClient = createRemoteClient({ apiKey });
2049
+ return remoteClient(modelName);
2050
+ }
2051
+ function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
2052
+ const relaxed = {
2053
+ remote: { low: 0.65, medium: 0.7, high: 0.8 },
2054
+ local: { low: 0.65, medium: 0.7, high: 0.8 }
2055
+ };
2056
+ const strict = {
2057
+ remote: { low: 0.8, medium: 0.85, high: 0.9 },
2058
+ local: { low: 0.8, medium: 0.85, high: 0.9 }
2059
+ };
2060
+ const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
2061
+ const override = overrides?.[riskLevel];
2062
+ if (typeof override === "number" && Number.isFinite(override)) {
2063
+ return Math.max(0, Math.min(1, override));
2064
+ }
2065
+ return baseThreshold;
2066
+ }
2067
+ function estimateModelCostUsd(params) {
2068
+ const inputTokens = params.promptChars / 4;
2069
+ const outputTokens = params.completionChars / 4;
2070
+ const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
2071
+ return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
2072
+ }
2073
+ function getNvdConfig() {
2074
+ return {
2075
+ apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
2076
+ };
2077
+ }
2078
+ function getGitHubToken() {
2079
+ return process.env.GITHUB_TOKEN;
2080
+ }
2081
+ function getIntelligenceSourceConfig() {
2082
+ return {
2083
+ gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
2084
+ certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
2085
+ epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
2086
+ cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
2087
+ depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
2088
+ scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
2089
+ vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
2090
+ commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
2091
+ commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
2092
+ };
2093
+ }
2094
+
2095
+ // src/intelligence/sources/epss.ts
2096
+ async function fetchEpss(cveId) {
2097
+ const { epssApi } = getIntelligenceSourceConfig();
2098
+ if (!epssApi) return void 0;
2099
+ try {
2100
+ const url = new URL(epssApi);
2101
+ url.searchParams.set("cve", cveId);
2102
+ const res = await httpClient({ url: url.toString() });
2103
+ if (!res.ok) return void 0;
2104
+ const body = res.data;
2105
+ return body.data?.[0];
2106
+ } catch {
2107
+ return void 0;
2108
+ }
2109
+ }
2110
+ async function enrichWithEpss(details) {
2111
+ const row = await fetchEpss(details.id);
2112
+ if (!row) return details;
2113
+ const score = Number.parseFloat(row.epss);
2114
+ const percentile = Number.parseFloat(row.percentile);
2115
+ if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
2116
+ return details;
2117
+ }
2118
+ details.epss = {
2119
+ score,
2120
+ percentile,
2121
+ date: row.date
2122
+ };
2123
+ return details;
2124
+ }
2125
+
2126
+ // src/intelligence/sources/cisa-kev.ts
2127
+ var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
2128
+ async function fetchCisaKevFeed() {
2129
+ try {
2130
+ const res = await httpClient({ url: CISA_KEV_URL });
2131
+ if (!res.ok) return void 0;
2132
+ return res.data;
2133
+ } catch {
2134
+ return void 0;
2135
+ }
2136
+ }
2137
+ function findKevEntry(feed, cveId) {
2138
+ if (!feed?.vulnerabilities?.length) return void 0;
2139
+ const normalized = cveId.toUpperCase();
2140
+ return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
2141
+ }
2142
+ async function enrichWithCisaKev(details) {
2143
+ const feed = await fetchCisaKevFeed();
2144
+ const entry = findKevEntry(feed, details.id);
2145
+ if (!entry) return details;
2146
+ details.kev = {
2147
+ knownExploited: true,
2148
+ dateAdded: entry.dateAdded,
2149
+ dueDate: entry.dueDate,
2150
+ requiredAction: entry.requiredAction,
2151
+ knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
2152
+ };
2153
+ if (!details.references.includes(CISA_KEV_URL)) {
2154
+ details.references.push(CISA_KEV_URL);
2155
+ }
2156
+ return details;
2157
+ }
2158
+
2159
+ // src/intelligence/sources/registry.ts
2160
+ import { readFileSync as readFileSync7 } from "fs";
2161
+ import { join as join9 } from "path";
2162
+ import { execa as execa4 } from "execa";
2163
+ import semver from "semver";
2164
+ var NPM_REGISTRY = "https://registry.npmjs.org";
2165
+ async function fetchPackageVersions(packageName) {
2166
+ const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2167
+ try {
2168
+ const res = await httpClient({ url });
2169
+ if (res.status === 404) return [];
2170
+ if (!res.ok) {
2171
+ throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2172
+ }
2173
+ const data = res.data;
2174
+ return Object.keys(data.versions);
2175
+ } catch (err) {
2176
+ if (err instanceof Error && err.message.includes("registry error")) {
2177
+ throw err;
2178
+ }
2179
+ return [];
2180
+ }
2181
+ }
2182
+ async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2183
+ const resolution = await resolveSafeUpgradeVersion(
2184
+ packageName,
2185
+ installedVersion,
2186
+ firstPatchedVersion,
2187
+ vulnerableRange
2188
+ );
2189
+ return resolution.safeVersion;
2190
+ }
2191
+ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2192
+ const versions = await fetchPackageVersions(packageName);
2193
+ if (!versions.length) {
2194
+ return {
2195
+ candidates: {},
2196
+ majorOnlyFixAvailable: false
2197
+ };
2198
+ }
2199
+ const installed = semver.parse(installedVersion);
2200
+ const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2201
+ if (!vulnerableRange) return true;
2202
+ try {
2203
+ return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2204
+ } catch {
2205
+ return true;
2206
+ }
2207
+ }).sort(semver.compare);
2208
+ if (!candidates.length) {
2209
+ return {
2210
+ candidates: {},
2211
+ majorOnlyFixAvailable: false
2212
+ };
2213
+ }
2214
+ const categorizedCandidates = {};
2215
+ for (const candidate of candidates) {
2216
+ const level = classifyUpgradeLevel(installedVersion, candidate);
2217
+ if (!level) continue;
2218
+ if (!categorizedCandidates[level]) {
2219
+ categorizedCandidates[level] = candidate;
2220
+ }
2221
+ }
2222
+ const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2223
+ if (!safeVersion) {
2224
+ return {
2225
+ candidates: categorizedCandidates,
2226
+ majorOnlyFixAvailable: false
2227
+ };
2228
+ }
2229
+ const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2230
+ const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2231
+ if (!installed || !upgradeLevel) {
2232
+ return {
2233
+ safeVersion,
2234
+ upgradeLevel,
2235
+ candidates: categorizedCandidates,
2236
+ majorOnlyFixAvailable
2237
+ };
2238
+ }
2239
+ return {
2240
+ safeVersion,
2241
+ upgradeLevel,
2242
+ candidates: categorizedCandidates,
2243
+ majorOnlyFixAvailable
2244
+ };
2245
+ }
2246
+ function classifyUpgradeLevel(installedVersion, candidateVersion) {
2247
+ const installed = semver.parse(installedVersion);
2248
+ const candidate = semver.parse(candidateVersion);
2249
+ if (!installed || !candidate) return void 0;
2250
+ if (candidate.major > installed.major) return "major";
2251
+ if (candidate.minor > installed.minor) return "minor";
2252
+ if (candidate.patch > installed.patch || candidate.version === installed.version) {
2253
+ return "patch";
2254
+ }
2255
+ return void 0;
2256
+ }
2257
+ async function queryOutdatedPackages(cwd, options = {}) {
2258
+ const pm = options.packageManager ?? detectPackageManager(cwd);
2259
+ let stdout;
2260
+ try {
2261
+ const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2262
+ const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2263
+ const finalArgs = yarnArgs ?? args;
2264
+ const result2 = await execa4(pm, finalArgs, {
2265
+ cwd,
2266
+ reject: false,
2267
+ stdio: "pipe"
2268
+ });
2269
+ if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2270
+ const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2271
+ if (!result2.stdout) {
2272
+ throw new Error(
2273
+ `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2274
+ );
2275
+ }
2276
+ }
2277
+ stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2278
+ } catch (err) {
2279
+ if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2280
+ throw new Error(`Failed to query outdated packages: ${err.message}`);
2281
+ }
2282
+ throw err;
2283
+ }
2284
+ if (!stdout.trim()) {
2285
+ return /* @__PURE__ */ new Map();
2286
+ }
2287
+ const rawEntries = {};
2288
+ if (pm === "yarn") {
2289
+ for (const line of stdout.split("\n")) {
2290
+ const trimmed = line.trim();
2291
+ if (!trimmed) continue;
2292
+ try {
2293
+ const parsed = JSON.parse(trimmed);
2294
+ if (parsed.type === "table" && parsed.data) {
2295
+ const tableData = parsed.data;
2296
+ const head = tableData.head ?? [];
2297
+ const currentIdx = head.indexOf("Current");
2298
+ const wantedIdx = head.indexOf("Wanted");
2299
+ const latestIdx = head.indexOf("Latest");
2300
+ for (const row of tableData.body ?? []) {
2301
+ const name = row[0];
2302
+ if (!name) continue;
2303
+ rawEntries[name] = {
2304
+ current: row[currentIdx] ?? "",
2305
+ wanted: row[wantedIdx] ?? "",
2306
+ latest: row[latestIdx] ?? "",
2307
+ type: row[head.indexOf("Package Type")] ?? "dependencies"
2308
+ };
2309
+ }
2310
+ }
2311
+ } catch {
2312
+ }
2313
+ }
2314
+ } else {
2315
+ try {
2316
+ const parsed = JSON.parse(stdout);
2317
+ if (Array.isArray(parsed)) {
2318
+ for (const entry of parsed) {
2319
+ const name = entry.name ?? entry.packageName;
2320
+ if (!name) continue;
2321
+ rawEntries[name] = {
2322
+ current: entry.current,
2323
+ wanted: entry.wanted,
2324
+ latest: entry.latest,
2325
+ type: entry.dependencyType
2326
+ };
2327
+ }
2328
+ } else {
2329
+ Object.assign(rawEntries, parsed);
2330
+ }
2331
+ } catch {
2332
+ throw new Error(`Could not parse ${pm} outdated --json output.`);
2333
+ }
2334
+ }
2335
+ let directDeps;
2336
+ try {
2337
+ const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
2338
+ directDeps = /* @__PURE__ */ new Set([
2339
+ ...Object.keys(pkgRaw.dependencies ?? {}),
2340
+ ...Object.keys(pkgRaw.devDependencies ?? {})
2341
+ ]);
2342
+ } catch {
2343
+ directDeps = /* @__PURE__ */ new Set();
2344
+ }
2345
+ const result = /* @__PURE__ */ new Map();
2346
+ for (const [name, entry] of Object.entries(rawEntries)) {
2347
+ const currentVersion = entry.current ?? "";
2348
+ const wantedVersion = entry.wanted ?? currentVersion;
2349
+ const latestVersion = entry.latest ?? currentVersion;
2350
+ if (!currentVersion || !latestVersion) continue;
2351
+ const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2352
+ if (!options.includeTransitive && dependencyScope === "transitive") continue;
2353
+ const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2354
+ result.set(name, {
2355
+ currentVersion,
2356
+ wantedVersion,
2357
+ latestVersion,
2358
+ isMajorBump,
2359
+ dependencyScope
2360
+ });
2361
+ }
2362
+ return result;
2363
+ }
2364
+
2365
+ // src/remediation/evaluate-package.ts
2366
+ async function evaluatePackage(packageName, options = {}) {
2367
+ const version = options.version?.trim();
2368
+ const enrich = options.enrichIntelligence !== false;
2369
+ let versions = [];
2370
+ let latestVersion;
2371
+ try {
2372
+ versions = await fetchPackageVersions(packageName);
2373
+ if (versions.length > 0) {
2374
+ const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
2375
+ latestVersion = valid[valid.length - 1];
2376
+ }
2377
+ } catch {
2378
+ }
2379
+ let rawVulns = [];
2380
+ try {
2381
+ rawVulns = await queryOsvPackage(packageName, version);
2382
+ } catch {
2383
+ rawVulns = [];
2384
+ }
2385
+ let kevFeed = null;
2386
+ if (enrich) {
2387
+ try {
2388
+ kevFeed = await fetchCisaKevFeed();
2389
+ } catch {
2390
+ kevFeed = null;
2391
+ }
2392
+ }
2393
+ const entries = [];
2394
+ for (const raw of rawVulns) {
2395
+ const cveDetails = parseOsvVuln(raw);
2396
+ const affected = cveDetails.affectedPackages.find(
2397
+ (p) => p.name.toLowerCase() === packageName.toLowerCase()
2398
+ );
2399
+ if (version && affected?.vulnerableRange) {
2400
+ const cleanVer = semver2.clean(version) || version;
2401
+ if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
2402
+ continue;
2403
+ }
2404
+ }
2405
+ let inCisaKev = false;
2406
+ let epssScore;
2407
+ let epssPercentile;
2408
+ if (enrich && cveDetails.id.startsWith("CVE-")) {
2409
+ if (kevFeed) {
2410
+ inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
2411
+ }
2412
+ try {
2413
+ const epss = await fetchEpss(cveDetails.id);
2414
+ if (epss) {
2415
+ epssScore = Number.parseFloat(epss.epss);
2416
+ epssPercentile = Number.parseFloat(epss.percentile);
2417
+ }
2418
+ } catch {
2419
+ }
2420
+ }
2421
+ let safeUpgradeVersion;
2422
+ if (affected?.firstPatchedVersion) {
2423
+ try {
2424
+ const safe = await findSafeUpgradeVersion(
2425
+ packageName,
2426
+ version || "0.0.0",
2427
+ affected.firstPatchedVersion
2428
+ );
2429
+ safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
2430
+ } catch {
2431
+ safeUpgradeVersion = affected.firstPatchedVersion;
2432
+ }
2433
+ }
2434
+ entries.push({
2435
+ cveId: cveDetails.id,
2436
+ severity: cveDetails.severity,
2437
+ summary: cveDetails.summary,
2438
+ vulnerableRange: affected?.vulnerableRange,
2439
+ firstPatchedVersion: affected?.firstPatchedVersion,
2440
+ safeUpgradeVersion,
2441
+ epssScore,
2442
+ epssPercentile,
2443
+ inCisaKev,
2444
+ references: cveDetails.references
2445
+ });
2446
+ }
2447
+ const isVulnerable = entries.length > 0;
2448
+ let verdict = "safe";
2449
+ if (isVulnerable) {
2450
+ const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
2451
+ const hasHighOrCritical = entries.some(
2452
+ (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
2453
+ );
2454
+ if (hasKevOrHighEpss) {
2455
+ verdict = "actively-exploited";
2456
+ } else if (hasHighOrCritical) {
2457
+ verdict = "vulnerable";
2458
+ } else {
2459
+ verdict = "caution";
2460
+ }
2461
+ }
2462
+ let recommendedVersion;
2463
+ if (isVulnerable) {
2464
+ const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
2465
+ recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
2466
+ } else {
2467
+ recommendedVersion = version || latestVersion;
2468
+ }
2469
+ const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
2470
+ return {
2471
+ schemaVersion: "1.0",
2472
+ packageName,
2473
+ evaluatedVersion: version,
2474
+ isVulnerable,
2475
+ verdict,
2476
+ summary,
2477
+ vulnerabilities: entries,
2478
+ recommendedVersion,
2479
+ latestVersion,
2480
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2481
+ };
2482
+ }
2483
+
2484
+ // src/api/patches/reconcile.ts
2485
+ async function reconcilePatchArtifacts(options = {}) {
2486
+ const cwd = options.cwd ?? process.cwd();
2487
+ const pm = options.packageManager ?? detectPackageManager(cwd);
2488
+ const summaries = await listPatchArtifacts(options);
2489
+ const results = [];
2490
+ for (const summary of summaries) {
2491
+ const inspection = await inspectPatchArtifact(summary.patchFilePath, options);
2492
+ let packageName = inspection.packageName;
2493
+ let vulnerableVersion = inspection.vulnerableVersion;
2494
+ const cveId = inspection.cveId;
2495
+ if (!packageName && summary.patchFileName) {
2496
+ const basename = summary.patchFileName.replace(/\.patch$/, "");
2497
+ const plusIdx = basename.lastIndexOf("+");
2498
+ if (plusIdx > 0) {
2499
+ let rawPkg = basename.slice(0, plusIdx);
2500
+ vulnerableVersion = basename.slice(plusIdx + 1);
2501
+ if (rawPkg.includes("+")) {
2502
+ rawPkg = "@" + rawPkg.replace("+", "/");
2503
+ }
2504
+ packageName = rawPkg;
2505
+ } else {
2506
+ packageName = basename;
2507
+ }
2508
+ }
2509
+ if (!packageName) {
2510
+ results.push({
2511
+ patchFilePath: summary.patchFilePath,
2512
+ packageName: "unknown",
2513
+ cveId,
2514
+ status: "patch-retained",
2515
+ reason: "Cannot determine package name from patch artifact."
2516
+ });
2517
+ continue;
2518
+ }
2519
+ let safeUpgradeVersion;
2520
+ try {
2521
+ const evaluation = await evaluatePackage(packageName, {
2522
+ version: vulnerableVersion,
2523
+ enrichIntelligence: false
2524
+ });
2525
+ if (cveId) {
2526
+ const vuln = evaluation.vulnerabilities.find(
2527
+ (v) => v.cveId.toLowerCase() === cveId.toLowerCase()
2528
+ );
2529
+ if (vuln?.safeUpgradeVersion) {
2530
+ safeUpgradeVersion = vuln.safeUpgradeVersion;
2531
+ }
2532
+ } else {
2533
+ safeUpgradeVersion = evaluation.vulnerabilities.find(
2534
+ (v) => v.safeUpgradeVersion
2535
+ )?.safeUpgradeVersion;
2536
+ }
2537
+ } catch {
2538
+ }
2539
+ if (!safeUpgradeVersion) {
2540
+ results.push({
2541
+ patchFilePath: summary.patchFilePath,
2542
+ packageName,
2543
+ cveId,
2544
+ installedVersion: vulnerableVersion,
2545
+ status: "patch-retained",
2546
+ reason: "No safe upstream release exists yet; patch remains necessary."
2547
+ });
2548
+ continue;
2549
+ }
2550
+ if (!options.autoApply) {
2551
+ results.push({
2552
+ patchFilePath: summary.patchFilePath,
2553
+ packageName,
2554
+ cveId,
2555
+ installedVersion: vulnerableVersion,
2556
+ safeUpstreamVersion: safeUpgradeVersion,
2557
+ status: "safe-version-available",
2558
+ reason: `Safe upstream version ${safeUpgradeVersion} is available. Re-run with autoApply: true or --auto-apply to upgrade.`
2559
+ });
2560
+ continue;
2561
+ }
2562
+ try {
2563
+ const pkgJsonPath = join10(cwd, "package.json");
2564
+ if (existsSync8(pkgJsonPath)) {
2565
+ const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
2566
+ let updated = false;
2567
+ const depSections = [
2568
+ "dependencies",
2569
+ "devDependencies",
2570
+ "peerDependencies",
2571
+ "optionalDependencies"
2572
+ ];
2573
+ for (const depSection of depSections) {
2574
+ if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
2575
+ pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
2576
+ updated = true;
2577
+ }
2578
+ }
2579
+ if (pkgJson.pnpm?.patchedDependencies) {
2580
+ for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
2581
+ if (key.startsWith(`${packageName}@`)) {
2582
+ delete pkgJson.pnpm.patchedDependencies[key];
2583
+ updated = true;
2584
+ }
2585
+ }
2586
+ }
2587
+ if (updated) {
2588
+ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2589
+ }
2590
+ }
2591
+ await rm(summary.patchFilePath, { force: true });
2592
+ if (inspection.manifestFilePath) {
2593
+ await rm(inspection.manifestFilePath, { force: true });
2594
+ }
2595
+ const installCmd = resolveInstallCommand(pm);
2596
+ await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
2597
+ if (options.runTests) {
2598
+ const testCmd = resolveTestCommand(pm);
2599
+ await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
2600
+ }
2601
+ results.push({
2602
+ patchFilePath: summary.patchFilePath,
2603
+ packageName,
2604
+ cveId,
2605
+ installedVersion: vulnerableVersion,
2606
+ safeUpstreamVersion: safeUpgradeVersion,
2607
+ status: "upgraded",
2608
+ reason: `Successfully upgraded to ${safeUpgradeVersion} and retired patch artifact.`
2609
+ });
2610
+ } catch (err) {
2611
+ results.push({
2612
+ patchFilePath: summary.patchFilePath,
2613
+ packageName,
2614
+ cveId,
2615
+ installedVersion: vulnerableVersion,
2616
+ safeUpstreamVersion: safeUpgradeVersion,
2617
+ status: "error",
2618
+ reason: err instanceof Error ? err.message : String(err)
2619
+ });
2620
+ }
2621
+ }
2622
+ const reconciledCount = results.filter((r) => r.status === "upgraded").length;
2623
+ const retainedCount = results.filter(
2624
+ (r) => r.status === "patch-retained" || r.status === "safe-version-available"
2625
+ ).length;
2626
+ return {
2627
+ reconciled: results,
2628
+ reconciledCount,
2629
+ retainedCount
2630
+ };
2631
+ }
2632
+
2633
+ // src/platform/idempotency.ts
2634
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
2635
+ import { join as join11 } from "path";
2636
+ var DEFAULT_INDEX = {
2637
+ schemaVersion: "1.0",
2638
+ entries: {}
2639
+ };
2640
+ function indexFilePath(cwd) {
2641
+ return join11(cwd, ".autoremediator", "state", "idempotency.json");
2642
+ }
2643
+ function entryKey(idempotencyKey, cveId) {
2644
+ return `${idempotencyKey}::${cveId.toUpperCase()}`;
2645
+ }
2646
+ function loadIndex(cwd) {
2647
+ const filePath = indexFilePath(cwd);
2648
+ if (!existsSync9(filePath)) return DEFAULT_INDEX;
2649
+ try {
2650
+ const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
2651
+ if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
2652
+ return parsed;
2653
+ }
2654
+ return DEFAULT_INDEX;
2655
+ } catch {
2656
+ return DEFAULT_INDEX;
2657
+ }
2658
+ }
2659
+ function saveIndex(cwd, index) {
2660
+ const filePath = indexFilePath(cwd);
2661
+ mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
2662
+ writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2663
+ }
2664
+ function readIdempotentReport(cwd, idempotencyKey, cveId) {
2665
+ const index = loadIndex(cwd);
2666
+ const key = entryKey(idempotencyKey, cveId);
2667
+ return index.entries[key]?.report;
2668
+ }
2669
+ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
2670
+ const index = loadIndex(cwd);
2671
+ const key = entryKey(idempotencyKey, cveId);
2672
+ index.entries[key] = {
2673
+ key: idempotencyKey,
2674
+ cveId: cveId.toUpperCase(),
2675
+ report,
2676
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
2677
+ };
2678
+ saveIndex(cwd, index);
2679
+ }
2680
+
2681
+ // src/remediation/pipeline.ts
2682
+ import { generateText as generateText2 } from "ai";
2683
+
2684
+ // src/remediation/local/run.ts
2685
+ import semver6 from "semver";
2686
+
2687
+ // src/intelligence/sources/github-advisory.ts
2688
+ var GITHUB_API_BASE = "https://api.github.com/advisories";
2689
+ async function fetchGhAdvisories(cveId) {
2690
+ const token = getGitHubToken();
2691
+ const headers = {
2692
+ "X-GitHub-Api-Version": "2022-11-28",
2693
+ "User-Agent": "autoremediator"
2694
+ };
2695
+ if (token) {
2696
+ headers["Authorization"] = `token ${token}`;
2697
+ }
2698
+ const normalized = cveId.trim();
2699
+ if (normalized.toUpperCase().startsWith("GHSA-")) {
2700
+ const url2 = `${GITHUB_API_BASE}/${encodeURIComponent(normalized)}`;
2701
+ try {
2702
+ const res = await httpClient({ url: url2, headers });
2703
+ if (!res.ok) {
2704
+ if (res.status === 404) return [];
2705
+ throw new Error(`HTTP ${res.status}: ${res.text}`);
2706
+ }
2707
+ return res.data ? [res.data] : [];
2708
+ } catch (err) {
2709
+ const errorMsg = err instanceof Error ? err.message : String(err);
2710
+ console.warn(
2711
+ `[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
2712
+ );
2713
+ return [];
2714
+ }
2715
+ }
2716
+ const url = new URL(GITHUB_API_BASE);
2717
+ url.searchParams.set("cve_id", cveId);
2718
+ url.searchParams.set("ecosystem", "npm");
2719
+ url.searchParams.set("type", "reviewed");
2720
+ url.searchParams.set("per_page", "10");
2721
+ try {
2722
+ const res = await httpClient({
2723
+ url: url.toString(),
2724
+ headers
1999
2725
  });
2000
2726
  if (!res.ok) {
2001
2727
  if (res.status === 404) return [];
@@ -2095,9 +2821,9 @@ async function enrichWithNvd(details) {
2095
2821
  }
2096
2822
 
2097
2823
  // src/intelligence/sources/snapshot.ts
2098
- import { readFileSync as readFileSync6 } from "fs";
2824
+ import { readFileSync as readFileSync9 } from "fs";
2099
2825
  function loadIntelligenceSnapshot(filePath) {
2100
- const raw = readFileSync6(filePath, "utf8");
2826
+ const raw = readFileSync9(filePath, "utf8");
2101
2827
  const parsed = JSON.parse(raw);
2102
2828
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2103
2829
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -2109,8 +2835,8 @@ function lookupSnapshotCve(snapshot, cveId) {
2109
2835
  }
2110
2836
 
2111
2837
  // src/remediation/tools/check-reachability.ts
2112
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync } from "fs";
2113
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "path";
2838
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
2839
+ import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
2114
2840
  import { parseSync } from "oxc-parser";
2115
2841
  import { z as z2 } from "zod";
2116
2842
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
@@ -2126,16 +2852,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
2126
2852
  ]);
2127
2853
  function loadTsconfigPaths(cwd) {
2128
2854
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
2129
- const fullPath = join9(cwd, configName);
2130
- if (!existsSync8(fullPath)) continue;
2855
+ const fullPath = join12(cwd, configName);
2856
+ if (!existsSync10(fullPath)) continue;
2131
2857
  try {
2132
- const raw = readFileSync7(fullPath, "utf8");
2858
+ const raw = readFileSync10(fullPath, "utf8");
2133
2859
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
2134
2860
  const parsed = JSON.parse(cleaned);
2135
2861
  const compilerOptions = parsed.compilerOptions;
2136
2862
  if (compilerOptions?.paths) {
2137
2863
  return {
2138
- baseUrl: compilerOptions.baseUrl ? resolve2(cwd, compilerOptions.baseUrl) : cwd,
2864
+ baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
2139
2865
  paths: compilerOptions.paths
2140
2866
  };
2141
2867
  }
@@ -2150,14 +2876,14 @@ function resolveAliasedPath(importSource, config) {
2150
2876
  }
2151
2877
  for (const [pattern, replacements] of Object.entries(config.paths)) {
2152
2878
  if (pattern === importSource && replacements[0]) {
2153
- return resolve2(config.baseUrl, replacements[0]);
2879
+ return resolve3(config.baseUrl, replacements[0]);
2154
2880
  }
2155
2881
  if (pattern.endsWith("/*")) {
2156
2882
  const prefix = pattern.slice(0, -2);
2157
2883
  if (importSource.startsWith(prefix)) {
2158
2884
  const suffix = importSource.slice(prefix.length);
2159
2885
  const replacement = replacements[0]?.slice(0, -2) ?? "";
2160
- return resolve2(config.baseUrl, replacement + suffix);
2886
+ return resolve3(config.baseUrl, replacement + suffix);
2161
2887
  }
2162
2888
  }
2163
2889
  }
@@ -2166,16 +2892,16 @@ function resolveAliasedPath(importSource, config) {
2166
2892
  function* walkSourceFiles(dir) {
2167
2893
  let entries;
2168
2894
  try {
2169
- entries = readdirSync(dir);
2895
+ entries = readdirSync2(dir);
2170
2896
  } catch {
2171
2897
  return;
2172
2898
  }
2173
2899
  for (const entry of entries) {
2174
2900
  if (IGNORED_DIRS.has(entry)) continue;
2175
- const full = join9(dir, entry);
2901
+ const full = join12(dir, entry);
2176
2902
  let stat2;
2177
2903
  try {
2178
- stat2 = statSync(full);
2904
+ stat2 = statSync2(full);
2179
2905
  } catch {
2180
2906
  continue;
2181
2907
  }
@@ -2213,7 +2939,31 @@ function walkAst(node, visitor) {
2213
2939
  }
2214
2940
  }
2215
2941
  }
2216
- function extractPackageReferences(filename, content, packageName, tsconfig) {
2942
+ function toRootPackageName(importSource) {
2943
+ if (importSource.startsWith("@")) {
2944
+ const parts = importSource.split("/");
2945
+ return parts.slice(0, 2).join("/");
2946
+ }
2947
+ return importSource.split("/")[0] ?? importSource;
2948
+ }
2949
+ function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
2950
+ for (const directPkg of importedDirectPackages) {
2951
+ const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
2952
+ if (!existsSync10(manifestPath)) continue;
2953
+ try {
2954
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
2955
+ if (manifest.dependencies && manifest.dependencies[targetPackage]) {
2956
+ return {
2957
+ directDependency: directPkg,
2958
+ version: manifest.dependencies[targetPackage]
2959
+ };
2960
+ }
2961
+ } catch {
2962
+ }
2963
+ }
2964
+ return null;
2965
+ }
2966
+ function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
2217
2967
  const results = [];
2218
2968
  let parseResult;
2219
2969
  try {
@@ -2225,6 +2975,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2225
2975
  walkAst(parseResult.program, (node) => {
2226
2976
  if (node.type === "ImportDeclaration") {
2227
2977
  const source = node.source;
2978
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2979
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2980
+ }
2228
2981
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2229
2982
  const symbols = [];
2230
2983
  const importedIdentifiers = /* @__PURE__ */ new Map();
@@ -2259,6 +3012,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2259
3012
  }
2260
3013
  if (node.type === "ImportExpression") {
2261
3014
  const source = node.source;
3015
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3016
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3017
+ }
2262
3018
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2263
3019
  results.push({
2264
3020
  matchType: "dynamic-import",
@@ -2270,6 +3026,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2270
3026
  }
2271
3027
  if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
2272
3028
  const firstArg = node.arguments[0];
3029
+ if (firstArg?.value && typeof firstArg.value === "string" && !firstArg.value.startsWith(".") && !isAbsolute2(firstArg.value)) {
3030
+ outImportedDirectPackages?.add(toRootPackageName(firstArg.value));
3031
+ }
2273
3032
  if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
2274
3033
  results.push({
2275
3034
  matchType: "require",
@@ -2281,6 +3040,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2281
3040
  }
2282
3041
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
2283
3042
  const source = node.source;
3043
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3044
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3045
+ }
2284
3046
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2285
3047
  const symbols = [];
2286
3048
  if (node.type === "ExportAllDeclaration") {
@@ -2357,15 +3119,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2357
3119
  let hasReExport = false;
2358
3120
  let totalInvocations = 0;
2359
3121
  let hasPackageImport = false;
3122
+ const importedDirectPackages = /* @__PURE__ */ new Set();
2360
3123
  for (const filePath of walkSourceFiles(cwd)) {
2361
3124
  scanned++;
2362
3125
  let content;
2363
3126
  try {
2364
- content = readFileSync7(filePath, "utf8");
3127
+ content = readFileSync10(filePath, "utf8");
2365
3128
  } catch {
2366
3129
  continue;
2367
3130
  }
2368
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
3131
+ const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
2369
3132
  if (refs.length > 0) {
2370
3133
  hasPackageImport = true;
2371
3134
  }
@@ -2408,6 +3171,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2408
3171
  };
2409
3172
  }
2410
3173
  if (!hasPackageImport) {
3174
+ const transitivePath = findTransitiveDependencyPath(cwd, packageName, importedDirectPackages);
3175
+ if (transitivePath) {
3176
+ return {
3177
+ packageName,
3178
+ status: "reachable",
3179
+ reason: `Package '${packageName}' is a transitive dependency of imported package '${transitivePath.directDependency}'`,
3180
+ reachabilityBasis: "transitive-path-found",
3181
+ evidence: []
3182
+ };
3183
+ }
2411
3184
  return {
2412
3185
  packageName,
2413
3186
  status: "not-reachable",
@@ -2536,270 +3309,64 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
2536
3309
  id: cveDetails.id,
2537
3310
  kev: cveDetails.kev,
2538
3311
  epss: cveDetails.epss
2539
- },
2540
- policy: { exploitSignalOverride: opts.exploitSignalOverride }
2541
- });
2542
- if (result.exploitSignalTriggered) {
2543
- exploitSignalTriggered = true;
2544
- }
2545
- }
2546
- let slaBreaches;
2547
- if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
2548
- const breach = checkSlaBreach(
2549
- normalizedId,
2550
- cveDetails.severity,
2551
- cveDetails.publishedAt,
2552
- opts.slaPolicy
2553
- );
2554
- if (breach) {
2555
- slaBreaches = [breach];
2556
- }
2557
- }
2558
- return { suppressed: false, exploitSignalTriggered, slaBreaches };
2559
- }
2560
-
2561
- // src/remediation/local/sbom.ts
2562
- function buildSbom(packages, vulnerableNames, results) {
2563
- const statusByPackage = /* @__PURE__ */ new Map();
2564
- for (const result of results) {
2565
- if (!vulnerableNames.has(result.packageName)) continue;
2566
- if (result.suppressedBy) {
2567
- statusByPackage.set(result.packageName, "suppressed");
2568
- } else if (!result.applied && result.strategy === "none") {
2569
- statusByPackage.set(result.packageName, "skipped");
2570
- } else if (result.applied) {
2571
- statusByPackage.set(result.packageName, "patched");
2572
- } else {
2573
- statusByPackage.set(result.packageName, "unpatched");
2574
- }
2575
- }
2576
- return packages.map((pkg) => {
2577
- const isVulnerable = vulnerableNames.has(pkg.name);
2578
- const entry = {
2579
- name: pkg.name,
2580
- version: pkg.version,
2581
- scope: pkg.type === "direct" ? "direct" : "transitive"
2582
- };
2583
- if (isVulnerable) {
2584
- entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2585
- }
2586
- return entry;
2587
- });
2588
- }
2589
-
2590
- // src/intelligence/sources/registry.ts
2591
- import { readFileSync as readFileSync8 } from "fs";
2592
- import { join as join10 } from "path";
2593
- import { execa as execa4 } from "execa";
2594
- import semver from "semver";
2595
- var NPM_REGISTRY = "https://registry.npmjs.org";
2596
- async function fetchPackageVersions(packageName) {
2597
- const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2598
- try {
2599
- const res = await httpClient({ url });
2600
- if (res.status === 404) return [];
2601
- if (!res.ok) {
2602
- throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2603
- }
2604
- const data = res.data;
2605
- return Object.keys(data.versions);
2606
- } catch (err) {
2607
- if (err instanceof Error && err.message.includes("registry error")) {
2608
- throw err;
2609
- }
2610
- return [];
2611
- }
2612
- }
2613
- async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2614
- const resolution = await resolveSafeUpgradeVersion(
2615
- packageName,
2616
- installedVersion,
2617
- firstPatchedVersion,
2618
- vulnerableRange
2619
- );
2620
- return resolution.safeVersion;
2621
- }
2622
- async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2623
- const versions = await fetchPackageVersions(packageName);
2624
- if (!versions.length) {
2625
- return {
2626
- candidates: {},
2627
- majorOnlyFixAvailable: false
2628
- };
2629
- }
2630
- const installed = semver.parse(installedVersion);
2631
- const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2632
- if (!vulnerableRange) return true;
2633
- try {
2634
- return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2635
- } catch {
2636
- return true;
2637
- }
2638
- }).sort(semver.compare);
2639
- if (!candidates.length) {
2640
- return {
2641
- candidates: {},
2642
- majorOnlyFixAvailable: false
2643
- };
2644
- }
2645
- const categorizedCandidates = {};
2646
- for (const candidate of candidates) {
2647
- const level = classifyUpgradeLevel(installedVersion, candidate);
2648
- if (!level) continue;
2649
- if (!categorizedCandidates[level]) {
2650
- categorizedCandidates[level] = candidate;
2651
- }
2652
- }
2653
- const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2654
- if (!safeVersion) {
2655
- return {
2656
- candidates: categorizedCandidates,
2657
- majorOnlyFixAvailable: false
2658
- };
2659
- }
2660
- const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2661
- const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2662
- if (!installed || !upgradeLevel) {
2663
- return {
2664
- safeVersion,
2665
- upgradeLevel,
2666
- candidates: categorizedCandidates,
2667
- majorOnlyFixAvailable
2668
- };
2669
- }
2670
- return {
2671
- safeVersion,
2672
- upgradeLevel,
2673
- candidates: categorizedCandidates,
2674
- majorOnlyFixAvailable
2675
- };
2676
- }
2677
- function classifyUpgradeLevel(installedVersion, candidateVersion) {
2678
- const installed = semver.parse(installedVersion);
2679
- const candidate = semver.parse(candidateVersion);
2680
- if (!installed || !candidate) return void 0;
2681
- if (candidate.major > installed.major) return "major";
2682
- if (candidate.minor > installed.minor) return "minor";
2683
- if (candidate.patch > installed.patch || candidate.version === installed.version) {
2684
- return "patch";
2685
- }
2686
- return void 0;
2687
- }
2688
- async function queryOutdatedPackages(cwd, options = {}) {
2689
- const pm = options.packageManager ?? detectPackageManager(cwd);
2690
- let stdout;
2691
- try {
2692
- const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2693
- const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2694
- const finalArgs = yarnArgs ?? args;
2695
- const result2 = await execa4(pm, finalArgs, {
2696
- cwd,
2697
- reject: false,
2698
- stdio: "pipe"
2699
- });
2700
- if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2701
- const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2702
- if (!result2.stdout) {
2703
- throw new Error(
2704
- `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2705
- );
2706
- }
2707
- }
2708
- stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2709
- } catch (err) {
2710
- if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2711
- throw new Error(`Failed to query outdated packages: ${err.message}`);
2712
- }
2713
- throw err;
2714
- }
2715
- if (!stdout.trim()) {
2716
- return /* @__PURE__ */ new Map();
2717
- }
2718
- const rawEntries = {};
2719
- if (pm === "yarn") {
2720
- for (const line of stdout.split("\n")) {
2721
- const trimmed = line.trim();
2722
- if (!trimmed) continue;
2723
- try {
2724
- const parsed = JSON.parse(trimmed);
2725
- if (parsed.type === "table" && parsed.data) {
2726
- const tableData = parsed.data;
2727
- const head = tableData.head ?? [];
2728
- const currentIdx = head.indexOf("Current");
2729
- const wantedIdx = head.indexOf("Wanted");
2730
- const latestIdx = head.indexOf("Latest");
2731
- for (const row of tableData.body ?? []) {
2732
- const name = row[0];
2733
- if (!name) continue;
2734
- rawEntries[name] = {
2735
- current: row[currentIdx] ?? "",
2736
- wanted: row[wantedIdx] ?? "",
2737
- latest: row[latestIdx] ?? "",
2738
- type: row[head.indexOf("Package Type")] ?? "dependencies"
2739
- };
2740
- }
2741
- }
2742
- } catch {
2743
- }
2744
- }
2745
- } else {
2746
- try {
2747
- const parsed = JSON.parse(stdout);
2748
- if (Array.isArray(parsed)) {
2749
- for (const entry of parsed) {
2750
- const name = entry.name ?? entry.packageName;
2751
- if (!name) continue;
2752
- rawEntries[name] = {
2753
- current: entry.current,
2754
- wanted: entry.wanted,
2755
- latest: entry.latest,
2756
- type: entry.dependencyType
2757
- };
2758
- }
2759
- } else {
2760
- Object.assign(rawEntries, parsed);
2761
- }
2762
- } catch {
2763
- throw new Error(`Could not parse ${pm} outdated --json output.`);
2764
- }
2765
- }
2766
- let directDeps;
2767
- try {
2768
- const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
2769
- directDeps = /* @__PURE__ */ new Set([
2770
- ...Object.keys(pkgRaw.dependencies ?? {}),
2771
- ...Object.keys(pkgRaw.devDependencies ?? {})
2772
- ]);
2773
- } catch {
2774
- directDeps = /* @__PURE__ */ new Set();
2775
- }
2776
- const result = /* @__PURE__ */ new Map();
2777
- for (const [name, entry] of Object.entries(rawEntries)) {
2778
- const currentVersion = entry.current ?? "";
2779
- const wantedVersion = entry.wanted ?? currentVersion;
2780
- const latestVersion = entry.latest ?? currentVersion;
2781
- if (!currentVersion || !latestVersion) continue;
2782
- const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2783
- if (!options.includeTransitive && dependencyScope === "transitive") continue;
2784
- const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2785
- result.set(name, {
2786
- currentVersion,
2787
- wantedVersion,
2788
- latestVersion,
2789
- isMajorBump,
2790
- dependencyScope
3312
+ },
3313
+ policy: { exploitSignalOverride: opts.exploitSignalOverride }
2791
3314
  });
3315
+ if (result.exploitSignalTriggered) {
3316
+ exploitSignalTriggered = true;
3317
+ }
2792
3318
  }
2793
- return result;
3319
+ let slaBreaches;
3320
+ if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
3321
+ const breach = checkSlaBreach(
3322
+ normalizedId,
3323
+ cveDetails.severity,
3324
+ cveDetails.publishedAt,
3325
+ opts.slaPolicy
3326
+ );
3327
+ if (breach) {
3328
+ slaBreaches = [breach];
3329
+ }
3330
+ }
3331
+ return { suppressed: false, exploitSignalTriggered, slaBreaches };
3332
+ }
3333
+
3334
+ // src/remediation/local/sbom.ts
3335
+ function buildSbom(packages, vulnerableNames, results) {
3336
+ const statusByPackage = /* @__PURE__ */ new Map();
3337
+ for (const result of results) {
3338
+ if (!vulnerableNames.has(result.packageName)) continue;
3339
+ if (result.suppressedBy) {
3340
+ statusByPackage.set(result.packageName, "suppressed");
3341
+ } else if (!result.applied && result.strategy === "none") {
3342
+ statusByPackage.set(result.packageName, "skipped");
3343
+ } else if (result.applied) {
3344
+ statusByPackage.set(result.packageName, "patched");
3345
+ } else {
3346
+ statusByPackage.set(result.packageName, "unpatched");
3347
+ }
3348
+ }
3349
+ return packages.map((pkg) => {
3350
+ const isVulnerable = vulnerableNames.has(pkg.name);
3351
+ const entry = {
3352
+ name: pkg.name,
3353
+ version: pkg.version,
3354
+ scope: pkg.type === "direct" ? "direct" : "transitive"
3355
+ };
3356
+ if (isVulnerable) {
3357
+ entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
3358
+ }
3359
+ return entry;
3360
+ });
2794
3361
  }
2795
3362
 
2796
3363
  // src/remediation/tools/apply-version-bump.ts
2797
3364
  import { z as z4 } from "zod";
2798
- import { join as join12 } from "path";
2799
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
3365
+ import { join as join14 } from "path";
3366
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
2800
3367
 
2801
3368
  // src/platform/safe-exec.ts
2802
- import { execa as execa5 } from "execa";
3369
+ import { execa as execa6 } from "execa";
2803
3370
  var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2804
3371
  "NODE_OPTIONS",
2805
3372
  "NODE_PATH",
@@ -2848,7 +3415,7 @@ function safeExeca(file, args = [], options = {}) {
2848
3415
  options.env ?? process.env,
2849
3416
  options.allowNodeOptions
2850
3417
  );
2851
- return execa5(file, args, {
3418
+ return execa6(file, args, {
2852
3419
  ...options,
2853
3420
  timeout,
2854
3421
  env: sanitizedEnv
@@ -2856,19 +3423,19 @@ function safeExeca(file, args = [], options = {}) {
2856
3423
  }
2857
3424
 
2858
3425
  // src/remediation/tools/apply-version-bump.ts
2859
- import semver2 from "semver";
3426
+ import semver3 from "semver";
2860
3427
 
2861
3428
  // src/platform/repo-lock.ts
2862
- import { mkdir, rm } from "fs/promises";
2863
- import { join as join11 } from "path";
3429
+ import { mkdir, rm as rm2 } from "fs/promises";
3430
+ import { join as join13 } from "path";
2864
3431
  async function sleep(ms) {
2865
- await new Promise((resolve4) => setTimeout(resolve4, ms));
3432
+ await new Promise((resolve5) => setTimeout(resolve5, ms));
2866
3433
  }
2867
3434
  async function acquireRepoLock(cwd, options = {}) {
2868
3435
  const timeoutMs = options.timeoutMs ?? 15e3;
2869
3436
  const retryDelayMs = options.retryDelayMs ?? 125;
2870
- const lockRoot = join11(cwd, ".autoremediator", "locks");
2871
- const lockPath = join11(cwd, ".autoremediator", "locks", "remediation.lock");
3437
+ const lockRoot = join13(cwd, ".autoremediator", "locks");
3438
+ const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
2872
3439
  const startedAt = Date.now();
2873
3440
  await mkdir(lockRoot, { recursive: true });
2874
3441
  while (true) {
@@ -2877,7 +3444,7 @@ async function acquireRepoLock(cwd, options = {}) {
2877
3444
  return {
2878
3445
  lockPath,
2879
3446
  release: async () => {
2880
- await rm(lockPath, { recursive: true, force: true });
3447
+ await rm2(lockPath, { recursive: true, force: true });
2881
3448
  }
2882
3449
  };
2883
3450
  } catch {
@@ -2909,6 +3476,7 @@ var applyVersionBumpTool = defineTool({
2909
3476
  dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
2910
3477
  policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
2911
3478
  runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
3479
+ typecheck: z4.boolean().default(false).describe("If true, run typecheck validation after applying the fix"),
2912
3480
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2913
3481
  installPreferOffline: z4.boolean().optional(),
2914
3482
  enforceFrozenLockfile: z4.boolean().optional(),
@@ -2923,13 +3491,14 @@ var applyVersionBumpTool = defineTool({
2923
3491
  dryRun,
2924
3492
  policy,
2925
3493
  runTests,
3494
+ typecheck,
2926
3495
  installMode,
2927
3496
  installPreferOffline,
2928
3497
  enforceFrozenLockfile,
2929
3498
  workspace
2930
3499
  }) => {
2931
3500
  const pm = packageManager ?? detectPackageManager(cwd);
2932
- const pkgPath = join12(cwd, "package.json");
3501
+ let pkgPath = join14(cwd, "package.json");
2933
3502
  const loadedPolicy = loadPolicy(cwd, policy);
2934
3503
  const commandConstraints = {
2935
3504
  ...loadedPolicy.constraints,
@@ -2939,9 +3508,6 @@ var applyVersionBumpTool = defineTool({
2939
3508
  workspace: workspace ?? loadedPolicy.constraints?.workspace
2940
3509
  };
2941
3510
  const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
2942
- const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
2943
- const testCommand = resolveTestCommand(pm, commandConstraints);
2944
- const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
2945
3511
  if (!isPackageAllowed(loadedPolicy, packageName)) {
2946
3512
  return {
2947
3513
  packageName,
@@ -2954,7 +3520,7 @@ var applyVersionBumpTool = defineTool({
2954
3520
  message: `Policy blocked changes for package "${packageName}".`
2955
3521
  };
2956
3522
  }
2957
- const isMajorBump = semver2.valid(fromVersion) && semver2.valid(toVersion) && semver2.major(toVersion) > semver2.major(fromVersion);
3523
+ const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
2958
3524
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2959
3525
  return {
2960
3526
  packageName,
@@ -2967,9 +3533,10 @@ var applyVersionBumpTool = defineTool({
2967
3533
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
2968
3534
  };
2969
3535
  }
3536
+ pkgPath = join14(cwd, "package.json");
2970
3537
  let pkgJson;
2971
3538
  try {
2972
- pkgJson = JSON.parse(readFileSync9(pkgPath, "utf8"));
3539
+ pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
2973
3540
  } catch {
2974
3541
  return {
2975
3542
  packageName,
@@ -2981,9 +3548,20 @@ var applyVersionBumpTool = defineTool({
2981
3548
  message: `Could not read package.json at "${pkgPath}".`
2982
3549
  };
2983
3550
  }
2984
- const depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3551
+ let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
2985
3552
  (f) => pkgJson[f]?.[packageName] !== void 0
2986
3553
  );
3554
+ if (!depField) {
3555
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, packageName, workspace);
3556
+ if (declaringWorkspace) {
3557
+ pkgPath = declaringWorkspace.manifestPath;
3558
+ pkgJson = declaringWorkspace.manifest;
3559
+ depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3560
+ (f) => pkgJson[f]?.[packageName] !== void 0
3561
+ );
3562
+ commandConstraints.workspace = workspace ?? declaringWorkspace.name;
3563
+ }
3564
+ }
2987
3565
  if (!depField) {
2988
3566
  return {
2989
3567
  packageName,
@@ -2999,9 +3577,14 @@ var applyVersionBumpTool = defineTool({
2999
3577
  const prefixMatch = currentRange.match(/^([~^]?)/);
3000
3578
  const prefix = prefixMatch?.[1] ?? "";
3001
3579
  const newRange = `${prefix}${toVersion}`;
3580
+ const effectiveInstallCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
3581
+ const effectiveTestCommand = resolveTestCommand(pm, commandConstraints);
3582
+ const effectiveTypecheckCommand = resolveTypecheckCommand(pm, commandConstraints, cwd);
3583
+ const effectiveDedupeCommand = resolveDedupeCommand(pm, commandConstraints);
3584
+ const shouldTypecheck = typecheck || Boolean(commandConstraints.typecheck);
3002
3585
  if (dryRun) {
3003
- const installCmd = installCommand.join(" ");
3004
- const testCmd = testCommand.join(" ");
3586
+ const installCmd = effectiveInstallCommand.join(" ");
3587
+ const testCmd = effectiveTestCommand.join(" ");
3005
3588
  return {
3006
3589
  packageName,
3007
3590
  strategy: "version-bump",
@@ -3009,14 +3592,14 @@ var applyVersionBumpTool = defineTool({
3009
3592
  toVersion,
3010
3593
  applied: false,
3011
3594
  dryRun: true,
3012
- message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${dedupeCommand.length > 0 ? ` and ${dedupeCommand.join(" ")} (best-effort)` : ""}.`
3595
+ message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${effectiveDedupeCommand.length > 0 ? ` and ${effectiveDedupeCommand.join(" ")} (best-effort)` : ""}.`
3013
3596
  };
3014
3597
  }
3015
3598
  return withRepoLock(cwd, async () => {
3016
3599
  pkgJson[depField][packageName] = newRange;
3017
3600
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3018
3601
  try {
3019
- const [installCmd, ...installArgs] = installCommand;
3602
+ const [installCmd, ...installArgs] = effectiveInstallCommand;
3020
3603
  await safeExeca(installCmd, installArgs, {
3021
3604
  cwd,
3022
3605
  stdio: "pipe"
@@ -3033,12 +3616,12 @@ var applyVersionBumpTool = defineTool({
3033
3616
  applied: false,
3034
3617
  dryRun: false,
3035
3618
  unresolvedReason: "install-failed",
3036
- message: `${installCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3619
+ message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3037
3620
  };
3038
3621
  }
3039
3622
  if (runTests) {
3040
3623
  try {
3041
- const [testCmd, ...testArgs] = testCommand;
3624
+ const [testCmd, ...testArgs] = effectiveTestCommand;
3042
3625
  await safeExeca(testCmd, testArgs, {
3043
3626
  cwd,
3044
3627
  stdio: "pipe"
@@ -3047,7 +3630,38 @@ var applyVersionBumpTool = defineTool({
3047
3630
  pkgJson[depField][packageName] = currentRange;
3048
3631
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3049
3632
  try {
3050
- const [rollbackCmd, ...rollbackArgs] = installCommand;
3633
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3634
+ await safeExeca(rollbackCmd, rollbackArgs, {
3635
+ cwd,
3636
+ stdio: "pipe"
3637
+ });
3638
+ } catch {
3639
+ }
3640
+ const message = err instanceof Error ? err.message : String(err);
3641
+ return {
3642
+ packageName,
3643
+ strategy: "version-bump",
3644
+ fromVersion,
3645
+ toVersion,
3646
+ applied: false,
3647
+ dryRun: false,
3648
+ unresolvedReason: "validation-failed",
3649
+ message: `${effectiveTestCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3650
+ };
3651
+ }
3652
+ }
3653
+ if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
3654
+ try {
3655
+ const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
3656
+ await safeExeca(tcCmd, tcArgs, {
3657
+ cwd,
3658
+ stdio: "pipe"
3659
+ });
3660
+ } catch (err) {
3661
+ pkgJson[depField][packageName] = currentRange;
3662
+ writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3663
+ try {
3664
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3051
3665
  await safeExeca(rollbackCmd, rollbackArgs, {
3052
3666
  cwd,
3053
3667
  stdio: "pipe"
@@ -3063,20 +3677,22 @@ var applyVersionBumpTool = defineTool({
3063
3677
  applied: false,
3064
3678
  dryRun: false,
3065
3679
  unresolvedReason: "validation-failed",
3066
- message: `${testCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3680
+ message: `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")}) after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3067
3681
  };
3068
3682
  }
3069
3683
  }
3070
3684
  let dedupeNote = "";
3071
- try {
3072
- const [dedupeCmd, ...dedupeArgs] = dedupeCommand;
3073
- await safeExeca(dedupeCmd, dedupeArgs, {
3074
- cwd,
3075
- stdio: "pipe"
3076
- });
3077
- } catch (err) {
3078
- const message = err instanceof Error ? err.message : String(err);
3079
- dedupeNote = ` Dedupe warning: ${dedupeCommand.join(" ")} failed (${message}).`;
3685
+ if (effectiveDedupeCommand.length > 0) {
3686
+ try {
3687
+ const [dedupeCmd, ...dedupeArgs] = effectiveDedupeCommand;
3688
+ await safeExeca(dedupeCmd, dedupeArgs, {
3689
+ cwd,
3690
+ stdio: "pipe"
3691
+ });
3692
+ } catch (err) {
3693
+ const message = err instanceof Error ? err.message : String(err);
3694
+ dedupeNote = ` Dedupe warning: ${effectiveDedupeCommand.join(" ")} failed (${message}).`;
3695
+ }
3080
3696
  }
3081
3697
  return {
3082
3698
  packageName,
@@ -3085,7 +3701,7 @@ var applyVersionBumpTool = defineTool({
3085
3701
  toVersion,
3086
3702
  applied: true,
3087
3703
  dryRun: false,
3088
- message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${installCommand.join(" ")}${runTests ? `, and passed ${testCommand.join(" ")}` : ""}.${dedupeNote}`
3704
+ message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
3089
3705
  };
3090
3706
  });
3091
3707
  }
@@ -3093,9 +3709,9 @@ var applyVersionBumpTool = defineTool({
3093
3709
 
3094
3710
  // src/remediation/tools/apply-package-override/index.ts
3095
3711
  import { z as z5 } from "zod";
3096
- import { join as join13 } from "path";
3097
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
3098
- import semver3 from "semver";
3712
+ import { join as join15 } from "path";
3713
+ import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
3714
+ import semver4 from "semver";
3099
3715
 
3100
3716
  // src/remediation/tools/apply-package-override/helpers.ts
3101
3717
  async function collectDependencyTrace(cwd, pm, packageName, constraints) {
@@ -3237,9 +3853,9 @@ var applyPackageOverrideTool = defineTool({
3237
3853
  workspace
3238
3854
  }) => {
3239
3855
  const pm = packageManager ?? detectPackageManager(cwd);
3240
- const pkgPath = join13(cwd, "package.json");
3241
- const denoJsonPath = join13(cwd, "deno.json");
3242
- const isDenoNative = pm === "deno" && !existsSync9(pkgPath);
3856
+ const pkgPath = join15(cwd, "package.json");
3857
+ const denoJsonPath = join15(cwd, "deno.json");
3858
+ const isDenoNative = pm === "deno" && !existsSync11(pkgPath);
3243
3859
  const loadedPolicy = loadPolicy(cwd, policy);
3244
3860
  const commandConstraints = {
3245
3861
  ...loadedPolicy.constraints,
@@ -3265,7 +3881,7 @@ var applyPackageOverrideTool = defineTool({
3265
3881
  message: `Policy blocked changes for package "${packageName}".`
3266
3882
  };
3267
3883
  }
3268
- const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
3884
+ const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
3269
3885
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
3270
3886
  return {
3271
3887
  packageName,
@@ -3281,7 +3897,7 @@ var applyPackageOverrideTool = defineTool({
3281
3897
  if (isDenoNative) {
3282
3898
  let denoJson;
3283
3899
  try {
3284
- denoJson = JSON.parse(readFileSync10(denoJsonPath, "utf8"));
3900
+ denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
3285
3901
  } catch {
3286
3902
  return {
3287
3903
  packageName,
@@ -3384,7 +4000,7 @@ var applyPackageOverrideTool = defineTool({
3384
4000
  }
3385
4001
  let pkgJson;
3386
4002
  try {
3387
- pkgJson = JSON.parse(readFileSync10(pkgPath, "utf8"));
4003
+ pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
3388
4004
  } catch {
3389
4005
  return {
3390
4006
  packageName,
@@ -3481,6 +4097,76 @@ var applyPackageOverrideTool = defineTool({
3481
4097
  }
3482
4098
  });
3483
4099
 
4100
+ // src/platform/supply-chain.ts
4101
+ async function verifyPackageTrust(packageName, targetVersion, policy) {
4102
+ if (!policy || !policy.minPublishAgeHours && !policy.blockInstallScripts && !policy.requireProvenance) {
4103
+ return { trusted: true };
4104
+ }
4105
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
4106
+ let packument = null;
4107
+ try {
4108
+ const res = await httpClient({
4109
+ url,
4110
+ headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" }
4111
+ });
4112
+ if (res.ok && res.data) {
4113
+ packument = res.data;
4114
+ }
4115
+ } catch (err) {
4116
+ const msg = err instanceof Error ? err.message : String(err);
4117
+ return {
4118
+ trusted: false,
4119
+ reason: `Could not verify supply-chain trust for "${packageName}@${targetVersion}": ${msg}`
4120
+ };
4121
+ }
4122
+ if (!packument) {
4123
+ return {
4124
+ trusted: false,
4125
+ reason: `Package packument for "${packageName}" not found on registry.`
4126
+ };
4127
+ }
4128
+ let publishAgeHours;
4129
+ if (packument.time?.[targetVersion]) {
4130
+ const publishedAt = new Date(packument.time[targetVersion]).getTime();
4131
+ publishAgeHours = Math.max(0, (Date.now() - publishedAt) / (1e3 * 60 * 60));
4132
+ if (policy.minPublishAgeHours && publishAgeHours < policy.minPublishAgeHours) {
4133
+ return {
4134
+ trusted: false,
4135
+ publishAgeHours,
4136
+ reason: `Target release "${packageName}@${targetVersion}" was published only ${publishAgeHours.toFixed(1)}h ago (minimum required age is ${policy.minPublishAgeHours}h).`
4137
+ };
4138
+ }
4139
+ }
4140
+ const versionData = packument.versions?.[targetVersion];
4141
+ const scripts = versionData?.scripts ?? {};
4142
+ const installScriptKeys = ["preinstall", "install", "postinstall"].filter((k) => Boolean(scripts[k]));
4143
+ const hasInstallScripts = installScriptKeys.length > 0;
4144
+ if (policy.blockInstallScripts && hasInstallScripts) {
4145
+ return {
4146
+ trusted: false,
4147
+ publishAgeHours,
4148
+ hasInstallScripts: true,
4149
+ reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
4150
+ };
4151
+ }
4152
+ const hasProvenance = Boolean(versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url);
4153
+ if (policy.requireProvenance && !hasProvenance) {
4154
+ return {
4155
+ trusted: false,
4156
+ publishAgeHours,
4157
+ hasInstallScripts,
4158
+ hasProvenance: false,
4159
+ reason: `Target release "${packageName}@${targetVersion}" does not have cryptographic build provenance attestations.`
4160
+ };
4161
+ }
4162
+ return {
4163
+ trusted: true,
4164
+ publishAgeHours,
4165
+ hasInstallScripts,
4166
+ hasProvenance
4167
+ };
4168
+ }
4169
+
3484
4170
  // src/remediation/local/primary-strategy.ts
3485
4171
  async function resolvePrimaryResult(params) {
3486
4172
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
@@ -3551,6 +4237,22 @@ async function resolvePrimaryResult(params) {
3551
4237
  }
3552
4238
  };
3553
4239
  }
4240
+ const trust2 = await verifyPackageTrust(pkg.name, safeUpgrade2.safeVersion, constraints.supplyChainPolicy);
4241
+ if (!trust2.trusted) {
4242
+ return {
4243
+ steps: 1,
4244
+ result: {
4245
+ packageName: pkg.name,
4246
+ strategy: "none",
4247
+ fromVersion: pkg.version,
4248
+ applied: false,
4249
+ dryRun,
4250
+ unresolvedReason: "policy-blocked",
4251
+ vulnerableRange: vulnerable.affected.vulnerableRange,
4252
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade2.safeVersion}": ${trust2.reason}`
4253
+ }
4254
+ };
4255
+ }
3554
4256
  const overrideResult = await applyPackageOverrideTool.execute({
3555
4257
  cwd,
3556
4258
  packageManager,
@@ -3606,6 +4308,22 @@ async function resolvePrimaryResult(params) {
3606
4308
  }
3607
4309
  };
3608
4310
  }
4311
+ const trust = await verifyPackageTrust(pkg.name, safeUpgrade.safeVersion, constraints.supplyChainPolicy);
4312
+ if (!trust.trusted) {
4313
+ return {
4314
+ steps: 1,
4315
+ result: {
4316
+ packageName: pkg.name,
4317
+ strategy: "none",
4318
+ fromVersion: pkg.version,
4319
+ applied: false,
4320
+ dryRun,
4321
+ unresolvedReason: "policy-blocked",
4322
+ vulnerableRange: vulnerable.affected.vulnerableRange,
4323
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade.safeVersion}": ${trust.reason}`
4324
+ }
4325
+ };
4326
+ }
3609
4327
  const applyResult = await applyVersionBumpTool.execute({
3610
4328
  cwd,
3611
4329
  packageManager,
@@ -3615,6 +4333,7 @@ async function resolvePrimaryResult(params) {
3615
4333
  dryRun,
3616
4334
  policy,
3617
4335
  runTests,
4336
+ typecheck: constraints.typecheck,
3618
4337
  installMode: constraints.installMode,
3619
4338
  installPreferOffline: constraints.installPreferOffline,
3620
4339
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
@@ -3628,10 +4347,10 @@ async function resolvePrimaryResult(params) {
3628
4347
 
3629
4348
  // src/remediation/tools/fetch-package-source.ts
3630
4349
  import { z as z6 } from "zod";
3631
- import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
3632
- import { join as join14 } from "path";
4350
+ import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
4351
+ import { join as join16 } from "path";
3633
4352
  import { tmpdir } from "os";
3634
- import { execa as execa6 } from "execa";
4353
+ import { execa as execa7 } from "execa";
3635
4354
  var fetchPackageSourceTool = defineTool({
3636
4355
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
3637
4356
  parameters: z6.object({
@@ -3649,25 +4368,25 @@ var fetchPackageSourceTool = defineTool({
3649
4368
  if (safePatterns.length === 0) {
3650
4369
  return { success: false, error: "No valid file patterns provided." };
3651
4370
  }
3652
- const tempBaseDir = await mkdtemp(join14(tmpdir(), "autoremediator-pkg-"));
3653
- const extractDir = join14(tempBaseDir, "out");
4371
+ const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
4372
+ const extractDir = join16(tempBaseDir, "out");
3654
4373
  try {
3655
4374
  const scopedName = packageName.split("/").pop();
3656
4375
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3657
4376
  await mkdir2(tempBaseDir, { recursive: true });
3658
- const tarballPath = join14(tempBaseDir, "package.tgz");
3659
- await execa6("curl", ["-L", "-o", tarballPath, npmUrl]);
4377
+ const tarballPath = join16(tempBaseDir, "package.tgz");
4378
+ await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3660
4379
  await mkdir2(extractDir, { recursive: true });
3661
- await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
4380
+ await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3662
4381
  const extractedContents = await readdir2(extractDir);
3663
- const packageRootDir = extractedContents.includes("package") ? join14(extractDir, "package") : extractDir;
4382
+ const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
3664
4383
  const sourceCode = {};
3665
4384
  async function walkDir(dir, relativeBase) {
3666
4385
  try {
3667
4386
  const files = await readdir2(dir, { withFileTypes: true });
3668
4387
  for (const file of files) {
3669
- const fullPath = join14(dir, file.name);
3670
- const relPath = join14(relativeBase, file.name);
4388
+ const fullPath = join16(dir, file.name);
4389
+ const relPath = join16(relativeBase, file.name);
3671
4390
  if (file.isDirectory()) {
3672
4391
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3673
4392
  file.name
@@ -3681,7 +4400,7 @@ var fetchPackageSourceTool = defineTool({
3681
4400
  });
3682
4401
  if (matches) {
3683
4402
  try {
3684
- const content = await readFile3(fullPath, "utf8");
4403
+ const content = await readFile4(fullPath, "utf8");
3685
4404
  sourceCode[relPath] = content;
3686
4405
  } catch {
3687
4406
  }
@@ -3716,7 +4435,7 @@ var fetchPackageSourceTool = defineTool({
3716
4435
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
3717
4436
  };
3718
4437
  } finally {
3719
- await rm2(tempBaseDir, { recursive: true, force: true });
4438
+ await rm3(tempBaseDir, { recursive: true, force: true });
3720
4439
  }
3721
4440
  }
3722
4441
  });
@@ -3774,6 +4493,7 @@ Analyze the source code to:
3774
4493
  2. Explain the root cause of the security issue
3775
4494
  3. Propose a secure fix that addresses the vulnerability
3776
4495
  4. Provide the complete fixed version of affected files
4496
+ 5. Optionally provide a standalone Node.js reproduction/regression test script
3777
4497
 
3778
4498
  ## Response Format
3779
4499
  Respond ONLY with valid JSON (no markdown, no extra text):
@@ -3784,7 +4504,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
3784
4504
  "path/to/other.ts": "Complete fixed source code for this file"
3785
4505
  },
3786
4506
  "confidence": 0.95,
3787
- "riskLevel": "medium"
4507
+ "riskLevel": "medium",
4508
+ "reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
3788
4509
  }
3789
4510
 
3790
4511
  Important:
@@ -4064,6 +4785,7 @@ var generatePatchTool = defineTool({
4064
4785
  success: true,
4065
4786
  patches,
4066
4787
  patchContent: patches[0]?.unifiedDiff,
4788
+ reproductionTest: analysis.reproductionTest,
4067
4789
  llmProvider: provider,
4068
4790
  llmModel: modelName,
4069
4791
  latencyMs,
@@ -4088,17 +4810,17 @@ var generatePatchTool = defineTool({
4088
4810
 
4089
4811
  // src/remediation/tools/apply-patch-file/index.ts
4090
4812
  import { z as z8 } from "zod";
4091
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
4092
- import { join as join16 } from "path";
4093
- import { execa as execa8 } from "execa";
4813
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
4814
+ import { join as join18 } from "path";
4815
+ import { execa as execa9 } from "execa";
4094
4816
 
4095
4817
  // src/remediation/tools/apply-patch-file/helpers.ts
4096
- import { existsSync as existsSync10 } from "fs";
4097
- import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
4818
+ import { existsSync as existsSync12 } from "fs";
4819
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
4098
4820
  import { createHash } from "crypto";
4099
4821
  import { tmpdir as tmpdir2 } from "os";
4100
- import { join as join15 } from "path";
4101
- import { execa as execa7 } from "execa";
4822
+ import { join as join17 } from "path";
4823
+ import { execa as execa8 } from "execa";
4102
4824
  async function resolvePatchMode(packageManager, cwd) {
4103
4825
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
4104
4826
  return "patch-package";
@@ -4129,13 +4851,13 @@ function computePatchIntegrity(patchContent) {
4129
4851
  return `sha256:${hex}`;
4130
4852
  }
4131
4853
  async function writePatchManifest(manifestFilePath, artifact) {
4132
- await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4854
+ await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4133
4855
  }
4134
4856
  async function configurePatchPackagePostinstall(cwd, packageManager) {
4135
- const pkgJsonPath = join15(cwd, "package.json");
4857
+ const pkgJsonPath = join17(cwd, "package.json");
4136
4858
  let pkgJson;
4137
4859
  try {
4138
- pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
4860
+ pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
4139
4861
  } catch {
4140
4862
  return {
4141
4863
  success: false,
@@ -4147,7 +4869,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4147
4869
  try {
4148
4870
  const commands = getPackageManagerCommands(packageManager);
4149
4871
  const [cmd, ...args] = commands.installDev("patch-package");
4150
- await execa7(cmd, args, {
4872
+ await execa8(cmd, args, {
4151
4873
  cwd,
4152
4874
  stdio: "pipe"
4153
4875
  });
@@ -4168,13 +4890,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4168
4890
  } else if (!currentPostinstall) {
4169
4891
  pkgJson.scripts.postinstall = patchApplyCmd;
4170
4892
  }
4171
- await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4893
+ await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4172
4894
  return { success: true };
4173
4895
  }
4174
4896
  async function capturePackageJsonSnapshot(cwd) {
4175
- const path = join15(cwd, "package.json");
4897
+ const path = join17(cwd, "package.json");
4176
4898
  try {
4177
- const content = await readFile4(path, "utf8");
4899
+ const content = await readFile5(path, "utf8");
4178
4900
  return { path, content };
4179
4901
  } catch {
4180
4902
  return void 0;
@@ -4190,19 +4912,19 @@ async function cleanupPatchArtifacts(params) {
4190
4912
  installCommand,
4191
4913
  rerunInstall
4192
4914
  } = params;
4193
- await rm3(patchFilePath, { force: true }).catch(() => void 0);
4915
+ await rm4(patchFilePath, { force: true }).catch(() => void 0);
4194
4916
  if (manifestFilePath) {
4195
- await rm3(manifestFilePath, { force: true }).catch(() => void 0);
4917
+ await rm4(manifestFilePath, { force: true }).catch(() => void 0);
4196
4918
  }
4197
4919
  if (patchMode === "patch-package" && packageJsonSnapshot) {
4198
- await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4920
+ await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4199
4921
  () => void 0
4200
4922
  );
4201
4923
  }
4202
4924
  if (!rerunInstall) return;
4203
4925
  try {
4204
4926
  const [installCmd, ...installArgs] = installCommand;
4205
- await execa7(installCmd, installArgs, {
4927
+ await execa8(installCmd, installArgs, {
4206
4928
  cwd,
4207
4929
  stdio: "pipe"
4208
4930
  });
@@ -4216,7 +4938,7 @@ async function applyNativePatch(params) {
4216
4938
  const createArgs = ["patch", packageSpec];
4217
4939
  let patchDir;
4218
4940
  try {
4219
- const createResult = await execa7(createCommand, createArgs, {
4941
+ const createResult = await execa8(createCommand, createArgs, {
4220
4942
  cwd,
4221
4943
  stdio: "pipe"
4222
4944
  });
@@ -4244,17 +4966,17 @@ ${createResult.stderr}`);
4244
4966
  error: `Could not determine native patch directory for ${packageSpec}.`
4245
4967
  };
4246
4968
  }
4247
- const tempPatchDir = await mkdtemp2(join15(tmpdir2(), "autoremediator-native-patch-"));
4248
- const tempPatchFile = join15(tempPatchDir, "change.patch");
4969
+ const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
4970
+ const tempPatchFile = join17(tempPatchDir, "change.patch");
4249
4971
  try {
4250
- await writeFile(tempPatchFile, patchContent, "utf8");
4251
- await execa7("patch", ["-p1", "-i", tempPatchFile], {
4972
+ await writeFile2(tempPatchFile, patchContent, "utf8");
4973
+ await execa8("patch", ["-p1", "-i", tempPatchFile], {
4252
4974
  cwd: patchDir,
4253
4975
  stdio: "pipe"
4254
4976
  });
4255
4977
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
4256
4978
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
4257
- await execa7(commitCommand, commitArgs, {
4979
+ await execa8(commitCommand, commitArgs, {
4258
4980
  cwd,
4259
4981
  stdio: "pipe"
4260
4982
  });
@@ -4269,19 +4991,19 @@ ${createResult.stderr}`);
4269
4991
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
4270
4992
  };
4271
4993
  } finally {
4272
- await rm3(tempPatchDir, { recursive: true, force: true });
4994
+ await rm4(tempPatchDir, { recursive: true, force: true });
4273
4995
  }
4274
4996
  return { success: true };
4275
4997
  }
4276
4998
  function extractPatchDirectory(output) {
4277
4999
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4278
5000
  for (const line of lines) {
4279
- if (existsSync10(line)) {
5001
+ if (existsSync12(line)) {
4280
5002
  return line;
4281
5003
  }
4282
5004
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
4283
5005
  for (const token of tokens) {
4284
- if (token.startsWith("/") && existsSync10(token)) {
5006
+ if (token.startsWith("/") && existsSync12(token)) {
4285
5007
  return token;
4286
5008
  }
4287
5009
  }
@@ -4291,7 +5013,7 @@ function extractPatchDirectory(output) {
4291
5013
  async function validatePatchWithTests(cwd, testCommand) {
4292
5014
  try {
4293
5015
  const [cmd, ...args] = testCommand;
4294
- const result = await execa7(cmd, args, {
5016
+ const result = await execa8(cmd, args, {
4295
5017
  cwd,
4296
5018
  timeout: 6e4,
4297
5019
  stdio: "pipe"
@@ -4351,6 +5073,7 @@ var applyPatchFileTool = defineTool({
4351
5073
  enforceFrozenLockfile: z8.boolean().optional(),
4352
5074
  workspace: z8.string().optional(),
4353
5075
  validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
5076
+ reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
4354
5077
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
4355
5078
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
4356
5079
  message: "Either patchContent or patches must be provided"
@@ -4372,6 +5095,7 @@ var applyPatchFileTool = defineTool({
4372
5095
  workspace,
4373
5096
  riskLevel,
4374
5097
  validateWithTests,
5098
+ reproductionTest,
4375
5099
  dryRun
4376
5100
  }) => {
4377
5101
  try {
@@ -4422,7 +5146,7 @@ var applyPatchFileTool = defineTool({
4422
5146
  };
4423
5147
  }
4424
5148
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
4425
- const patchFilePath = join16(cwd, patchesDir, patchFileName);
5149
+ const patchFilePath = join18(cwd, patchesDir, patchFileName);
4426
5150
  const manifestFilePath = `${patchFilePath}.json`;
4427
5151
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4428
5152
  const baseArtifact = {
@@ -4461,9 +5185,9 @@ var applyPatchFileTool = defineTool({
4461
5185
  }
4462
5186
  return withRepoLock(cwd, async () => {
4463
5187
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
4464
- const patchesDirPath = join16(cwd, patchesDir);
5188
+ const patchesDirPath = join18(cwd, patchesDir);
4465
5189
  await mkdir3(patchesDirPath, { recursive: true });
4466
- await writeFile2(patchFilePath, selectedPatch, "utf8");
5190
+ await writeFile3(patchFilePath, selectedPatch, "utf8");
4467
5191
  validationPhases.push({
4468
5192
  phase: "patch-write",
4469
5193
  passed: true,
@@ -4531,7 +5255,7 @@ var applyPatchFileTool = defineTool({
4531
5255
  if (patchMode === "patch-package") {
4532
5256
  try {
4533
5257
  const [installCmd, ...installArgs] = installCommand;
4534
- await execa8(installCmd, installArgs, {
5258
+ await execa9(installCmd, installArgs, {
4535
5259
  cwd,
4536
5260
  stdio: "pipe"
4537
5261
  });
@@ -4624,10 +5348,33 @@ var applyPatchFileTool = defineTool({
4624
5348
  message: "Patch validation tests passed."
4625
5349
  });
4626
5350
  }
5351
+ let reproductionTestPath;
5352
+ if (reproductionTest) {
5353
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5354
+ reproductionTestPath = join18(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5355
+ await writeFile3(reproductionTestPath, reproductionTest, "utf8");
5356
+ try {
5357
+ const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
5358
+ validationPhases.push({
5359
+ phase: "reproduction-test",
5360
+ passed: reproRun.exitCode === 0,
5361
+ message: "Reproduction test verified fix successfully."
5362
+ });
5363
+ } catch (err) {
5364
+ const reproErr = err instanceof Error ? err.message : String(err);
5365
+ validationPhases.push({
5366
+ phase: "reproduction-test",
5367
+ passed: false,
5368
+ error: `Reproduction test failed: ${reproErr}`
5369
+ });
5370
+ }
5371
+ }
4627
5372
  const finalArtifact = {
4628
5373
  ...artifact,
4629
5374
  applied: true,
4630
5375
  dryRun: false,
5376
+ reproductionTest,
5377
+ reproductionTestPath,
4631
5378
  validationPhases
4632
5379
  };
4633
5380
  await writePatchManifest(manifestFilePath, finalArtifact);
@@ -4937,7 +5684,11 @@ function resolveLocalRunOptions(options) {
4937
5684
  const runTests = options.runTests ?? false;
4938
5685
  const policy = options.policy ?? "";
4939
5686
  const patchesDir = options.patchesDir || "./patches";
4940
- const constraints = options.constraints ?? {};
5687
+ const constraints = {
5688
+ ...options.constraints,
5689
+ typecheck: options.typecheck ?? options.constraints?.typecheck,
5690
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy
5691
+ };
4941
5692
  const loadedPolicy = loadPolicy(cwd, options.policy);
4942
5693
  const llmProvider = resolveProvider(options);
4943
5694
  return {
@@ -4974,7 +5725,7 @@ function resolveLocalRunOptions(options) {
4974
5725
  }
4975
5726
 
4976
5727
  // src/remediation/local/vulnerability-match.ts
4977
- import semver4 from "semver";
5728
+ import semver5 from "semver";
4978
5729
  function findVulnerablePackages(cveDetails, installedPackages) {
4979
5730
  const vulnerablePackages = [];
4980
5731
  for (const affected of cveDetails.affectedPackages) {
@@ -4983,10 +5734,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
4983
5734
  if (affected.ecosystem !== "npm") continue;
4984
5735
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
4985
5736
  for (const installed of matches) {
4986
- if (!semver4.valid(installed.version)) continue;
5737
+ if (!semver5.valid(installed.version)) continue;
4987
5738
  let isVulnerable = false;
4988
5739
  try {
4989
- isVulnerable = semver4.satisfies(installed.version, affected.vulnerableRange, {
5740
+ isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4990
5741
  includePrerelease: false
4991
5742
  });
4992
5743
  } catch {
@@ -5329,7 +6080,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5329
6080
  };
5330
6081
  if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
5331
6082
  try {
5332
- if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
6083
+ if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
5333
6084
  includePrerelease: false
5334
6085
  })) {
5335
6086
  primaryResult.regressionDetected = true;
@@ -5420,8 +6171,8 @@ function accumulateStepResults(params) {
5420
6171
  }
5421
6172
 
5422
6173
  // src/remediation/orchestration-prompt.ts
5423
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
5424
- import { join as join17 } from "path";
6174
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
6175
+ import { join as join19 } from "path";
5425
6176
  function buildProviderAddendum(provider, personality = "balanced") {
5426
6177
  const personalityDirective = personality === "analytical" ? "Use concise, explicit rationale for tool decisions and unresolved outcomes." : personality === "pragmatic" ? "Prefer the smallest safe remediation path while preserving policy and validation gates." : "Balance concise execution with brief rationale for risky or unresolved outcomes.";
5427
6178
  const providerDirective = provider === "remote" ? "Use strict structured output and deterministic reporting fields." : "Use deterministic-first behavior and only rely on remote model fallback when required by patch generation.";
@@ -5432,13 +6183,13 @@ Provider profile:
5432
6183
  - ${personalityDirective}`;
5433
6184
  }
5434
6185
  function loadOrchestrationPrompt(ctx) {
5435
- const promptPath = join17(
6186
+ const promptPath = join19(
5436
6187
  process.cwd(),
5437
6188
  ".github",
5438
6189
  "instructions",
5439
6190
  "orchestration.instructions.md"
5440
6191
  );
5441
- if (!existsSync11(promptPath)) {
6192
+ if (!existsSync13(promptPath)) {
5442
6193
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
5443
6194
  Working directory: ${ctx.cwd}
5444
6195
  Package manager: ${ctx.packageManager}
@@ -5464,7 +6215,7 @@ Fallback sequence (when neither version bump nor override can be applied):
5464
6215
 
5465
6216
  Always respect dryRun and policy constraints.`;
5466
6217
  }
5467
- const template = readFileSync11(promptPath, "utf8");
6218
+ const template = readFileSync13(promptPath, "utf8");
5468
6219
  return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll(
5469
6220
  "{{directDependenciesOnly}}",
5470
6221
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -5481,77 +6232,13 @@ function createProgressEmitter(options) {
5481
6232
  at: (/* @__PURE__ */ new Date()).toISOString(),
5482
6233
  provider: extra?.provider,
5483
6234
  model: extra?.model
5484
- });
5485
- };
5486
- }
5487
-
5488
- // src/remediation/tools/lookup-cve.ts
5489
- import { z as z9 } from "zod";
5490
-
5491
- // src/intelligence/sources/cisa-kev.ts
5492
- var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
5493
- async function fetchCisaKevFeed() {
5494
- try {
5495
- const res = await httpClient({ url: CISA_KEV_URL });
5496
- if (!res.ok) return void 0;
5497
- return res.data;
5498
- } catch {
5499
- return void 0;
5500
- }
5501
- }
5502
- function findKevEntry(feed, cveId) {
5503
- if (!feed?.vulnerabilities?.length) return void 0;
5504
- const normalized = cveId.toUpperCase();
5505
- return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
5506
- }
5507
- async function enrichWithCisaKev(details) {
5508
- const feed = await fetchCisaKevFeed();
5509
- const entry = findKevEntry(feed, details.id);
5510
- if (!entry) return details;
5511
- details.kev = {
5512
- knownExploited: true,
5513
- dateAdded: entry.dateAdded,
5514
- dueDate: entry.dueDate,
5515
- requiredAction: entry.requiredAction,
5516
- knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
5517
- };
5518
- if (!details.references.includes(CISA_KEV_URL)) {
5519
- details.references.push(CISA_KEV_URL);
5520
- }
5521
- return details;
5522
- }
5523
-
5524
- // src/intelligence/sources/epss.ts
5525
- async function fetchEpss(cveId) {
5526
- const { epssApi } = getIntelligenceSourceConfig();
5527
- if (!epssApi) return void 0;
5528
- try {
5529
- const url = new URL(epssApi);
5530
- url.searchParams.set("cve", cveId);
5531
- const res = await httpClient({ url: url.toString() });
5532
- if (!res.ok) return void 0;
5533
- const body = res.data;
5534
- return body.data?.[0];
5535
- } catch {
5536
- return void 0;
5537
- }
5538
- }
5539
- async function enrichWithEpss(details) {
5540
- const row = await fetchEpss(details.id);
5541
- if (!row) return details;
5542
- const score = Number.parseFloat(row.epss);
5543
- const percentile = Number.parseFloat(row.percentile);
5544
- if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
5545
- return details;
5546
- }
5547
- details.epss = {
5548
- score,
5549
- percentile,
5550
- date: row.date
6235
+ });
5551
6236
  };
5552
- return details;
5553
6237
  }
5554
6238
 
6239
+ // src/remediation/tools/lookup-cve.ts
6240
+ import { z as z9 } from "zod";
6241
+
5555
6242
  // src/intelligence/sources/cve-services.ts
5556
6243
  function pickEnglishDescription(container) {
5557
6244
  if (!container?.descriptions?.length) return void 0;
@@ -5835,7 +6522,7 @@ var lookupCveTool = defineTool({
5835
6522
 
5836
6523
  // src/remediation/tools/check-version-match.ts
5837
6524
  import { z as z10 } from "zod";
5838
- import semver6 from "semver";
6525
+ import semver7 from "semver";
5839
6526
  var affectedPackageSchema = z10.object({
5840
6527
  name: z10.string(),
5841
6528
  ecosystem: z10.literal("npm"),
@@ -5864,10 +6551,10 @@ var checkVersionMatchTool = defineTool({
5864
6551
  (p) => p.name === affected.name
5865
6552
  );
5866
6553
  for (const installed of matches) {
5867
- if (!semver6.valid(installed.version)) continue;
6554
+ if (!semver7.valid(installed.version)) continue;
5868
6555
  let isVulnerable = false;
5869
6556
  try {
5870
- isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
6557
+ isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
5871
6558
  includePrerelease: false
5872
6559
  });
5873
6560
  } catch {
@@ -6185,7 +6872,7 @@ async function runRemediationPipeline(cveId, options = {}) {
6185
6872
  }
6186
6873
 
6187
6874
  // src/api/change-request/index.ts
6188
- import { execa as execa9 } from "execa";
6875
+ import { execa as execa10 } from "execa";
6189
6876
  import { randomBytes } from "crypto";
6190
6877
  function sanitizeBranchToken(value) {
6191
6878
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -6214,22 +6901,68 @@ function resolveProvider2(options) {
6214
6901
  return options.provider ?? "github";
6215
6902
  }
6216
6903
  function resolveGrouping(options) {
6217
- if (options.grouping && options.grouping !== "all") {
6904
+ const allowed = ["all", "per-cve", "per-package", "by-severity"];
6905
+ if (options.grouping && !allowed.includes(options.grouping)) {
6218
6906
  throw new Error(
6219
- "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
6907
+ `Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
6220
6908
  );
6221
6909
  }
6222
- return "all";
6910
+ return options.grouping ?? "all";
6911
+ }
6912
+ function groupReports(reports, grouping) {
6913
+ if (reports.length === 0) return [];
6914
+ if (grouping === "all") {
6915
+ return [reports];
6916
+ }
6917
+ if (grouping === "per-cve") {
6918
+ const map = /* @__PURE__ */ new Map();
6919
+ for (const report of reports) {
6920
+ const key = report.cveId.toUpperCase();
6921
+ const list = map.get(key) ?? [];
6922
+ list.push(report);
6923
+ map.set(key, list);
6924
+ }
6925
+ return Array.from(map.values());
6926
+ }
6927
+ if (grouping === "per-package") {
6928
+ const map = /* @__PURE__ */ new Map();
6929
+ for (const report of reports) {
6930
+ const pkgs = new Set(report.results.map((r) => r.packageName));
6931
+ if (pkgs.size === 0) {
6932
+ const list = map.get("misc") ?? [];
6933
+ list.push(report);
6934
+ map.set("misc", list);
6935
+ } else {
6936
+ for (const pkg of pkgs) {
6937
+ const list = map.get(pkg) ?? [];
6938
+ list.push(report);
6939
+ map.set(pkg, list);
6940
+ }
6941
+ }
6942
+ }
6943
+ return Array.from(map.values());
6944
+ }
6945
+ if (grouping === "by-severity") {
6946
+ const map = /* @__PURE__ */ new Map();
6947
+ for (const report of reports) {
6948
+ const sev = report.cveDetails?.severity ?? "UNKNOWN";
6949
+ const list = map.get(sev) ?? [];
6950
+ list.push(report);
6951
+ map.set(sev, list);
6952
+ }
6953
+ return Array.from(map.values());
6954
+ }
6955
+ return [reports];
6223
6956
  }
6224
6957
  async function detectCurrentBranch(cwd) {
6225
- const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6958
+ const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6226
6959
  cwd,
6227
6960
  stdio: "pipe"
6228
6961
  });
6229
6962
  return result.stdout.trim();
6230
6963
  }
6231
6964
  async function ensureRepoHasChanges(cwd) {
6232
- const diff = await execa9("git", ["status", "--porcelain"], {
6965
+ const diff = await execa10("git", ["status", "--porcelain"], {
6233
6966
  cwd,
6234
6967
  stdio: "pipe"
6235
6968
  });
@@ -6258,7 +6991,7 @@ async function createGitHubRequest(params) {
6258
6991
  if (params.draft) {
6259
6992
  args.push("--draft");
6260
6993
  }
6261
- const result = await execa9("gh", args, {
6994
+ const result = await execa10("gh", args, {
6262
6995
  cwd: params.cwd,
6263
6996
  stdio: "pipe"
6264
6997
  });
@@ -6283,7 +7016,7 @@ async function createGitLabRequest(params) {
6283
7016
  if (params.draft) {
6284
7017
  args.push("--draft");
6285
7018
  }
6286
- const result = await execa9("glab", args, {
7019
+ const result = await execa10("glab", args, {
6287
7020
  cwd: params.cwd,
6288
7021
  stdio: "pipe"
6289
7022
  });
@@ -6293,64 +7026,68 @@ async function createChangeRequestsForReports(params) {
6293
7026
  const { cwd, options, reports } = params;
6294
7027
  const provider = resolveProvider2(options);
6295
7028
  const grouping = resolveGrouping(options);
6296
- const plan = buildPlan(reports);
6297
- const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
6298
- const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
6299
- const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
6300
- const body = bodyFooter ? `${plan.body}
7029
+ const groups = groupReports(reports, grouping);
7030
+ if (groups.length === 0) {
7031
+ return [];
7032
+ }
7033
+ const results = [];
7034
+ const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
7035
+ const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
7036
+ const branchPrefix = options.branchPrefix ?? "autoremediator";
7037
+ const pushRemote = options.pushRemote ?? "origin";
7038
+ if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
7039
+ throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
7040
+ }
7041
+ if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
7042
+ throw new Error(`Invalid repository format: ${options.repository}`);
7043
+ }
7044
+ for (const group of groups) {
7045
+ const plan = buildPlan(group);
7046
+ const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
7047
+ const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
7048
+ const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
7049
+ const body = bodyFooter ? `${plan.body}
6301
7050
 
6302
7051
  ${bodyFooter}` : plan.body;
6303
- try {
6304
- const hasChanges = await ensureRepoHasChanges(cwd);
6305
7052
  if (!hasChanges) {
6306
- return [
6307
- {
6308
- provider,
6309
- grouping,
6310
- repository: options.repository,
6311
- branchName: "",
6312
- title,
6313
- body,
6314
- created: false,
6315
- cveIds: plan.cveIds,
6316
- packageNames: plan.packageNames,
6317
- error: "No repository changes to include in change request."
6318
- }
6319
- ];
7053
+ results.push({
7054
+ provider,
7055
+ grouping,
7056
+ repository: options.repository,
7057
+ branchName: "",
7058
+ title,
7059
+ body,
7060
+ created: false,
7061
+ cveIds: plan.cveIds,
7062
+ packageNames: plan.packageNames,
7063
+ error: "No repository changes to include in change request."
7064
+ });
7065
+ continue;
6320
7066
  }
6321
- const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
6322
- const branchPrefix = options.branchPrefix ?? "autoremediator";
6323
7067
  const branchName = toBranchName(branchPrefix, plan.cveIds);
6324
- const pushRemote = options.pushRemote ?? "origin";
6325
- if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
6326
- throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
6327
- }
6328
- if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
6329
- throw new Error(`Invalid repository format: ${options.repository}`);
6330
- }
6331
- await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
6332
- await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
6333
- await execa9("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
6334
- await execa9("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
6335
- const url = provider === "github" ? await createGitHubRequest({
6336
- cwd,
6337
- repository: options.repository,
6338
- head: branchName,
6339
- base: baseBranch,
6340
- title,
6341
- body,
6342
- draft: options.draft
6343
- }) : await createGitLabRequest({
6344
- cwd,
6345
- repository: options.repository,
6346
- source: branchName,
6347
- target: baseBranch,
6348
- title,
6349
- body,
6350
- draft: options.draft
6351
- });
6352
- return [
6353
- {
7068
+ try {
7069
+ await execa10("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
7070
+ await execa10("git", ["add", "-A"], { cwd, stdio: "pipe" });
7071
+ await execa10("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
7072
+ await execa10("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
7073
+ const url = provider === "github" ? await createGitHubRequest({
7074
+ cwd,
7075
+ repository: options.repository,
7076
+ head: branchName,
7077
+ base: baseBranch,
7078
+ title,
7079
+ body,
7080
+ draft: options.draft
7081
+ }) : await createGitLabRequest({
7082
+ cwd,
7083
+ repository: options.repository,
7084
+ source: branchName,
7085
+ target: baseBranch,
7086
+ title,
7087
+ body,
7088
+ draft: options.draft
7089
+ });
7090
+ results.push({
6354
7091
  provider,
6355
7092
  grouping,
6356
7093
  repository: options.repository,
@@ -6362,11 +7099,13 @@ ${bodyFooter}` : plan.body;
6362
7099
  url,
6363
7100
  cveIds: plan.cveIds,
6364
7101
  packageNames: plan.packageNames
7102
+ });
7103
+ if (groups.length > 1) {
7104
+ await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
7105
+ });
6365
7106
  }
6366
- ];
6367
- } catch (error) {
6368
- return [
6369
- {
7107
+ } catch (error) {
7108
+ results.push({
6370
7109
  provider,
6371
7110
  grouping,
6372
7111
  repository: options.repository,
@@ -6378,9 +7117,10 @@ ${bodyFooter}` : plan.body;
6378
7117
  cveIds: plan.cveIds,
6379
7118
  packageNames: plan.packageNames,
6380
7119
  error: error instanceof Error ? error.message : String(error)
6381
- }
6382
- ];
7120
+ });
7121
+ }
6383
7122
  }
7123
+ return results;
6384
7124
  }
6385
7125
 
6386
7126
  // src/api/context.ts
@@ -6414,7 +7154,7 @@ function resolveConstraints(options, cwd) {
6414
7154
 
6415
7155
  // src/platform/evidence.ts
6416
7156
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6417
- import { join as join18 } from "path";
7157
+ import { join as join20 } from "path";
6418
7158
  function createEvidenceLog(cwd, cveIds, context = {}) {
6419
7159
  return {
6420
7160
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -6446,9 +7186,9 @@ function finalizeEvidence(log) {
6446
7186
  return log;
6447
7187
  }
6448
7188
  function writeEvidenceLog(cwd, log) {
6449
- const dir = join18(cwd, ".autoremediator", "evidence");
7189
+ const dir = join20(cwd, ".autoremediator", "evidence");
6450
7190
  mkdirSync3(dir, { recursive: true });
6451
- const filePath = join18(dir, `${log.runId}.json`);
7191
+ const filePath = join20(dir, `${log.runId}.json`);
6452
7192
  writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
6453
7193
  return filePath;
6454
7194
  }
@@ -6587,10 +7327,15 @@ function writeRemediateEvidence(cwd, evidence) {
6587
7327
  }
6588
7328
 
6589
7329
  // src/api/remediate/validators.ts
7330
+ var CVE_ID_REGEX = /^CVE-\d{4}-\d{1,7}$/i;
7331
+ var GHSA_ID_REGEX = /^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/i;
7332
+ function isVulnerabilityId(id) {
7333
+ return CVE_ID_REGEX.test(id) || GHSA_ID_REGEX.test(id);
7334
+ }
6590
7335
  function assertValidCveId(cveId) {
6591
- if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
7336
+ if (!isVulnerabilityId(cveId)) {
6592
7337
  throw new Error(
6593
- `Invalid CVE ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337).`
7338
+ `Invalid vulnerability ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337) or GHSA-xxxx-xxxx-xxxx.`
6594
7339
  );
6595
7340
  }
6596
7341
  }
@@ -6686,13 +7431,14 @@ async function planRemediation(cveId, options = {}) {
6686
7431
  }
6687
7432
 
6688
7433
  // src/scanner/parse-input.ts
6689
- import { extname, resolve as resolve3 } from "path";
6690
- import { readFileSync as readFileSync15 } from "fs";
6691
- import { execa as execa10 } from "execa";
7434
+ import { extname, resolve as resolve4 } from "path";
7435
+ import { readFileSync as readFileSync17 } from "fs";
7436
+ import { execa as execa11 } from "execa";
6692
7437
 
6693
7438
  // src/scanner/adapters/npm-audit.ts
6694
- import { readFileSync as readFileSync12 } from "fs";
7439
+ import { readFileSync as readFileSync14 } from "fs";
6695
7440
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
7441
+ var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6696
7442
  function normalizeSeverity(raw) {
6697
7443
  if (!raw) return "UNKNOWN";
6698
7444
  const up = raw.toUpperCase();
@@ -6707,7 +7453,8 @@ function parseNpmAuditJsonFromString(content) {
6707
7453
  for (const vuln of Object.values(report.vulnerabilities ?? {})) {
6708
7454
  for (const viaEntry of vuln.via ?? []) {
6709
7455
  const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
6710
- const matches = text.match(CVE_REGEX) ?? [];
7456
+ const cveMatches = text.match(CVE_REGEX) ?? [];
7457
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
6711
7458
  for (const match of matches) {
6712
7459
  const cveId = match.toUpperCase();
6713
7460
  const key = `${cveId}:${vuln.name}`;
@@ -6725,13 +7472,14 @@ function parseNpmAuditJsonFromString(content) {
6725
7472
  return findings;
6726
7473
  }
6727
7474
  function parseNpmAuditJsonFile(filePath) {
6728
- const content = readFileSync12(filePath, "utf8");
7475
+ const content = readFileSync14(filePath, "utf8");
6729
7476
  return parseNpmAuditJsonFromString(content);
6730
7477
  }
6731
7478
 
6732
7479
  // src/scanner/adapters/yarn-audit.ts
6733
- import { readFileSync as readFileSync13 } from "fs";
7480
+ import { readFileSync as readFileSync15 } from "fs";
6734
7481
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
7482
+ var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6735
7483
  function normalizeSeverity2(raw) {
6736
7484
  if (!raw) return "UNKNOWN";
6737
7485
  const up = raw.toUpperCase();
@@ -6756,7 +7504,8 @@ function parseYarnAuditJsonFromString(content) {
6756
7504
  const packageName = advisory?.module_name;
6757
7505
  const severity = normalizeSeverity2(advisory?.severity);
6758
7506
  const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
6759
- const matches = text.match(CVE_REGEX2) ?? [];
7507
+ const cveMatches = text.match(CVE_REGEX2) ?? [];
7508
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
6760
7509
  for (const match of matches) {
6761
7510
  const cveId = match.toUpperCase();
6762
7511
  const key = `${cveId}:${packageName ?? ""}`;
@@ -6773,12 +7522,12 @@ function parseYarnAuditJsonFromString(content) {
6773
7522
  return findings;
6774
7523
  }
6775
7524
  function parseYarnAuditJsonFile(filePath) {
6776
- const content = readFileSync13(filePath, "utf8");
7525
+ const content = readFileSync15(filePath, "utf8");
6777
7526
  return parseYarnAuditJsonFromString(content);
6778
7527
  }
6779
7528
 
6780
7529
  // src/scanner/adapters/sarif.ts
6781
- import { readFileSync as readFileSync14 } from "fs";
7530
+ import { readFileSync as readFileSync16 } from "fs";
6782
7531
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
6783
7532
  function extractPackageName(result) {
6784
7533
  const pkg = result.properties?.["packageName"];
@@ -6816,7 +7565,7 @@ function parseSarifFromString(content) {
6816
7565
  return findings;
6817
7566
  }
6818
7567
  function parseSarifFile(filePath) {
6819
- const content = readFileSync14(filePath, "utf8");
7568
+ const content = readFileSync16(filePath, "utf8");
6820
7569
  return parseSarifFromString(content);
6821
7570
  }
6822
7571
 
@@ -6825,7 +7574,7 @@ function parseScanInput(filePath, format) {
6825
7574
  if (filePath.includes("\0")) {
6826
7575
  throw new Error("Invalid scan input path: path contains null bytes");
6827
7576
  }
6828
- const resolvedPath = resolve3(filePath);
7577
+ const resolvedPath = resolve4(filePath);
6829
7578
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
6830
7579
  if (resolved === "npm-audit") {
6831
7580
  return parseNpmAuditJsonFile(resolvedPath);
@@ -6847,7 +7596,7 @@ async function parseScanInputFromAudit(params) {
6847
7596
  }
6848
7597
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
6849
7598
  const [cmd, ...args] = command;
6850
- const result = await execa10(cmd, args, {
7599
+ const result = await execa11(cmd, args, {
6851
7600
  cwd: params.cwd,
6852
7601
  stdio: "pipe",
6853
7602
  reject: false
@@ -6901,7 +7650,7 @@ function inferFormat(filePath) {
6901
7650
  const ext = extname(filePath).toLowerCase();
6902
7651
  if (ext === ".sarif") return "sarif";
6903
7652
  try {
6904
- const content = readFileSync15(filePath, "utf8");
7653
+ const content = readFileSync17(filePath, "utf8");
6905
7654
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
6906
7655
  if (firstLine) {
6907
7656
  const parsed = JSON.parse(firstLine);
@@ -7312,14 +8061,14 @@ async function remediatePortfolio(targets, options = {}) {
7312
8061
  }
7313
8062
 
7314
8063
  // src/api/update-outdated/index.ts
7315
- import { join as join19 } from "path";
7316
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
7317
- import { execa as execa11 } from "execa";
8064
+ import { join as join21 } from "path";
8065
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "fs";
8066
+ import { execa as execa12 } from "execa";
7318
8067
  async function applyBump(params) {
7319
- const pkgPath = join19(params.cwd, "package.json");
8068
+ const pkgPath = join21(params.cwd, "package.json");
7320
8069
  let pkgJson;
7321
8070
  try {
7322
- pkgJson = JSON.parse(readFileSync16(pkgPath, "utf8"));
8071
+ pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
7323
8072
  } catch {
7324
8073
  return {
7325
8074
  applied: false,
@@ -7344,7 +8093,7 @@ async function applyBump(params) {
7344
8093
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7345
8094
  try {
7346
8095
  const [installCmd, ...installArgs] = params.installCommand;
7347
- await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8096
+ await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
7348
8097
  } catch (err) {
7349
8098
  pkgJson[depField][params.packageName] = currentRange;
7350
8099
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -7357,13 +8106,13 @@ async function applyBump(params) {
7357
8106
  if (params.runTests) {
7358
8107
  try {
7359
8108
  const [testCmd, ...testArgs] = params.testCommand;
7360
- await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8109
+ await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
7361
8110
  } catch (err) {
7362
8111
  pkgJson[depField][params.packageName] = currentRange;
7363
8112
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7364
8113
  try {
7365
8114
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
7366
- await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8115
+ await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
7367
8116
  } catch {
7368
8117
  }
7369
8118
  const message = err instanceof Error ? err.message : String(err);
@@ -7564,131 +8313,169 @@ async function checkReachability(options) {
7564
8313
  return assessPackageReachability(cwd, options.packageName, options.symbol);
7565
8314
  }
7566
8315
 
7567
- // src/remediation/evaluate-package.ts
7568
- import semver7 from "semver";
7569
- async function evaluatePackage(packageName, options = {}) {
7570
- const version = options.version?.trim();
7571
- const enrich = options.enrichIntelligence !== false;
7572
- let versions = [];
7573
- let latestVersion;
7574
- try {
7575
- versions = await fetchPackageVersions(packageName);
7576
- if (versions.length > 0) {
7577
- const valid = versions.filter((v) => semver7.valid(v)).sort((a, b) => semver7.compare(a, b));
7578
- latestVersion = valid[valid.length - 1];
7579
- }
7580
- } catch {
7581
- }
7582
- let rawVulns = [];
7583
- try {
7584
- rawVulns = await queryOsvPackage(packageName, version);
7585
- } catch {
7586
- rawVulns = [];
7587
- }
7588
- let kevFeed = null;
7589
- if (enrich) {
8316
+ // src/scanner/delta-scan.ts
8317
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
8318
+ import { join as join22 } from "path";
8319
+ import { execa as execa13 } from "execa";
8320
+ import semver9 from "semver";
8321
+
8322
+ // src/scanner/lockfile-diff.ts
8323
+ import YAML2 from "yaml";
8324
+ import semver8 from "semver";
8325
+ function parseLockfileDependencies(filename, content) {
8326
+ const result = /* @__PURE__ */ new Map();
8327
+ const add = (name, ver) => {
8328
+ if (!name || !ver) return;
8329
+ const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
8330
+ let set = result.get(name);
8331
+ if (!set) {
8332
+ set = /* @__PURE__ */ new Set();
8333
+ result.set(name, set);
8334
+ }
8335
+ set.add(cleanVer);
8336
+ };
8337
+ if (filename.endsWith("package-lock.json")) {
7590
8338
  try {
7591
- kevFeed = await fetchCisaKevFeed();
7592
- } catch {
7593
- kevFeed = null;
7594
- }
7595
- }
7596
- const entries = [];
7597
- for (const raw of rawVulns) {
7598
- const cveDetails = parseOsvVuln(raw);
7599
- const affected = cveDetails.affectedPackages.find(
7600
- (p) => p.name.toLowerCase() === packageName.toLowerCase()
7601
- );
7602
- if (version && affected?.vulnerableRange) {
7603
- const cleanVer = semver7.clean(version) || version;
7604
- if (semver7.valid(cleanVer) && !semver7.satisfies(cleanVer, affected.vulnerableRange)) {
7605
- continue;
8339
+ const data = JSON.parse(content);
8340
+ if (data.packages && typeof data.packages === "object") {
8341
+ for (const [key, pkg] of Object.entries(data.packages)) {
8342
+ if (!key || key === "" || typeof pkg !== "object" || !pkg) continue;
8343
+ const idx = key.lastIndexOf("node_modules/");
8344
+ if (idx !== -1) {
8345
+ const rawName = key.slice(idx + "node_modules/".length);
8346
+ const ver = pkg.version;
8347
+ add(rawName, ver);
8348
+ }
8349
+ }
8350
+ }
8351
+ if (data.dependencies && typeof data.dependencies === "object") {
8352
+ const walkDeps = (deps) => {
8353
+ for (const [name, info] of Object.entries(deps)) {
8354
+ if (!info || typeof info !== "object") continue;
8355
+ const depObj = info;
8356
+ add(name, depObj.version);
8357
+ if (depObj.dependencies && typeof depObj.dependencies === "object") {
8358
+ walkDeps(depObj.dependencies);
8359
+ }
8360
+ }
8361
+ };
8362
+ walkDeps(data.dependencies);
7606
8363
  }
8364
+ } catch {
7607
8365
  }
7608
- let inCisaKev = false;
7609
- let epssScore;
7610
- let epssPercentile;
7611
- if (enrich && cveDetails.id.startsWith("CVE-")) {
7612
- if (kevFeed) {
7613
- inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
8366
+ } else if (filename.endsWith("pnpm-lock.yaml")) {
8367
+ try {
8368
+ const data = YAML2.parse(content);
8369
+ if (data?.packages && typeof data.packages === "object") {
8370
+ for (const [key, val] of Object.entries(data.packages)) {
8371
+ let rawKey = key.replace(/^\//, "");
8372
+ const parenIdx = rawKey.indexOf("(");
8373
+ if (parenIdx !== -1) {
8374
+ rawKey = rawKey.slice(0, parenIdx);
8375
+ }
8376
+ const lastAt = rawKey.lastIndexOf("@");
8377
+ if (lastAt > 0) {
8378
+ const name = val?.name ?? rawKey.slice(0, lastAt);
8379
+ const version = val?.version ?? rawKey.slice(lastAt + 1);
8380
+ add(name, version);
8381
+ }
8382
+ }
7614
8383
  }
7615
- try {
7616
- const epss = await fetchEpss(cveDetails.id);
7617
- if (epss) {
7618
- epssScore = Number.parseFloat(epss.epss);
7619
- epssPercentile = Number.parseFloat(epss.percentile);
8384
+ if (data?.snapshots && typeof data.snapshots === "object") {
8385
+ for (const key of Object.keys(data.snapshots)) {
8386
+ let rawKey = key.replace(/^\//, "");
8387
+ const parenIdx = rawKey.indexOf("(");
8388
+ if (parenIdx !== -1) {
8389
+ rawKey = rawKey.slice(0, parenIdx);
8390
+ }
8391
+ const lastAt = rawKey.lastIndexOf("@");
8392
+ if (lastAt > 0) {
8393
+ const name = rawKey.slice(0, lastAt);
8394
+ const version = rawKey.slice(lastAt + 1);
8395
+ add(name, version);
8396
+ }
7620
8397
  }
7621
- } catch {
7622
8398
  }
8399
+ } catch {
7623
8400
  }
7624
- let safeUpgradeVersion;
7625
- if (affected?.firstPatchedVersion) {
7626
- try {
7627
- const safe = await findSafeUpgradeVersion(
7628
- packageName,
7629
- version || "0.0.0",
7630
- affected.firstPatchedVersion
7631
- );
7632
- safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
7633
- } catch {
7634
- safeUpgradeVersion = affected.firstPatchedVersion;
8401
+ } else if (filename.endsWith("yarn.lock")) {
8402
+ const lines = content.split("\n");
8403
+ let currentPackages = [];
8404
+ for (const line of lines) {
8405
+ if (line.startsWith("#") || line.trim() === "") continue;
8406
+ if (!line.startsWith(" ")) {
8407
+ currentPackages = [];
8408
+ const parts = line.split(",").map((p) => p.trim().replace(/^"/, "").replace(/":?$/, ""));
8409
+ for (const part of parts) {
8410
+ const lastAt = part.lastIndexOf("@");
8411
+ if (lastAt > 0) {
8412
+ currentPackages.push(part.slice(0, lastAt));
8413
+ }
8414
+ }
8415
+ } else {
8416
+ const match = line.match(/^\s+version\s+"?([^"\s]+)"?/);
8417
+ if (match && currentPackages.length > 0) {
8418
+ const ver = match[1];
8419
+ for (const pkg of currentPackages) {
8420
+ add(pkg, ver);
8421
+ }
8422
+ currentPackages = [];
8423
+ }
7635
8424
  }
7636
8425
  }
7637
- entries.push({
7638
- cveId: cveDetails.id,
7639
- severity: cveDetails.severity,
7640
- summary: cveDetails.summary,
7641
- vulnerableRange: affected?.vulnerableRange,
7642
- firstPatchedVersion: affected?.firstPatchedVersion,
7643
- safeUpgradeVersion,
7644
- epssScore,
7645
- epssPercentile,
7646
- inCisaKev,
7647
- references: cveDetails.references
7648
- });
7649
8426
  }
7650
- const isVulnerable = entries.length > 0;
7651
- let verdict = "safe";
7652
- if (isVulnerable) {
7653
- const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
7654
- const hasHighOrCritical = entries.some(
7655
- (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
7656
- );
7657
- if (hasKevOrHighEpss) {
7658
- verdict = "actively-exploited";
7659
- } else if (hasHighOrCritical) {
7660
- verdict = "vulnerable";
7661
- } else {
7662
- verdict = "caution";
8427
+ return result;
8428
+ }
8429
+ function computeLockfileDiff(filename, oldContent, newContent) {
8430
+ const changes = [];
8431
+ const oldDeps = oldContent ? parseLockfileDependencies(filename, oldContent) : /* @__PURE__ */ new Map();
8432
+ const newDeps = newContent ? parseLockfileDependencies(filename, newContent) : /* @__PURE__ */ new Map();
8433
+ const allPkgs = /* @__PURE__ */ new Set([...oldDeps.keys(), ...newDeps.keys()]);
8434
+ for (const pkg of allPkgs) {
8435
+ const oldVers = oldDeps.get(pkg) ?? /* @__PURE__ */ new Set();
8436
+ const newVers = newDeps.get(pkg) ?? /* @__PURE__ */ new Set();
8437
+ for (const nVer of newVers) {
8438
+ if (!oldVers.has(nVer)) {
8439
+ if (oldVers.size === 0) {
8440
+ changes.push({
8441
+ packageName: pkg,
8442
+ changeType: "added",
8443
+ newVersion: nVer,
8444
+ dependencyType: "transitive",
8445
+ manifestPath: filename
8446
+ });
8447
+ } else {
8448
+ const [firstOld] = oldVers;
8449
+ const cleanOld = semver8.clean(firstOld) || semver8.coerce(firstOld)?.version;
8450
+ const cleanNew = semver8.clean(nVer) || semver8.coerce(nVer)?.version;
8451
+ const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
8452
+ changes.push({
8453
+ packageName: pkg,
8454
+ changeType: isUpgrade ? "upgraded" : "downgraded",
8455
+ oldVersion: firstOld,
8456
+ newVersion: nVer,
8457
+ dependencyType: "transitive",
8458
+ manifestPath: filename
8459
+ });
8460
+ }
8461
+ }
8462
+ }
8463
+ if (newVers.size === 0 && oldVers.size > 0) {
8464
+ for (const oVer of oldVers) {
8465
+ changes.push({
8466
+ packageName: pkg,
8467
+ changeType: "removed",
8468
+ oldVersion: oVer,
8469
+ dependencyType: "transitive",
8470
+ manifestPath: filename
8471
+ });
8472
+ }
7663
8473
  }
7664
8474
  }
7665
- let recommendedVersion;
7666
- if (isVulnerable) {
7667
- const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver7.valid(v))).sort((a, b) => semver7.compare(a, b));
7668
- recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
7669
- } else {
7670
- recommendedVersion = version || latestVersion;
7671
- }
7672
- const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
7673
- return {
7674
- schemaVersion: "1.0",
7675
- packageName,
7676
- evaluatedVersion: version,
7677
- isVulnerable,
7678
- verdict,
7679
- summary,
7680
- vulnerabilities: entries,
7681
- recommendedVersion,
7682
- latestVersion,
7683
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
7684
- };
8475
+ return changes;
7685
8476
  }
7686
8477
 
7687
8478
  // src/scanner/delta-scan.ts
7688
- import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
7689
- import { join as join20 } from "path";
7690
- import { execa as execa12 } from "execa";
7691
- import semver8 from "semver";
7692
8479
  var DEP_SECTIONS = [
7693
8480
  "dependencies",
7694
8481
  "devDependencies",
@@ -7698,7 +8485,7 @@ var DEP_SECTIONS = [
7698
8485
  async function getChangedManifestPaths(cwd, baseRef) {
7699
8486
  const manifests = /* @__PURE__ */ new Set();
7700
8487
  try {
7701
- const { stdout: diffFiles } = await execa12(
8488
+ const { stdout: diffFiles } = await execa13(
7702
8489
  "git",
7703
8490
  ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
7704
8491
  { cwd, reject: false }
@@ -7709,7 +8496,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7709
8496
  manifests.add(trimmed);
7710
8497
  }
7711
8498
  }
7712
- const { stdout: untrackedFiles } = await execa12(
8499
+ const { stdout: untrackedFiles } = await execa13(
7713
8500
  "git",
7714
8501
  ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
7715
8502
  { cwd, reject: false }
@@ -7721,7 +8508,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7721
8508
  }
7722
8509
  }
7723
8510
  } catch {
7724
- if (existsSync12(join20(cwd, "package.json"))) {
8511
+ if (existsSync14(join22(cwd, "package.json"))) {
7725
8512
  manifests.add("package.json");
7726
8513
  }
7727
8514
  }
@@ -7729,7 +8516,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7729
8516
  }
7730
8517
  async function getOldManifestContent(cwd, baseRef, relPath) {
7731
8518
  try {
7732
- const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
8519
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
7733
8520
  cwd,
7734
8521
  reject: false
7735
8522
  });
@@ -7740,10 +8527,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
7740
8527
  }
7741
8528
  }
7742
8529
  function getNewManifestContent(cwd, relPath) {
7743
- const fullPath = join20(cwd, relPath);
7744
- if (!existsSync12(fullPath)) return null;
8530
+ const fullPath = join22(cwd, relPath);
8531
+ if (!existsSync14(fullPath)) return null;
7745
8532
  try {
7746
- return JSON.parse(readFileSync17(fullPath, "utf8"));
8533
+ return JSON.parse(readFileSync19(fullPath, "utf8"));
7747
8534
  } catch {
7748
8535
  return null;
7749
8536
  }
@@ -7764,9 +8551,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7764
8551
  manifestPath: relPath
7765
8552
  });
7766
8553
  } else if (oldVer !== newVer) {
7767
- const cleanOld = semver8.clean(oldVer) || semver8.coerce(oldVer)?.version;
7768
- const cleanNew = semver8.clean(newVer) || semver8.coerce(newVer)?.version;
7769
- const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
8554
+ const cleanOld = semver9.clean(oldVer) || semver9.coerce(oldVer)?.version;
8555
+ const cleanNew = semver9.clean(newVer) || semver9.coerce(newVer)?.version;
8556
+ const isUpgrade = cleanOld && cleanNew && semver9.valid(cleanOld) && semver9.valid(cleanNew) ? semver9.gt(cleanNew, cleanOld) : true;
7770
8557
  changes.push({
7771
8558
  packageName: pkgName,
7772
8559
  changeType: isUpgrade ? "upgraded" : "downgraded",
@@ -7791,22 +8578,89 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7791
8578
  }
7792
8579
  return changes;
7793
8580
  }
8581
+ var LOCKFILE_PATTERNS = [
8582
+ "package-lock.json",
8583
+ "**/package-lock.json",
8584
+ "pnpm-lock.yaml",
8585
+ "**/pnpm-lock.yaml",
8586
+ "yarn.lock",
8587
+ "**/yarn.lock"
8588
+ ];
8589
+ async function getChangedLockfilePaths(cwd, baseRef) {
8590
+ const lockfiles = /* @__PURE__ */ new Set();
8591
+ try {
8592
+ const { stdout: diffFiles } = await execa13(
8593
+ "git",
8594
+ ["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
8595
+ { cwd, reject: false }
8596
+ );
8597
+ for (const f of diffFiles.split("\n")) {
8598
+ const trimmed = f.trim();
8599
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
8600
+ lockfiles.add(trimmed);
8601
+ }
8602
+ }
8603
+ const { stdout: untrackedFiles } = await execa13(
8604
+ "git",
8605
+ ["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
8606
+ { cwd, reject: false }
8607
+ );
8608
+ for (const f of untrackedFiles.split("\n")) {
8609
+ const trimmed = f.trim();
8610
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
8611
+ lockfiles.add(trimmed);
8612
+ }
8613
+ }
8614
+ } catch {
8615
+ }
8616
+ return Array.from(lockfiles);
8617
+ }
8618
+ async function getFileContentAtRef(cwd, baseRef, relPath) {
8619
+ try {
8620
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
8621
+ cwd,
8622
+ reject: false
8623
+ });
8624
+ return stdout || null;
8625
+ } catch {
8626
+ return null;
8627
+ }
8628
+ }
7794
8629
  async function scanDelta(options = {}) {
7795
8630
  const cwd = options.cwd ?? process.cwd();
7796
8631
  const baseRef = options.baseRef ?? "HEAD";
7797
8632
  const changedManifests = await getChangedManifestPaths(cwd, baseRef);
8633
+ const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
7798
8634
  const dependencyChanges = [];
8635
+ const directPkgs = /* @__PURE__ */ new Set();
7799
8636
  for (const manifestRel of changedManifests) {
7800
8637
  const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
7801
8638
  const newPkg = getNewManifestContent(cwd, manifestRel);
7802
8639
  const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
8640
+ for (const d of diffs) {
8641
+ directPkgs.add(d.packageName);
8642
+ }
7803
8643
  dependencyChanges.push(...diffs);
7804
8644
  }
8645
+ for (const lockfileRel of changedLockfiles) {
8646
+ const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
8647
+ const fullPath = join22(cwd, lockfileRel);
8648
+ const newContent = existsSync14(fullPath) ? readFileSync19(fullPath, "utf8") : null;
8649
+ const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
8650
+ for (const change of lockDiffs) {
8651
+ if (!directPkgs.has(change.packageName)) {
8652
+ dependencyChanges.push(change);
8653
+ }
8654
+ }
8655
+ if (!changedManifests.includes(lockfileRel)) {
8656
+ changedManifests.push(lockfileRel);
8657
+ }
8658
+ }
7805
8659
  const introducedFindings = [];
7806
8660
  const resolvedFindings = [];
7807
8661
  for (const change of dependencyChanges) {
7808
8662
  if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
7809
- const cleanVer = semver8.coerce(change.newVersion)?.version ?? change.newVersion;
8663
+ const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
7810
8664
  const evaluation = await evaluatePackage(change.packageName, {
7811
8665
  version: cleanVer,
7812
8666
  enrichIntelligence: true
@@ -7826,12 +8680,12 @@ async function scanDelta(options = {}) {
7826
8680
  }
7827
8681
  }
7828
8682
  if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
7829
- const cleanOldVer = semver8.coerce(change.oldVersion)?.version ?? change.oldVersion;
8683
+ const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
7830
8684
  const oldEval = await evaluatePackage(change.packageName, {
7831
8685
  version: cleanOldVer,
7832
8686
  enrichIntelligence: true
7833
8687
  });
7834
- const cleanNewVer = change.newVersion ? semver8.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
8688
+ const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
7835
8689
  const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
7836
8690
  version: cleanNewVer,
7837
8691
  enrichIntelligence: false
@@ -7949,6 +8803,7 @@ export {
7949
8803
  listPatchArtifacts,
7950
8804
  planRemediation,
7951
8805
  pollJob,
8806
+ reconcilePatchArtifacts,
7952
8807
  remediate,
7953
8808
  remediateFromScan,
7954
8809
  remediatePortfolio,