autoremediator 0.16.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 (93) 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/delta-scan/delta-scan.test.d.ts +2 -0
  5. package/dist/api/delta-scan/delta-scan.test.d.ts.map +1 -0
  6. package/dist/api/delta-scan/index.d.ts +5 -0
  7. package/dist/api/delta-scan/index.d.ts.map +1 -0
  8. package/dist/api/evaluate/evaluate.test.d.ts +2 -0
  9. package/dist/api/evaluate/evaluate.test.d.ts.map +1 -0
  10. package/dist/api/evaluate/index.d.ts +5 -0
  11. package/dist/api/evaluate/index.d.ts.map +1 -0
  12. package/dist/api/index.d.ts +5 -2
  13. package/dist/api/index.d.ts.map +1 -1
  14. package/dist/api/patches/index.d.ts +1 -0
  15. package/dist/api/patches/index.d.ts.map +1 -1
  16. package/dist/api/patches/reconcile.d.ts +7 -0
  17. package/dist/api/patches/reconcile.d.ts.map +1 -0
  18. package/dist/api/patches/reconcile.test.d.ts +2 -0
  19. package/dist/api/patches/reconcile.test.d.ts.map +1 -0
  20. package/dist/api/reachability/index.d.ts +3 -0
  21. package/dist/api/reachability/index.d.ts.map +1 -0
  22. package/dist/api/reachability/reachability.test.d.ts +2 -0
  23. package/dist/api/reachability/reachability.test.d.ts.map +1 -0
  24. package/dist/api/remediate/validators.d.ts +3 -0
  25. package/dist/api/remediate/validators.d.ts.map +1 -1
  26. package/dist/api/reporting.d.ts.map +1 -1
  27. package/dist/api/vex/index.d.ts +1 -1
  28. package/dist/api/vex/index.d.ts.map +1 -1
  29. package/dist/{chunk-CQXNIRHT.js → chunk-FXOUJDQ3.js} +2003 -756
  30. package/dist/cli/program.d.ts.map +1 -1
  31. package/dist/cli/runners.d.ts +22 -0
  32. package/dist/cli/runners.d.ts.map +1 -1
  33. package/dist/cli/triage.d.ts +7 -0
  34. package/dist/cli/triage.d.ts.map +1 -0
  35. package/dist/cli/types.d.ts +2 -0
  36. package/dist/cli/types.d.ts.map +1 -1
  37. package/dist/cli.js +365 -3
  38. package/dist/index.js +1967 -721
  39. package/dist/index.js.map +1 -1
  40. package/dist/intelligence/index.d.ts +1 -1
  41. package/dist/intelligence/index.d.ts.map +1 -1
  42. package/dist/intelligence/sources/github-advisory.d.ts +8 -0
  43. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  44. package/dist/intelligence/sources/osv.d.ts +4 -0
  45. package/dist/intelligence/sources/osv.d.ts.map +1 -1
  46. package/dist/mcp/server.d.ts +106 -1
  47. package/dist/mcp/server.d.ts.map +1 -1
  48. package/dist/mcp/server.js +100 -1
  49. package/dist/openapi/rbac.d.ts +2 -2
  50. package/dist/openapi/rbac.d.ts.map +1 -1
  51. package/dist/openapi/server.d.ts.map +1 -1
  52. package/dist/openapi/server.js +13 -6
  53. package/dist/platform/package-manager/index.d.ts +1 -0
  54. package/dist/platform/package-manager/index.d.ts.map +1 -1
  55. package/dist/platform/supply-chain.d.ts +10 -0
  56. package/dist/platform/supply-chain.d.ts.map +1 -0
  57. package/dist/platform/supply-chain.test.d.ts +2 -0
  58. package/dist/platform/supply-chain.test.d.ts.map +1 -0
  59. package/dist/platform/types.d.ts +121 -4
  60. package/dist/platform/types.d.ts.map +1 -1
  61. package/dist/platform/workspace.d.ts +28 -0
  62. package/dist/platform/workspace.d.ts.map +1 -0
  63. package/dist/platform/workspace.test.d.ts +2 -0
  64. package/dist/platform/workspace.test.d.ts.map +1 -0
  65. package/dist/remediation/evaluate-package.d.ts +3 -0
  66. package/dist/remediation/evaluate-package.d.ts.map +1 -0
  67. package/dist/remediation/local/options.d.ts.map +1 -1
  68. package/dist/remediation/local/primary-strategy.d.ts +3 -1
  69. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  70. package/dist/remediation/runtime-tools.d.ts +1 -1
  71. package/dist/remediation/runtime-tools.d.ts.map +1 -1
  72. package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
  73. package/dist/remediation/tools/apply-patch-file/helpers.d.ts.map +1 -1
  74. package/dist/remediation/tools/apply-patch-file/index.d.ts +1 -0
  75. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  76. package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
  77. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  78. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  79. package/dist/remediation/tools/check-reachability.d.ts +6 -1
  80. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  81. package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
  82. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  83. package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
  84. package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
  85. package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
  86. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  87. package/dist/scanner/delta-scan.d.ts +3 -0
  88. package/dist/scanner/delta-scan.d.ts.map +1 -0
  89. package/dist/scanner/lockfile-diff.d.ts +10 -0
  90. package/dist/scanner/lockfile-diff.d.ts.map +1 -0
  91. package/dist/scanner/lockfile-diff.test.d.ts +2 -0
  92. package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
  93. package/package.json +7 -9
package/dist/index.js CHANGED
@@ -534,6 +534,24 @@ function buildResultSimulation(report, result, options) {
534
534
  const dependencyScope = result.dependencyScope ?? dependencyScopeLookup.get(result.packageName);
535
535
  const plannedMutations = buildPlannedMutations(result, options.runTests === true);
536
536
  const wouldMutate = plannedMutations.length > 0;
537
+ let unifiedDiff;
538
+ if (result.patchArtifact?.patchFilePath) {
539
+ unifiedDiff = `--- ${result.patchArtifact.patchFilePath}
540
+ +++ ${result.patchArtifact.patchFilePath}`;
541
+ } else if (result.strategy === "version-bump" && result.toVersion) {
542
+ unifiedDiff = `--- package.json
543
+ +++ package.json
544
+ @@ -1,3 +1,3 @@
545
+ - "${result.packageName}": "${result.fromVersion}"
546
+ + "${result.packageName}": "^${result.toVersion}"`;
547
+ } else if (result.strategy === "override" && result.toVersion) {
548
+ unifiedDiff = `--- package.json
549
+ +++ package.json
550
+ @@ -1,3 +1,3 @@
551
+ + "overrides": {
552
+ + "${result.packageName}": "${result.toVersion}"
553
+ + }`;
554
+ }
537
555
  return {
538
556
  mode,
539
557
  wouldMutate,
@@ -544,7 +562,8 @@ function buildResultSimulation(report, result, options) {
544
562
  dependencyScope,
545
563
  wouldMutate,
546
564
  runTests: options.runTests === true
547
- })
565
+ }),
566
+ unifiedDiff
548
567
  };
549
568
  }
550
569
  function buildSimulationSummary(reports) {
@@ -868,12 +887,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
868
887
  import { join as join5 } from "path";
869
888
 
870
889
  // src/remediation/strategies/patch-utils.ts
871
- import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
890
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
872
891
  import { join as join4 } from "path";
873
892
  import { execa as execa2 } from "execa";
874
893
 
875
894
  // src/platform/package-manager/index.ts
876
- import { existsSync as existsSync2 } from "fs";
895
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
877
896
  import { join as join3 } from "path";
878
897
  import { execa } from "execa";
879
898
 
@@ -1066,6 +1085,32 @@ function resolveTestCommand(pm, constraints) {
1066
1085
  const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
1067
1086
  return withWorkspace(base, pm, constraints?.workspace);
1068
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
+ }
1069
1114
  function resolveAuditCommand(pm, constraints) {
1070
1115
  if (pm === "deno") {
1071
1116
  throw new Error(
@@ -1310,12 +1355,12 @@ function defineTool(config) {
1310
1355
 
1311
1356
  // src/remediation/tools/check-inventory.ts
1312
1357
  import { z } from "zod";
1313
- import { readFileSync as readFileSync4 } from "fs";
1314
- import { join as join7 } from "path";
1358
+ import { readFileSync as readFileSync6 } from "fs";
1359
+ import { join as join8 } from "path";
1315
1360
  import { execa as execa3 } from "execa";
1316
1361
 
1317
1362
  // src/platform/policy.ts
1318
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1363
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1319
1364
  import { join as join6 } from "path";
1320
1365
  import { parse as yamlParse } from "yaml";
1321
1366
  var DEFAULT_POLICY = {
@@ -1350,7 +1395,7 @@ function loadPolicy(cwd, explicitPath) {
1350
1395
  const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
1351
1396
  if (!existsSync6(candidate)) return DEFAULT_POLICY;
1352
1397
  try {
1353
- const parsed = yamlParse(readFileSync3(candidate, "utf8"));
1398
+ const parsed = yamlParse(readFileSync4(candidate, "utf8"));
1354
1399
  return {
1355
1400
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
1356
1401
  denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
@@ -1411,7 +1456,7 @@ function isActiveSuppression(suppression) {
1411
1456
  }
1412
1457
  function loadSuppressionsFile(filePath) {
1413
1458
  try {
1414
- const content = readFileSync3(filePath, "utf8");
1459
+ const content = readFileSync4(filePath, "utf8");
1415
1460
  const parsed = yamlParse(content);
1416
1461
  return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
1417
1462
  } catch {
@@ -1437,6 +1482,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1437
1482
  };
1438
1483
  }
1439
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
+
1440
1619
  // src/remediation/tools/check-inventory.ts
1441
1620
  var checkInventoryTool = defineTool({
1442
1621
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
@@ -1454,7 +1633,7 @@ var checkInventoryTool = defineTool({
1454
1633
  }) => {
1455
1634
  let pkgJson;
1456
1635
  try {
1457
- pkgJson = JSON.parse(readFileSync4(join7(cwd, "package.json"), "utf8"));
1636
+ pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1458
1637
  } catch {
1459
1638
  return {
1460
1639
  packages: [],
@@ -1486,7 +1665,8 @@ var checkInventoryTool = defineTool({
1486
1665
  }
1487
1666
  const packages = [];
1488
1667
  for (const [name, version] of installedVersions.entries()) {
1489
- 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);
1490
1670
  packages.push({
1491
1671
  name,
1492
1672
  version,
@@ -1609,173 +1789,14 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
1609
1789
  };
1610
1790
  }
1611
1791
 
1612
- // src/platform/config.ts
1613
- function resolveProvider(options = {}) {
1614
- const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1615
- if (raw !== "remote" && raw !== "local") {
1616
- throw new Error(
1617
- `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1618
- );
1619
- }
1620
- return raw;
1621
- }
1622
- function resolveModelName(provider, options = {}, routing = {}) {
1623
- if (options.model) return options.model;
1624
- if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1625
- const cwd = options.cwd ?? process.cwd();
1626
- const policy = loadPolicy(cwd, options.policy);
1627
- const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1628
- if (providerEnvModel) return providerEnvModel;
1629
- const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1630
- if (policyPinnedModel) return policyPinnedModel;
1631
- const defaults = {
1632
- remote: "remote-default",
1633
- local: "local"
1634
- };
1635
- const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
1636
- const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
1637
- if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
1638
- return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
1639
- }
1640
- return defaults[provider];
1641
- }
1642
- async function loadRemoteFactory() {
1643
- const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
1644
- const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
1645
- if (!moduleName) {
1646
- throw new Error(
1647
- "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
1648
- );
1649
- }
1650
- if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
1651
- throw new Error(
1652
- `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
1653
- );
1654
- }
1655
- const loaded = await import(moduleName);
1656
- const factory = loaded[exportName];
1657
- if (typeof factory !== "function") {
1658
- throw new Error(
1659
- `Remote client factory "${exportName}" was not found in module "${moduleName}".`
1660
- );
1661
- }
1662
- return factory;
1663
- }
1664
- async function createModel(options = {}, routing = {}) {
1665
- const provider = resolveProvider(options);
1666
- if (provider === "local") {
1667
- throw new Error(
1668
- "Local provider does not create a language model. Use the deterministic pipeline path instead."
1669
- );
1670
- }
1671
- const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
1672
- if (!apiKey) {
1673
- throw new Error(
1674
- "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
1675
- );
1676
- }
1677
- const modelName = resolveModelName(provider, options, routing);
1678
- const createRemoteClient = await loadRemoteFactory();
1679
- const remoteClient = createRemoteClient({ apiKey });
1680
- return remoteClient(modelName);
1681
- }
1682
- function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
1683
- const relaxed = {
1684
- remote: { low: 0.65, medium: 0.7, high: 0.8 },
1685
- local: { low: 0.65, medium: 0.7, high: 0.8 }
1686
- };
1687
- const strict = {
1688
- remote: { low: 0.8, medium: 0.85, high: 0.9 },
1689
- local: { low: 0.8, medium: 0.85, high: 0.9 }
1690
- };
1691
- const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
1692
- const override = overrides?.[riskLevel];
1693
- if (typeof override === "number" && Number.isFinite(override)) {
1694
- return Math.max(0, Math.min(1, override));
1695
- }
1696
- return baseThreshold;
1697
- }
1698
- function estimateModelCostUsd(params) {
1699
- const inputTokens = params.promptChars / 4;
1700
- const outputTokens = params.completionChars / 4;
1701
- const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
1702
- return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
1703
- }
1704
- function getNvdConfig() {
1705
- return {
1706
- apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
1707
- };
1708
- }
1709
- function getGitHubToken() {
1710
- return process.env.GITHUB_TOKEN;
1711
- }
1712
- function getIntelligenceSourceConfig() {
1713
- return {
1714
- gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
1715
- certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
1716
- epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
1717
- cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
1718
- depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
1719
- scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
1720
- vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1721
- commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1722
- commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
1723
- };
1724
- }
1725
-
1726
- // src/platform/idempotency.ts
1727
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1728
- import { join as join8 } from "path";
1729
- var DEFAULT_INDEX = {
1730
- schemaVersion: "1.0",
1731
- entries: {}
1732
- };
1733
- function indexFilePath(cwd) {
1734
- return join8(cwd, ".autoremediator", "state", "idempotency.json");
1735
- }
1736
- function entryKey(idempotencyKey, cveId) {
1737
- return `${idempotencyKey}::${cveId.toUpperCase()}`;
1738
- }
1739
- function loadIndex(cwd) {
1740
- const filePath = indexFilePath(cwd);
1741
- if (!existsSync7(filePath)) return DEFAULT_INDEX;
1742
- try {
1743
- const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
1744
- if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
1745
- return parsed;
1746
- }
1747
- return DEFAULT_INDEX;
1748
- } catch {
1749
- return DEFAULT_INDEX;
1750
- }
1751
- }
1752
- function saveIndex(cwd, index) {
1753
- const filePath = indexFilePath(cwd);
1754
- mkdirSync2(join8(cwd, ".autoremediator", "state"), { recursive: true });
1755
- writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
1756
- }
1757
- function readIdempotentReport(cwd, idempotencyKey, cveId) {
1758
- const index = loadIndex(cwd);
1759
- const key = entryKey(idempotencyKey, cveId);
1760
- return index.entries[key]?.report;
1761
- }
1762
- function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
1763
- const index = loadIndex(cwd);
1764
- const key = entryKey(idempotencyKey, cveId);
1765
- index.entries[key] = {
1766
- key: idempotencyKey,
1767
- cveId: cveId.toUpperCase(),
1768
- report,
1769
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
1770
- };
1771
- saveIndex(cwd, index);
1772
- }
1773
-
1774
- // src/remediation/pipeline.ts
1775
- 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";
1776
1797
 
1777
- // src/remediation/local/run.ts
1778
- import semver5 from "semver";
1798
+ // src/remediation/evaluate-package.ts
1799
+ import semver2 from "semver";
1779
1800
 
1780
1801
  // src/platform/http-client.ts
1781
1802
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -1863,6 +1884,37 @@ async function fetchOsvVuln(cveId) {
1863
1884
  throw err;
1864
1885
  }
1865
1886
  }
1887
+ async function queryOsvPackage(packageName, version) {
1888
+ const url = `${OSV_BASE}/query`;
1889
+ const body = {
1890
+ package: {
1891
+ name: packageName,
1892
+ ecosystem: "npm"
1893
+ }
1894
+ };
1895
+ if (version) {
1896
+ body.version = version;
1897
+ }
1898
+ try {
1899
+ const res = await httpClient({
1900
+ url,
1901
+ method: "POST",
1902
+ body,
1903
+ headers: { "Content-Type": "application/json" }
1904
+ });
1905
+ if (res.status === 404) return [];
1906
+ if (!res.ok) {
1907
+ throw new Error(`OSV API error ${res.status} for query "${packageName}": ${res.text}`);
1908
+ }
1909
+ const data = res.data;
1910
+ return data.vulns ?? [];
1911
+ } catch (err) {
1912
+ if (err instanceof Error) {
1913
+ throw new Error(`OSV API error for query "${packageName}": ${err.message}`);
1914
+ }
1915
+ throw err;
1916
+ }
1917
+ }
1866
1918
  function osvEventsToSemverRange(events) {
1867
1919
  const parts = [];
1868
1920
  for (const event of events) {
@@ -1926,25 +1978,756 @@ async function lookupCveOsv(cveId) {
1926
1978
  return parseOsvVuln(vuln);
1927
1979
  }
1928
1980
 
1929
- // src/intelligence/sources/github-advisory.ts
1930
- import { Octokit } from "@octokit/rest";
1931
- function createOctokitClient() {
1932
- const token = getGitHubToken();
1933
- return new Octokit(token ? { auth: `token ${token}` } : {});
1934
- }
1935
- async function fetchGhAdvisories(cveId) {
1936
- const octokit = createOctokitClient();
1937
- try {
1938
- const response = await octokit.request("GET /advisories", {
1939
- cve_id: cveId,
1940
- ecosystem: "npm",
1941
- type: "reviewed",
1942
- per_page: 10,
1943
- headers: {
1944
- "X-GitHub-Api-Version": "2022-11-28"
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
+ );
1988
+ }
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}`);
1945
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
1946
2725
  });
1947
- return response.data;
2726
+ if (!res.ok) {
2727
+ if (res.status === 404) return [];
2728
+ throw new Error(`HTTP ${res.status}: ${res.text}`);
2729
+ }
2730
+ return Array.isArray(res.data) ? res.data : [];
1948
2731
  } catch (err) {
1949
2732
  const errorMsg = err instanceof Error ? err.message : String(err);
1950
2733
  console.warn(
@@ -2038,9 +2821,9 @@ async function enrichWithNvd(details) {
2038
2821
  }
2039
2822
 
2040
2823
  // src/intelligence/sources/snapshot.ts
2041
- import { readFileSync as readFileSync6 } from "fs";
2824
+ import { readFileSync as readFileSync9 } from "fs";
2042
2825
  function loadIntelligenceSnapshot(filePath) {
2043
- const raw = readFileSync6(filePath, "utf8");
2826
+ const raw = readFileSync9(filePath, "utf8");
2044
2827
  const parsed = JSON.parse(raw);
2045
2828
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2046
2829
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -2052,8 +2835,8 @@ function lookupSnapshotCve(snapshot, cveId) {
2052
2835
  }
2053
2836
 
2054
2837
  // src/remediation/tools/check-reachability.ts
2055
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync } from "fs";
2056
- 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";
2057
2840
  import { parseSync } from "oxc-parser";
2058
2841
  import { z as z2 } from "zod";
2059
2842
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
@@ -2069,16 +2852,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
2069
2852
  ]);
2070
2853
  function loadTsconfigPaths(cwd) {
2071
2854
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
2072
- const fullPath = join9(cwd, configName);
2073
- if (!existsSync8(fullPath)) continue;
2855
+ const fullPath = join12(cwd, configName);
2856
+ if (!existsSync10(fullPath)) continue;
2074
2857
  try {
2075
- const raw = readFileSync7(fullPath, "utf8");
2858
+ const raw = readFileSync10(fullPath, "utf8");
2076
2859
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
2077
2860
  const parsed = JSON.parse(cleaned);
2078
2861
  const compilerOptions = parsed.compilerOptions;
2079
2862
  if (compilerOptions?.paths) {
2080
2863
  return {
2081
- baseUrl: compilerOptions.baseUrl ? resolve2(cwd, compilerOptions.baseUrl) : cwd,
2864
+ baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
2082
2865
  paths: compilerOptions.paths
2083
2866
  };
2084
2867
  }
@@ -2093,14 +2876,14 @@ function resolveAliasedPath(importSource, config) {
2093
2876
  }
2094
2877
  for (const [pattern, replacements] of Object.entries(config.paths)) {
2095
2878
  if (pattern === importSource && replacements[0]) {
2096
- return resolve2(config.baseUrl, replacements[0]);
2879
+ return resolve3(config.baseUrl, replacements[0]);
2097
2880
  }
2098
2881
  if (pattern.endsWith("/*")) {
2099
2882
  const prefix = pattern.slice(0, -2);
2100
2883
  if (importSource.startsWith(prefix)) {
2101
2884
  const suffix = importSource.slice(prefix.length);
2102
2885
  const replacement = replacements[0]?.slice(0, -2) ?? "";
2103
- return resolve2(config.baseUrl, replacement + suffix);
2886
+ return resolve3(config.baseUrl, replacement + suffix);
2104
2887
  }
2105
2888
  }
2106
2889
  }
@@ -2109,16 +2892,16 @@ function resolveAliasedPath(importSource, config) {
2109
2892
  function* walkSourceFiles(dir) {
2110
2893
  let entries;
2111
2894
  try {
2112
- entries = readdirSync(dir);
2895
+ entries = readdirSync2(dir);
2113
2896
  } catch {
2114
2897
  return;
2115
2898
  }
2116
2899
  for (const entry of entries) {
2117
2900
  if (IGNORED_DIRS.has(entry)) continue;
2118
- const full = join9(dir, entry);
2901
+ const full = join12(dir, entry);
2119
2902
  let stat2;
2120
2903
  try {
2121
- stat2 = statSync(full);
2904
+ stat2 = statSync2(full);
2122
2905
  } catch {
2123
2906
  continue;
2124
2907
  }
@@ -2156,7 +2939,31 @@ function walkAst(node, visitor) {
2156
2939
  }
2157
2940
  }
2158
2941
  }
2159
- 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) {
2160
2967
  const results = [];
2161
2968
  let parseResult;
2162
2969
  try {
@@ -2168,6 +2975,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2168
2975
  walkAst(parseResult.program, (node) => {
2169
2976
  if (node.type === "ImportDeclaration") {
2170
2977
  const source = node.source;
2978
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2979
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2980
+ }
2171
2981
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2172
2982
  const symbols = [];
2173
2983
  const importedIdentifiers = /* @__PURE__ */ new Map();
@@ -2202,6 +3012,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2202
3012
  }
2203
3013
  if (node.type === "ImportExpression") {
2204
3014
  const source = node.source;
3015
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3016
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3017
+ }
2205
3018
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2206
3019
  results.push({
2207
3020
  matchType: "dynamic-import",
@@ -2213,6 +3026,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2213
3026
  }
2214
3027
  if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
2215
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
+ }
2216
3032
  if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
2217
3033
  results.push({
2218
3034
  matchType: "require",
@@ -2224,6 +3040,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2224
3040
  }
2225
3041
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
2226
3042
  const source = node.source;
3043
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3044
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3045
+ }
2227
3046
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2228
3047
  const symbols = [];
2229
3048
  if (node.type === "ExportAllDeclaration") {
@@ -2300,15 +3119,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2300
3119
  let hasReExport = false;
2301
3120
  let totalInvocations = 0;
2302
3121
  let hasPackageImport = false;
3122
+ const importedDirectPackages = /* @__PURE__ */ new Set();
2303
3123
  for (const filePath of walkSourceFiles(cwd)) {
2304
3124
  scanned++;
2305
3125
  let content;
2306
3126
  try {
2307
- content = readFileSync7(filePath, "utf8");
3127
+ content = readFileSync10(filePath, "utf8");
2308
3128
  } catch {
2309
3129
  continue;
2310
3130
  }
2311
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
3131
+ const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
2312
3132
  if (refs.length > 0) {
2313
3133
  hasPackageImport = true;
2314
3134
  }
@@ -2351,6 +3171,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2351
3171
  };
2352
3172
  }
2353
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
+ }
2354
3184
  return {
2355
3185
  packageName,
2356
3186
  status: "not-reachable",
@@ -2510,230 +3340,33 @@ function buildSbom(packages, vulnerableNames, results) {
2510
3340
  statusByPackage.set(result.packageName, "suppressed");
2511
3341
  } else if (!result.applied && result.strategy === "none") {
2512
3342
  statusByPackage.set(result.packageName, "skipped");
2513
- } else if (result.applied) {
2514
- statusByPackage.set(result.packageName, "patched");
2515
- } else {
2516
- statusByPackage.set(result.packageName, "unpatched");
2517
- }
2518
- }
2519
- return packages.map((pkg) => {
2520
- const isVulnerable = vulnerableNames.has(pkg.name);
2521
- const entry = {
2522
- name: pkg.name,
2523
- version: pkg.version,
2524
- scope: pkg.type === "direct" ? "direct" : "transitive"
2525
- };
2526
- if (isVulnerable) {
2527
- entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2528
- }
2529
- return entry;
2530
- });
2531
- }
2532
-
2533
- // src/intelligence/sources/registry.ts
2534
- import { readFileSync as readFileSync8 } from "fs";
2535
- import { join as join10 } from "path";
2536
- import { execa as execa4 } from "execa";
2537
- import semver from "semver";
2538
- var NPM_REGISTRY = "https://registry.npmjs.org";
2539
- async function fetchPackageVersions(packageName) {
2540
- const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2541
- try {
2542
- const res = await httpClient({ url });
2543
- if (res.status === 404) return [];
2544
- if (!res.ok) {
2545
- throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2546
- }
2547
- const data = res.data;
2548
- return Object.keys(data.versions);
2549
- } catch (err) {
2550
- if (err instanceof Error && err.message.includes("registry error")) {
2551
- throw err;
2552
- }
2553
- return [];
2554
- }
2555
- }
2556
- async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2557
- const versions = await fetchPackageVersions(packageName);
2558
- if (!versions.length) {
2559
- return {
2560
- candidates: {},
2561
- majorOnlyFixAvailable: false
2562
- };
2563
- }
2564
- const installed = semver.parse(installedVersion);
2565
- const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2566
- if (!vulnerableRange) return true;
2567
- try {
2568
- return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2569
- } catch {
2570
- return true;
2571
- }
2572
- }).sort(semver.compare);
2573
- if (!candidates.length) {
2574
- return {
2575
- candidates: {},
2576
- majorOnlyFixAvailable: false
2577
- };
2578
- }
2579
- const categorizedCandidates = {};
2580
- for (const candidate of candidates) {
2581
- const level = classifyUpgradeLevel(installedVersion, candidate);
2582
- if (!level) continue;
2583
- if (!categorizedCandidates[level]) {
2584
- categorizedCandidates[level] = candidate;
2585
- }
2586
- }
2587
- const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2588
- if (!safeVersion) {
2589
- return {
2590
- candidates: categorizedCandidates,
2591
- majorOnlyFixAvailable: false
2592
- };
2593
- }
2594
- const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2595
- const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2596
- if (!installed || !upgradeLevel) {
2597
- return {
2598
- safeVersion,
2599
- upgradeLevel,
2600
- candidates: categorizedCandidates,
2601
- majorOnlyFixAvailable
2602
- };
2603
- }
2604
- return {
2605
- safeVersion,
2606
- upgradeLevel,
2607
- candidates: categorizedCandidates,
2608
- majorOnlyFixAvailable
2609
- };
2610
- }
2611
- function classifyUpgradeLevel(installedVersion, candidateVersion) {
2612
- const installed = semver.parse(installedVersion);
2613
- const candidate = semver.parse(candidateVersion);
2614
- if (!installed || !candidate) return void 0;
2615
- if (candidate.major > installed.major) return "major";
2616
- if (candidate.minor > installed.minor) return "minor";
2617
- if (candidate.patch > installed.patch || candidate.version === installed.version) {
2618
- return "patch";
2619
- }
2620
- return void 0;
2621
- }
2622
- async function queryOutdatedPackages(cwd, options = {}) {
2623
- const pm = options.packageManager ?? detectPackageManager(cwd);
2624
- let stdout;
2625
- try {
2626
- const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2627
- const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2628
- const finalArgs = yarnArgs ?? args;
2629
- const result2 = await execa4(pm, finalArgs, {
2630
- cwd,
2631
- reject: false,
2632
- stdio: "pipe"
2633
- });
2634
- if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2635
- const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2636
- if (!result2.stdout) {
2637
- throw new Error(
2638
- `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2639
- );
2640
- }
2641
- }
2642
- stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2643
- } catch (err) {
2644
- if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2645
- throw new Error(`Failed to query outdated packages: ${err.message}`);
2646
- }
2647
- throw err;
2648
- }
2649
- if (!stdout.trim()) {
2650
- return /* @__PURE__ */ new Map();
2651
- }
2652
- const rawEntries = {};
2653
- if (pm === "yarn") {
2654
- for (const line of stdout.split("\n")) {
2655
- const trimmed = line.trim();
2656
- if (!trimmed) continue;
2657
- try {
2658
- const parsed = JSON.parse(trimmed);
2659
- if (parsed.type === "table" && parsed.data) {
2660
- const tableData = parsed.data;
2661
- const head = tableData.head ?? [];
2662
- const currentIdx = head.indexOf("Current");
2663
- const wantedIdx = head.indexOf("Wanted");
2664
- const latestIdx = head.indexOf("Latest");
2665
- for (const row of tableData.body ?? []) {
2666
- const name = row[0];
2667
- if (!name) continue;
2668
- rawEntries[name] = {
2669
- current: row[currentIdx] ?? "",
2670
- wanted: row[wantedIdx] ?? "",
2671
- latest: row[latestIdx] ?? "",
2672
- type: row[head.indexOf("Package Type")] ?? "dependencies"
2673
- };
2674
- }
2675
- }
2676
- } catch {
2677
- }
2678
- }
2679
- } else {
2680
- try {
2681
- const parsed = JSON.parse(stdout);
2682
- if (Array.isArray(parsed)) {
2683
- for (const entry of parsed) {
2684
- const name = entry.name ?? entry.packageName;
2685
- if (!name) continue;
2686
- rawEntries[name] = {
2687
- current: entry.current,
2688
- wanted: entry.wanted,
2689
- latest: entry.latest,
2690
- type: entry.dependencyType
2691
- };
2692
- }
2693
- } else {
2694
- Object.assign(rawEntries, parsed);
2695
- }
2696
- } catch {
2697
- throw new Error(`Could not parse ${pm} outdated --json output.`);
3343
+ } else if (result.applied) {
3344
+ statusByPackage.set(result.packageName, "patched");
3345
+ } else {
3346
+ statusByPackage.set(result.packageName, "unpatched");
2698
3347
  }
2699
3348
  }
2700
- let directDeps;
2701
- try {
2702
- const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
2703
- directDeps = /* @__PURE__ */ new Set([
2704
- ...Object.keys(pkgRaw.dependencies ?? {}),
2705
- ...Object.keys(pkgRaw.devDependencies ?? {})
2706
- ]);
2707
- } catch {
2708
- directDeps = /* @__PURE__ */ new Set();
2709
- }
2710
- const result = /* @__PURE__ */ new Map();
2711
- for (const [name, entry] of Object.entries(rawEntries)) {
2712
- const currentVersion = entry.current ?? "";
2713
- const wantedVersion = entry.wanted ?? currentVersion;
2714
- const latestVersion = entry.latest ?? currentVersion;
2715
- if (!currentVersion || !latestVersion) continue;
2716
- const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2717
- if (!options.includeTransitive && dependencyScope === "transitive") continue;
2718
- const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2719
- result.set(name, {
2720
- currentVersion,
2721
- wantedVersion,
2722
- latestVersion,
2723
- isMajorBump,
2724
- dependencyScope
2725
- });
2726
- }
2727
- return result;
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
+ });
2728
3361
  }
2729
3362
 
2730
3363
  // src/remediation/tools/apply-version-bump.ts
2731
3364
  import { z as z4 } from "zod";
2732
- import { join as join12 } from "path";
2733
- 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";
2734
3367
 
2735
3368
  // src/platform/safe-exec.ts
2736
- import { execa as execa5 } from "execa";
3369
+ import { execa as execa6 } from "execa";
2737
3370
  var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2738
3371
  "NODE_OPTIONS",
2739
3372
  "NODE_PATH",
@@ -2782,7 +3415,7 @@ function safeExeca(file, args = [], options = {}) {
2782
3415
  options.env ?? process.env,
2783
3416
  options.allowNodeOptions
2784
3417
  );
2785
- return execa5(file, args, {
3418
+ return execa6(file, args, {
2786
3419
  ...options,
2787
3420
  timeout,
2788
3421
  env: sanitizedEnv
@@ -2790,19 +3423,19 @@ function safeExeca(file, args = [], options = {}) {
2790
3423
  }
2791
3424
 
2792
3425
  // src/remediation/tools/apply-version-bump.ts
2793
- import semver2 from "semver";
3426
+ import semver3 from "semver";
2794
3427
 
2795
3428
  // src/platform/repo-lock.ts
2796
- import { mkdir, rm } from "fs/promises";
2797
- import { join as join11 } from "path";
3429
+ import { mkdir, rm as rm2 } from "fs/promises";
3430
+ import { join as join13 } from "path";
2798
3431
  async function sleep(ms) {
2799
- await new Promise((resolve4) => setTimeout(resolve4, ms));
3432
+ await new Promise((resolve5) => setTimeout(resolve5, ms));
2800
3433
  }
2801
3434
  async function acquireRepoLock(cwd, options = {}) {
2802
3435
  const timeoutMs = options.timeoutMs ?? 15e3;
2803
3436
  const retryDelayMs = options.retryDelayMs ?? 125;
2804
- const lockRoot = join11(cwd, ".autoremediator", "locks");
2805
- const lockPath = join11(cwd, ".autoremediator", "locks", "remediation.lock");
3437
+ const lockRoot = join13(cwd, ".autoremediator", "locks");
3438
+ const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
2806
3439
  const startedAt = Date.now();
2807
3440
  await mkdir(lockRoot, { recursive: true });
2808
3441
  while (true) {
@@ -2811,7 +3444,7 @@ async function acquireRepoLock(cwd, options = {}) {
2811
3444
  return {
2812
3445
  lockPath,
2813
3446
  release: async () => {
2814
- await rm(lockPath, { recursive: true, force: true });
3447
+ await rm2(lockPath, { recursive: true, force: true });
2815
3448
  }
2816
3449
  };
2817
3450
  } catch {
@@ -2843,6 +3476,7 @@ var applyVersionBumpTool = defineTool({
2843
3476
  dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
2844
3477
  policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
2845
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"),
2846
3480
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2847
3481
  installPreferOffline: z4.boolean().optional(),
2848
3482
  enforceFrozenLockfile: z4.boolean().optional(),
@@ -2857,13 +3491,14 @@ var applyVersionBumpTool = defineTool({
2857
3491
  dryRun,
2858
3492
  policy,
2859
3493
  runTests,
3494
+ typecheck,
2860
3495
  installMode,
2861
3496
  installPreferOffline,
2862
3497
  enforceFrozenLockfile,
2863
3498
  workspace
2864
3499
  }) => {
2865
3500
  const pm = packageManager ?? detectPackageManager(cwd);
2866
- const pkgPath = join12(cwd, "package.json");
3501
+ let pkgPath = join14(cwd, "package.json");
2867
3502
  const loadedPolicy = loadPolicy(cwd, policy);
2868
3503
  const commandConstraints = {
2869
3504
  ...loadedPolicy.constraints,
@@ -2873,9 +3508,6 @@ var applyVersionBumpTool = defineTool({
2873
3508
  workspace: workspace ?? loadedPolicy.constraints?.workspace
2874
3509
  };
2875
3510
  const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
2876
- const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
2877
- const testCommand = resolveTestCommand(pm, commandConstraints);
2878
- const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
2879
3511
  if (!isPackageAllowed(loadedPolicy, packageName)) {
2880
3512
  return {
2881
3513
  packageName,
@@ -2888,7 +3520,7 @@ var applyVersionBumpTool = defineTool({
2888
3520
  message: `Policy blocked changes for package "${packageName}".`
2889
3521
  };
2890
3522
  }
2891
- 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);
2892
3524
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2893
3525
  return {
2894
3526
  packageName,
@@ -2901,9 +3533,10 @@ var applyVersionBumpTool = defineTool({
2901
3533
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
2902
3534
  };
2903
3535
  }
3536
+ pkgPath = join14(cwd, "package.json");
2904
3537
  let pkgJson;
2905
3538
  try {
2906
- pkgJson = JSON.parse(readFileSync9(pkgPath, "utf8"));
3539
+ pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
2907
3540
  } catch {
2908
3541
  return {
2909
3542
  packageName,
@@ -2915,9 +3548,20 @@ var applyVersionBumpTool = defineTool({
2915
3548
  message: `Could not read package.json at "${pkgPath}".`
2916
3549
  };
2917
3550
  }
2918
- const depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3551
+ let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
2919
3552
  (f) => pkgJson[f]?.[packageName] !== void 0
2920
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
+ }
2921
3565
  if (!depField) {
2922
3566
  return {
2923
3567
  packageName,
@@ -2933,9 +3577,14 @@ var applyVersionBumpTool = defineTool({
2933
3577
  const prefixMatch = currentRange.match(/^([~^]?)/);
2934
3578
  const prefix = prefixMatch?.[1] ?? "";
2935
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);
2936
3585
  if (dryRun) {
2937
- const installCmd = installCommand.join(" ");
2938
- const testCmd = testCommand.join(" ");
3586
+ const installCmd = effectiveInstallCommand.join(" ");
3587
+ const testCmd = effectiveTestCommand.join(" ");
2939
3588
  return {
2940
3589
  packageName,
2941
3590
  strategy: "version-bump",
@@ -2943,14 +3592,14 @@ var applyVersionBumpTool = defineTool({
2943
3592
  toVersion,
2944
3593
  applied: false,
2945
3594
  dryRun: true,
2946
- 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)` : ""}.`
2947
3596
  };
2948
3597
  }
2949
3598
  return withRepoLock(cwd, async () => {
2950
3599
  pkgJson[depField][packageName] = newRange;
2951
3600
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2952
3601
  try {
2953
- const [installCmd, ...installArgs] = installCommand;
3602
+ const [installCmd, ...installArgs] = effectiveInstallCommand;
2954
3603
  await safeExeca(installCmd, installArgs, {
2955
3604
  cwd,
2956
3605
  stdio: "pipe"
@@ -2967,12 +3616,12 @@ var applyVersionBumpTool = defineTool({
2967
3616
  applied: false,
2968
3617
  dryRun: false,
2969
3618
  unresolvedReason: "install-failed",
2970
- 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}`
2971
3620
  };
2972
3621
  }
2973
3622
  if (runTests) {
2974
3623
  try {
2975
- const [testCmd, ...testArgs] = testCommand;
3624
+ const [testCmd, ...testArgs] = effectiveTestCommand;
2976
3625
  await safeExeca(testCmd, testArgs, {
2977
3626
  cwd,
2978
3627
  stdio: "pipe"
@@ -2981,7 +3630,38 @@ var applyVersionBumpTool = defineTool({
2981
3630
  pkgJson[depField][packageName] = currentRange;
2982
3631
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2983
3632
  try {
2984
- 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;
2985
3665
  await safeExeca(rollbackCmd, rollbackArgs, {
2986
3666
  cwd,
2987
3667
  stdio: "pipe"
@@ -2997,20 +3677,22 @@ var applyVersionBumpTool = defineTool({
2997
3677
  applied: false,
2998
3678
  dryRun: false,
2999
3679
  unresolvedReason: "validation-failed",
3000
- 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}`
3001
3681
  };
3002
3682
  }
3003
3683
  }
3004
3684
  let dedupeNote = "";
3005
- try {
3006
- const [dedupeCmd, ...dedupeArgs] = dedupeCommand;
3007
- await safeExeca(dedupeCmd, dedupeArgs, {
3008
- cwd,
3009
- stdio: "pipe"
3010
- });
3011
- } catch (err) {
3012
- const message = err instanceof Error ? err.message : String(err);
3013
- 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
+ }
3014
3696
  }
3015
3697
  return {
3016
3698
  packageName,
@@ -3019,7 +3701,7 @@ var applyVersionBumpTool = defineTool({
3019
3701
  toVersion,
3020
3702
  applied: true,
3021
3703
  dryRun: false,
3022
- 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}`
3023
3705
  };
3024
3706
  });
3025
3707
  }
@@ -3027,9 +3709,9 @@ var applyVersionBumpTool = defineTool({
3027
3709
 
3028
3710
  // src/remediation/tools/apply-package-override/index.ts
3029
3711
  import { z as z5 } from "zod";
3030
- import { join as join13 } from "path";
3031
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
3032
- 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";
3033
3715
 
3034
3716
  // src/remediation/tools/apply-package-override/helpers.ts
3035
3717
  async function collectDependencyTrace(cwd, pm, packageName, constraints) {
@@ -3171,9 +3853,9 @@ var applyPackageOverrideTool = defineTool({
3171
3853
  workspace
3172
3854
  }) => {
3173
3855
  const pm = packageManager ?? detectPackageManager(cwd);
3174
- const pkgPath = join13(cwd, "package.json");
3175
- const denoJsonPath = join13(cwd, "deno.json");
3176
- 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);
3177
3859
  const loadedPolicy = loadPolicy(cwd, policy);
3178
3860
  const commandConstraints = {
3179
3861
  ...loadedPolicy.constraints,
@@ -3199,7 +3881,7 @@ var applyPackageOverrideTool = defineTool({
3199
3881
  message: `Policy blocked changes for package "${packageName}".`
3200
3882
  };
3201
3883
  }
3202
- 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);
3203
3885
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
3204
3886
  return {
3205
3887
  packageName,
@@ -3215,7 +3897,7 @@ var applyPackageOverrideTool = defineTool({
3215
3897
  if (isDenoNative) {
3216
3898
  let denoJson;
3217
3899
  try {
3218
- denoJson = JSON.parse(readFileSync10(denoJsonPath, "utf8"));
3900
+ denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
3219
3901
  } catch {
3220
3902
  return {
3221
3903
  packageName,
@@ -3318,7 +4000,7 @@ var applyPackageOverrideTool = defineTool({
3318
4000
  }
3319
4001
  let pkgJson;
3320
4002
  try {
3321
- pkgJson = JSON.parse(readFileSync10(pkgPath, "utf8"));
4003
+ pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
3322
4004
  } catch {
3323
4005
  return {
3324
4006
  packageName,
@@ -3415,6 +4097,76 @@ var applyPackageOverrideTool = defineTool({
3415
4097
  }
3416
4098
  });
3417
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
+
3418
4170
  // src/remediation/local/primary-strategy.ts
3419
4171
  async function resolvePrimaryResult(params) {
3420
4172
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
@@ -3485,6 +4237,22 @@ async function resolvePrimaryResult(params) {
3485
4237
  }
3486
4238
  };
3487
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
+ }
3488
4256
  const overrideResult = await applyPackageOverrideTool.execute({
3489
4257
  cwd,
3490
4258
  packageManager,
@@ -3540,6 +4308,22 @@ async function resolvePrimaryResult(params) {
3540
4308
  }
3541
4309
  };
3542
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
+ }
3543
4327
  const applyResult = await applyVersionBumpTool.execute({
3544
4328
  cwd,
3545
4329
  packageManager,
@@ -3549,6 +4333,7 @@ async function resolvePrimaryResult(params) {
3549
4333
  dryRun,
3550
4334
  policy,
3551
4335
  runTests,
4336
+ typecheck: constraints.typecheck,
3552
4337
  installMode: constraints.installMode,
3553
4338
  installPreferOffline: constraints.installPreferOffline,
3554
4339
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
@@ -3562,10 +4347,10 @@ async function resolvePrimaryResult(params) {
3562
4347
 
3563
4348
  // src/remediation/tools/fetch-package-source.ts
3564
4349
  import { z as z6 } from "zod";
3565
- import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
3566
- 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";
3567
4352
  import { tmpdir } from "os";
3568
- import { execa as execa6 } from "execa";
4353
+ import { execa as execa7 } from "execa";
3569
4354
  var fetchPackageSourceTool = defineTool({
3570
4355
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
3571
4356
  parameters: z6.object({
@@ -3583,25 +4368,25 @@ var fetchPackageSourceTool = defineTool({
3583
4368
  if (safePatterns.length === 0) {
3584
4369
  return { success: false, error: "No valid file patterns provided." };
3585
4370
  }
3586
- const tempBaseDir = await mkdtemp(join14(tmpdir(), "autoremediator-pkg-"));
3587
- const extractDir = join14(tempBaseDir, "out");
4371
+ const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
4372
+ const extractDir = join16(tempBaseDir, "out");
3588
4373
  try {
3589
4374
  const scopedName = packageName.split("/").pop();
3590
4375
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3591
4376
  await mkdir2(tempBaseDir, { recursive: true });
3592
- const tarballPath = join14(tempBaseDir, "package.tgz");
3593
- await execa6("curl", ["-L", "-o", tarballPath, npmUrl]);
4377
+ const tarballPath = join16(tempBaseDir, "package.tgz");
4378
+ await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3594
4379
  await mkdir2(extractDir, { recursive: true });
3595
- await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
4380
+ await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3596
4381
  const extractedContents = await readdir2(extractDir);
3597
- const packageRootDir = extractedContents.includes("package") ? join14(extractDir, "package") : extractDir;
4382
+ const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
3598
4383
  const sourceCode = {};
3599
4384
  async function walkDir(dir, relativeBase) {
3600
4385
  try {
3601
4386
  const files = await readdir2(dir, { withFileTypes: true });
3602
4387
  for (const file of files) {
3603
- const fullPath = join14(dir, file.name);
3604
- const relPath = join14(relativeBase, file.name);
4388
+ const fullPath = join16(dir, file.name);
4389
+ const relPath = join16(relativeBase, file.name);
3605
4390
  if (file.isDirectory()) {
3606
4391
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3607
4392
  file.name
@@ -3615,7 +4400,7 @@ var fetchPackageSourceTool = defineTool({
3615
4400
  });
3616
4401
  if (matches) {
3617
4402
  try {
3618
- const content = await readFile3(fullPath, "utf8");
4403
+ const content = await readFile4(fullPath, "utf8");
3619
4404
  sourceCode[relPath] = content;
3620
4405
  } catch {
3621
4406
  }
@@ -3650,7 +4435,7 @@ var fetchPackageSourceTool = defineTool({
3650
4435
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
3651
4436
  };
3652
4437
  } finally {
3653
- await rm2(tempBaseDir, { recursive: true, force: true });
4438
+ await rm3(tempBaseDir, { recursive: true, force: true });
3654
4439
  }
3655
4440
  }
3656
4441
  });
@@ -3708,6 +4493,7 @@ Analyze the source code to:
3708
4493
  2. Explain the root cause of the security issue
3709
4494
  3. Propose a secure fix that addresses the vulnerability
3710
4495
  4. Provide the complete fixed version of affected files
4496
+ 5. Optionally provide a standalone Node.js reproduction/regression test script
3711
4497
 
3712
4498
  ## Response Format
3713
4499
  Respond ONLY with valid JSON (no markdown, no extra text):
@@ -3718,7 +4504,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
3718
4504
  "path/to/other.ts": "Complete fixed source code for this file"
3719
4505
  },
3720
4506
  "confidence": 0.95,
3721
- "riskLevel": "medium"
4507
+ "riskLevel": "medium",
4508
+ "reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
3722
4509
  }
3723
4510
 
3724
4511
  Important:
@@ -3998,6 +4785,7 @@ var generatePatchTool = defineTool({
3998
4785
  success: true,
3999
4786
  patches,
4000
4787
  patchContent: patches[0]?.unifiedDiff,
4788
+ reproductionTest: analysis.reproductionTest,
4001
4789
  llmProvider: provider,
4002
4790
  llmModel: modelName,
4003
4791
  latencyMs,
@@ -4022,17 +4810,17 @@ var generatePatchTool = defineTool({
4022
4810
 
4023
4811
  // src/remediation/tools/apply-patch-file/index.ts
4024
4812
  import { z as z8 } from "zod";
4025
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
4026
- import { join as join16 } from "path";
4027
- 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";
4028
4816
 
4029
4817
  // src/remediation/tools/apply-patch-file/helpers.ts
4030
- import { existsSync as existsSync10 } from "fs";
4031
- 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";
4032
4820
  import { createHash } from "crypto";
4033
4821
  import { tmpdir as tmpdir2 } from "os";
4034
- import { join as join15 } from "path";
4035
- import { execa as execa7 } from "execa";
4822
+ import { join as join17 } from "path";
4823
+ import { execa as execa8 } from "execa";
4036
4824
  async function resolvePatchMode(packageManager, cwd) {
4037
4825
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
4038
4826
  return "patch-package";
@@ -4063,13 +4851,13 @@ function computePatchIntegrity(patchContent) {
4063
4851
  return `sha256:${hex}`;
4064
4852
  }
4065
4853
  async function writePatchManifest(manifestFilePath, artifact) {
4066
- await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4854
+ await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4067
4855
  }
4068
4856
  async function configurePatchPackagePostinstall(cwd, packageManager) {
4069
- const pkgJsonPath = join15(cwd, "package.json");
4857
+ const pkgJsonPath = join17(cwd, "package.json");
4070
4858
  let pkgJson;
4071
4859
  try {
4072
- pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
4860
+ pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
4073
4861
  } catch {
4074
4862
  return {
4075
4863
  success: false,
@@ -4081,7 +4869,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4081
4869
  try {
4082
4870
  const commands = getPackageManagerCommands(packageManager);
4083
4871
  const [cmd, ...args] = commands.installDev("patch-package");
4084
- await execa7(cmd, args, {
4872
+ await execa8(cmd, args, {
4085
4873
  cwd,
4086
4874
  stdio: "pipe"
4087
4875
  });
@@ -4102,13 +4890,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4102
4890
  } else if (!currentPostinstall) {
4103
4891
  pkgJson.scripts.postinstall = patchApplyCmd;
4104
4892
  }
4105
- await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4893
+ await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4106
4894
  return { success: true };
4107
4895
  }
4108
4896
  async function capturePackageJsonSnapshot(cwd) {
4109
- const path = join15(cwd, "package.json");
4897
+ const path = join17(cwd, "package.json");
4110
4898
  try {
4111
- const content = await readFile4(path, "utf8");
4899
+ const content = await readFile5(path, "utf8");
4112
4900
  return { path, content };
4113
4901
  } catch {
4114
4902
  return void 0;
@@ -4124,19 +4912,19 @@ async function cleanupPatchArtifacts(params) {
4124
4912
  installCommand,
4125
4913
  rerunInstall
4126
4914
  } = params;
4127
- await rm3(patchFilePath, { force: true }).catch(() => void 0);
4915
+ await rm4(patchFilePath, { force: true }).catch(() => void 0);
4128
4916
  if (manifestFilePath) {
4129
- await rm3(manifestFilePath, { force: true }).catch(() => void 0);
4917
+ await rm4(manifestFilePath, { force: true }).catch(() => void 0);
4130
4918
  }
4131
4919
  if (patchMode === "patch-package" && packageJsonSnapshot) {
4132
- await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4920
+ await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4133
4921
  () => void 0
4134
4922
  );
4135
4923
  }
4136
4924
  if (!rerunInstall) return;
4137
4925
  try {
4138
4926
  const [installCmd, ...installArgs] = installCommand;
4139
- await execa7(installCmd, installArgs, {
4927
+ await execa8(installCmd, installArgs, {
4140
4928
  cwd,
4141
4929
  stdio: "pipe"
4142
4930
  });
@@ -4150,7 +4938,7 @@ async function applyNativePatch(params) {
4150
4938
  const createArgs = ["patch", packageSpec];
4151
4939
  let patchDir;
4152
4940
  try {
4153
- const createResult = await execa7(createCommand, createArgs, {
4941
+ const createResult = await execa8(createCommand, createArgs, {
4154
4942
  cwd,
4155
4943
  stdio: "pipe"
4156
4944
  });
@@ -4178,17 +4966,17 @@ ${createResult.stderr}`);
4178
4966
  error: `Could not determine native patch directory for ${packageSpec}.`
4179
4967
  };
4180
4968
  }
4181
- const tempPatchDir = await mkdtemp2(join15(tmpdir2(), "autoremediator-native-patch-"));
4182
- const tempPatchFile = join15(tempPatchDir, "change.patch");
4969
+ const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
4970
+ const tempPatchFile = join17(tempPatchDir, "change.patch");
4183
4971
  try {
4184
- await writeFile(tempPatchFile, patchContent, "utf8");
4185
- await execa7("patch", ["-p1", "-i", tempPatchFile], {
4972
+ await writeFile2(tempPatchFile, patchContent, "utf8");
4973
+ await execa8("patch", ["-p1", "-i", tempPatchFile], {
4186
4974
  cwd: patchDir,
4187
4975
  stdio: "pipe"
4188
4976
  });
4189
4977
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
4190
4978
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
4191
- await execa7(commitCommand, commitArgs, {
4979
+ await execa8(commitCommand, commitArgs, {
4192
4980
  cwd,
4193
4981
  stdio: "pipe"
4194
4982
  });
@@ -4203,19 +4991,19 @@ ${createResult.stderr}`);
4203
4991
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
4204
4992
  };
4205
4993
  } finally {
4206
- await rm3(tempPatchDir, { recursive: true, force: true });
4994
+ await rm4(tempPatchDir, { recursive: true, force: true });
4207
4995
  }
4208
4996
  return { success: true };
4209
4997
  }
4210
4998
  function extractPatchDirectory(output) {
4211
4999
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4212
5000
  for (const line of lines) {
4213
- if (existsSync10(line)) {
5001
+ if (existsSync12(line)) {
4214
5002
  return line;
4215
5003
  }
4216
5004
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
4217
5005
  for (const token of tokens) {
4218
- if (token.startsWith("/") && existsSync10(token)) {
5006
+ if (token.startsWith("/") && existsSync12(token)) {
4219
5007
  return token;
4220
5008
  }
4221
5009
  }
@@ -4225,7 +5013,7 @@ function extractPatchDirectory(output) {
4225
5013
  async function validatePatchWithTests(cwd, testCommand) {
4226
5014
  try {
4227
5015
  const [cmd, ...args] = testCommand;
4228
- const result = await execa7(cmd, args, {
5016
+ const result = await execa8(cmd, args, {
4229
5017
  cwd,
4230
5018
  timeout: 6e4,
4231
5019
  stdio: "pipe"
@@ -4235,7 +5023,8 @@ async function validatePatchWithTests(cwd, testCommand) {
4235
5023
  output: result.stdout
4236
5024
  };
4237
5025
  } catch (err) {
4238
- const errorOutput = typeof err === "object" && err !== null && "stdout" in err ? String(err.stdout ?? "") : "";
5026
+ const stdoutVal = typeof err === "object" && err !== null && "stdout" in err ? err.stdout : "";
5027
+ const errorOutput = typeof stdoutVal === "string" ? stdoutVal : Buffer.isBuffer(stdoutVal) ? stdoutVal.toString("utf8") : "";
4239
5028
  const failedTests = extractFailedTests(errorOutput);
4240
5029
  return {
4241
5030
  passed: false,
@@ -4284,6 +5073,7 @@ var applyPatchFileTool = defineTool({
4284
5073
  enforceFrozenLockfile: z8.boolean().optional(),
4285
5074
  workspace: z8.string().optional(),
4286
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"),
4287
5077
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
4288
5078
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
4289
5079
  message: "Either patchContent or patches must be provided"
@@ -4305,6 +5095,7 @@ var applyPatchFileTool = defineTool({
4305
5095
  workspace,
4306
5096
  riskLevel,
4307
5097
  validateWithTests,
5098
+ reproductionTest,
4308
5099
  dryRun
4309
5100
  }) => {
4310
5101
  try {
@@ -4355,7 +5146,7 @@ var applyPatchFileTool = defineTool({
4355
5146
  };
4356
5147
  }
4357
5148
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
4358
- const patchFilePath = join16(cwd, patchesDir, patchFileName);
5149
+ const patchFilePath = join18(cwd, patchesDir, patchFileName);
4359
5150
  const manifestFilePath = `${patchFilePath}.json`;
4360
5151
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4361
5152
  const baseArtifact = {
@@ -4394,9 +5185,9 @@ var applyPatchFileTool = defineTool({
4394
5185
  }
4395
5186
  return withRepoLock(cwd, async () => {
4396
5187
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
4397
- const patchesDirPath = join16(cwd, patchesDir);
5188
+ const patchesDirPath = join18(cwd, patchesDir);
4398
5189
  await mkdir3(patchesDirPath, { recursive: true });
4399
- await writeFile2(patchFilePath, selectedPatch, "utf8");
5190
+ await writeFile3(patchFilePath, selectedPatch, "utf8");
4400
5191
  validationPhases.push({
4401
5192
  phase: "patch-write",
4402
5193
  passed: true,
@@ -4464,7 +5255,7 @@ var applyPatchFileTool = defineTool({
4464
5255
  if (patchMode === "patch-package") {
4465
5256
  try {
4466
5257
  const [installCmd, ...installArgs] = installCommand;
4467
- await execa8(installCmd, installArgs, {
5258
+ await execa9(installCmd, installArgs, {
4468
5259
  cwd,
4469
5260
  stdio: "pipe"
4470
5261
  });
@@ -4557,10 +5348,33 @@ var applyPatchFileTool = defineTool({
4557
5348
  message: "Patch validation tests passed."
4558
5349
  });
4559
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
+ }
4560
5372
  const finalArtifact = {
4561
5373
  ...artifact,
4562
5374
  applied: true,
4563
5375
  dryRun: false,
5376
+ reproductionTest,
5377
+ reproductionTestPath,
4564
5378
  validationPhases
4565
5379
  };
4566
5380
  await writePatchManifest(manifestFilePath, finalArtifact);
@@ -4870,7 +5684,11 @@ function resolveLocalRunOptions(options) {
4870
5684
  const runTests = options.runTests ?? false;
4871
5685
  const policy = options.policy ?? "";
4872
5686
  const patchesDir = options.patchesDir || "./patches";
4873
- 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
+ };
4874
5692
  const loadedPolicy = loadPolicy(cwd, options.policy);
4875
5693
  const llmProvider = resolveProvider(options);
4876
5694
  return {
@@ -4907,7 +5725,7 @@ function resolveLocalRunOptions(options) {
4907
5725
  }
4908
5726
 
4909
5727
  // src/remediation/local/vulnerability-match.ts
4910
- import semver4 from "semver";
5728
+ import semver5 from "semver";
4911
5729
  function findVulnerablePackages(cveDetails, installedPackages) {
4912
5730
  const vulnerablePackages = [];
4913
5731
  for (const affected of cveDetails.affectedPackages) {
@@ -4916,10 +5734,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
4916
5734
  if (affected.ecosystem !== "npm") continue;
4917
5735
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
4918
5736
  for (const installed of matches) {
4919
- if (!semver4.valid(installed.version)) continue;
5737
+ if (!semver5.valid(installed.version)) continue;
4920
5738
  let isVulnerable = false;
4921
5739
  try {
4922
- isVulnerable = semver4.satisfies(installed.version, affected.vulnerableRange, {
5740
+ isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4923
5741
  includePrerelease: false
4924
5742
  });
4925
5743
  } catch {
@@ -5262,7 +6080,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5262
6080
  };
5263
6081
  if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
5264
6082
  try {
5265
- if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
6083
+ if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
5266
6084
  includePrerelease: false
5267
6085
  })) {
5268
6086
  primaryResult.regressionDetected = true;
@@ -5353,8 +6171,8 @@ function accumulateStepResults(params) {
5353
6171
  }
5354
6172
 
5355
6173
  // src/remediation/orchestration-prompt.ts
5356
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
5357
- import { join as join17 } from "path";
6174
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
6175
+ import { join as join19 } from "path";
5358
6176
  function buildProviderAddendum(provider, personality = "balanced") {
5359
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.";
5360
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.";
@@ -5365,13 +6183,13 @@ Provider profile:
5365
6183
  - ${personalityDirective}`;
5366
6184
  }
5367
6185
  function loadOrchestrationPrompt(ctx) {
5368
- const promptPath = join17(
6186
+ const promptPath = join19(
5369
6187
  process.cwd(),
5370
6188
  ".github",
5371
6189
  "instructions",
5372
6190
  "orchestration.instructions.md"
5373
6191
  );
5374
- if (!existsSync11(promptPath)) {
6192
+ if (!existsSync13(promptPath)) {
5375
6193
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
5376
6194
  Working directory: ${ctx.cwd}
5377
6195
  Package manager: ${ctx.packageManager}
@@ -5379,112 +6197,48 @@ Dry run: ${ctx.dryRun}
5379
6197
  Run tests: ${ctx.runTests}
5380
6198
  Policy: ${ctx.policy || "undefined"}
5381
6199
  Patches dir: ${ctx.patchesDir}
5382
- Direct dependencies only: ${String(ctx.constraints.directDependenciesOnly ?? false)}
5383
- Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
5384
-
5385
- Required sequence:
5386
- 1. lookup-cve
5387
- 2. check-inventory
5388
- 3. check-version-match
5389
- 4. find-fixed-version
5390
- 5. apply-version-bump
5391
- 6. apply-package-override
5392
-
5393
- Fallback sequence (when neither version bump nor override can be applied):
5394
- 1. fetch-package-source
5395
- 2. generate-patch
5396
- 3. apply-patch-file
5397
-
5398
- Always respect dryRun and policy constraints.`;
5399
- }
5400
- const template = readFileSync11(promptPath, "utf8");
5401
- 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(
5402
- "{{directDependenciesOnly}}",
5403
- String(ctx.constraints.directDependenciesOnly ?? false)
5404
- ).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
5405
- }
5406
-
5407
- // src/remediation/pipeline-runtime/progress.ts
5408
- function createProgressEmitter(options) {
5409
- return (stage, detail, extra) => {
5410
- if (!options.onProgress) return;
5411
- options.onProgress({
5412
- stage,
5413
- detail,
5414
- at: (/* @__PURE__ */ new Date()).toISOString(),
5415
- provider: extra?.provider,
5416
- model: extra?.model
5417
- });
5418
- };
5419
- }
5420
-
5421
- // src/remediation/tools/lookup-cve.ts
5422
- import { z as z9 } from "zod";
5423
-
5424
- // src/intelligence/sources/cisa-kev.ts
5425
- var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
5426
- async function fetchCisaKevFeed() {
5427
- try {
5428
- const res = await httpClient({ url: CISA_KEV_URL });
5429
- if (!res.ok) return void 0;
5430
- return res.data;
5431
- } catch {
5432
- return void 0;
5433
- }
5434
- }
5435
- function findKevEntry(feed, cveId) {
5436
- if (!feed?.vulnerabilities?.length) return void 0;
5437
- const normalized = cveId.toUpperCase();
5438
- return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
5439
- }
5440
- async function enrichWithCisaKev(details) {
5441
- const feed = await fetchCisaKevFeed();
5442
- const entry = findKevEntry(feed, details.id);
5443
- if (!entry) return details;
5444
- details.kev = {
5445
- knownExploited: true,
5446
- dateAdded: entry.dateAdded,
5447
- dueDate: entry.dueDate,
5448
- requiredAction: entry.requiredAction,
5449
- knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
5450
- };
5451
- if (!details.references.includes(CISA_KEV_URL)) {
5452
- details.references.push(CISA_KEV_URL);
5453
- }
5454
- return details;
5455
- }
6200
+ Direct dependencies only: ${String(ctx.constraints.directDependenciesOnly ?? false)}
6201
+ Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
5456
6202
 
5457
- // src/intelligence/sources/epss.ts
5458
- async function fetchEpss(cveId) {
5459
- const { epssApi } = getIntelligenceSourceConfig();
5460
- if (!epssApi) return void 0;
5461
- try {
5462
- const url = new URL(epssApi);
5463
- url.searchParams.set("cve", cveId);
5464
- const res = await httpClient({ url: url.toString() });
5465
- if (!res.ok) return void 0;
5466
- const body = res.data;
5467
- return body.data?.[0];
5468
- } catch {
5469
- return void 0;
6203
+ Required sequence:
6204
+ 1. lookup-cve
6205
+ 2. check-inventory
6206
+ 3. check-version-match
6207
+ 4. find-fixed-version
6208
+ 5. apply-version-bump
6209
+ 6. apply-package-override
6210
+
6211
+ Fallback sequence (when neither version bump nor override can be applied):
6212
+ 1. fetch-package-source
6213
+ 2. generate-patch
6214
+ 3. apply-patch-file
6215
+
6216
+ Always respect dryRun and policy constraints.`;
5470
6217
  }
6218
+ const template = readFileSync13(promptPath, "utf8");
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(
6220
+ "{{directDependenciesOnly}}",
6221
+ String(ctx.constraints.directDependenciesOnly ?? false)
6222
+ ).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
5471
6223
  }
5472
- async function enrichWithEpss(details) {
5473
- const row = await fetchEpss(details.id);
5474
- if (!row) return details;
5475
- const score = Number.parseFloat(row.epss);
5476
- const percentile = Number.parseFloat(row.percentile);
5477
- if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
5478
- return details;
5479
- }
5480
- details.epss = {
5481
- score,
5482
- percentile,
5483
- date: row.date
6224
+
6225
+ // src/remediation/pipeline-runtime/progress.ts
6226
+ function createProgressEmitter(options) {
6227
+ return (stage, detail, extra) => {
6228
+ if (!options.onProgress) return;
6229
+ options.onProgress({
6230
+ stage,
6231
+ detail,
6232
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6233
+ provider: extra?.provider,
6234
+ model: extra?.model
6235
+ });
5484
6236
  };
5485
- return details;
5486
6237
  }
5487
6238
 
6239
+ // src/remediation/tools/lookup-cve.ts
6240
+ import { z as z9 } from "zod";
6241
+
5488
6242
  // src/intelligence/sources/cve-services.ts
5489
6243
  function pickEnglishDescription(container) {
5490
6244
  if (!container?.descriptions?.length) return void 0;
@@ -5768,7 +6522,7 @@ var lookupCveTool = defineTool({
5768
6522
 
5769
6523
  // src/remediation/tools/check-version-match.ts
5770
6524
  import { z as z10 } from "zod";
5771
- import semver6 from "semver";
6525
+ import semver7 from "semver";
5772
6526
  var affectedPackageSchema = z10.object({
5773
6527
  name: z10.string(),
5774
6528
  ecosystem: z10.literal("npm"),
@@ -5797,10 +6551,10 @@ var checkVersionMatchTool = defineTool({
5797
6551
  (p) => p.name === affected.name
5798
6552
  );
5799
6553
  for (const installed of matches) {
5800
- if (!semver6.valid(installed.version)) continue;
6554
+ if (!semver7.valid(installed.version)) continue;
5801
6555
  let isVulnerable = false;
5802
6556
  try {
5803
- isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
6557
+ isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
5804
6558
  includePrerelease: false
5805
6559
  });
5806
6560
  } catch {
@@ -6118,7 +6872,7 @@ async function runRemediationPipeline(cveId, options = {}) {
6118
6872
  }
6119
6873
 
6120
6874
  // src/api/change-request/index.ts
6121
- import { execa as execa9 } from "execa";
6875
+ import { execa as execa10 } from "execa";
6122
6876
  import { randomBytes } from "crypto";
6123
6877
  function sanitizeBranchToken(value) {
6124
6878
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -6147,22 +6901,68 @@ function resolveProvider2(options) {
6147
6901
  return options.provider ?? "github";
6148
6902
  }
6149
6903
  function resolveGrouping(options) {
6150
- if (options.grouping && options.grouping !== "all") {
6904
+ const allowed = ["all", "per-cve", "per-package", "by-severity"];
6905
+ if (options.grouping && !allowed.includes(options.grouping)) {
6151
6906
  throw new Error(
6152
- "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
6907
+ `Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
6153
6908
  );
6154
6909
  }
6155
- 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];
6156
6956
  }
6157
6957
  async function detectCurrentBranch(cwd) {
6158
- const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6958
+ const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6159
6959
  cwd,
6160
6960
  stdio: "pipe"
6161
6961
  });
6162
6962
  return result.stdout.trim();
6163
6963
  }
6164
6964
  async function ensureRepoHasChanges(cwd) {
6165
- const diff = await execa9("git", ["status", "--porcelain"], {
6965
+ const diff = await execa10("git", ["status", "--porcelain"], {
6166
6966
  cwd,
6167
6967
  stdio: "pipe"
6168
6968
  });
@@ -6191,7 +6991,7 @@ async function createGitHubRequest(params) {
6191
6991
  if (params.draft) {
6192
6992
  args.push("--draft");
6193
6993
  }
6194
- const result = await execa9("gh", args, {
6994
+ const result = await execa10("gh", args, {
6195
6995
  cwd: params.cwd,
6196
6996
  stdio: "pipe"
6197
6997
  });
@@ -6216,7 +7016,7 @@ async function createGitLabRequest(params) {
6216
7016
  if (params.draft) {
6217
7017
  args.push("--draft");
6218
7018
  }
6219
- const result = await execa9("glab", args, {
7019
+ const result = await execa10("glab", args, {
6220
7020
  cwd: params.cwd,
6221
7021
  stdio: "pipe"
6222
7022
  });
@@ -6226,64 +7026,68 @@ async function createChangeRequestsForReports(params) {
6226
7026
  const { cwd, options, reports } = params;
6227
7027
  const provider = resolveProvider2(options);
6228
7028
  const grouping = resolveGrouping(options);
6229
- const plan = buildPlan(reports);
6230
- const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
6231
- const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
6232
- const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
6233
- 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}
6234
7050
 
6235
7051
  ${bodyFooter}` : plan.body;
6236
- try {
6237
- const hasChanges = await ensureRepoHasChanges(cwd);
6238
7052
  if (!hasChanges) {
6239
- return [
6240
- {
6241
- provider,
6242
- grouping,
6243
- repository: options.repository,
6244
- branchName: "",
6245
- title,
6246
- body,
6247
- created: false,
6248
- cveIds: plan.cveIds,
6249
- packageNames: plan.packageNames,
6250
- error: "No repository changes to include in change request."
6251
- }
6252
- ];
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;
6253
7066
  }
6254
- const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
6255
- const branchPrefix = options.branchPrefix ?? "autoremediator";
6256
7067
  const branchName = toBranchName(branchPrefix, plan.cveIds);
6257
- const pushRemote = options.pushRemote ?? "origin";
6258
- if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
6259
- throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
6260
- }
6261
- if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
6262
- throw new Error(`Invalid repository format: ${options.repository}`);
6263
- }
6264
- await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
6265
- await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
6266
- await execa9("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
6267
- await execa9("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
6268
- const url = provider === "github" ? await createGitHubRequest({
6269
- cwd,
6270
- repository: options.repository,
6271
- head: branchName,
6272
- base: baseBranch,
6273
- title,
6274
- body,
6275
- draft: options.draft
6276
- }) : await createGitLabRequest({
6277
- cwd,
6278
- repository: options.repository,
6279
- source: branchName,
6280
- target: baseBranch,
6281
- title,
6282
- body,
6283
- draft: options.draft
6284
- });
6285
- return [
6286
- {
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({
6287
7091
  provider,
6288
7092
  grouping,
6289
7093
  repository: options.repository,
@@ -6295,11 +7099,13 @@ ${bodyFooter}` : plan.body;
6295
7099
  url,
6296
7100
  cveIds: plan.cveIds,
6297
7101
  packageNames: plan.packageNames
7102
+ });
7103
+ if (groups.length > 1) {
7104
+ await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
7105
+ });
6298
7106
  }
6299
- ];
6300
- } catch (error) {
6301
- return [
6302
- {
7107
+ } catch (error) {
7108
+ results.push({
6303
7109
  provider,
6304
7110
  grouping,
6305
7111
  repository: options.repository,
@@ -6311,9 +7117,10 @@ ${bodyFooter}` : plan.body;
6311
7117
  cveIds: plan.cveIds,
6312
7118
  packageNames: plan.packageNames,
6313
7119
  error: error instanceof Error ? error.message : String(error)
6314
- }
6315
- ];
7120
+ });
7121
+ }
6316
7122
  }
7123
+ return results;
6317
7124
  }
6318
7125
 
6319
7126
  // src/api/context.ts
@@ -6347,7 +7154,7 @@ function resolveConstraints(options, cwd) {
6347
7154
 
6348
7155
  // src/platform/evidence.ts
6349
7156
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6350
- import { join as join18 } from "path";
7157
+ import { join as join20 } from "path";
6351
7158
  function createEvidenceLog(cwd, cveIds, context = {}) {
6352
7159
  return {
6353
7160
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -6379,9 +7186,9 @@ function finalizeEvidence(log) {
6379
7186
  return log;
6380
7187
  }
6381
7188
  function writeEvidenceLog(cwd, log) {
6382
- const dir = join18(cwd, ".autoremediator", "evidence");
7189
+ const dir = join20(cwd, ".autoremediator", "evidence");
6383
7190
  mkdirSync3(dir, { recursive: true });
6384
- const filePath = join18(dir, `${log.runId}.json`);
7191
+ const filePath = join20(dir, `${log.runId}.json`);
6385
7192
  writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
6386
7193
  return filePath;
6387
7194
  }
@@ -6520,10 +7327,15 @@ function writeRemediateEvidence(cwd, evidence) {
6520
7327
  }
6521
7328
 
6522
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
+ }
6523
7335
  function assertValidCveId(cveId) {
6524
- if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
7336
+ if (!isVulnerabilityId(cveId)) {
6525
7337
  throw new Error(
6526
- `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.`
6527
7339
  );
6528
7340
  }
6529
7341
  }
@@ -6619,13 +7431,14 @@ async function planRemediation(cveId, options = {}) {
6619
7431
  }
6620
7432
 
6621
7433
  // src/scanner/parse-input.ts
6622
- import { extname, resolve as resolve3 } from "path";
6623
- import { readFileSync as readFileSync15 } from "fs";
6624
- 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";
6625
7437
 
6626
7438
  // src/scanner/adapters/npm-audit.ts
6627
- import { readFileSync as readFileSync12 } from "fs";
7439
+ import { readFileSync as readFileSync14 } from "fs";
6628
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;
6629
7442
  function normalizeSeverity(raw) {
6630
7443
  if (!raw) return "UNKNOWN";
6631
7444
  const up = raw.toUpperCase();
@@ -6640,7 +7453,8 @@ function parseNpmAuditJsonFromString(content) {
6640
7453
  for (const vuln of Object.values(report.vulnerabilities ?? {})) {
6641
7454
  for (const viaEntry of vuln.via ?? []) {
6642
7455
  const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
6643
- const matches = text.match(CVE_REGEX) ?? [];
7456
+ const cveMatches = text.match(CVE_REGEX) ?? [];
7457
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
6644
7458
  for (const match of matches) {
6645
7459
  const cveId = match.toUpperCase();
6646
7460
  const key = `${cveId}:${vuln.name}`;
@@ -6658,13 +7472,14 @@ function parseNpmAuditJsonFromString(content) {
6658
7472
  return findings;
6659
7473
  }
6660
7474
  function parseNpmAuditJsonFile(filePath) {
6661
- const content = readFileSync12(filePath, "utf8");
7475
+ const content = readFileSync14(filePath, "utf8");
6662
7476
  return parseNpmAuditJsonFromString(content);
6663
7477
  }
6664
7478
 
6665
7479
  // src/scanner/adapters/yarn-audit.ts
6666
- import { readFileSync as readFileSync13 } from "fs";
7480
+ import { readFileSync as readFileSync15 } from "fs";
6667
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;
6668
7483
  function normalizeSeverity2(raw) {
6669
7484
  if (!raw) return "UNKNOWN";
6670
7485
  const up = raw.toUpperCase();
@@ -6689,7 +7504,8 @@ function parseYarnAuditJsonFromString(content) {
6689
7504
  const packageName = advisory?.module_name;
6690
7505
  const severity = normalizeSeverity2(advisory?.severity);
6691
7506
  const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
6692
- const matches = text.match(CVE_REGEX2) ?? [];
7507
+ const cveMatches = text.match(CVE_REGEX2) ?? [];
7508
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
6693
7509
  for (const match of matches) {
6694
7510
  const cveId = match.toUpperCase();
6695
7511
  const key = `${cveId}:${packageName ?? ""}`;
@@ -6706,12 +7522,12 @@ function parseYarnAuditJsonFromString(content) {
6706
7522
  return findings;
6707
7523
  }
6708
7524
  function parseYarnAuditJsonFile(filePath) {
6709
- const content = readFileSync13(filePath, "utf8");
7525
+ const content = readFileSync15(filePath, "utf8");
6710
7526
  return parseYarnAuditJsonFromString(content);
6711
7527
  }
6712
7528
 
6713
7529
  // src/scanner/adapters/sarif.ts
6714
- import { readFileSync as readFileSync14 } from "fs";
7530
+ import { readFileSync as readFileSync16 } from "fs";
6715
7531
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
6716
7532
  function extractPackageName(result) {
6717
7533
  const pkg = result.properties?.["packageName"];
@@ -6749,7 +7565,7 @@ function parseSarifFromString(content) {
6749
7565
  return findings;
6750
7566
  }
6751
7567
  function parseSarifFile(filePath) {
6752
- const content = readFileSync14(filePath, "utf8");
7568
+ const content = readFileSync16(filePath, "utf8");
6753
7569
  return parseSarifFromString(content);
6754
7570
  }
6755
7571
 
@@ -6758,7 +7574,7 @@ function parseScanInput(filePath, format) {
6758
7574
  if (filePath.includes("\0")) {
6759
7575
  throw new Error("Invalid scan input path: path contains null bytes");
6760
7576
  }
6761
- const resolvedPath = resolve3(filePath);
7577
+ const resolvedPath = resolve4(filePath);
6762
7578
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
6763
7579
  if (resolved === "npm-audit") {
6764
7580
  return parseNpmAuditJsonFile(resolvedPath);
@@ -6769,7 +7585,7 @@ function parseScanInput(filePath, format) {
6769
7585
  if (resolved === "sarif") {
6770
7586
  return parseSarifFile(resolvedPath);
6771
7587
  }
6772
- throw new Error(`Unsupported input format: ${resolved}`);
7588
+ throw new Error(`Unsupported input format: ${String(resolved)}`);
6773
7589
  }
6774
7590
  async function parseScanInputFromAudit(params) {
6775
7591
  const pm = params.packageManager ?? detectPackageManager(params.cwd);
@@ -6780,7 +7596,7 @@ async function parseScanInputFromAudit(params) {
6780
7596
  }
6781
7597
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
6782
7598
  const [cmd, ...args] = command;
6783
- const result = await execa10(cmd, args, {
7599
+ const result = await execa11(cmd, args, {
6784
7600
  cwd: params.cwd,
6785
7601
  stdio: "pipe",
6786
7602
  reject: false
@@ -6834,7 +7650,7 @@ function inferFormat(filePath) {
6834
7650
  const ext = extname(filePath).toLowerCase();
6835
7651
  if (ext === ".sarif") return "sarif";
6836
7652
  try {
6837
- const content = readFileSync15(filePath, "utf8");
7653
+ const content = readFileSync17(filePath, "utf8");
6838
7654
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
6839
7655
  if (firstLine) {
6840
7656
  const parsed = JSON.parse(firstLine);
@@ -7245,14 +8061,14 @@ async function remediatePortfolio(targets, options = {}) {
7245
8061
  }
7246
8062
 
7247
8063
  // src/api/update-outdated/index.ts
7248
- import { join as join19 } from "path";
7249
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
7250
- 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";
7251
8067
  async function applyBump(params) {
7252
- const pkgPath = join19(params.cwd, "package.json");
8068
+ const pkgPath = join21(params.cwd, "package.json");
7253
8069
  let pkgJson;
7254
8070
  try {
7255
- pkgJson = JSON.parse(readFileSync16(pkgPath, "utf8"));
8071
+ pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
7256
8072
  } catch {
7257
8073
  return {
7258
8074
  applied: false,
@@ -7277,7 +8093,7 @@ async function applyBump(params) {
7277
8093
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7278
8094
  try {
7279
8095
  const [installCmd, ...installArgs] = params.installCommand;
7280
- await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8096
+ await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
7281
8097
  } catch (err) {
7282
8098
  pkgJson[depField][params.packageName] = currentRange;
7283
8099
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -7290,13 +8106,13 @@ async function applyBump(params) {
7290
8106
  if (params.runTests) {
7291
8107
  try {
7292
8108
  const [testCmd, ...testArgs] = params.testCommand;
7293
- await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8109
+ await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
7294
8110
  } catch (err) {
7295
8111
  pkgJson[depField][params.packageName] = currentRange;
7296
8112
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7297
8113
  try {
7298
8114
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
7299
- await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8115
+ await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
7300
8116
  } catch {
7301
8117
  }
7302
8118
  const message = err instanceof Error ? err.message : String(err);
@@ -7491,6 +8307,432 @@ async function updateOutdated(options = {}) {
7491
8307
  };
7492
8308
  }
7493
8309
 
8310
+ // src/api/reachability/index.ts
8311
+ async function checkReachability(options) {
8312
+ const cwd = options.cwd ?? process.cwd();
8313
+ return assessPackageReachability(cwd, options.packageName, options.symbol);
8314
+ }
8315
+
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")) {
8338
+ try {
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);
8363
+ }
8364
+ } catch {
8365
+ }
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
+ }
8383
+ }
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
+ }
8397
+ }
8398
+ }
8399
+ } catch {
8400
+ }
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
+ }
8424
+ }
8425
+ }
8426
+ }
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
+ }
8473
+ }
8474
+ }
8475
+ return changes;
8476
+ }
8477
+
8478
+ // src/scanner/delta-scan.ts
8479
+ var DEP_SECTIONS = [
8480
+ "dependencies",
8481
+ "devDependencies",
8482
+ "peerDependencies",
8483
+ "optionalDependencies"
8484
+ ];
8485
+ async function getChangedManifestPaths(cwd, baseRef) {
8486
+ const manifests = /* @__PURE__ */ new Set();
8487
+ try {
8488
+ const { stdout: diffFiles } = await execa13(
8489
+ "git",
8490
+ ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
8491
+ { cwd, reject: false }
8492
+ );
8493
+ for (const f of diffFiles.split("\n")) {
8494
+ const trimmed = f.trim();
8495
+ if (trimmed && trimmed.endsWith("package.json") && !trimmed.includes("node_modules")) {
8496
+ manifests.add(trimmed);
8497
+ }
8498
+ }
8499
+ const { stdout: untrackedFiles } = await execa13(
8500
+ "git",
8501
+ ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
8502
+ { cwd, reject: false }
8503
+ );
8504
+ for (const f of untrackedFiles.split("\n")) {
8505
+ const trimmed = f.trim();
8506
+ if (trimmed && trimmed.endsWith("package.json") && !trimmed.includes("node_modules")) {
8507
+ manifests.add(trimmed);
8508
+ }
8509
+ }
8510
+ } catch {
8511
+ if (existsSync14(join22(cwd, "package.json"))) {
8512
+ manifests.add("package.json");
8513
+ }
8514
+ }
8515
+ return Array.from(manifests);
8516
+ }
8517
+ async function getOldManifestContent(cwd, baseRef, relPath) {
8518
+ try {
8519
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
8520
+ cwd,
8521
+ reject: false
8522
+ });
8523
+ if (!stdout || !stdout.trim()) return null;
8524
+ return JSON.parse(stdout);
8525
+ } catch {
8526
+ return null;
8527
+ }
8528
+ }
8529
+ function getNewManifestContent(cwd, relPath) {
8530
+ const fullPath = join22(cwd, relPath);
8531
+ if (!existsSync14(fullPath)) return null;
8532
+ try {
8533
+ return JSON.parse(readFileSync19(fullPath, "utf8"));
8534
+ } catch {
8535
+ return null;
8536
+ }
8537
+ }
8538
+ function computeDependencyDiff(relPath, oldPkg, newPkg) {
8539
+ const changes = [];
8540
+ for (const section of DEP_SECTIONS) {
8541
+ const oldDeps = oldPkg?.[section] ?? {};
8542
+ const newDeps = newPkg?.[section] ?? {};
8543
+ for (const [pkgName, newVer] of Object.entries(newDeps)) {
8544
+ const oldVer = oldDeps[pkgName];
8545
+ if (!oldVer) {
8546
+ changes.push({
8547
+ packageName: pkgName,
8548
+ changeType: "added",
8549
+ newVersion: newVer,
8550
+ dependencyType: section,
8551
+ manifestPath: relPath
8552
+ });
8553
+ } else if (oldVer !== newVer) {
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;
8557
+ changes.push({
8558
+ packageName: pkgName,
8559
+ changeType: isUpgrade ? "upgraded" : "downgraded",
8560
+ oldVersion: oldVer,
8561
+ newVersion: newVer,
8562
+ dependencyType: section,
8563
+ manifestPath: relPath
8564
+ });
8565
+ }
8566
+ }
8567
+ for (const [pkgName, oldVer] of Object.entries(oldDeps)) {
8568
+ if (!(pkgName in newDeps)) {
8569
+ changes.push({
8570
+ packageName: pkgName,
8571
+ changeType: "removed",
8572
+ oldVersion: oldVer,
8573
+ dependencyType: section,
8574
+ manifestPath: relPath
8575
+ });
8576
+ }
8577
+ }
8578
+ }
8579
+ return changes;
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
+ }
8629
+ async function scanDelta(options = {}) {
8630
+ const cwd = options.cwd ?? process.cwd();
8631
+ const baseRef = options.baseRef ?? "HEAD";
8632
+ const changedManifests = await getChangedManifestPaths(cwd, baseRef);
8633
+ const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
8634
+ const dependencyChanges = [];
8635
+ const directPkgs = /* @__PURE__ */ new Set();
8636
+ for (const manifestRel of changedManifests) {
8637
+ const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
8638
+ const newPkg = getNewManifestContent(cwd, manifestRel);
8639
+ const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
8640
+ for (const d of diffs) {
8641
+ directPkgs.add(d.packageName);
8642
+ }
8643
+ dependencyChanges.push(...diffs);
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
+ }
8659
+ const introducedFindings = [];
8660
+ const resolvedFindings = [];
8661
+ for (const change of dependencyChanges) {
8662
+ if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
8663
+ const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
8664
+ const evaluation = await evaluatePackage(change.packageName, {
8665
+ version: cleanVer,
8666
+ enrichIntelligence: true
8667
+ });
8668
+ for (const vuln of evaluation.vulnerabilities) {
8669
+ introducedFindings.push({
8670
+ packageName: change.packageName,
8671
+ cveId: vuln.cveId,
8672
+ severity: vuln.severity,
8673
+ summary: vuln.summary,
8674
+ deltaType: "introduced",
8675
+ installedVersion: change.newVersion,
8676
+ safeUpgradeVersion: vuln.safeUpgradeVersion,
8677
+ inCisaKev: vuln.inCisaKev,
8678
+ epssScore: vuln.epssScore
8679
+ });
8680
+ }
8681
+ }
8682
+ if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
8683
+ const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
8684
+ const oldEval = await evaluatePackage(change.packageName, {
8685
+ version: cleanOldVer,
8686
+ enrichIntelligence: true
8687
+ });
8688
+ const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
8689
+ const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
8690
+ version: cleanNewVer,
8691
+ enrichIntelligence: false
8692
+ }) : { vulnerabilities: [] };
8693
+ const newCveSet = new Set(newEval.vulnerabilities.map((v) => v.cveId));
8694
+ for (const oldVuln of oldEval.vulnerabilities) {
8695
+ if (!newCveSet.has(oldVuln.cveId)) {
8696
+ resolvedFindings.push({
8697
+ packageName: change.packageName,
8698
+ cveId: oldVuln.cveId,
8699
+ severity: oldVuln.severity,
8700
+ summary: oldVuln.summary,
8701
+ deltaType: "resolved",
8702
+ installedVersion: change.oldVersion,
8703
+ safeUpgradeVersion: oldVuln.safeUpgradeVersion,
8704
+ inCisaKev: oldVuln.inCisaKev,
8705
+ epssScore: oldVuln.epssScore
8706
+ });
8707
+ }
8708
+ }
8709
+ }
8710
+ }
8711
+ let netRiskVerdict = "neutral";
8712
+ let status = "ok";
8713
+ if (introducedFindings.length > 0) {
8714
+ netRiskVerdict = "degraded";
8715
+ status = "degraded";
8716
+ } else if (resolvedFindings.length > 0) {
8717
+ netRiskVerdict = "improved";
8718
+ status = "improved";
8719
+ }
8720
+ const summary = introducedFindings.length > 0 ? `Delta scan against ${baseRef} found ${introducedFindings.length} newly introduced vulnerability(ies) across ${dependencyChanges.length} dependency change(s) (net risk: degraded).` : resolvedFindings.length > 0 ? `Delta scan against ${baseRef} resolved ${resolvedFindings.length} vulnerability(ies) across ${dependencyChanges.length} dependency change(s) (net risk: improved).` : `Delta scan against ${baseRef} found 0 new vulnerabilities across ${dependencyChanges.length} dependency change(s) (net risk: neutral).`;
8721
+ return {
8722
+ schemaVersion: "1.0",
8723
+ status,
8724
+ baseRef,
8725
+ targetRef: "working-tree",
8726
+ changedManifests,
8727
+ dependencyChanges,
8728
+ introducedFindings,
8729
+ resolvedFindings,
8730
+ netRiskVerdict,
8731
+ summary,
8732
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
8733
+ };
8734
+ }
8735
+
7494
8736
  // src/api/jobs/index.ts
7495
8737
  import { randomUUID as randomUUID2 } from "crypto";
7496
8738
 
@@ -7549,19 +8791,23 @@ export {
7549
8791
  buildDependencyScopeCounts,
7550
8792
  buildSlaBreachSummary,
7551
8793
  buildStrategyCounts,
8794
+ checkReachability,
7552
8795
  ciExitCode,
7553
8796
  createConstraintSchemaProperties,
7554
8797
  createRemediateOptionSchemaProperties,
7555
8798
  createScanOptionSchemaProperties,
7556
8799
  createScanReportSchemaProperties,
7557
8800
  createUpdateOutdatedOptionSchemaProperties,
8801
+ evaluatePackage,
7558
8802
  inspectPatchArtifact,
7559
8803
  listPatchArtifacts,
7560
8804
  planRemediation,
7561
8805
  pollJob,
8806
+ reconcilePatchArtifacts,
7562
8807
  remediate,
7563
8808
  remediateFromScan,
7564
8809
  remediatePortfolio,
8810
+ scanDelta,
7565
8811
  submitPortfolioJob,
7566
8812
  submitRemediateJob,
7567
8813
  submitScanJob,