autoremediator 0.17.0 → 0.19.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 (82) hide show
  1. package/README.md +20 -27
  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/context.d.ts.map +1 -1
  5. package/dist/api/index.d.ts +2 -2
  6. package/dist/api/index.d.ts.map +1 -1
  7. package/dist/api/options-schema.d.ts +3 -0
  8. package/dist/api/options-schema.d.ts.map +1 -1
  9. package/dist/api/patches/index.d.ts +1 -0
  10. package/dist/api/patches/index.d.ts.map +1 -1
  11. package/dist/api/patches/reconcile.d.ts +7 -0
  12. package/dist/api/patches/reconcile.d.ts.map +1 -0
  13. package/dist/api/patches/reconcile.test.d.ts +2 -0
  14. package/dist/api/patches/reconcile.test.d.ts.map +1 -0
  15. package/dist/api/remediate/validators.d.ts +3 -0
  16. package/dist/api/remediate/validators.d.ts.map +1 -1
  17. package/dist/{chunk-U4LIJIGD.js → chunk-7BORIBPY.js} +2476 -885
  18. package/dist/cli/program.d.ts.map +1 -1
  19. package/dist/cli/runners.d.ts +5 -0
  20. package/dist/cli/runners.d.ts.map +1 -1
  21. package/dist/cli/triage.d.ts +7 -0
  22. package/dist/cli/triage.d.ts.map +1 -0
  23. package/dist/cli/types.d.ts +5 -0
  24. package/dist/cli/types.d.ts.map +1 -1
  25. package/dist/cli.js +233 -6
  26. package/dist/index.js +2479 -889
  27. package/dist/index.js.map +1 -1
  28. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  29. package/dist/mcp/server.js +1 -1
  30. package/dist/openapi/server.js +1 -1
  31. package/dist/platform/evidence.d.ts +2 -0
  32. package/dist/platform/evidence.d.ts.map +1 -1
  33. package/dist/platform/package-manager/index.d.ts +1 -0
  34. package/dist/platform/package-manager/index.d.ts.map +1 -1
  35. package/dist/platform/supply-chain.d.ts +10 -0
  36. package/dist/platform/supply-chain.d.ts.map +1 -0
  37. package/dist/platform/supply-chain.test.d.ts +2 -0
  38. package/dist/platform/supply-chain.test.d.ts.map +1 -0
  39. package/dist/platform/types.d.ts +69 -6
  40. package/dist/platform/types.d.ts.map +1 -1
  41. package/dist/platform/workspace.d.ts +28 -0
  42. package/dist/platform/workspace.d.ts.map +1 -0
  43. package/dist/platform/workspace.test.d.ts +2 -0
  44. package/dist/platform/workspace.test.d.ts.map +1 -0
  45. package/dist/remediation/local/fallback.d.ts +2 -0
  46. package/dist/remediation/local/fallback.d.ts.map +1 -1
  47. package/dist/remediation/local/options.d.ts +3 -0
  48. package/dist/remediation/local/options.d.ts.map +1 -1
  49. package/dist/remediation/local/primary-strategy.d.ts +2 -9
  50. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  51. package/dist/remediation/local/run.d.ts.map +1 -1
  52. package/dist/remediation/pipeline-runtime/tool-wrappers.d.ts.map +1 -1
  53. package/dist/remediation/proof-of-fix/runner.d.ts +16 -0
  54. package/dist/remediation/proof-of-fix/runner.d.ts.map +1 -0
  55. package/dist/remediation/proof-of-fix/runner.test.d.ts +2 -0
  56. package/dist/remediation/proof-of-fix/runner.test.d.ts.map +1 -0
  57. package/dist/remediation/refactor/call-site-finder.d.ts +15 -0
  58. package/dist/remediation/refactor/call-site-finder.d.ts.map +1 -0
  59. package/dist/remediation/refactor/refactor-agent.d.ts +34 -0
  60. package/dist/remediation/refactor/refactor-agent.d.ts.map +1 -0
  61. package/dist/remediation/refactor/refactor.test.d.ts +2 -0
  62. package/dist/remediation/refactor/refactor.test.d.ts.map +1 -0
  63. package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
  64. package/dist/remediation/tools/apply-patch-file/index.d.ts +5 -1
  65. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  66. package/dist/remediation/tools/apply-version-bump.d.ts +2 -0
  67. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  68. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  69. package/dist/remediation/tools/check-reachability.d.ts +6 -1
  70. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  71. package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
  72. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  73. package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
  74. package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
  75. package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
  76. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  77. package/dist/scanner/delta-scan.d.ts.map +1 -1
  78. package/dist/scanner/lockfile-diff.d.ts +10 -0
  79. package/dist/scanner/lockfile-diff.d.ts.map +1 -0
  80. package/dist/scanner/lockfile-diff.test.d.ts +2 -0
  81. package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
  82. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -65,7 +65,10 @@ var OPTION_DESCRIPTIONS = {
65
65
  campaignMode: "Enable portfolio campaign mode to risk-rank targets before execution",
66
66
  escalationGraph: "Optional mapping from unresolved reasons to intended escalation actions",
67
67
  offlineIntelligence: "Skip all network calls to intelligence sources. Only local inventory and npm-registry version resolution proceed.",
68
- intelligenceSnapshotPath: "Path to a local JSON snapshot of pre-fetched CVE intelligence (keyed by CVE ID) used when offline mode is active."
68
+ intelligenceSnapshotPath: "Path to a local JSON snapshot of pre-fetched CVE intelligence (keyed by CVE ID) used when offline mode is active.",
69
+ proofOfFix: "If true, enforce dual-stage exploit reproduction proof-of-fix verification (assert failure pre-patch, assert pass post-patch)",
70
+ requireProofOfFix: "If true, reject patches and roll back if proof-of-fix verification fails",
71
+ breakingChangeRefactor: "If true, attempt agentic call-site refactoring when major version upgrades introduce breaking API changes"
69
72
  };
70
73
  function createConstraintSchemaProperties() {
71
74
  return {
@@ -87,7 +90,12 @@ function createConstraintSchemaProperties() {
87
90
  type: "boolean",
88
91
  description: OPTION_DESCRIPTIONS.enforceFrozenLockfile
89
92
  },
90
- workspace: { type: "string", description: OPTION_DESCRIPTIONS.workspace }
93
+ workspace: { type: "string", description: OPTION_DESCRIPTIONS.workspace },
94
+ requireProofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.requireProofOfFix },
95
+ enableBreakingChangeRefactor: {
96
+ type: "boolean",
97
+ description: OPTION_DESCRIPTIONS.breakingChangeRefactor
98
+ }
91
99
  };
92
100
  }
93
101
  function createRemediateOptionSchemaProperties(options) {
@@ -306,6 +314,14 @@ function createRemediateOptionSchemaProperties(options) {
306
314
  "validation-failed": {
307
315
  type: "string",
308
316
  enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
317
+ },
318
+ "proof-of-fix-failed": {
319
+ type: "string",
320
+ enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
321
+ },
322
+ "breaking-change-unresolved": {
323
+ type: "string",
324
+ enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
309
325
  }
310
326
  },
311
327
  additionalProperties: false
@@ -314,6 +330,12 @@ function createRemediateOptionSchemaProperties(options) {
314
330
  intelligenceSnapshotPath: {
315
331
  type: "string",
316
332
  description: OPTION_DESCRIPTIONS.intelligenceSnapshotPath
333
+ },
334
+ proofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.proofOfFix },
335
+ requireProofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.requireProofOfFix },
336
+ breakingChangeRefactor: {
337
+ type: "boolean",
338
+ description: OPTION_DESCRIPTIONS.breakingChangeRefactor
317
339
  }
318
340
  };
319
341
  }
@@ -887,12 +909,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
887
909
  import { join as join5 } from "path";
888
910
 
889
911
  // src/remediation/strategies/patch-utils.ts
890
- import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
912
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
891
913
  import { join as join4 } from "path";
892
914
  import { execa as execa2 } from "execa";
893
915
 
894
916
  // src/platform/package-manager/index.ts
895
- import { existsSync as existsSync2 } from "fs";
917
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
896
918
  import { join as join3 } from "path";
897
919
  import { execa } from "execa";
898
920
 
@@ -1085,6 +1107,32 @@ function resolveTestCommand(pm, constraints) {
1085
1107
  const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
1086
1108
  return withWorkspace(base, pm, constraints?.workspace);
1087
1109
  }
1110
+ function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
1111
+ const pkgJsonPath = join3(cwd, "package.json");
1112
+ let hasTypecheckScript = false;
1113
+ const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
1114
+ if (existsSync2(pkgJsonPath)) {
1115
+ try {
1116
+ const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
1117
+ if (pkg.scripts?.typecheck) {
1118
+ hasTypecheckScript = true;
1119
+ }
1120
+ } catch {
1121
+ }
1122
+ }
1123
+ if (hasTypecheckScript) {
1124
+ const base = [pm, "run", "typecheck"];
1125
+ return withWorkspace(base, pm, constraints?.workspace);
1126
+ }
1127
+ if (hasTsConfig) {
1128
+ if (pm === "deno") {
1129
+ return withWorkspace(["deno", "check"], pm, constraints?.workspace);
1130
+ }
1131
+ const base = ["npx", "tsc", "--noEmit"];
1132
+ return withWorkspace(base, pm, constraints?.workspace);
1133
+ }
1134
+ return [];
1135
+ }
1088
1136
  function resolveAuditCommand(pm, constraints) {
1089
1137
  if (pm === "deno") {
1090
1138
  throw new Error(
@@ -1329,12 +1377,12 @@ function defineTool(config) {
1329
1377
 
1330
1378
  // src/remediation/tools/check-inventory.ts
1331
1379
  import { z } from "zod";
1332
- import { readFileSync as readFileSync4 } from "fs";
1333
- import { join as join7 } from "path";
1380
+ import { readFileSync as readFileSync6 } from "fs";
1381
+ import { join as join8 } from "path";
1334
1382
  import { execa as execa3 } from "execa";
1335
1383
 
1336
1384
  // src/platform/policy.ts
1337
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1385
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1338
1386
  import { join as join6 } from "path";
1339
1387
  import { parse as yamlParse } from "yaml";
1340
1388
  var DEFAULT_POLICY = {
@@ -1369,7 +1417,7 @@ function loadPolicy(cwd, explicitPath) {
1369
1417
  const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
1370
1418
  if (!existsSync6(candidate)) return DEFAULT_POLICY;
1371
1419
  try {
1372
- const parsed = yamlParse(readFileSync3(candidate, "utf8"));
1420
+ const parsed = yamlParse(readFileSync4(candidate, "utf8"));
1373
1421
  return {
1374
1422
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
1375
1423
  denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
@@ -1430,7 +1478,7 @@ function isActiveSuppression(suppression) {
1430
1478
  }
1431
1479
  function loadSuppressionsFile(filePath) {
1432
1480
  try {
1433
- const content = readFileSync3(filePath, "utf8");
1481
+ const content = readFileSync4(filePath, "utf8");
1434
1482
  const parsed = yamlParse(content);
1435
1483
  return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
1436
1484
  } catch {
@@ -1456,6 +1504,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1456
1504
  };
1457
1505
  }
1458
1506
 
1507
+ // src/platform/workspace.ts
1508
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1509
+ import { join as join7, resolve as resolve2, relative } from "path";
1510
+ import yaml from "yaml";
1511
+ function findWorkspaceRoot(startDir) {
1512
+ let curr = resolve2(startDir);
1513
+ while (true) {
1514
+ if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
1515
+ const pkgPath = join7(curr, "package.json");
1516
+ if (existsSync7(pkgPath)) {
1517
+ try {
1518
+ const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
1519
+ if (parsed.workspaces) return curr;
1520
+ } catch {
1521
+ }
1522
+ }
1523
+ const parent = resolve2(curr, "..");
1524
+ if (parent === curr) break;
1525
+ curr = parent;
1526
+ }
1527
+ return null;
1528
+ }
1529
+ function findWorkspacePackages(cwd) {
1530
+ const rootCwd = findWorkspaceRoot(cwd) ?? resolve2(cwd);
1531
+ const packages = [];
1532
+ const packageGlobs = [];
1533
+ const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
1534
+ if (existsSync7(pnpmWorkspaceFile)) {
1535
+ try {
1536
+ const content = readFileSync5(pnpmWorkspaceFile, "utf8");
1537
+ const parsed = yaml.parse(content);
1538
+ if (Array.isArray(parsed?.packages)) {
1539
+ packageGlobs.push(...parsed.packages);
1540
+ }
1541
+ } catch {
1542
+ }
1543
+ }
1544
+ const rootPkgFile = join7(rootCwd, "package.json");
1545
+ if (existsSync7(rootPkgFile)) {
1546
+ try {
1547
+ const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
1548
+ if (Array.isArray(rootPkg.workspaces)) {
1549
+ packageGlobs.push(...rootPkg.workspaces);
1550
+ } else if (Array.isArray(rootPkg.workspaces?.packages)) {
1551
+ packageGlobs.push(...rootPkg.workspaces.packages);
1552
+ }
1553
+ } catch {
1554
+ }
1555
+ }
1556
+ const denoJsonFile = join7(rootCwd, "deno.json");
1557
+ if (existsSync7(denoJsonFile)) {
1558
+ try {
1559
+ const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
1560
+ if (Array.isArray(denoJson.workspace)) {
1561
+ packageGlobs.push(...denoJson.workspace);
1562
+ }
1563
+ } catch {
1564
+ }
1565
+ }
1566
+ if (packageGlobs.length === 0) {
1567
+ return packages;
1568
+ }
1569
+ const seenDirs = /* @__PURE__ */ new Set();
1570
+ for (const glob of packageGlobs) {
1571
+ const cleanGlob = glob.trim().replace(/\/+$/, "");
1572
+ if (!cleanGlob || cleanGlob.startsWith("!")) continue;
1573
+ if (cleanGlob.endsWith("/*")) {
1574
+ const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
1575
+ if (!existsSync7(parentDir)) continue;
1576
+ try {
1577
+ const entries = readdirSync(parentDir);
1578
+ for (const entry of entries) {
1579
+ if (entry.startsWith(".") || entry === "node_modules") continue;
1580
+ const subDir = join7(parentDir, entry);
1581
+ try {
1582
+ if (statSync(subDir).isDirectory()) {
1583
+ addWorkspacePackage(rootCwd, subDir, packages, seenDirs);
1584
+ }
1585
+ } catch {
1586
+ }
1587
+ }
1588
+ } catch {
1589
+ }
1590
+ } else {
1591
+ const directDir = join7(rootCwd, cleanGlob);
1592
+ if (existsSync7(directDir)) {
1593
+ try {
1594
+ if (statSync(directDir).isDirectory()) {
1595
+ addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
1596
+ }
1597
+ } catch {
1598
+ }
1599
+ }
1600
+ }
1601
+ }
1602
+ return packages;
1603
+ }
1604
+ function addWorkspacePackage(rootCwd, dir, out, seen) {
1605
+ const norm = resolve2(dir);
1606
+ if (seen.has(norm)) return;
1607
+ seen.add(norm);
1608
+ const manifestPath = join7(dir, "package.json");
1609
+ if (!existsSync7(manifestPath)) return;
1610
+ try {
1611
+ const content = readFileSync5(manifestPath, "utf8");
1612
+ const manifest = JSON.parse(content);
1613
+ out.push({
1614
+ name: manifest.name ?? relative(rootCwd, dir),
1615
+ relativeDir: relative(rootCwd, dir),
1616
+ absoluteDir: norm,
1617
+ manifestPath,
1618
+ manifest
1619
+ });
1620
+ } catch {
1621
+ }
1622
+ }
1623
+ function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
1624
+ const workspaces = findWorkspacePackages(cwd);
1625
+ if (workspaces.length === 0) return null;
1626
+ const matchesFilter = (wp) => {
1627
+ if (!filterWorkspace) return true;
1628
+ const filter = filterWorkspace.trim();
1629
+ return wp.name === filter || wp.relativeDir === filter || wp.relativeDir.replace(/\\/g, "/") === filter.replace(/\\/g, "/");
1630
+ };
1631
+ for (const wp of workspaces) {
1632
+ if (!matchesFilter(wp)) continue;
1633
+ const isDeclared = Boolean(wp.manifest.dependencies?.[dependencyName]) || Boolean(wp.manifest.devDependencies?.[dependencyName]) || Boolean(wp.manifest.peerDependencies?.[dependencyName]);
1634
+ if (isDeclared) {
1635
+ return wp;
1636
+ }
1637
+ }
1638
+ return null;
1639
+ }
1640
+
1459
1641
  // src/remediation/tools/check-inventory.ts
1460
1642
  var checkInventoryTool = defineTool({
1461
1643
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
@@ -1473,7 +1655,7 @@ var checkInventoryTool = defineTool({
1473
1655
  }) => {
1474
1656
  let pkgJson;
1475
1657
  try {
1476
- pkgJson = JSON.parse(readFileSync4(join7(cwd, "package.json"), "utf8"));
1658
+ pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1477
1659
  } catch {
1478
1660
  return {
1479
1661
  packages: [],
@@ -1505,7 +1687,8 @@ var checkInventoryTool = defineTool({
1505
1687
  }
1506
1688
  const packages = [];
1507
1689
  for (const [name, version] of installedVersions.entries()) {
1508
- const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]);
1690
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, name, workspace);
1691
+ const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]) || Boolean(declaringWorkspace);
1509
1692
  packages.push({
1510
1693
  name,
1511
1694
  version,
@@ -1628,173 +1811,14 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
1628
1811
  };
1629
1812
  }
1630
1813
 
1631
- // src/platform/config.ts
1632
- function resolveProvider(options = {}) {
1633
- const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1634
- if (raw !== "remote" && raw !== "local") {
1635
- throw new Error(
1636
- `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1637
- );
1638
- }
1639
- return raw;
1640
- }
1641
- function resolveModelName(provider, options = {}, routing = {}) {
1642
- if (options.model) return options.model;
1643
- if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1644
- const cwd = options.cwd ?? process.cwd();
1645
- const policy = loadPolicy(cwd, options.policy);
1646
- const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1647
- if (providerEnvModel) return providerEnvModel;
1648
- const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1649
- if (policyPinnedModel) return policyPinnedModel;
1650
- const defaults = {
1651
- remote: "remote-default",
1652
- local: "local"
1653
- };
1654
- const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
1655
- const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
1656
- if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
1657
- return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
1658
- }
1659
- return defaults[provider];
1660
- }
1661
- async function loadRemoteFactory() {
1662
- const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
1663
- const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
1664
- if (!moduleName) {
1665
- throw new Error(
1666
- "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
1667
- );
1668
- }
1669
- if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
1670
- throw new Error(
1671
- `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
1672
- );
1673
- }
1674
- const loaded = await import(moduleName);
1675
- const factory = loaded[exportName];
1676
- if (typeof factory !== "function") {
1677
- throw new Error(
1678
- `Remote client factory "${exportName}" was not found in module "${moduleName}".`
1679
- );
1680
- }
1681
- return factory;
1682
- }
1683
- async function createModel(options = {}, routing = {}) {
1684
- const provider = resolveProvider(options);
1685
- if (provider === "local") {
1686
- throw new Error(
1687
- "Local provider does not create a language model. Use the deterministic pipeline path instead."
1688
- );
1689
- }
1690
- const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
1691
- if (!apiKey) {
1692
- throw new Error(
1693
- "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
1694
- );
1695
- }
1696
- const modelName = resolveModelName(provider, options, routing);
1697
- const createRemoteClient = await loadRemoteFactory();
1698
- const remoteClient = createRemoteClient({ apiKey });
1699
- return remoteClient(modelName);
1700
- }
1701
- function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
1702
- const relaxed = {
1703
- remote: { low: 0.65, medium: 0.7, high: 0.8 },
1704
- local: { low: 0.65, medium: 0.7, high: 0.8 }
1705
- };
1706
- const strict = {
1707
- remote: { low: 0.8, medium: 0.85, high: 0.9 },
1708
- local: { low: 0.8, medium: 0.85, high: 0.9 }
1709
- };
1710
- const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
1711
- const override = overrides?.[riskLevel];
1712
- if (typeof override === "number" && Number.isFinite(override)) {
1713
- return Math.max(0, Math.min(1, override));
1714
- }
1715
- return baseThreshold;
1716
- }
1717
- function estimateModelCostUsd(params) {
1718
- const inputTokens = params.promptChars / 4;
1719
- const outputTokens = params.completionChars / 4;
1720
- const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
1721
- return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
1722
- }
1723
- function getNvdConfig() {
1724
- return {
1725
- apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
1726
- };
1727
- }
1728
- function getGitHubToken() {
1729
- return process.env.GITHUB_TOKEN;
1730
- }
1731
- function getIntelligenceSourceConfig() {
1732
- return {
1733
- gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
1734
- certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
1735
- epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
1736
- cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
1737
- depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
1738
- scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
1739
- vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1740
- commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1741
- commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
1742
- };
1743
- }
1744
-
1745
- // src/platform/idempotency.ts
1746
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1747
- import { join as join8 } from "path";
1748
- var DEFAULT_INDEX = {
1749
- schemaVersion: "1.0",
1750
- entries: {}
1751
- };
1752
- function indexFilePath(cwd) {
1753
- return join8(cwd, ".autoremediator", "state", "idempotency.json");
1754
- }
1755
- function entryKey(idempotencyKey, cveId) {
1756
- return `${idempotencyKey}::${cveId.toUpperCase()}`;
1757
- }
1758
- function loadIndex(cwd) {
1759
- const filePath = indexFilePath(cwd);
1760
- if (!existsSync7(filePath)) return DEFAULT_INDEX;
1761
- try {
1762
- const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
1763
- if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
1764
- return parsed;
1765
- }
1766
- return DEFAULT_INDEX;
1767
- } catch {
1768
- return DEFAULT_INDEX;
1769
- }
1770
- }
1771
- function saveIndex(cwd, index) {
1772
- const filePath = indexFilePath(cwd);
1773
- mkdirSync2(join8(cwd, ".autoremediator", "state"), { recursive: true });
1774
- writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
1775
- }
1776
- function readIdempotentReport(cwd, idempotencyKey, cveId) {
1777
- const index = loadIndex(cwd);
1778
- const key = entryKey(idempotencyKey, cveId);
1779
- return index.entries[key]?.report;
1780
- }
1781
- function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
1782
- const index = loadIndex(cwd);
1783
- const key = entryKey(idempotencyKey, cveId);
1784
- index.entries[key] = {
1785
- key: idempotencyKey,
1786
- cveId: cveId.toUpperCase(),
1787
- report,
1788
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
1789
- };
1790
- saveIndex(cwd, index);
1791
- }
1792
-
1793
- // src/remediation/pipeline.ts
1794
- import { generateText as generateText2 } from "ai";
1814
+ // src/api/patches/reconcile.ts
1815
+ import { existsSync as existsSync8 } from "fs";
1816
+ import { readFile as readFile3, rm, writeFile } from "fs/promises";
1817
+ import { join as join10 } from "path";
1818
+ import { execa as execa5 } from "execa";
1795
1819
 
1796
- // src/remediation/local/run.ts
1797
- import semver5 from "semver";
1820
+ // src/remediation/evaluate-package.ts
1821
+ import semver2 from "semver";
1798
1822
 
1799
1823
  // src/platform/http-client.ts
1800
1824
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -1976,22 +2000,746 @@ async function lookupCveOsv(cveId) {
1976
2000
  return parseOsvVuln(vuln);
1977
2001
  }
1978
2002
 
1979
- // src/intelligence/sources/github-advisory.ts
1980
- var GITHUB_API_BASE = "https://api.github.com/advisories";
1981
- async function fetchGhAdvisories(cveId) {
1982
- const token = getGitHubToken();
1983
- const headers = {
1984
- "X-GitHub-Api-Version": "2022-11-28",
1985
- "User-Agent": "autoremediator"
1986
- };
1987
- if (token) {
1988
- headers["Authorization"] = `token ${token}`;
1989
- }
1990
- const url = new URL(GITHUB_API_BASE);
1991
- url.searchParams.set("cve_id", cveId);
1992
- url.searchParams.set("ecosystem", "npm");
1993
- url.searchParams.set("type", "reviewed");
1994
- url.searchParams.set("per_page", "10");
2003
+ // src/platform/config.ts
2004
+ function resolveProvider(options = {}) {
2005
+ const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
2006
+ if (raw !== "remote" && raw !== "local") {
2007
+ throw new Error(
2008
+ `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
2009
+ );
2010
+ }
2011
+ return raw;
2012
+ }
2013
+ function resolveModelName(provider, options = {}, routing = {}) {
2014
+ if (options.model) return options.model;
2015
+ if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
2016
+ const cwd = options.cwd ?? process.cwd();
2017
+ const policy = loadPolicy(cwd, options.policy);
2018
+ const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
2019
+ if (providerEnvModel) return providerEnvModel;
2020
+ const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
2021
+ if (policyPinnedModel) return policyPinnedModel;
2022
+ const defaults = {
2023
+ remote: "remote-default",
2024
+ local: "local"
2025
+ };
2026
+ const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
2027
+ const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
2028
+ if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
2029
+ return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
2030
+ }
2031
+ return defaults[provider];
2032
+ }
2033
+ async function loadRemoteFactory() {
2034
+ const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
2035
+ const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
2036
+ if (!moduleName) {
2037
+ throw new Error(
2038
+ "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
2039
+ );
2040
+ }
2041
+ if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
2042
+ throw new Error(
2043
+ `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
2044
+ );
2045
+ }
2046
+ const loaded = await import(moduleName);
2047
+ const factory = loaded[exportName];
2048
+ if (typeof factory !== "function") {
2049
+ throw new Error(
2050
+ `Remote client factory "${exportName}" was not found in module "${moduleName}".`
2051
+ );
2052
+ }
2053
+ return factory;
2054
+ }
2055
+ async function createModel(options = {}, routing = {}) {
2056
+ const provider = resolveProvider(options);
2057
+ if (provider === "local") {
2058
+ throw new Error(
2059
+ "Local provider does not create a language model. Use the deterministic pipeline path instead."
2060
+ );
2061
+ }
2062
+ const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
2063
+ if (!apiKey) {
2064
+ throw new Error(
2065
+ "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
2066
+ );
2067
+ }
2068
+ const modelName = resolveModelName(provider, options, routing);
2069
+ const createRemoteClient = await loadRemoteFactory();
2070
+ const remoteClient = createRemoteClient({ apiKey });
2071
+ return remoteClient(modelName);
2072
+ }
2073
+ function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
2074
+ const relaxed = {
2075
+ remote: { low: 0.65, medium: 0.7, high: 0.8 },
2076
+ local: { low: 0.65, medium: 0.7, high: 0.8 }
2077
+ };
2078
+ const strict = {
2079
+ remote: { low: 0.8, medium: 0.85, high: 0.9 },
2080
+ local: { low: 0.8, medium: 0.85, high: 0.9 }
2081
+ };
2082
+ const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
2083
+ const override = overrides?.[riskLevel];
2084
+ if (typeof override === "number" && Number.isFinite(override)) {
2085
+ return Math.max(0, Math.min(1, override));
2086
+ }
2087
+ return baseThreshold;
2088
+ }
2089
+ function estimateModelCostUsd(params) {
2090
+ const inputTokens = params.promptChars / 4;
2091
+ const outputTokens = params.completionChars / 4;
2092
+ const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
2093
+ return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
2094
+ }
2095
+ function getNvdConfig() {
2096
+ return {
2097
+ apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
2098
+ };
2099
+ }
2100
+ function getGitHubToken() {
2101
+ return process.env.GITHUB_TOKEN;
2102
+ }
2103
+ function getIntelligenceSourceConfig() {
2104
+ return {
2105
+ gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
2106
+ certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
2107
+ epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
2108
+ cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
2109
+ depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
2110
+ scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
2111
+ vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
2112
+ commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
2113
+ commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
2114
+ };
2115
+ }
2116
+
2117
+ // src/intelligence/sources/epss.ts
2118
+ async function fetchEpss(cveId) {
2119
+ const { epssApi } = getIntelligenceSourceConfig();
2120
+ if (!epssApi) return void 0;
2121
+ try {
2122
+ const url = new URL(epssApi);
2123
+ url.searchParams.set("cve", cveId);
2124
+ const res = await httpClient({ url: url.toString() });
2125
+ if (!res.ok) return void 0;
2126
+ const body = res.data;
2127
+ return body.data?.[0];
2128
+ } catch {
2129
+ return void 0;
2130
+ }
2131
+ }
2132
+ async function enrichWithEpss(details) {
2133
+ const row = await fetchEpss(details.id);
2134
+ if (!row) return details;
2135
+ const score = Number.parseFloat(row.epss);
2136
+ const percentile = Number.parseFloat(row.percentile);
2137
+ if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
2138
+ return details;
2139
+ }
2140
+ details.epss = {
2141
+ score,
2142
+ percentile,
2143
+ date: row.date
2144
+ };
2145
+ return details;
2146
+ }
2147
+
2148
+ // src/intelligence/sources/cisa-kev.ts
2149
+ var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
2150
+ async function fetchCisaKevFeed() {
2151
+ try {
2152
+ const res = await httpClient({ url: CISA_KEV_URL });
2153
+ if (!res.ok) return void 0;
2154
+ return res.data;
2155
+ } catch {
2156
+ return void 0;
2157
+ }
2158
+ }
2159
+ function findKevEntry(feed, cveId) {
2160
+ if (!feed?.vulnerabilities?.length) return void 0;
2161
+ const normalized = cveId.toUpperCase();
2162
+ return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
2163
+ }
2164
+ async function enrichWithCisaKev(details) {
2165
+ const feed = await fetchCisaKevFeed();
2166
+ const entry = findKevEntry(feed, details.id);
2167
+ if (!entry) return details;
2168
+ details.kev = {
2169
+ knownExploited: true,
2170
+ dateAdded: entry.dateAdded,
2171
+ dueDate: entry.dueDate,
2172
+ requiredAction: entry.requiredAction,
2173
+ knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
2174
+ };
2175
+ if (!details.references.includes(CISA_KEV_URL)) {
2176
+ details.references.push(CISA_KEV_URL);
2177
+ }
2178
+ return details;
2179
+ }
2180
+
2181
+ // src/intelligence/sources/registry.ts
2182
+ import { readFileSync as readFileSync7 } from "fs";
2183
+ import { join as join9 } from "path";
2184
+ import { execa as execa4 } from "execa";
2185
+ import semver from "semver";
2186
+ var NPM_REGISTRY = "https://registry.npmjs.org";
2187
+ async function fetchPackageVersions(packageName) {
2188
+ const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2189
+ try {
2190
+ const res = await httpClient({ url });
2191
+ if (res.status === 404) return [];
2192
+ if (!res.ok) {
2193
+ throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2194
+ }
2195
+ const data = res.data;
2196
+ return Object.keys(data.versions);
2197
+ } catch (err) {
2198
+ if (err instanceof Error && err.message.includes("registry error")) {
2199
+ throw err;
2200
+ }
2201
+ return [];
2202
+ }
2203
+ }
2204
+ async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2205
+ const resolution = await resolveSafeUpgradeVersion(
2206
+ packageName,
2207
+ installedVersion,
2208
+ firstPatchedVersion,
2209
+ vulnerableRange
2210
+ );
2211
+ return resolution.safeVersion;
2212
+ }
2213
+ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2214
+ const versions = await fetchPackageVersions(packageName);
2215
+ if (!versions.length) {
2216
+ return {
2217
+ candidates: {},
2218
+ majorOnlyFixAvailable: false
2219
+ };
2220
+ }
2221
+ const installed = semver.parse(installedVersion);
2222
+ const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2223
+ if (!vulnerableRange) return true;
2224
+ try {
2225
+ return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2226
+ } catch {
2227
+ return true;
2228
+ }
2229
+ }).sort(semver.compare);
2230
+ if (!candidates.length) {
2231
+ return {
2232
+ candidates: {},
2233
+ majorOnlyFixAvailable: false
2234
+ };
2235
+ }
2236
+ const categorizedCandidates = {};
2237
+ for (const candidate of candidates) {
2238
+ const level = classifyUpgradeLevel(installedVersion, candidate);
2239
+ if (!level) continue;
2240
+ if (!categorizedCandidates[level]) {
2241
+ categorizedCandidates[level] = candidate;
2242
+ }
2243
+ }
2244
+ const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2245
+ if (!safeVersion) {
2246
+ return {
2247
+ candidates: categorizedCandidates,
2248
+ majorOnlyFixAvailable: false
2249
+ };
2250
+ }
2251
+ const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2252
+ const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2253
+ if (!installed || !upgradeLevel) {
2254
+ return {
2255
+ safeVersion,
2256
+ upgradeLevel,
2257
+ candidates: categorizedCandidates,
2258
+ majorOnlyFixAvailable
2259
+ };
2260
+ }
2261
+ return {
2262
+ safeVersion,
2263
+ upgradeLevel,
2264
+ candidates: categorizedCandidates,
2265
+ majorOnlyFixAvailable
2266
+ };
2267
+ }
2268
+ function classifyUpgradeLevel(installedVersion, candidateVersion) {
2269
+ const installed = semver.parse(installedVersion);
2270
+ const candidate = semver.parse(candidateVersion);
2271
+ if (!installed || !candidate) return void 0;
2272
+ if (candidate.major > installed.major) return "major";
2273
+ if (candidate.minor > installed.minor) return "minor";
2274
+ if (candidate.patch > installed.patch || candidate.version === installed.version) {
2275
+ return "patch";
2276
+ }
2277
+ return void 0;
2278
+ }
2279
+ async function queryOutdatedPackages(cwd, options = {}) {
2280
+ const pm = options.packageManager ?? detectPackageManager(cwd);
2281
+ let stdout;
2282
+ try {
2283
+ const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2284
+ const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2285
+ const finalArgs = yarnArgs ?? args;
2286
+ const result2 = await execa4(pm, finalArgs, {
2287
+ cwd,
2288
+ reject: false,
2289
+ stdio: "pipe"
2290
+ });
2291
+ if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2292
+ const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2293
+ if (!result2.stdout) {
2294
+ throw new Error(
2295
+ `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2296
+ );
2297
+ }
2298
+ }
2299
+ stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2300
+ } catch (err) {
2301
+ if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2302
+ throw new Error(`Failed to query outdated packages: ${err.message}`);
2303
+ }
2304
+ throw err;
2305
+ }
2306
+ if (!stdout.trim()) {
2307
+ return /* @__PURE__ */ new Map();
2308
+ }
2309
+ const rawEntries = {};
2310
+ if (pm === "yarn") {
2311
+ for (const line of stdout.split("\n")) {
2312
+ const trimmed = line.trim();
2313
+ if (!trimmed) continue;
2314
+ try {
2315
+ const parsed = JSON.parse(trimmed);
2316
+ if (parsed.type === "table" && parsed.data) {
2317
+ const tableData = parsed.data;
2318
+ const head = tableData.head ?? [];
2319
+ const currentIdx = head.indexOf("Current");
2320
+ const wantedIdx = head.indexOf("Wanted");
2321
+ const latestIdx = head.indexOf("Latest");
2322
+ for (const row of tableData.body ?? []) {
2323
+ const name = row[0];
2324
+ if (!name) continue;
2325
+ rawEntries[name] = {
2326
+ current: row[currentIdx] ?? "",
2327
+ wanted: row[wantedIdx] ?? "",
2328
+ latest: row[latestIdx] ?? "",
2329
+ type: row[head.indexOf("Package Type")] ?? "dependencies"
2330
+ };
2331
+ }
2332
+ }
2333
+ } catch {
2334
+ }
2335
+ }
2336
+ } else {
2337
+ try {
2338
+ const parsed = JSON.parse(stdout);
2339
+ if (Array.isArray(parsed)) {
2340
+ for (const entry of parsed) {
2341
+ const name = entry.name ?? entry.packageName;
2342
+ if (!name) continue;
2343
+ rawEntries[name] = {
2344
+ current: entry.current,
2345
+ wanted: entry.wanted,
2346
+ latest: entry.latest,
2347
+ type: entry.dependencyType
2348
+ };
2349
+ }
2350
+ } else {
2351
+ Object.assign(rawEntries, parsed);
2352
+ }
2353
+ } catch {
2354
+ throw new Error(`Could not parse ${pm} outdated --json output.`);
2355
+ }
2356
+ }
2357
+ let directDeps;
2358
+ try {
2359
+ const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
2360
+ directDeps = /* @__PURE__ */ new Set([
2361
+ ...Object.keys(pkgRaw.dependencies ?? {}),
2362
+ ...Object.keys(pkgRaw.devDependencies ?? {})
2363
+ ]);
2364
+ } catch {
2365
+ directDeps = /* @__PURE__ */ new Set();
2366
+ }
2367
+ const result = /* @__PURE__ */ new Map();
2368
+ for (const [name, entry] of Object.entries(rawEntries)) {
2369
+ const currentVersion = entry.current ?? "";
2370
+ const wantedVersion = entry.wanted ?? currentVersion;
2371
+ const latestVersion = entry.latest ?? currentVersion;
2372
+ if (!currentVersion || !latestVersion) continue;
2373
+ const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2374
+ if (!options.includeTransitive && dependencyScope === "transitive") continue;
2375
+ const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2376
+ result.set(name, {
2377
+ currentVersion,
2378
+ wantedVersion,
2379
+ latestVersion,
2380
+ isMajorBump,
2381
+ dependencyScope
2382
+ });
2383
+ }
2384
+ return result;
2385
+ }
2386
+
2387
+ // src/remediation/evaluate-package.ts
2388
+ async function evaluatePackage(packageName, options = {}) {
2389
+ const version = options.version?.trim();
2390
+ const enrich = options.enrichIntelligence !== false;
2391
+ let versions = [];
2392
+ let latestVersion;
2393
+ try {
2394
+ versions = await fetchPackageVersions(packageName);
2395
+ if (versions.length > 0) {
2396
+ const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
2397
+ latestVersion = valid[valid.length - 1];
2398
+ }
2399
+ } catch {
2400
+ }
2401
+ let rawVulns = [];
2402
+ try {
2403
+ rawVulns = await queryOsvPackage(packageName, version);
2404
+ } catch {
2405
+ rawVulns = [];
2406
+ }
2407
+ let kevFeed = null;
2408
+ if (enrich) {
2409
+ try {
2410
+ kevFeed = await fetchCisaKevFeed();
2411
+ } catch {
2412
+ kevFeed = null;
2413
+ }
2414
+ }
2415
+ const entries = [];
2416
+ for (const raw of rawVulns) {
2417
+ const cveDetails = parseOsvVuln(raw);
2418
+ const affected = cveDetails.affectedPackages.find(
2419
+ (p) => p.name.toLowerCase() === packageName.toLowerCase()
2420
+ );
2421
+ if (version && affected?.vulnerableRange) {
2422
+ const cleanVer = semver2.clean(version) || version;
2423
+ if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
2424
+ continue;
2425
+ }
2426
+ }
2427
+ let inCisaKev = false;
2428
+ let epssScore;
2429
+ let epssPercentile;
2430
+ if (enrich && cveDetails.id.startsWith("CVE-")) {
2431
+ if (kevFeed) {
2432
+ inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
2433
+ }
2434
+ try {
2435
+ const epss = await fetchEpss(cveDetails.id);
2436
+ if (epss) {
2437
+ epssScore = Number.parseFloat(epss.epss);
2438
+ epssPercentile = Number.parseFloat(epss.percentile);
2439
+ }
2440
+ } catch {
2441
+ }
2442
+ }
2443
+ let safeUpgradeVersion;
2444
+ if (affected?.firstPatchedVersion) {
2445
+ try {
2446
+ const safe = await findSafeUpgradeVersion(
2447
+ packageName,
2448
+ version || "0.0.0",
2449
+ affected.firstPatchedVersion
2450
+ );
2451
+ safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
2452
+ } catch {
2453
+ safeUpgradeVersion = affected.firstPatchedVersion;
2454
+ }
2455
+ }
2456
+ entries.push({
2457
+ cveId: cveDetails.id,
2458
+ severity: cveDetails.severity,
2459
+ summary: cveDetails.summary,
2460
+ vulnerableRange: affected?.vulnerableRange,
2461
+ firstPatchedVersion: affected?.firstPatchedVersion,
2462
+ safeUpgradeVersion,
2463
+ epssScore,
2464
+ epssPercentile,
2465
+ inCisaKev,
2466
+ references: cveDetails.references
2467
+ });
2468
+ }
2469
+ const isVulnerable = entries.length > 0;
2470
+ let verdict = "safe";
2471
+ if (isVulnerable) {
2472
+ const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
2473
+ const hasHighOrCritical = entries.some(
2474
+ (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
2475
+ );
2476
+ if (hasKevOrHighEpss) {
2477
+ verdict = "actively-exploited";
2478
+ } else if (hasHighOrCritical) {
2479
+ verdict = "vulnerable";
2480
+ } else {
2481
+ verdict = "caution";
2482
+ }
2483
+ }
2484
+ let recommendedVersion;
2485
+ if (isVulnerable) {
2486
+ const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
2487
+ recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
2488
+ } else {
2489
+ recommendedVersion = version || latestVersion;
2490
+ }
2491
+ 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.`;
2492
+ return {
2493
+ schemaVersion: "1.0",
2494
+ packageName,
2495
+ evaluatedVersion: version,
2496
+ isVulnerable,
2497
+ verdict,
2498
+ summary,
2499
+ vulnerabilities: entries,
2500
+ recommendedVersion,
2501
+ latestVersion,
2502
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2503
+ };
2504
+ }
2505
+
2506
+ // src/api/patches/reconcile.ts
2507
+ async function reconcilePatchArtifacts(options = {}) {
2508
+ const cwd = options.cwd ?? process.cwd();
2509
+ const pm = options.packageManager ?? detectPackageManager(cwd);
2510
+ const summaries = await listPatchArtifacts(options);
2511
+ const results = [];
2512
+ for (const summary of summaries) {
2513
+ const inspection = await inspectPatchArtifact(summary.patchFilePath, options);
2514
+ let packageName = inspection.packageName;
2515
+ let vulnerableVersion = inspection.vulnerableVersion;
2516
+ const cveId = inspection.cveId;
2517
+ if (!packageName && summary.patchFileName) {
2518
+ const basename = summary.patchFileName.replace(/\.patch$/, "");
2519
+ const plusIdx = basename.lastIndexOf("+");
2520
+ if (plusIdx > 0) {
2521
+ let rawPkg = basename.slice(0, plusIdx);
2522
+ vulnerableVersion = basename.slice(plusIdx + 1);
2523
+ if (rawPkg.includes("+")) {
2524
+ rawPkg = "@" + rawPkg.replace("+", "/");
2525
+ }
2526
+ packageName = rawPkg;
2527
+ } else {
2528
+ packageName = basename;
2529
+ }
2530
+ }
2531
+ if (!packageName) {
2532
+ results.push({
2533
+ patchFilePath: summary.patchFilePath,
2534
+ packageName: "unknown",
2535
+ cveId,
2536
+ status: "patch-retained",
2537
+ reason: "Cannot determine package name from patch artifact."
2538
+ });
2539
+ continue;
2540
+ }
2541
+ let safeUpgradeVersion;
2542
+ try {
2543
+ const evaluation = await evaluatePackage(packageName, {
2544
+ version: vulnerableVersion,
2545
+ enrichIntelligence: false
2546
+ });
2547
+ if (cveId) {
2548
+ const vuln = evaluation.vulnerabilities.find(
2549
+ (v) => v.cveId.toLowerCase() === cveId.toLowerCase()
2550
+ );
2551
+ if (vuln?.safeUpgradeVersion) {
2552
+ safeUpgradeVersion = vuln.safeUpgradeVersion;
2553
+ }
2554
+ } else {
2555
+ safeUpgradeVersion = evaluation.vulnerabilities.find(
2556
+ (v) => v.safeUpgradeVersion
2557
+ )?.safeUpgradeVersion;
2558
+ }
2559
+ } catch {
2560
+ }
2561
+ if (!safeUpgradeVersion) {
2562
+ results.push({
2563
+ patchFilePath: summary.patchFilePath,
2564
+ packageName,
2565
+ cveId,
2566
+ installedVersion: vulnerableVersion,
2567
+ status: "patch-retained",
2568
+ reason: "No safe upstream release exists yet; patch remains necessary."
2569
+ });
2570
+ continue;
2571
+ }
2572
+ if (!options.autoApply) {
2573
+ results.push({
2574
+ patchFilePath: summary.patchFilePath,
2575
+ packageName,
2576
+ cveId,
2577
+ installedVersion: vulnerableVersion,
2578
+ safeUpstreamVersion: safeUpgradeVersion,
2579
+ status: "safe-version-available",
2580
+ reason: `Safe upstream version ${safeUpgradeVersion} is available. Re-run with autoApply: true or --auto-apply to upgrade.`
2581
+ });
2582
+ continue;
2583
+ }
2584
+ try {
2585
+ const pkgJsonPath = join10(cwd, "package.json");
2586
+ if (existsSync8(pkgJsonPath)) {
2587
+ const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
2588
+ let updated = false;
2589
+ const depSections = [
2590
+ "dependencies",
2591
+ "devDependencies",
2592
+ "peerDependencies",
2593
+ "optionalDependencies"
2594
+ ];
2595
+ for (const depSection of depSections) {
2596
+ if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
2597
+ pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
2598
+ updated = true;
2599
+ }
2600
+ }
2601
+ if (pkgJson.pnpm?.patchedDependencies) {
2602
+ for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
2603
+ if (key.startsWith(`${packageName}@`)) {
2604
+ delete pkgJson.pnpm.patchedDependencies[key];
2605
+ updated = true;
2606
+ }
2607
+ }
2608
+ }
2609
+ if (updated) {
2610
+ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2611
+ }
2612
+ }
2613
+ await rm(summary.patchFilePath, { force: true });
2614
+ if (inspection.manifestFilePath) {
2615
+ await rm(inspection.manifestFilePath, { force: true });
2616
+ }
2617
+ const installCmd = resolveInstallCommand(pm);
2618
+ await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
2619
+ if (options.runTests) {
2620
+ const testCmd = resolveTestCommand(pm);
2621
+ await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
2622
+ }
2623
+ results.push({
2624
+ patchFilePath: summary.patchFilePath,
2625
+ packageName,
2626
+ cveId,
2627
+ installedVersion: vulnerableVersion,
2628
+ safeUpstreamVersion: safeUpgradeVersion,
2629
+ status: "upgraded",
2630
+ reason: `Successfully upgraded to ${safeUpgradeVersion} and retired patch artifact.`
2631
+ });
2632
+ } catch (err) {
2633
+ results.push({
2634
+ patchFilePath: summary.patchFilePath,
2635
+ packageName,
2636
+ cveId,
2637
+ installedVersion: vulnerableVersion,
2638
+ safeUpstreamVersion: safeUpgradeVersion,
2639
+ status: "error",
2640
+ reason: err instanceof Error ? err.message : String(err)
2641
+ });
2642
+ }
2643
+ }
2644
+ const reconciledCount = results.filter((r) => r.status === "upgraded").length;
2645
+ const retainedCount = results.filter(
2646
+ (r) => r.status === "patch-retained" || r.status === "safe-version-available"
2647
+ ).length;
2648
+ return {
2649
+ reconciled: results,
2650
+ reconciledCount,
2651
+ retainedCount
2652
+ };
2653
+ }
2654
+
2655
+ // src/platform/idempotency.ts
2656
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
2657
+ import { join as join11 } from "path";
2658
+ var DEFAULT_INDEX = {
2659
+ schemaVersion: "1.0",
2660
+ entries: {}
2661
+ };
2662
+ function indexFilePath(cwd) {
2663
+ return join11(cwd, ".autoremediator", "state", "idempotency.json");
2664
+ }
2665
+ function entryKey(idempotencyKey, cveId) {
2666
+ return `${idempotencyKey}::${cveId.toUpperCase()}`;
2667
+ }
2668
+ function loadIndex(cwd) {
2669
+ const filePath = indexFilePath(cwd);
2670
+ if (!existsSync9(filePath)) return DEFAULT_INDEX;
2671
+ try {
2672
+ const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
2673
+ if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
2674
+ return parsed;
2675
+ }
2676
+ return DEFAULT_INDEX;
2677
+ } catch {
2678
+ return DEFAULT_INDEX;
2679
+ }
2680
+ }
2681
+ function saveIndex(cwd, index) {
2682
+ const filePath = indexFilePath(cwd);
2683
+ mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
2684
+ writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2685
+ }
2686
+ function readIdempotentReport(cwd, idempotencyKey, cveId) {
2687
+ const index = loadIndex(cwd);
2688
+ const key = entryKey(idempotencyKey, cveId);
2689
+ return index.entries[key]?.report;
2690
+ }
2691
+ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
2692
+ const index = loadIndex(cwd);
2693
+ const key = entryKey(idempotencyKey, cveId);
2694
+ index.entries[key] = {
2695
+ key: idempotencyKey,
2696
+ cveId: cveId.toUpperCase(),
2697
+ report,
2698
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
2699
+ };
2700
+ saveIndex(cwd, index);
2701
+ }
2702
+
2703
+ // src/remediation/pipeline.ts
2704
+ import { generateText as generateText2 } from "ai";
2705
+
2706
+ // src/remediation/local/run.ts
2707
+ import semver6 from "semver";
2708
+
2709
+ // src/intelligence/sources/github-advisory.ts
2710
+ var GITHUB_API_BASE = "https://api.github.com/advisories";
2711
+ async function fetchGhAdvisories(cveId) {
2712
+ const token = getGitHubToken();
2713
+ const headers = {
2714
+ "X-GitHub-Api-Version": "2022-11-28",
2715
+ "User-Agent": "autoremediator"
2716
+ };
2717
+ if (token) {
2718
+ headers["Authorization"] = `token ${token}`;
2719
+ }
2720
+ const normalized = cveId.trim();
2721
+ if (normalized.toUpperCase().startsWith("GHSA-")) {
2722
+ const url2 = `${GITHUB_API_BASE}/${encodeURIComponent(normalized)}`;
2723
+ try {
2724
+ const res = await httpClient({ url: url2, headers });
2725
+ if (!res.ok) {
2726
+ if (res.status === 404) return [];
2727
+ throw new Error(`HTTP ${res.status}: ${res.text}`);
2728
+ }
2729
+ return res.data ? [res.data] : [];
2730
+ } catch (err) {
2731
+ const errorMsg = err instanceof Error ? err.message : String(err);
2732
+ console.warn(
2733
+ `[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
2734
+ );
2735
+ return [];
2736
+ }
2737
+ }
2738
+ const url = new URL(GITHUB_API_BASE);
2739
+ url.searchParams.set("cve_id", cveId);
2740
+ url.searchParams.set("ecosystem", "npm");
2741
+ url.searchParams.set("type", "reviewed");
2742
+ url.searchParams.set("per_page", "10");
1995
2743
  try {
1996
2744
  const res = await httpClient({
1997
2745
  url: url.toString(),
@@ -2095,9 +2843,9 @@ async function enrichWithNvd(details) {
2095
2843
  }
2096
2844
 
2097
2845
  // src/intelligence/sources/snapshot.ts
2098
- import { readFileSync as readFileSync6 } from "fs";
2846
+ import { readFileSync as readFileSync9 } from "fs";
2099
2847
  function loadIntelligenceSnapshot(filePath) {
2100
- const raw = readFileSync6(filePath, "utf8");
2848
+ const raw = readFileSync9(filePath, "utf8");
2101
2849
  const parsed = JSON.parse(raw);
2102
2850
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2103
2851
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -2109,8 +2857,8 @@ function lookupSnapshotCve(snapshot, cveId) {
2109
2857
  }
2110
2858
 
2111
2859
  // src/remediation/tools/check-reachability.ts
2112
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync } from "fs";
2113
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "path";
2860
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
2861
+ import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
2114
2862
  import { parseSync } from "oxc-parser";
2115
2863
  import { z as z2 } from "zod";
2116
2864
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
@@ -2126,16 +2874,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
2126
2874
  ]);
2127
2875
  function loadTsconfigPaths(cwd) {
2128
2876
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
2129
- const fullPath = join9(cwd, configName);
2130
- if (!existsSync8(fullPath)) continue;
2877
+ const fullPath = join12(cwd, configName);
2878
+ if (!existsSync10(fullPath)) continue;
2131
2879
  try {
2132
- const raw = readFileSync7(fullPath, "utf8");
2880
+ const raw = readFileSync10(fullPath, "utf8");
2133
2881
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
2134
2882
  const parsed = JSON.parse(cleaned);
2135
2883
  const compilerOptions = parsed.compilerOptions;
2136
2884
  if (compilerOptions?.paths) {
2137
2885
  return {
2138
- baseUrl: compilerOptions.baseUrl ? resolve2(cwd, compilerOptions.baseUrl) : cwd,
2886
+ baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
2139
2887
  paths: compilerOptions.paths
2140
2888
  };
2141
2889
  }
@@ -2150,14 +2898,14 @@ function resolveAliasedPath(importSource, config) {
2150
2898
  }
2151
2899
  for (const [pattern, replacements] of Object.entries(config.paths)) {
2152
2900
  if (pattern === importSource && replacements[0]) {
2153
- return resolve2(config.baseUrl, replacements[0]);
2901
+ return resolve3(config.baseUrl, replacements[0]);
2154
2902
  }
2155
2903
  if (pattern.endsWith("/*")) {
2156
2904
  const prefix = pattern.slice(0, -2);
2157
2905
  if (importSource.startsWith(prefix)) {
2158
2906
  const suffix = importSource.slice(prefix.length);
2159
2907
  const replacement = replacements[0]?.slice(0, -2) ?? "";
2160
- return resolve2(config.baseUrl, replacement + suffix);
2908
+ return resolve3(config.baseUrl, replacement + suffix);
2161
2909
  }
2162
2910
  }
2163
2911
  }
@@ -2166,16 +2914,16 @@ function resolveAliasedPath(importSource, config) {
2166
2914
  function* walkSourceFiles(dir) {
2167
2915
  let entries;
2168
2916
  try {
2169
- entries = readdirSync(dir);
2917
+ entries = readdirSync2(dir);
2170
2918
  } catch {
2171
2919
  return;
2172
2920
  }
2173
2921
  for (const entry of entries) {
2174
2922
  if (IGNORED_DIRS.has(entry)) continue;
2175
- const full = join9(dir, entry);
2923
+ const full = join12(dir, entry);
2176
2924
  let stat2;
2177
2925
  try {
2178
- stat2 = statSync(full);
2926
+ stat2 = statSync2(full);
2179
2927
  } catch {
2180
2928
  continue;
2181
2929
  }
@@ -2213,7 +2961,31 @@ function walkAst(node, visitor) {
2213
2961
  }
2214
2962
  }
2215
2963
  }
2216
- function extractPackageReferences(filename, content, packageName, tsconfig) {
2964
+ function toRootPackageName(importSource) {
2965
+ if (importSource.startsWith("@")) {
2966
+ const parts = importSource.split("/");
2967
+ return parts.slice(0, 2).join("/");
2968
+ }
2969
+ return importSource.split("/")[0] ?? importSource;
2970
+ }
2971
+ function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
2972
+ for (const directPkg of importedDirectPackages) {
2973
+ const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
2974
+ if (!existsSync10(manifestPath)) continue;
2975
+ try {
2976
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
2977
+ if (manifest.dependencies && manifest.dependencies[targetPackage]) {
2978
+ return {
2979
+ directDependency: directPkg,
2980
+ version: manifest.dependencies[targetPackage]
2981
+ };
2982
+ }
2983
+ } catch {
2984
+ }
2985
+ }
2986
+ return null;
2987
+ }
2988
+ function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
2217
2989
  const results = [];
2218
2990
  let parseResult;
2219
2991
  try {
@@ -2225,6 +2997,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2225
2997
  walkAst(parseResult.program, (node) => {
2226
2998
  if (node.type === "ImportDeclaration") {
2227
2999
  const source = node.source;
3000
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3001
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3002
+ }
2228
3003
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2229
3004
  const symbols = [];
2230
3005
  const importedIdentifiers = /* @__PURE__ */ new Map();
@@ -2259,6 +3034,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2259
3034
  }
2260
3035
  if (node.type === "ImportExpression") {
2261
3036
  const source = node.source;
3037
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3038
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3039
+ }
2262
3040
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2263
3041
  results.push({
2264
3042
  matchType: "dynamic-import",
@@ -2270,6 +3048,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2270
3048
  }
2271
3049
  if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
2272
3050
  const firstArg = node.arguments[0];
3051
+ if (firstArg?.value && typeof firstArg.value === "string" && !firstArg.value.startsWith(".") && !isAbsolute2(firstArg.value)) {
3052
+ outImportedDirectPackages?.add(toRootPackageName(firstArg.value));
3053
+ }
2273
3054
  if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
2274
3055
  results.push({
2275
3056
  matchType: "require",
@@ -2281,6 +3062,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
2281
3062
  }
2282
3063
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
2283
3064
  const source = node.source;
3065
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
3066
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
3067
+ }
2284
3068
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
2285
3069
  const symbols = [];
2286
3070
  if (node.type === "ExportAllDeclaration") {
@@ -2357,15 +3141,22 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2357
3141
  let hasReExport = false;
2358
3142
  let totalInvocations = 0;
2359
3143
  let hasPackageImport = false;
3144
+ const importedDirectPackages = /* @__PURE__ */ new Set();
2360
3145
  for (const filePath of walkSourceFiles(cwd)) {
2361
3146
  scanned++;
2362
3147
  let content;
2363
3148
  try {
2364
- content = readFileSync7(filePath, "utf8");
3149
+ content = readFileSync10(filePath, "utf8");
2365
3150
  } catch {
2366
3151
  continue;
2367
3152
  }
2368
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
3153
+ const refs = extractPackageReferences(
3154
+ filePath,
3155
+ content,
3156
+ packageName,
3157
+ tsconfig,
3158
+ importedDirectPackages
3159
+ );
2369
3160
  if (refs.length > 0) {
2370
3161
  hasPackageImport = true;
2371
3162
  }
@@ -2408,6 +3199,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2408
3199
  };
2409
3200
  }
2410
3201
  if (!hasPackageImport) {
3202
+ const transitivePath = findTransitiveDependencyPath(cwd, packageName, importedDirectPackages);
3203
+ if (transitivePath) {
3204
+ return {
3205
+ packageName,
3206
+ status: "reachable",
3207
+ reason: `Package '${packageName}' is a transitive dependency of imported package '${transitivePath.directDependency}'`,
3208
+ reachabilityBasis: "transitive-path-found",
3209
+ evidence: []
3210
+ };
3211
+ }
2411
3212
  return {
2412
3213
  packageName,
2413
3214
  status: "not-reachable",
@@ -2552,254 +3353,48 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
2552
3353
  opts.slaPolicy
2553
3354
  );
2554
3355
  if (breach) {
2555
- slaBreaches = [breach];
2556
- }
2557
- }
2558
- return { suppressed: false, exploitSignalTriggered, slaBreaches };
2559
- }
2560
-
2561
- // src/remediation/local/sbom.ts
2562
- function buildSbom(packages, vulnerableNames, results) {
2563
- const statusByPackage = /* @__PURE__ */ new Map();
2564
- for (const result of results) {
2565
- if (!vulnerableNames.has(result.packageName)) continue;
2566
- if (result.suppressedBy) {
2567
- statusByPackage.set(result.packageName, "suppressed");
2568
- } else if (!result.applied && result.strategy === "none") {
2569
- statusByPackage.set(result.packageName, "skipped");
2570
- } else if (result.applied) {
2571
- statusByPackage.set(result.packageName, "patched");
2572
- } else {
2573
- statusByPackage.set(result.packageName, "unpatched");
2574
- }
2575
- }
2576
- return packages.map((pkg) => {
2577
- const isVulnerable = vulnerableNames.has(pkg.name);
2578
- const entry = {
2579
- name: pkg.name,
2580
- version: pkg.version,
2581
- scope: pkg.type === "direct" ? "direct" : "transitive"
2582
- };
2583
- if (isVulnerable) {
2584
- entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2585
- }
2586
- return entry;
2587
- });
2588
- }
2589
-
2590
- // src/intelligence/sources/registry.ts
2591
- import { readFileSync as readFileSync8 } from "fs";
2592
- import { join as join10 } from "path";
2593
- import { execa as execa4 } from "execa";
2594
- import semver from "semver";
2595
- var NPM_REGISTRY = "https://registry.npmjs.org";
2596
- async function fetchPackageVersions(packageName) {
2597
- const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2598
- try {
2599
- const res = await httpClient({ url });
2600
- if (res.status === 404) return [];
2601
- if (!res.ok) {
2602
- throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2603
- }
2604
- const data = res.data;
2605
- return Object.keys(data.versions);
2606
- } catch (err) {
2607
- if (err instanceof Error && err.message.includes("registry error")) {
2608
- throw err;
2609
- }
2610
- return [];
2611
- }
2612
- }
2613
- async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2614
- const resolution = await resolveSafeUpgradeVersion(
2615
- packageName,
2616
- installedVersion,
2617
- firstPatchedVersion,
2618
- vulnerableRange
2619
- );
2620
- return resolution.safeVersion;
2621
- }
2622
- async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2623
- const versions = await fetchPackageVersions(packageName);
2624
- if (!versions.length) {
2625
- return {
2626
- candidates: {},
2627
- majorOnlyFixAvailable: false
2628
- };
2629
- }
2630
- const installed = semver.parse(installedVersion);
2631
- const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2632
- if (!vulnerableRange) return true;
2633
- try {
2634
- return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2635
- } catch {
2636
- return true;
2637
- }
2638
- }).sort(semver.compare);
2639
- if (!candidates.length) {
2640
- return {
2641
- candidates: {},
2642
- majorOnlyFixAvailable: false
2643
- };
2644
- }
2645
- const categorizedCandidates = {};
2646
- for (const candidate of candidates) {
2647
- const level = classifyUpgradeLevel(installedVersion, candidate);
2648
- if (!level) continue;
2649
- if (!categorizedCandidates[level]) {
2650
- categorizedCandidates[level] = candidate;
2651
- }
2652
- }
2653
- const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2654
- if (!safeVersion) {
2655
- return {
2656
- candidates: categorizedCandidates,
2657
- majorOnlyFixAvailable: false
2658
- };
2659
- }
2660
- const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2661
- const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2662
- if (!installed || !upgradeLevel) {
2663
- return {
2664
- safeVersion,
2665
- upgradeLevel,
2666
- candidates: categorizedCandidates,
2667
- majorOnlyFixAvailable
2668
- };
2669
- }
2670
- return {
2671
- safeVersion,
2672
- upgradeLevel,
2673
- candidates: categorizedCandidates,
2674
- majorOnlyFixAvailable
2675
- };
2676
- }
2677
- function classifyUpgradeLevel(installedVersion, candidateVersion) {
2678
- const installed = semver.parse(installedVersion);
2679
- const candidate = semver.parse(candidateVersion);
2680
- if (!installed || !candidate) return void 0;
2681
- if (candidate.major > installed.major) return "major";
2682
- if (candidate.minor > installed.minor) return "minor";
2683
- if (candidate.patch > installed.patch || candidate.version === installed.version) {
2684
- return "patch";
2685
- }
2686
- return void 0;
2687
- }
2688
- async function queryOutdatedPackages(cwd, options = {}) {
2689
- const pm = options.packageManager ?? detectPackageManager(cwd);
2690
- let stdout;
2691
- try {
2692
- const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2693
- const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2694
- const finalArgs = yarnArgs ?? args;
2695
- const result2 = await execa4(pm, finalArgs, {
2696
- cwd,
2697
- reject: false,
2698
- stdio: "pipe"
2699
- });
2700
- if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2701
- const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2702
- if (!result2.stdout) {
2703
- throw new Error(
2704
- `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2705
- );
2706
- }
2707
- }
2708
- stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2709
- } catch (err) {
2710
- if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2711
- throw new Error(`Failed to query outdated packages: ${err.message}`);
2712
- }
2713
- throw err;
2714
- }
2715
- if (!stdout.trim()) {
2716
- return /* @__PURE__ */ new Map();
2717
- }
2718
- const rawEntries = {};
2719
- if (pm === "yarn") {
2720
- for (const line of stdout.split("\n")) {
2721
- const trimmed = line.trim();
2722
- if (!trimmed) continue;
2723
- try {
2724
- const parsed = JSON.parse(trimmed);
2725
- if (parsed.type === "table" && parsed.data) {
2726
- const tableData = parsed.data;
2727
- const head = tableData.head ?? [];
2728
- const currentIdx = head.indexOf("Current");
2729
- const wantedIdx = head.indexOf("Wanted");
2730
- const latestIdx = head.indexOf("Latest");
2731
- for (const row of tableData.body ?? []) {
2732
- const name = row[0];
2733
- if (!name) continue;
2734
- rawEntries[name] = {
2735
- current: row[currentIdx] ?? "",
2736
- wanted: row[wantedIdx] ?? "",
2737
- latest: row[latestIdx] ?? "",
2738
- type: row[head.indexOf("Package Type")] ?? "dependencies"
2739
- };
2740
- }
2741
- }
2742
- } catch {
2743
- }
2744
- }
2745
- } else {
2746
- try {
2747
- const parsed = JSON.parse(stdout);
2748
- if (Array.isArray(parsed)) {
2749
- for (const entry of parsed) {
2750
- const name = entry.name ?? entry.packageName;
2751
- if (!name) continue;
2752
- rawEntries[name] = {
2753
- current: entry.current,
2754
- wanted: entry.wanted,
2755
- latest: entry.latest,
2756
- type: entry.dependencyType
2757
- };
2758
- }
2759
- } else {
2760
- Object.assign(rawEntries, parsed);
2761
- }
2762
- } catch {
2763
- throw new Error(`Could not parse ${pm} outdated --json output.`);
3356
+ slaBreaches = [breach];
2764
3357
  }
2765
3358
  }
2766
- let directDeps;
2767
- try {
2768
- const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
2769
- directDeps = /* @__PURE__ */ new Set([
2770
- ...Object.keys(pkgRaw.dependencies ?? {}),
2771
- ...Object.keys(pkgRaw.devDependencies ?? {})
2772
- ]);
2773
- } catch {
2774
- directDeps = /* @__PURE__ */ new Set();
2775
- }
2776
- const result = /* @__PURE__ */ new Map();
2777
- for (const [name, entry] of Object.entries(rawEntries)) {
2778
- const currentVersion = entry.current ?? "";
2779
- const wantedVersion = entry.wanted ?? currentVersion;
2780
- const latestVersion = entry.latest ?? currentVersion;
2781
- if (!currentVersion || !latestVersion) continue;
2782
- const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2783
- if (!options.includeTransitive && dependencyScope === "transitive") continue;
2784
- const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2785
- result.set(name, {
2786
- currentVersion,
2787
- wantedVersion,
2788
- latestVersion,
2789
- isMajorBump,
2790
- dependencyScope
2791
- });
3359
+ return { suppressed: false, exploitSignalTriggered, slaBreaches };
3360
+ }
3361
+
3362
+ // src/remediation/local/sbom.ts
3363
+ function buildSbom(packages, vulnerableNames, results) {
3364
+ const statusByPackage = /* @__PURE__ */ new Map();
3365
+ for (const result of results) {
3366
+ if (!vulnerableNames.has(result.packageName)) continue;
3367
+ if (result.suppressedBy) {
3368
+ statusByPackage.set(result.packageName, "suppressed");
3369
+ } else if (!result.applied && result.strategy === "none") {
3370
+ statusByPackage.set(result.packageName, "skipped");
3371
+ } else if (result.applied) {
3372
+ statusByPackage.set(result.packageName, "patched");
3373
+ } else {
3374
+ statusByPackage.set(result.packageName, "unpatched");
3375
+ }
2792
3376
  }
2793
- return result;
3377
+ return packages.map((pkg) => {
3378
+ const isVulnerable = vulnerableNames.has(pkg.name);
3379
+ const entry = {
3380
+ name: pkg.name,
3381
+ version: pkg.version,
3382
+ scope: pkg.type === "direct" ? "direct" : "transitive"
3383
+ };
3384
+ if (isVulnerable) {
3385
+ entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
3386
+ }
3387
+ return entry;
3388
+ });
2794
3389
  }
2795
3390
 
2796
3391
  // src/remediation/tools/apply-version-bump.ts
2797
3392
  import { z as z4 } from "zod";
2798
- import { join as join12 } from "path";
2799
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
3393
+ import { join as join15 } from "path";
3394
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
2800
3395
 
2801
3396
  // src/platform/safe-exec.ts
2802
- import { execa as execa5 } from "execa";
3397
+ import { execa as execa6 } from "execa";
2803
3398
  var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2804
3399
  "NODE_OPTIONS",
2805
3400
  "NODE_PATH",
@@ -2848,7 +3443,7 @@ function safeExeca(file, args = [], options = {}) {
2848
3443
  options.env ?? process.env,
2849
3444
  options.allowNodeOptions
2850
3445
  );
2851
- return execa5(file, args, {
3446
+ return execa6(file, args, {
2852
3447
  ...options,
2853
3448
  timeout,
2854
3449
  env: sanitizedEnv
@@ -2856,19 +3451,19 @@ function safeExeca(file, args = [], options = {}) {
2856
3451
  }
2857
3452
 
2858
3453
  // src/remediation/tools/apply-version-bump.ts
2859
- import semver2 from "semver";
3454
+ import semver3 from "semver";
2860
3455
 
2861
3456
  // src/platform/repo-lock.ts
2862
- import { mkdir, rm } from "fs/promises";
2863
- import { join as join11 } from "path";
3457
+ import { mkdir, rm as rm2 } from "fs/promises";
3458
+ import { join as join13 } from "path";
2864
3459
  async function sleep(ms) {
2865
- await new Promise((resolve4) => setTimeout(resolve4, ms));
3460
+ await new Promise((resolve6) => setTimeout(resolve6, ms));
2866
3461
  }
2867
3462
  async function acquireRepoLock(cwd, options = {}) {
2868
3463
  const timeoutMs = options.timeoutMs ?? 15e3;
2869
3464
  const retryDelayMs = options.retryDelayMs ?? 125;
2870
- const lockRoot = join11(cwd, ".autoremediator", "locks");
2871
- const lockPath = join11(cwd, ".autoremediator", "locks", "remediation.lock");
3465
+ const lockRoot = join13(cwd, ".autoremediator", "locks");
3466
+ const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
2872
3467
  const startedAt = Date.now();
2873
3468
  await mkdir(lockRoot, { recursive: true });
2874
3469
  while (true) {
@@ -2877,7 +3472,7 @@ async function acquireRepoLock(cwd, options = {}) {
2877
3472
  return {
2878
3473
  lockPath,
2879
3474
  release: async () => {
2880
- await rm(lockPath, { recursive: true, force: true });
3475
+ await rm2(lockPath, { recursive: true, force: true });
2881
3476
  }
2882
3477
  };
2883
3478
  } catch {
@@ -2897,6 +3492,380 @@ async function withRepoLock(cwd, fn, options) {
2897
3492
  }
2898
3493
  }
2899
3494
 
3495
+ // src/remediation/refactor/call-site-finder.ts
3496
+ import { readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
3497
+ import { join as join14 } from "path";
3498
+ import { parseSync as parseSync2 } from "oxc-parser";
3499
+ var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
3500
+ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
3501
+ "node_modules",
3502
+ ".git",
3503
+ "dist",
3504
+ "build",
3505
+ "coverage",
3506
+ ".next",
3507
+ ".turbo",
3508
+ ".pnpm-store"
3509
+ ]);
3510
+ function* walkFiles(dir) {
3511
+ let entries;
3512
+ try {
3513
+ entries = readdirSync3(dir);
3514
+ } catch {
3515
+ return;
3516
+ }
3517
+ for (const entry of entries) {
3518
+ if (IGNORED_DIRS2.has(entry)) continue;
3519
+ const full = join14(dir, entry);
3520
+ let stat2;
3521
+ try {
3522
+ stat2 = statSync3(full);
3523
+ } catch {
3524
+ continue;
3525
+ }
3526
+ if (stat2.isDirectory()) {
3527
+ yield* walkFiles(full);
3528
+ } else {
3529
+ const ext = full.slice(full.lastIndexOf("."));
3530
+ if (SOURCE_EXTENSIONS2.has(ext)) {
3531
+ yield full;
3532
+ }
3533
+ }
3534
+ }
3535
+ }
3536
+ function findPackageCallSites(cwd, packageName) {
3537
+ const callSites = [];
3538
+ for (const filePath of walkFiles(cwd)) {
3539
+ let content;
3540
+ try {
3541
+ content = readFileSync11(filePath, "utf8");
3542
+ } catch {
3543
+ continue;
3544
+ }
3545
+ if (!content.includes(packageName)) {
3546
+ continue;
3547
+ }
3548
+ try {
3549
+ const parseResult = parseSync2(filePath, content);
3550
+ if (!parseResult.program) continue;
3551
+ const program = parseResult.program;
3552
+ const imports = [];
3553
+ const importSources = [];
3554
+ const localNames = /* @__PURE__ */ new Set();
3555
+ for (const statement of program.body) {
3556
+ if (statement.type === "ImportDeclaration" && statement.source?.value) {
3557
+ const importSource = statement.source.value;
3558
+ if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
3559
+ importSources.push(importSource);
3560
+ for (const spec of statement.specifiers || []) {
3561
+ const localName = spec.local?.name || "";
3562
+ if (!localName) continue;
3563
+ localNames.add(localName);
3564
+ if (spec.type === "ImportDefaultSpecifier") {
3565
+ imports.push({
3566
+ localName,
3567
+ importedName: "default",
3568
+ isDefault: true,
3569
+ isNamespace: false
3570
+ });
3571
+ } else if (spec.type === "ImportNamespaceSpecifier") {
3572
+ imports.push({
3573
+ localName,
3574
+ importedName: "*",
3575
+ isDefault: false,
3576
+ isNamespace: true
3577
+ });
3578
+ } else if (spec.type === "ImportSpecifier") {
3579
+ imports.push({
3580
+ localName,
3581
+ importedName: spec.imported?.name || localName,
3582
+ isDefault: false,
3583
+ isNamespace: false
3584
+ });
3585
+ }
3586
+ }
3587
+ }
3588
+ }
3589
+ }
3590
+ if (imports.length > 0 || importSources.length > 0) {
3591
+ const lines = content.split("\n");
3592
+ const referencedLines = [];
3593
+ lines.forEach((line, index) => {
3594
+ for (const local of localNames) {
3595
+ const regex = new RegExp(`\\b${local}\\b`);
3596
+ if (regex.test(line)) {
3597
+ referencedLines.push(index + 1);
3598
+ break;
3599
+ }
3600
+ }
3601
+ });
3602
+ callSites.push({
3603
+ filePath,
3604
+ sourceContent: content,
3605
+ importSources,
3606
+ imports,
3607
+ referencedLines
3608
+ });
3609
+ }
3610
+ } catch {
3611
+ }
3612
+ }
3613
+ return callSites;
3614
+ }
3615
+
3616
+ // src/remediation/refactor/refactor-agent.ts
3617
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
3618
+ function parseCompilerDiagnostics(diagnosticError) {
3619
+ const diagnostics = [];
3620
+ const lines = diagnosticError.split("\n");
3621
+ for (const line of lines) {
3622
+ const trimmed = line.trim();
3623
+ if (!trimmed) continue;
3624
+ const match = trimmed.match(
3625
+ /(?:([^\s:(]+)[:(](\d+)[:,](\d+)\)?:?\s*(?:-\s*)?(?:error\s+)?(TS\d+)?:?\s*(.*))/i
3626
+ );
3627
+ if (match) {
3628
+ const [, filePath, lineStr, colStr, code, message] = match;
3629
+ const diag = {
3630
+ filePath,
3631
+ line: lineStr ? parseInt(lineStr, 10) : void 0,
3632
+ column: colStr ? parseInt(colStr, 10) : void 0,
3633
+ code,
3634
+ message: message || trimmed
3635
+ };
3636
+ const suggestionMatch = diag.message.match(/Did you mean ['"]([a-zA-Z0-9_$]+)['"]\?/i);
3637
+ if (suggestionMatch) {
3638
+ diag.suggestedSymbol = suggestionMatch[1];
3639
+ }
3640
+ const missingMatch = diag.message.match(/has no exported member ['"]([a-zA-Z0-9_$]+)['"]/i) || diag.message.match(/Property ['"]([a-zA-Z0-9_$]+)['"] does not exist/i);
3641
+ if (missingMatch) {
3642
+ diag.missingMember = missingMatch[1];
3643
+ }
3644
+ diagnostics.push(diag);
3645
+ }
3646
+ }
3647
+ return diagnostics;
3648
+ }
3649
+ function revertRefactorSnapshots(snapshots) {
3650
+ for (const [filePath, content] of Object.entries(snapshots)) {
3651
+ try {
3652
+ writeFileSync3(filePath, content, "utf8");
3653
+ } catch {
3654
+ }
3655
+ }
3656
+ }
3657
+ var KNOWN_MIGRATIONS = {
3658
+ rimraf: [
3659
+ {
3660
+ detect: (content, diag) => diag.includes("sync") && content.includes("rimraf.sync("),
3661
+ transform: (content) => {
3662
+ let res = content.replace(/import\s+rimraf\s+from\s+['"]rimraf['"];?/g, "");
3663
+ res = `import { rimrafSync } from "rimraf";
3664
+ ` + res;
3665
+ return res.replace(/rimraf\.sync\(/g, "rimrafSync(");
3666
+ }
3667
+ }
3668
+ ],
3669
+ uuid: [
3670
+ {
3671
+ detect: (content, diag) => diag.includes("v4") && content.includes("uuid.v4("),
3672
+ transform: (content) => {
3673
+ let res = content.replace(/import\s+uuid\s+from\s+['"]uuid['"];?/g, "");
3674
+ res = `import { v4 as uuidv4 } from "uuid";
3675
+ ` + res;
3676
+ return res.replace(/uuid\.v4\(/g, "uuidv4(");
3677
+ }
3678
+ },
3679
+ {
3680
+ // Subpath import migration: `import v4 from "uuid/v4"` -> `import { v4 } from "uuid"`
3681
+ detect: (content) => /from\s+['"]uuid\/v4['"]/.test(content),
3682
+ transform: (content) => content.replace(
3683
+ /import\s+([a-zA-Z0-9_$]+)\s+from\s+['"]uuid\/v4['"];?/g,
3684
+ `import { v4 as $1 } from "uuid";`
3685
+ )
3686
+ }
3687
+ ],
3688
+ glob: [
3689
+ {
3690
+ detect: (content, diag) => diag.includes("sync") && content.includes("glob.sync("),
3691
+ transform: (content) => {
3692
+ let res = content.replace(/import\s+glob\s+from\s+['"]glob['"];?/g, "");
3693
+ res = `import { globSync } from "glob";
3694
+ ` + res;
3695
+ return res.replace(/glob\.sync\(/g, "globSync(");
3696
+ }
3697
+ }
3698
+ ],
3699
+ mkdirp: [
3700
+ {
3701
+ detect: (content, diag) => diag.includes("sync") && content.includes("mkdirp.sync("),
3702
+ transform: (content) => {
3703
+ let res = content.replace(/import\s+mkdirp\s+from\s+['"]mkdirp['"];?/g, "");
3704
+ res = `import { mkdirpSync } from "mkdirp";
3705
+ ` + res;
3706
+ return res.replace(/mkdirp\.sync\(/g, "mkdirpSync(");
3707
+ }
3708
+ }
3709
+ ]
3710
+ };
3711
+ function escapeRegex(str) {
3712
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3713
+ }
3714
+ function transformFileContent(content, packageName, diagnosticError, filePath) {
3715
+ let modified = false;
3716
+ let newContent = content;
3717
+ const migrations = KNOWN_MIGRATIONS[packageName];
3718
+ if (migrations) {
3719
+ for (const mig of migrations) {
3720
+ if (mig.detect(newContent, diagnosticError)) {
3721
+ const transformed = mig.transform(newContent);
3722
+ if (transformed !== newContent) {
3723
+ newContent = transformed;
3724
+ modified = true;
3725
+ }
3726
+ }
3727
+ }
3728
+ }
3729
+ if (diagnosticError.includes("no default export") || diagnosticError.includes("has no exported member 'default'") || diagnosticError.includes("has no exported member default") || diagnosticError.includes("TS2614") || diagnosticError.includes("TS1192")) {
3730
+ const defaultImportRegex = new RegExp(
3731
+ `import\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3732
+ "g"
3733
+ );
3734
+ if (defaultImportRegex.test(newContent)) {
3735
+ newContent = newContent.replace(
3736
+ defaultImportRegex,
3737
+ `import * as $1 from "${packageName}"`
3738
+ );
3739
+ modified = true;
3740
+ }
3741
+ }
3742
+ if (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259")) {
3743
+ const starImportRegex = new RegExp(
3744
+ `import\\s+\\*\\s+as\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3745
+ "g"
3746
+ );
3747
+ if (starImportRegex.test(newContent)) {
3748
+ newContent = newContent.replace(
3749
+ starImportRegex,
3750
+ `import $1 from "${packageName}"`
3751
+ );
3752
+ modified = true;
3753
+ }
3754
+ }
3755
+ const diagnostics = parseCompilerDiagnostics(diagnosticError);
3756
+ for (const diag of diagnostics) {
3757
+ if (diag.missingMember && diag.suggestedSymbol) {
3758
+ const missingMember = diag.missingMember;
3759
+ const suggestedSymbol = diag.suggestedSymbol;
3760
+ if (filePath && diag.filePath && filePath.endsWith(diag.filePath) && diag.line && diag.line > 0) {
3761
+ const fileLines = newContent.split("\n");
3762
+ const idx = diag.line - 1;
3763
+ if (idx >= 0 && idx < fileLines.length) {
3764
+ const line = fileLines[idx];
3765
+ const replaced = line.replace(new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g"), `.${suggestedSymbol}`).replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol);
3766
+ if (replaced !== line) {
3767
+ fileLines[idx] = replaced;
3768
+ newContent = fileLines.join("\n");
3769
+ modified = true;
3770
+ }
3771
+ }
3772
+ } else {
3773
+ const memberRegex = new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g");
3774
+ if (memberRegex.test(newContent)) {
3775
+ newContent = newContent.replace(memberRegex, `.${suggestedSymbol}`);
3776
+ modified = true;
3777
+ }
3778
+ const importPattern = new RegExp(
3779
+ `import\\s*\\{[^}]*\\b${escapeRegex(missingMember)}\\b[^}]*\\}\\s*from\\s*['"]${escapeRegex(packageName)}['"]`,
3780
+ "g"
3781
+ );
3782
+ if (importPattern.test(newContent)) {
3783
+ newContent = newContent.replace(
3784
+ importPattern,
3785
+ (match) => match.replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol)
3786
+ );
3787
+ modified = true;
3788
+ }
3789
+ }
3790
+ }
3791
+ }
3792
+ const lineDiags = diagnostics.filter(
3793
+ (d) => Boolean(filePath && d.filePath && filePath.endsWith(d.filePath) && typeof d.line === "number" && d.line > 0)
3794
+ ).sort((a, b) => b.line - a.line);
3795
+ const processedLines = /* @__PURE__ */ new Set();
3796
+ for (const diag of lineDiags) {
3797
+ if (processedLines.has(diag.line)) continue;
3798
+ processedLines.add(diag.line);
3799
+ const fileLines = newContent.split("\n");
3800
+ const targetLineIdx = diag.line - 1;
3801
+ if (targetLineIdx >= 0 && targetLineIdx < fileLines.length) {
3802
+ const prevLine = targetLineIdx > 0 ? fileLines[targetLineIdx - 1] : "";
3803
+ if (!prevLine?.includes("@ts-expect-error") && !fileLines[targetLineIdx]?.includes("@ts-expect-error")) {
3804
+ const indent = fileLines[targetLineIdx]?.match(/^\s*/)?.[0] || "";
3805
+ fileLines.splice(
3806
+ targetLineIdx,
3807
+ 0,
3808
+ `${indent}// @ts-expect-error [autoremediator-refactor]: breaking change in ${packageName}`
3809
+ );
3810
+ newContent = fileLines.join("\n");
3811
+ modified = true;
3812
+ }
3813
+ }
3814
+ }
3815
+ return { modified, newContent };
3816
+ }
3817
+ async function refactorConsumerCallSites(params) {
3818
+ const { packageName, diagnosticError, callSites } = params;
3819
+ if (callSites.length === 0) {
3820
+ return {
3821
+ applied: false,
3822
+ refactoredFiles: [],
3823
+ snapshots: {},
3824
+ error: `No consumer call sites found for "${packageName}".`
3825
+ };
3826
+ }
3827
+ const snapshots = {};
3828
+ const refactoredFiles = [];
3829
+ try {
3830
+ for (const site of callSites) {
3831
+ const originalContent = site.sourceContent || readFileSync12(site.filePath, "utf8");
3832
+ snapshots[site.filePath] = originalContent;
3833
+ const { modified, newContent } = transformFileContent(
3834
+ originalContent,
3835
+ packageName,
3836
+ diagnosticError,
3837
+ site.filePath
3838
+ );
3839
+ if (modified && newContent !== originalContent) {
3840
+ writeFileSync3(site.filePath, newContent, "utf8");
3841
+ refactoredFiles.push(site.filePath);
3842
+ }
3843
+ }
3844
+ if (refactoredFiles.length === 0) {
3845
+ return {
3846
+ applied: false,
3847
+ refactoredFiles: [],
3848
+ snapshots,
3849
+ error: "No automated codemod pattern matched the diagnostic error."
3850
+ };
3851
+ }
3852
+ return {
3853
+ applied: true,
3854
+ refactoredFiles,
3855
+ snapshots
3856
+ };
3857
+ } catch (err) {
3858
+ revertRefactorSnapshots(snapshots);
3859
+ const message = err instanceof Error ? err.message : String(err);
3860
+ return {
3861
+ applied: false,
3862
+ refactoredFiles: [],
3863
+ snapshots: {},
3864
+ error: `Failed to apply breaking change refactoring: ${message}`
3865
+ };
3866
+ }
3867
+ }
3868
+
2900
3869
  // src/remediation/tools/apply-version-bump.ts
2901
3870
  var applyVersionBumpTool = defineTool({
2902
3871
  description: "Update package.json to use the safe version of a vulnerable package and run the project's package manager install. In dry-run mode, only reports what would change.",
@@ -2909,10 +3878,12 @@ var applyVersionBumpTool = defineTool({
2909
3878
  dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
2910
3879
  policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
2911
3880
  runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
3881
+ typecheck: z4.boolean().default(false).describe("If true, run typecheck validation after applying the fix"),
2912
3882
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2913
3883
  installPreferOffline: z4.boolean().optional(),
2914
3884
  enforceFrozenLockfile: z4.boolean().optional(),
2915
- workspace: z4.string().optional()
3885
+ workspace: z4.string().optional(),
3886
+ breakingChangeRefactor: z4.boolean().optional().describe("If true, attempt agentic call-site refactoring when validation fails after major upgrade")
2916
3887
  }),
2917
3888
  execute: async ({
2918
3889
  cwd,
@@ -2923,13 +3894,15 @@ var applyVersionBumpTool = defineTool({
2923
3894
  dryRun,
2924
3895
  policy,
2925
3896
  runTests,
3897
+ typecheck,
2926
3898
  installMode,
2927
3899
  installPreferOffline,
2928
3900
  enforceFrozenLockfile,
2929
- workspace
3901
+ workspace,
3902
+ breakingChangeRefactor
2930
3903
  }) => {
2931
3904
  const pm = packageManager ?? detectPackageManager(cwd);
2932
- const pkgPath = join12(cwd, "package.json");
3905
+ let pkgPath = join15(cwd, "package.json");
2933
3906
  const loadedPolicy = loadPolicy(cwd, policy);
2934
3907
  const commandConstraints = {
2935
3908
  ...loadedPolicy.constraints,
@@ -2939,9 +3912,6 @@ var applyVersionBumpTool = defineTool({
2939
3912
  workspace: workspace ?? loadedPolicy.constraints?.workspace
2940
3913
  };
2941
3914
  const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
2942
- const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
2943
- const testCommand = resolveTestCommand(pm, commandConstraints);
2944
- const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
2945
3915
  if (!isPackageAllowed(loadedPolicy, packageName)) {
2946
3916
  return {
2947
3917
  packageName,
@@ -2954,7 +3924,7 @@ var applyVersionBumpTool = defineTool({
2954
3924
  message: `Policy blocked changes for package "${packageName}".`
2955
3925
  };
2956
3926
  }
2957
- const isMajorBump = semver2.valid(fromVersion) && semver2.valid(toVersion) && semver2.major(toVersion) > semver2.major(fromVersion);
3927
+ const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
2958
3928
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2959
3929
  return {
2960
3930
  packageName,
@@ -2967,9 +3937,10 @@ var applyVersionBumpTool = defineTool({
2967
3937
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
2968
3938
  };
2969
3939
  }
3940
+ pkgPath = join15(cwd, "package.json");
2970
3941
  let pkgJson;
2971
3942
  try {
2972
- pkgJson = JSON.parse(readFileSync9(pkgPath, "utf8"));
3943
+ pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
2973
3944
  } catch {
2974
3945
  return {
2975
3946
  packageName,
@@ -2981,9 +3952,20 @@ var applyVersionBumpTool = defineTool({
2981
3952
  message: `Could not read package.json at "${pkgPath}".`
2982
3953
  };
2983
3954
  }
2984
- const depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3955
+ let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
2985
3956
  (f) => pkgJson[f]?.[packageName] !== void 0
2986
3957
  );
3958
+ if (!depField) {
3959
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, packageName, workspace);
3960
+ if (declaringWorkspace) {
3961
+ pkgPath = declaringWorkspace.manifestPath;
3962
+ pkgJson = declaringWorkspace.manifest;
3963
+ depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3964
+ (f) => pkgJson[f]?.[packageName] !== void 0
3965
+ );
3966
+ commandConstraints.workspace = workspace ?? declaringWorkspace.name;
3967
+ }
3968
+ }
2987
3969
  if (!depField) {
2988
3970
  return {
2989
3971
  packageName,
@@ -2999,9 +3981,14 @@ var applyVersionBumpTool = defineTool({
2999
3981
  const prefixMatch = currentRange.match(/^([~^]?)/);
3000
3982
  const prefix = prefixMatch?.[1] ?? "";
3001
3983
  const newRange = `${prefix}${toVersion}`;
3984
+ const effectiveInstallCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
3985
+ const effectiveTestCommand = resolveTestCommand(pm, commandConstraints);
3986
+ const effectiveTypecheckCommand = resolveTypecheckCommand(pm, commandConstraints, cwd);
3987
+ const effectiveDedupeCommand = resolveDedupeCommand(pm, commandConstraints);
3988
+ const shouldTypecheck = typecheck || Boolean(commandConstraints.typecheck);
3002
3989
  if (dryRun) {
3003
- const installCmd = installCommand.join(" ");
3004
- const testCmd = testCommand.join(" ");
3990
+ const installCmd = effectiveInstallCommand.join(" ");
3991
+ const testCmd = effectiveTestCommand.join(" ");
3005
3992
  return {
3006
3993
  packageName,
3007
3994
  strategy: "version-bump",
@@ -3009,21 +3996,21 @@ var applyVersionBumpTool = defineTool({
3009
3996
  toVersion,
3010
3997
  applied: false,
3011
3998
  dryRun: true,
3012
- message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${dedupeCommand.length > 0 ? ` and ${dedupeCommand.join(" ")} (best-effort)` : ""}.`
3999
+ message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${effectiveDedupeCommand.length > 0 ? ` and ${effectiveDedupeCommand.join(" ")} (best-effort)` : ""}.`
3013
4000
  };
3014
4001
  }
3015
4002
  return withRepoLock(cwd, async () => {
3016
4003
  pkgJson[depField][packageName] = newRange;
3017
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4004
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3018
4005
  try {
3019
- const [installCmd, ...installArgs] = installCommand;
4006
+ const [installCmd, ...installArgs] = effectiveInstallCommand;
3020
4007
  await safeExeca(installCmd, installArgs, {
3021
4008
  cwd,
3022
4009
  stdio: "pipe"
3023
4010
  });
3024
4011
  } catch (err) {
3025
4012
  pkgJson[depField][packageName] = currentRange;
3026
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4013
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3027
4014
  const message = err instanceof Error ? err.message : String(err);
3028
4015
  return {
3029
4016
  packageName,
@@ -3032,52 +4019,116 @@ var applyVersionBumpTool = defineTool({
3032
4019
  toVersion,
3033
4020
  applied: false,
3034
4021
  dryRun: false,
3035
- unresolvedReason: "install-failed",
3036
- message: `${installCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
4022
+ unresolvedReason: "install-failed",
4023
+ message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
4024
+ };
4025
+ }
4026
+ const shouldAttemptRefactor = Boolean(
4027
+ breakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor
4028
+ );
4029
+ async function runValidationSteps() {
4030
+ if (runTests) {
4031
+ try {
4032
+ const [testCmd, ...testArgs] = effectiveTestCommand;
4033
+ await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
4034
+ } catch (err) {
4035
+ const execErr = err;
4036
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
4037
+ return {
4038
+ passed: false,
4039
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
4040
+ };
4041
+ }
4042
+ }
4043
+ if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
4044
+ try {
4045
+ const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
4046
+ await safeExeca(tcCmd, tcArgs, { cwd, stdio: "pipe" });
4047
+ } catch (err) {
4048
+ const execErr = err;
4049
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
4050
+ return {
4051
+ passed: false,
4052
+ isTypecheck: true,
4053
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
4054
+ };
4055
+ }
4056
+ }
4057
+ return { passed: true };
4058
+ }
4059
+ let validationResult = await runValidationSteps();
4060
+ let refactoredFilesList;
4061
+ if (!validationResult.passed && shouldAttemptRefactor) {
4062
+ const callSites = findPackageCallSites(cwd, packageName);
4063
+ if (callSites.length > 0) {
4064
+ const refactorAttempt = await refactorConsumerCallSites({
4065
+ cwd,
4066
+ packageName,
4067
+ fromVersion,
4068
+ toVersion,
4069
+ diagnosticError: validationResult.error || "",
4070
+ callSites
4071
+ });
4072
+ if (refactorAttempt.applied) {
4073
+ const postRefactorValidation = await runValidationSteps();
4074
+ if (postRefactorValidation.passed) {
4075
+ validationResult = { passed: true };
4076
+ refactoredFilesList = refactorAttempt.refactoredFiles;
4077
+ } else {
4078
+ revertRefactorSnapshots(refactorAttempt.snapshots);
4079
+ }
4080
+ }
4081
+ }
4082
+ }
4083
+ if (!validationResult.passed) {
4084
+ pkgJson[depField][packageName] = currentRange;
4085
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4086
+ try {
4087
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
4088
+ await safeExeca(rollbackCmd, rollbackArgs, {
4089
+ cwd,
4090
+ stdio: "pipe"
4091
+ });
4092
+ } catch {
4093
+ }
4094
+ const failureMessage = validationResult.error || "Unknown validation error";
4095
+ const prefix2 = validationResult.isTypecheck ? `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")})` : `${effectiveTestCommand.join(" ")} failed`;
4096
+ return {
4097
+ packageName,
4098
+ strategy: "version-bump",
4099
+ fromVersion,
4100
+ toVersion,
4101
+ applied: false,
4102
+ dryRun: false,
4103
+ unresolvedReason: shouldAttemptRefactor ? "breaking-change-unresolved" : "validation-failed",
4104
+ message: `${prefix2} after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${failureMessage}`
4105
+ };
4106
+ }
4107
+ if (refactoredFilesList && refactoredFilesList.length > 0) {
4108
+ return {
4109
+ packageName,
4110
+ strategy: "breaking-change-refactor",
4111
+ fromVersion,
4112
+ toVersion,
4113
+ applied: true,
4114
+ dryRun: false,
4115
+ refactoredFiles: refactoredFilesList,
4116
+ message: `Upgraded "${packageName}" from ${fromVersion} to ${toVersion} and refactored ${refactoredFilesList.length} call-site file(s) across breaking API changes.`
3037
4117
  };
3038
4118
  }
3039
- if (runTests) {
4119
+ let dedupeNote = "";
4120
+ if (effectiveDedupeCommand.length > 0) {
3040
4121
  try {
3041
- const [testCmd, ...testArgs] = testCommand;
3042
- await safeExeca(testCmd, testArgs, {
4122
+ const [dedupeCmd, ...dedupeArgs] = effectiveDedupeCommand;
4123
+ await safeExeca(dedupeCmd, dedupeArgs, {
3043
4124
  cwd,
3044
4125
  stdio: "pipe"
3045
4126
  });
3046
4127
  } catch (err) {
3047
- pkgJson[depField][packageName] = currentRange;
3048
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3049
- try {
3050
- const [rollbackCmd, ...rollbackArgs] = installCommand;
3051
- await safeExeca(rollbackCmd, rollbackArgs, {
3052
- cwd,
3053
- stdio: "pipe"
3054
- });
3055
- } catch {
3056
- }
3057
4128
  const message = err instanceof Error ? err.message : String(err);
3058
- return {
3059
- packageName,
3060
- strategy: "version-bump",
3061
- fromVersion,
3062
- toVersion,
3063
- applied: false,
3064
- dryRun: false,
3065
- unresolvedReason: "validation-failed",
3066
- message: `${testCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3067
- };
4129
+ dedupeNote = ` Dedupe warning: ${effectiveDedupeCommand.join(" ")} failed (${message}).`;
3068
4130
  }
3069
4131
  }
3070
- let dedupeNote = "";
3071
- try {
3072
- const [dedupeCmd, ...dedupeArgs] = dedupeCommand;
3073
- await safeExeca(dedupeCmd, dedupeArgs, {
3074
- cwd,
3075
- stdio: "pipe"
3076
- });
3077
- } catch (err) {
3078
- const message = err instanceof Error ? err.message : String(err);
3079
- dedupeNote = ` Dedupe warning: ${dedupeCommand.join(" ")} failed (${message}).`;
3080
- }
3081
4132
  return {
3082
4133
  packageName,
3083
4134
  strategy: "version-bump",
@@ -3085,7 +4136,7 @@ var applyVersionBumpTool = defineTool({
3085
4136
  toVersion,
3086
4137
  applied: true,
3087
4138
  dryRun: false,
3088
- message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${installCommand.join(" ")}${runTests ? `, and passed ${testCommand.join(" ")}` : ""}.${dedupeNote}`
4139
+ message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
3089
4140
  };
3090
4141
  });
3091
4142
  }
@@ -3093,9 +4144,9 @@ var applyVersionBumpTool = defineTool({
3093
4144
 
3094
4145
  // src/remediation/tools/apply-package-override/index.ts
3095
4146
  import { z as z5 } from "zod";
3096
- import { join as join13 } from "path";
3097
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
3098
- import semver3 from "semver";
4147
+ import { join as join16 } from "path";
4148
+ import { existsSync as existsSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
4149
+ import semver4 from "semver";
3099
4150
 
3100
4151
  // src/remediation/tools/apply-package-override/helpers.ts
3101
4152
  async function collectDependencyTrace(cwd, pm, packageName, constraints) {
@@ -3237,9 +4288,9 @@ var applyPackageOverrideTool = defineTool({
3237
4288
  workspace
3238
4289
  }) => {
3239
4290
  const pm = packageManager ?? detectPackageManager(cwd);
3240
- const pkgPath = join13(cwd, "package.json");
3241
- const denoJsonPath = join13(cwd, "deno.json");
3242
- const isDenoNative = pm === "deno" && !existsSync9(pkgPath);
4291
+ const pkgPath = join16(cwd, "package.json");
4292
+ const denoJsonPath = join16(cwd, "deno.json");
4293
+ const isDenoNative = pm === "deno" && !existsSync12(pkgPath);
3243
4294
  const loadedPolicy = loadPolicy(cwd, policy);
3244
4295
  const commandConstraints = {
3245
4296
  ...loadedPolicy.constraints,
@@ -3265,7 +4316,7 @@ var applyPackageOverrideTool = defineTool({
3265
4316
  message: `Policy blocked changes for package "${packageName}".`
3266
4317
  };
3267
4318
  }
3268
- const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
4319
+ const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
3269
4320
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
3270
4321
  return {
3271
4322
  packageName,
@@ -3281,7 +4332,7 @@ var applyPackageOverrideTool = defineTool({
3281
4332
  if (isDenoNative) {
3282
4333
  let denoJson;
3283
4334
  try {
3284
- denoJson = JSON.parse(readFileSync10(denoJsonPath, "utf8"));
4335
+ denoJson = JSON.parse(readFileSync14(denoJsonPath, "utf8"));
3285
4336
  } catch {
3286
4337
  return {
3287
4338
  packageName,
@@ -3327,13 +4378,13 @@ var applyPackageOverrideTool = defineTool({
3327
4378
  }
3328
4379
  return withRepoLock(cwd, async () => {
3329
4380
  setDenoJsonImportValue(denoJson, overrideSelector, toVersion);
3330
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4381
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3331
4382
  try {
3332
4383
  const [installCmd, ...installArgs] = installCommand;
3333
4384
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
3334
4385
  } catch (err) {
3335
4386
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
3336
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4387
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3337
4388
  const message = err instanceof Error ? err.message : String(err);
3338
4389
  return {
3339
4390
  packageName,
@@ -3352,7 +4403,7 @@ var applyPackageOverrideTool = defineTool({
3352
4403
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3353
4404
  } catch (err) {
3354
4405
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
3355
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4406
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3356
4407
  try {
3357
4408
  const [rollbackCmd, ...rollbackArgs] = installCommand;
3358
4409
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -3384,7 +4435,7 @@ var applyPackageOverrideTool = defineTool({
3384
4435
  }
3385
4436
  let pkgJson;
3386
4437
  try {
3387
- pkgJson = JSON.parse(readFileSync10(pkgPath, "utf8"));
4438
+ pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
3388
4439
  } catch {
3389
4440
  return {
3390
4441
  packageName,
@@ -3414,13 +4465,13 @@ var applyPackageOverrideTool = defineTool({
3414
4465
  }
3415
4466
  return withRepoLock(cwd, async () => {
3416
4467
  setOverrideValue(pkgJson, pm, overrideSelector, toVersion);
3417
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4468
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3418
4469
  try {
3419
4470
  const [installCmd, ...installArgs] = installCommand;
3420
4471
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
3421
4472
  } catch (err) {
3422
4473
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
3423
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4474
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3424
4475
  const message = err instanceof Error ? err.message : String(err);
3425
4476
  return {
3426
4477
  packageName,
@@ -3439,7 +4490,7 @@ var applyPackageOverrideTool = defineTool({
3439
4490
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3440
4491
  } catch (err) {
3441
4492
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
3442
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4493
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3443
4494
  try {
3444
4495
  const [rollbackCmd, ...rollbackArgs] = installCommand;
3445
4496
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -3481,6 +4532,80 @@ var applyPackageOverrideTool = defineTool({
3481
4532
  }
3482
4533
  });
3483
4534
 
4535
+ // src/platform/supply-chain.ts
4536
+ async function verifyPackageTrust(packageName, targetVersion, policy) {
4537
+ if (!policy || !policy.minPublishAgeHours && !policy.blockInstallScripts && !policy.requireProvenance) {
4538
+ return { trusted: true };
4539
+ }
4540
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
4541
+ let packument = null;
4542
+ try {
4543
+ const res = await httpClient({
4544
+ url,
4545
+ headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" }
4546
+ });
4547
+ if (res.ok && res.data) {
4548
+ packument = res.data;
4549
+ }
4550
+ } catch (err) {
4551
+ const msg = err instanceof Error ? err.message : String(err);
4552
+ return {
4553
+ trusted: false,
4554
+ reason: `Could not verify supply-chain trust for "${packageName}@${targetVersion}": ${msg}`
4555
+ };
4556
+ }
4557
+ if (!packument) {
4558
+ return {
4559
+ trusted: false,
4560
+ reason: `Package packument for "${packageName}" not found on registry.`
4561
+ };
4562
+ }
4563
+ let publishAgeHours;
4564
+ if (packument.time?.[targetVersion]) {
4565
+ const publishedAt = new Date(packument.time[targetVersion]).getTime();
4566
+ publishAgeHours = Math.max(0, (Date.now() - publishedAt) / (1e3 * 60 * 60));
4567
+ if (policy.minPublishAgeHours && publishAgeHours < policy.minPublishAgeHours) {
4568
+ return {
4569
+ trusted: false,
4570
+ publishAgeHours,
4571
+ reason: `Target release "${packageName}@${targetVersion}" was published only ${publishAgeHours.toFixed(1)}h ago (minimum required age is ${policy.minPublishAgeHours}h).`
4572
+ };
4573
+ }
4574
+ }
4575
+ const versionData = packument.versions?.[targetVersion];
4576
+ const scripts = versionData?.scripts ?? {};
4577
+ const installScriptKeys = ["preinstall", "install", "postinstall"].filter(
4578
+ (k) => Boolean(scripts[k])
4579
+ );
4580
+ const hasInstallScripts = installScriptKeys.length > 0;
4581
+ if (policy.blockInstallScripts && hasInstallScripts) {
4582
+ return {
4583
+ trusted: false,
4584
+ publishAgeHours,
4585
+ hasInstallScripts: true,
4586
+ reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
4587
+ };
4588
+ }
4589
+ const hasProvenance = Boolean(
4590
+ versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url
4591
+ );
4592
+ if (policy.requireProvenance && !hasProvenance) {
4593
+ return {
4594
+ trusted: false,
4595
+ publishAgeHours,
4596
+ hasInstallScripts,
4597
+ hasProvenance: false,
4598
+ reason: `Target release "${packageName}@${targetVersion}" does not have cryptographic build provenance attestations.`
4599
+ };
4600
+ }
4601
+ return {
4602
+ trusted: true,
4603
+ publishAgeHours,
4604
+ hasInstallScripts,
4605
+ hasProvenance
4606
+ };
4607
+ }
4608
+
3484
4609
  // src/remediation/local/primary-strategy.ts
3485
4610
  async function resolvePrimaryResult(params) {
3486
4611
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
@@ -3551,6 +4676,26 @@ async function resolvePrimaryResult(params) {
3551
4676
  }
3552
4677
  };
3553
4678
  }
4679
+ const trust2 = await verifyPackageTrust(
4680
+ pkg.name,
4681
+ safeUpgrade2.safeVersion,
4682
+ constraints.supplyChainPolicy
4683
+ );
4684
+ if (!trust2.trusted) {
4685
+ return {
4686
+ steps: 1,
4687
+ result: {
4688
+ packageName: pkg.name,
4689
+ strategy: "none",
4690
+ fromVersion: pkg.version,
4691
+ applied: false,
4692
+ dryRun,
4693
+ unresolvedReason: "policy-blocked",
4694
+ vulnerableRange: vulnerable.affected.vulnerableRange,
4695
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade2.safeVersion}": ${trust2.reason}`
4696
+ }
4697
+ };
4698
+ }
3554
4699
  const overrideResult = await applyPackageOverrideTool.execute({
3555
4700
  cwd,
3556
4701
  packageManager,
@@ -3606,6 +4751,26 @@ async function resolvePrimaryResult(params) {
3606
4751
  }
3607
4752
  };
3608
4753
  }
4754
+ const trust = await verifyPackageTrust(
4755
+ pkg.name,
4756
+ safeUpgrade.safeVersion,
4757
+ constraints.supplyChainPolicy
4758
+ );
4759
+ if (!trust.trusted) {
4760
+ return {
4761
+ steps: 1,
4762
+ result: {
4763
+ packageName: pkg.name,
4764
+ strategy: "none",
4765
+ fromVersion: pkg.version,
4766
+ applied: false,
4767
+ dryRun,
4768
+ unresolvedReason: "policy-blocked",
4769
+ vulnerableRange: vulnerable.affected.vulnerableRange,
4770
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade.safeVersion}": ${trust.reason}`
4771
+ }
4772
+ };
4773
+ }
3609
4774
  const applyResult = await applyVersionBumpTool.execute({
3610
4775
  cwd,
3611
4776
  packageManager,
@@ -3615,10 +4780,12 @@ async function resolvePrimaryResult(params) {
3615
4780
  dryRun,
3616
4781
  policy,
3617
4782
  runTests,
4783
+ typecheck: constraints.typecheck,
3618
4784
  installMode: constraints.installMode,
3619
4785
  installPreferOffline: constraints.installPreferOffline,
3620
4786
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
3621
- workspace: constraints.workspace
4787
+ workspace: constraints.workspace,
4788
+ breakingChangeRefactor: constraints.enableBreakingChangeRefactor
3622
4789
  });
3623
4790
  return {
3624
4791
  steps: 2,
@@ -3628,10 +4795,10 @@ async function resolvePrimaryResult(params) {
3628
4795
 
3629
4796
  // src/remediation/tools/fetch-package-source.ts
3630
4797
  import { z as z6 } from "zod";
3631
- import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
3632
- import { join as join14 } from "path";
4798
+ import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
4799
+ import { join as join17 } from "path";
3633
4800
  import { tmpdir } from "os";
3634
- import { execa as execa6 } from "execa";
4801
+ import { execa as execa7 } from "execa";
3635
4802
  var fetchPackageSourceTool = defineTool({
3636
4803
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
3637
4804
  parameters: z6.object({
@@ -3649,25 +4816,25 @@ var fetchPackageSourceTool = defineTool({
3649
4816
  if (safePatterns.length === 0) {
3650
4817
  return { success: false, error: "No valid file patterns provided." };
3651
4818
  }
3652
- const tempBaseDir = await mkdtemp(join14(tmpdir(), "autoremediator-pkg-"));
3653
- const extractDir = join14(tempBaseDir, "out");
4819
+ const tempBaseDir = await mkdtemp(join17(tmpdir(), "autoremediator-pkg-"));
4820
+ const extractDir = join17(tempBaseDir, "out");
3654
4821
  try {
3655
4822
  const scopedName = packageName.split("/").pop();
3656
4823
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3657
4824
  await mkdir2(tempBaseDir, { recursive: true });
3658
- const tarballPath = join14(tempBaseDir, "package.tgz");
3659
- await execa6("curl", ["-L", "-o", tarballPath, npmUrl]);
4825
+ const tarballPath = join17(tempBaseDir, "package.tgz");
4826
+ await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3660
4827
  await mkdir2(extractDir, { recursive: true });
3661
- await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
4828
+ await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3662
4829
  const extractedContents = await readdir2(extractDir);
3663
- const packageRootDir = extractedContents.includes("package") ? join14(extractDir, "package") : extractDir;
4830
+ const packageRootDir = extractedContents.includes("package") ? join17(extractDir, "package") : extractDir;
3664
4831
  const sourceCode = {};
3665
4832
  async function walkDir(dir, relativeBase) {
3666
4833
  try {
3667
4834
  const files = await readdir2(dir, { withFileTypes: true });
3668
4835
  for (const file of files) {
3669
- const fullPath = join14(dir, file.name);
3670
- const relPath = join14(relativeBase, file.name);
4836
+ const fullPath = join17(dir, file.name);
4837
+ const relPath = join17(relativeBase, file.name);
3671
4838
  if (file.isDirectory()) {
3672
4839
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3673
4840
  file.name
@@ -3681,7 +4848,7 @@ var fetchPackageSourceTool = defineTool({
3681
4848
  });
3682
4849
  if (matches) {
3683
4850
  try {
3684
- const content = await readFile3(fullPath, "utf8");
4851
+ const content = await readFile4(fullPath, "utf8");
3685
4852
  sourceCode[relPath] = content;
3686
4853
  } catch {
3687
4854
  }
@@ -3716,7 +4883,7 @@ var fetchPackageSourceTool = defineTool({
3716
4883
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
3717
4884
  };
3718
4885
  } finally {
3719
- await rm2(tempBaseDir, { recursive: true, force: true });
4886
+ await rm3(tempBaseDir, { recursive: true, force: true });
3720
4887
  }
3721
4888
  }
3722
4889
  });
@@ -3774,6 +4941,7 @@ Analyze the source code to:
3774
4941
  2. Explain the root cause of the security issue
3775
4942
  3. Propose a secure fix that addresses the vulnerability
3776
4943
  4. Provide the complete fixed version of affected files
4944
+ 5. Optionally provide a standalone Node.js reproduction/regression test script
3777
4945
 
3778
4946
  ## Response Format
3779
4947
  Respond ONLY with valid JSON (no markdown, no extra text):
@@ -3784,7 +4952,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
3784
4952
  "path/to/other.ts": "Complete fixed source code for this file"
3785
4953
  },
3786
4954
  "confidence": 0.95,
3787
- "riskLevel": "medium"
4955
+ "riskLevel": "medium",
4956
+ "reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
3788
4957
  }
3789
4958
 
3790
4959
  Important:
@@ -3795,7 +4964,7 @@ Important:
3795
4964
  }
3796
4965
 
3797
4966
  // src/remediation/tools/generate-patch/helpers.ts
3798
- import { parseSync as parseSync2 } from "oxc-parser";
4967
+ import { parseSync as parseSync3 } from "oxc-parser";
3799
4968
  import YAML from "yaml";
3800
4969
  function parseLlmAnalysisResponse(text) {
3801
4970
  try {
@@ -3818,7 +4987,7 @@ function validatePatchedCodeSyntax(filePath, code) {
3818
4987
  const lowerPath = filePath.toLowerCase();
3819
4988
  if (/\.[cm]?[jt]sx?$/i.test(lowerPath)) {
3820
4989
  try {
3821
- const res = parseSync2(filePath, code);
4990
+ const res = parseSync3(filePath, code);
3822
4991
  if (res.errors && res.errors.length > 0) {
3823
4992
  const errorMsg = res.errors.map((e) => e.message).join("; ");
3824
4993
  return {
@@ -4064,6 +5233,7 @@ var generatePatchTool = defineTool({
4064
5233
  success: true,
4065
5234
  patches,
4066
5235
  patchContent: patches[0]?.unifiedDiff,
5236
+ reproductionTest: analysis.reproductionTest,
4067
5237
  llmProvider: provider,
4068
5238
  llmModel: modelName,
4069
5239
  latencyMs,
@@ -4088,17 +5258,126 @@ var generatePatchTool = defineTool({
4088
5258
 
4089
5259
  // src/remediation/tools/apply-patch-file/index.ts
4090
5260
  import { z as z8 } from "zod";
4091
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
4092
- import { join as join16 } from "path";
4093
- import { execa as execa8 } from "execa";
5261
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
5262
+ import { existsSync as existsSync14 } from "fs";
5263
+ import { isAbsolute as isAbsolute4, join as join19 } from "path";
5264
+ import { execa as execa9 } from "execa";
5265
+
5266
+ // src/remediation/proof-of-fix/runner.ts
5267
+ async function runProofOfFixPhase(options) {
5268
+ const { stage, testFilePath, cwd, timeoutMs = 15e3 } = options;
5269
+ const started = Date.now();
5270
+ const isTs = testFilePath.endsWith(".ts");
5271
+ const nodeArgs = isTs ? ["--experimental-strip-types", testFilePath] : [testFilePath];
5272
+ try {
5273
+ const result = await safeExeca("node", nodeArgs, {
5274
+ cwd,
5275
+ timeoutMs,
5276
+ stdio: "pipe"
5277
+ });
5278
+ const durationMs = Date.now() - started;
5279
+ const output = `${result.stdout || ""}
5280
+ ${result.stderr || ""}`.trim();
5281
+ if (stage === "negative") {
5282
+ return {
5283
+ executed: true,
5284
+ passed: false,
5285
+ exitCode: 0,
5286
+ output,
5287
+ durationMs,
5288
+ error: "Exploit reproduction test passed on vulnerable target (failed to reproduce vulnerability)."
5289
+ };
5290
+ }
5291
+ return {
5292
+ executed: true,
5293
+ passed: true,
5294
+ exitCode: 0,
5295
+ output,
5296
+ durationMs
5297
+ };
5298
+ } catch (err) {
5299
+ const durationMs = Date.now() - started;
5300
+ const execaErr = err;
5301
+ const exitCode = typeof execaErr.exitCode === "number" ? execaErr.exitCode : 1;
5302
+ const errorMsg = execaErr.message || String(err);
5303
+ const output = [execaErr.stdout, execaErr.stderr, errorMsg].filter(Boolean).join("\n").trim();
5304
+ if (stage === "negative") {
5305
+ if (execaErr.timedOut || errorMsg.includes("timed out")) {
5306
+ return {
5307
+ executed: true,
5308
+ passed: false,
5309
+ exitCode,
5310
+ output,
5311
+ durationMs,
5312
+ error: "Exploit reproduction test timed out (execution hung)."
5313
+ };
5314
+ }
5315
+ const isHarnessFailure = exitCode === 127 || errorMsg.includes("ENOENT") || output.includes("bad option:") || output.includes("Cannot find module") && output.includes(testFilePath);
5316
+ if (isHarnessFailure) {
5317
+ return {
5318
+ executed: false,
5319
+ passed: false,
5320
+ exitCode,
5321
+ output,
5322
+ durationMs,
5323
+ error: `Reproduction test failed to execute (harness error): ${errorMsg}`
5324
+ };
5325
+ }
5326
+ return {
5327
+ executed: true,
5328
+ passed: true,
5329
+ exitCode,
5330
+ output,
5331
+ durationMs
5332
+ };
5333
+ }
5334
+ return {
5335
+ executed: true,
5336
+ passed: false,
5337
+ exitCode,
5338
+ output,
5339
+ durationMs,
5340
+ error: `Reproduction test failed on patched target: ${errorMsg}`
5341
+ };
5342
+ }
5343
+ }
5344
+ function assembleProofOfFixVerification(params) {
5345
+ const { reproductionTestPath, negativePhase, positivePhase, regressionPhase } = params;
5346
+ const regPhase = regressionPhase ?? {
5347
+ executed: false,
5348
+ passed: true
5349
+ };
5350
+ let status = "verified";
5351
+ let failureReason;
5352
+ if (!negativePhase.passed) {
5353
+ status = "failed";
5354
+ failureReason = negativePhase.error || "negative-phase-failed";
5355
+ } else if (!positivePhase.passed) {
5356
+ status = "failed";
5357
+ failureReason = positivePhase.error || "positive-phase-failed";
5358
+ } else if (regPhase.executed && !regPhase.passed) {
5359
+ status = "failed";
5360
+ failureReason = regPhase.error || "regression-phase-failed";
5361
+ }
5362
+ const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
5363
+ return {
5364
+ status,
5365
+ reproductionTestPath,
5366
+ negativePhase,
5367
+ positivePhase,
5368
+ regressionPhase: regPhase,
5369
+ durationMs,
5370
+ failureReason
5371
+ };
5372
+ }
4094
5373
 
4095
5374
  // src/remediation/tools/apply-patch-file/helpers.ts
4096
- import { existsSync as existsSync10 } from "fs";
4097
- import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
5375
+ import { existsSync as existsSync13 } from "fs";
5376
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
4098
5377
  import { createHash } from "crypto";
4099
5378
  import { tmpdir as tmpdir2 } from "os";
4100
- import { join as join15 } from "path";
4101
- import { execa as execa7 } from "execa";
5379
+ import { join as join18 } from "path";
5380
+ import { execa as execa8 } from "execa";
4102
5381
  async function resolvePatchMode(packageManager, cwd) {
4103
5382
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
4104
5383
  return "patch-package";
@@ -4129,13 +5408,13 @@ function computePatchIntegrity(patchContent) {
4129
5408
  return `sha256:${hex}`;
4130
5409
  }
4131
5410
  async function writePatchManifest(manifestFilePath, artifact) {
4132
- await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
5411
+ await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4133
5412
  }
4134
5413
  async function configurePatchPackagePostinstall(cwd, packageManager) {
4135
- const pkgJsonPath = join15(cwd, "package.json");
5414
+ const pkgJsonPath = join18(cwd, "package.json");
4136
5415
  let pkgJson;
4137
5416
  try {
4138
- pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
5417
+ pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
4139
5418
  } catch {
4140
5419
  return {
4141
5420
  success: false,
@@ -4147,7 +5426,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4147
5426
  try {
4148
5427
  const commands = getPackageManagerCommands(packageManager);
4149
5428
  const [cmd, ...args] = commands.installDev("patch-package");
4150
- await execa7(cmd, args, {
5429
+ await execa8(cmd, args, {
4151
5430
  cwd,
4152
5431
  stdio: "pipe"
4153
5432
  });
@@ -4168,13 +5447,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4168
5447
  } else if (!currentPostinstall) {
4169
5448
  pkgJson.scripts.postinstall = patchApplyCmd;
4170
5449
  }
4171
- await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5450
+ await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4172
5451
  return { success: true };
4173
5452
  }
4174
5453
  async function capturePackageJsonSnapshot(cwd) {
4175
- const path = join15(cwd, "package.json");
5454
+ const path = join18(cwd, "package.json");
4176
5455
  try {
4177
- const content = await readFile4(path, "utf8");
5456
+ const content = await readFile5(path, "utf8");
4178
5457
  return { path, content };
4179
5458
  } catch {
4180
5459
  return void 0;
@@ -4190,19 +5469,19 @@ async function cleanupPatchArtifacts(params) {
4190
5469
  installCommand,
4191
5470
  rerunInstall
4192
5471
  } = params;
4193
- await rm3(patchFilePath, { force: true }).catch(() => void 0);
5472
+ await rm4(patchFilePath, { force: true }).catch(() => void 0);
4194
5473
  if (manifestFilePath) {
4195
- await rm3(manifestFilePath, { force: true }).catch(() => void 0);
5474
+ await rm4(manifestFilePath, { force: true }).catch(() => void 0);
4196
5475
  }
4197
5476
  if (patchMode === "patch-package" && packageJsonSnapshot) {
4198
- await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
5477
+ await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4199
5478
  () => void 0
4200
5479
  );
4201
5480
  }
4202
5481
  if (!rerunInstall) return;
4203
5482
  try {
4204
5483
  const [installCmd, ...installArgs] = installCommand;
4205
- await execa7(installCmd, installArgs, {
5484
+ await execa8(installCmd, installArgs, {
4206
5485
  cwd,
4207
5486
  stdio: "pipe"
4208
5487
  });
@@ -4216,7 +5495,7 @@ async function applyNativePatch(params) {
4216
5495
  const createArgs = ["patch", packageSpec];
4217
5496
  let patchDir;
4218
5497
  try {
4219
- const createResult = await execa7(createCommand, createArgs, {
5498
+ const createResult = await execa8(createCommand, createArgs, {
4220
5499
  cwd,
4221
5500
  stdio: "pipe"
4222
5501
  });
@@ -4244,17 +5523,17 @@ ${createResult.stderr}`);
4244
5523
  error: `Could not determine native patch directory for ${packageSpec}.`
4245
5524
  };
4246
5525
  }
4247
- const tempPatchDir = await mkdtemp2(join15(tmpdir2(), "autoremediator-native-patch-"));
4248
- const tempPatchFile = join15(tempPatchDir, "change.patch");
5526
+ const tempPatchDir = await mkdtemp2(join18(tmpdir2(), "autoremediator-native-patch-"));
5527
+ const tempPatchFile = join18(tempPatchDir, "change.patch");
4249
5528
  try {
4250
- await writeFile(tempPatchFile, patchContent, "utf8");
4251
- await execa7("patch", ["-p1", "-i", tempPatchFile], {
5529
+ await writeFile2(tempPatchFile, patchContent, "utf8");
5530
+ await execa8("patch", ["-p1", "-i", tempPatchFile], {
4252
5531
  cwd: patchDir,
4253
5532
  stdio: "pipe"
4254
5533
  });
4255
5534
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
4256
5535
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
4257
- await execa7(commitCommand, commitArgs, {
5536
+ await execa8(commitCommand, commitArgs, {
4258
5537
  cwd,
4259
5538
  stdio: "pipe"
4260
5539
  });
@@ -4269,19 +5548,19 @@ ${createResult.stderr}`);
4269
5548
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
4270
5549
  };
4271
5550
  } finally {
4272
- await rm3(tempPatchDir, { recursive: true, force: true });
5551
+ await rm4(tempPatchDir, { recursive: true, force: true });
4273
5552
  }
4274
5553
  return { success: true };
4275
5554
  }
4276
5555
  function extractPatchDirectory(output) {
4277
5556
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4278
5557
  for (const line of lines) {
4279
- if (existsSync10(line)) {
5558
+ if (existsSync13(line)) {
4280
5559
  return line;
4281
5560
  }
4282
5561
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
4283
5562
  for (const token of tokens) {
4284
- if (token.startsWith("/") && existsSync10(token)) {
5563
+ if (token.startsWith("/") && existsSync13(token)) {
4285
5564
  return token;
4286
5565
  }
4287
5566
  }
@@ -4291,7 +5570,7 @@ function extractPatchDirectory(output) {
4291
5570
  async function validatePatchWithTests(cwd, testCommand) {
4292
5571
  try {
4293
5572
  const [cmd, ...args] = testCommand;
4294
- const result = await execa7(cmd, args, {
5573
+ const result = await execa8(cmd, args, {
4295
5574
  cwd,
4296
5575
  timeout: 6e4,
4297
5576
  stdio: "pipe"
@@ -4351,6 +5630,11 @@ var applyPatchFileTool = defineTool({
4351
5630
  enforceFrozenLockfile: z8.boolean().optional(),
4352
5631
  workspace: z8.string().optional(),
4353
5632
  validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
5633
+ reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
5634
+ proofOfFix: z8.union([z8.boolean(), z8.string()]).optional().describe(
5635
+ "If true or script path/content, enforce dual-stage exploit reproduction (assert failure pre-patch, assert pass post-patch)"
5636
+ ),
5637
+ requireProofOfFix: z8.boolean().optional().describe("If true, fail and roll back patch if proof-of-fix verification fails"),
4354
5638
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
4355
5639
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
4356
5640
  message: "Either patchContent or patches must be provided"
@@ -4372,6 +5656,9 @@ var applyPatchFileTool = defineTool({
4372
5656
  workspace,
4373
5657
  riskLevel,
4374
5658
  validateWithTests,
5659
+ reproductionTest,
5660
+ proofOfFix,
5661
+ requireProofOfFix,
4375
5662
  dryRun
4376
5663
  }) => {
4377
5664
  try {
@@ -4422,7 +5709,7 @@ var applyPatchFileTool = defineTool({
4422
5709
  };
4423
5710
  }
4424
5711
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
4425
- const patchFilePath = join16(cwd, patchesDir, patchFileName);
5712
+ const patchFilePath = join19(cwd, patchesDir, patchFileName);
4426
5713
  const manifestFilePath = `${patchFilePath}.json`;
4427
5714
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4428
5715
  const baseArtifact = {
@@ -4461,9 +5748,9 @@ var applyPatchFileTool = defineTool({
4461
5748
  }
4462
5749
  return withRepoLock(cwd, async () => {
4463
5750
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
4464
- const patchesDirPath = join16(cwd, patchesDir);
5751
+ const patchesDirPath = join19(cwd, patchesDir);
4465
5752
  await mkdir3(patchesDirPath, { recursive: true });
4466
- await writeFile2(patchFilePath, selectedPatch, "utf8");
5753
+ await writeFile3(patchFilePath, selectedPatch, "utf8");
4467
5754
  validationPhases.push({
4468
5755
  phase: "patch-write",
4469
5756
  passed: true,
@@ -4484,6 +5771,101 @@ var applyPatchFileTool = defineTool({
4484
5771
  });
4485
5772
  artifact.validationPhases = validationPhases;
4486
5773
  await writePatchManifest(manifestFilePath, artifact);
5774
+ let reproductionTestPath;
5775
+ let negativePhaseResult;
5776
+ if (typeof proofOfFix === "string" && proofOfFix.trim().length > 0) {
5777
+ const resolvedPath = isAbsolute4(proofOfFix) ? proofOfFix : join19(cwd, proofOfFix);
5778
+ if (existsSync14(resolvedPath)) {
5779
+ reproductionTestPath = resolvedPath;
5780
+ } else {
5781
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5782
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5783
+ await writeFile3(reproductionTestPath, proofOfFix, "utf8");
5784
+ }
5785
+ } else if (reproductionTest) {
5786
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5787
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5788
+ await writeFile3(reproductionTestPath, reproductionTest, "utf8");
5789
+ }
5790
+ const isProofOfFixActive = Boolean(proofOfFix || requireProofOfFix);
5791
+ if (isProofOfFixActive) {
5792
+ if (!reproductionTestPath) {
5793
+ if (requireProofOfFix) {
5794
+ await cleanupPatchArtifacts({
5795
+ cwd,
5796
+ patchFilePath,
5797
+ manifestFilePath,
5798
+ patchMode,
5799
+ packageJsonSnapshot,
5800
+ installCommand,
5801
+ rerunInstall: false
5802
+ });
5803
+ return {
5804
+ success: false,
5805
+ packageName,
5806
+ vulnerableVersion,
5807
+ applied: false,
5808
+ dryRun: false,
5809
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied.",
5810
+ patchFilePath,
5811
+ manifestFilePath,
5812
+ patchPath: patchFilePath,
5813
+ patchMode,
5814
+ postinstallConfigured: false,
5815
+ patchArtifact: {
5816
+ ...artifact,
5817
+ applied: false,
5818
+ validationPhases
5819
+ },
5820
+ validationPhases,
5821
+ error: "Proof-of-fix verification required, but no reproduction test or script was supplied."
5822
+ };
5823
+ }
5824
+ } else {
5825
+ negativePhaseResult = await runProofOfFixPhase({
5826
+ stage: "negative",
5827
+ testFilePath: reproductionTestPath,
5828
+ cwd
5829
+ });
5830
+ validationPhases.push({
5831
+ phase: "proof-of-fix-negative",
5832
+ passed: negativePhaseResult.passed,
5833
+ message: negativePhaseResult.passed ? "Reproduction test reproduced vulnerability on unpatched code." : void 0,
5834
+ error: negativePhaseResult.passed ? void 0 : negativePhaseResult.error
5835
+ });
5836
+ if (!negativePhaseResult.passed && requireProofOfFix) {
5837
+ await cleanupPatchArtifacts({
5838
+ cwd,
5839
+ patchFilePath,
5840
+ manifestFilePath,
5841
+ patchMode,
5842
+ packageJsonSnapshot,
5843
+ installCommand,
5844
+ rerunInstall: false
5845
+ });
5846
+ return {
5847
+ success: false,
5848
+ packageName,
5849
+ vulnerableVersion,
5850
+ applied: false,
5851
+ dryRun: false,
5852
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`,
5853
+ patchFilePath,
5854
+ manifestFilePath,
5855
+ patchPath: patchFilePath,
5856
+ patchMode,
5857
+ postinstallConfigured: false,
5858
+ patchArtifact: {
5859
+ ...artifact,
5860
+ applied: false,
5861
+ validationPhases
5862
+ },
5863
+ validationPhases,
5864
+ error: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
5865
+ };
5866
+ }
5867
+ }
5868
+ }
4487
5869
  const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
4488
5870
  cwd,
4489
5871
  packageName,
@@ -4531,7 +5913,7 @@ var applyPatchFileTool = defineTool({
4531
5913
  if (patchMode === "patch-package") {
4532
5914
  try {
4533
5915
  const [installCmd, ...installArgs] = installCommand;
4534
- await execa8(installCmd, installArgs, {
5916
+ await execa9(installCmd, installArgs, {
4535
5917
  cwd,
4536
5918
  stdio: "pipe"
4537
5919
  });
@@ -4624,11 +6006,90 @@ var applyPatchFileTool = defineTool({
4624
6006
  message: "Patch validation tests passed."
4625
6007
  });
4626
6008
  }
6009
+ let positivePhaseResult;
6010
+ if (reproductionTestPath) {
6011
+ if (isProofOfFixActive) {
6012
+ positivePhaseResult = await runProofOfFixPhase({
6013
+ stage: "positive",
6014
+ testFilePath: reproductionTestPath,
6015
+ cwd
6016
+ });
6017
+ validationPhases.push({
6018
+ phase: "proof-of-fix-positive",
6019
+ passed: positivePhaseResult.passed,
6020
+ message: positivePhaseResult.passed ? "Reproduction test passed on patched code (vulnerability mitigated)." : void 0,
6021
+ error: positivePhaseResult.passed ? void 0 : positivePhaseResult.error
6022
+ });
6023
+ if (!positivePhaseResult.passed && (requireProofOfFix || validateWithTests)) {
6024
+ await cleanupPatchArtifacts({
6025
+ cwd,
6026
+ patchFilePath,
6027
+ manifestFilePath,
6028
+ patchMode,
6029
+ packageJsonSnapshot,
6030
+ installCommand,
6031
+ rerunInstall: patchMode === "patch-package"
6032
+ });
6033
+ return {
6034
+ success: false,
6035
+ packageName,
6036
+ vulnerableVersion,
6037
+ applied: false,
6038
+ dryRun: false,
6039
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`,
6040
+ patchFilePath,
6041
+ manifestFilePath,
6042
+ patchPath: patchFilePath,
6043
+ patchMode,
6044
+ postinstallConfigured: false,
6045
+ patchArtifact: {
6046
+ ...artifact,
6047
+ applied: false,
6048
+ validationPhases
6049
+ },
6050
+ validationPhases,
6051
+ error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
6052
+ };
6053
+ }
6054
+ } else {
6055
+ try {
6056
+ const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
6057
+ validationPhases.push({
6058
+ phase: "reproduction-test",
6059
+ passed: reproRun.exitCode === 0,
6060
+ message: "Reproduction test verified fix successfully."
6061
+ });
6062
+ } catch (err) {
6063
+ const reproErr = err instanceof Error ? err.message : String(err);
6064
+ validationPhases.push({
6065
+ phase: "reproduction-test",
6066
+ passed: false,
6067
+ error: `Reproduction test failed: ${reproErr}`
6068
+ });
6069
+ }
6070
+ }
6071
+ }
6072
+ let proofOfFixVerification;
6073
+ if (negativePhaseResult && positivePhaseResult) {
6074
+ proofOfFixVerification = assembleProofOfFixVerification({
6075
+ reproductionTestPath,
6076
+ negativePhase: negativePhaseResult,
6077
+ positivePhase: positivePhaseResult,
6078
+ regressionPhase: validationResult ? {
6079
+ executed: true,
6080
+ passed: validationResult.passed,
6081
+ error: validationResult.error
6082
+ } : void 0
6083
+ });
6084
+ }
4627
6085
  const finalArtifact = {
4628
6086
  ...artifact,
4629
6087
  applied: true,
4630
6088
  dryRun: false,
4631
- validationPhases
6089
+ reproductionTest,
6090
+ reproductionTestPath,
6091
+ validationPhases,
6092
+ proofOfFix: proofOfFixVerification
4632
6093
  };
4633
6094
  await writePatchManifest(manifestFilePath, finalArtifact);
4634
6095
  return {
@@ -4645,7 +6106,8 @@ var applyPatchFileTool = defineTool({
4645
6106
  postinstallConfigured: patchMode === "patch-package",
4646
6107
  patchArtifact: finalArtifact,
4647
6108
  validation: validationResult,
4648
- validationPhases
6109
+ validationPhases,
6110
+ proofOfFix: proofOfFixVerification
4649
6111
  };
4650
6112
  });
4651
6113
  } catch (err) {
@@ -4889,6 +6351,9 @@ async function tryLocalPatchFallback(params) {
4889
6351
  workspace: params.workspace,
4890
6352
  riskLevel: patchResult.riskLevel,
4891
6353
  validateWithTests: params.runTests,
6354
+ reproductionTest: patchResult.reproductionTest,
6355
+ proofOfFix: params.proofOfFix,
6356
+ requireProofOfFix: params.requireProofOfFix,
4892
6357
  dryRun: params.dryRun
4893
6358
  });
4894
6359
  steps += 1;
@@ -4937,8 +6402,14 @@ function resolveLocalRunOptions(options) {
4937
6402
  const runTests = options.runTests ?? false;
4938
6403
  const policy = options.policy ?? "";
4939
6404
  const patchesDir = options.patchesDir || "./patches";
4940
- const constraints = options.constraints ?? {};
4941
6405
  const loadedPolicy = loadPolicy(cwd, options.policy);
6406
+ const constraints = {
6407
+ ...options.constraints,
6408
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? loadedPolicy.constraints?.typecheck,
6409
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? loadedPolicy.constraints?.supplyChainPolicy,
6410
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor,
6411
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? loadedPolicy.constraints?.requireProofOfFix
6412
+ };
4942
6413
  const llmProvider = resolveProvider(options);
4943
6414
  return {
4944
6415
  cwd,
@@ -4969,12 +6440,15 @@ function resolveLocalRunOptions(options) {
4969
6440
  regressionCheck: options.regressionCheck ?? false,
4970
6441
  escalationGraph: options.escalationGraph ?? loadedPolicy.escalationGraph,
4971
6442
  offlineIntelligence: options.offlineIntelligence ?? false,
4972
- intelligenceSnapshotPath: options.intelligenceSnapshotPath
6443
+ intelligenceSnapshotPath: options.intelligenceSnapshotPath,
6444
+ proofOfFix: options.proofOfFix,
6445
+ requireProofOfFix: options.requireProofOfFix ?? constraints.requireProofOfFix,
6446
+ breakingChangeRefactor: options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor
4973
6447
  };
4974
6448
  }
4975
6449
 
4976
6450
  // src/remediation/local/vulnerability-match.ts
4977
- import semver4 from "semver";
6451
+ import semver5 from "semver";
4978
6452
  function findVulnerablePackages(cveDetails, installedPackages) {
4979
6453
  const vulnerablePackages = [];
4980
6454
  for (const affected of cveDetails.affectedPackages) {
@@ -4983,10 +6457,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
4983
6457
  if (affected.ecosystem !== "npm") continue;
4984
6458
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
4985
6459
  for (const installed of matches) {
4986
- if (!semver4.valid(installed.version)) continue;
6460
+ if (!semver5.valid(installed.version)) continue;
4987
6461
  let isVulnerable = false;
4988
6462
  try {
4989
- isVulnerable = semver4.satisfies(installed.version, affected.vulnerableRange, {
6463
+ isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4990
6464
  includePrerelease: false
4991
6465
  });
4992
6466
  } catch {
@@ -5137,7 +6611,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5137
6611
  regressionCheck,
5138
6612
  escalationGraph,
5139
6613
  offlineIntelligence,
5140
- intelligenceSnapshotPath
6614
+ intelligenceSnapshotPath,
6615
+ proofOfFix,
6616
+ requireProofOfFix
5141
6617
  } = resolved;
5142
6618
  const correlation = {
5143
6619
  requestId: options.requestId,
@@ -5314,7 +6790,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5314
6790
  installPreferOffline: constraints.installPreferOffline,
5315
6791
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
5316
6792
  workspace: constraints.workspace,
5317
- vulnerableRange: vulnerable.affected.vulnerableRange
6793
+ vulnerableRange: vulnerable.affected.vulnerableRange,
6794
+ proofOfFix,
6795
+ requireProofOfFix
5318
6796
  });
5319
6797
  agentSteps += fallback.steps;
5320
6798
  collectedResults.push(finalizePatchResult(fallback.result));
@@ -5329,7 +6807,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5329
6807
  };
5330
6808
  if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
5331
6809
  try {
5332
- if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
6810
+ if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
5333
6811
  includePrerelease: false
5334
6812
  })) {
5335
6813
  primaryResult.regressionDetected = true;
@@ -5420,8 +6898,8 @@ function accumulateStepResults(params) {
5420
6898
  }
5421
6899
 
5422
6900
  // src/remediation/orchestration-prompt.ts
5423
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
5424
- import { join as join17 } from "path";
6901
+ import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
6902
+ import { join as join20 } from "path";
5425
6903
  function buildProviderAddendum(provider, personality = "balanced") {
5426
6904
  const personalityDirective = personality === "analytical" ? "Use concise, explicit rationale for tool decisions and unresolved outcomes." : personality === "pragmatic" ? "Prefer the smallest safe remediation path while preserving policy and validation gates." : "Balance concise execution with brief rationale for risky or unresolved outcomes.";
5427
6905
  const providerDirective = provider === "remote" ? "Use strict structured output and deterministic reporting fields." : "Use deterministic-first behavior and only rely on remote model fallback when required by patch generation.";
@@ -5432,13 +6910,13 @@ Provider profile:
5432
6910
  - ${personalityDirective}`;
5433
6911
  }
5434
6912
  function loadOrchestrationPrompt(ctx) {
5435
- const promptPath = join17(
6913
+ const promptPath = join20(
5436
6914
  process.cwd(),
5437
6915
  ".github",
5438
6916
  "instructions",
5439
6917
  "orchestration.instructions.md"
5440
6918
  );
5441
- if (!existsSync11(promptPath)) {
6919
+ if (!existsSync15(promptPath)) {
5442
6920
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
5443
6921
  Working directory: ${ctx.cwd}
5444
6922
  Package manager: ${ctx.packageManager}
@@ -5464,7 +6942,7 @@ Fallback sequence (when neither version bump nor override can be applied):
5464
6942
 
5465
6943
  Always respect dryRun and policy constraints.`;
5466
6944
  }
5467
- const template = readFileSync11(promptPath, "utf8");
6945
+ const template = readFileSync15(promptPath, "utf8");
5468
6946
  return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll(
5469
6947
  "{{directDependenciesOnly}}",
5470
6948
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -5473,85 +6951,21 @@ Always respect dryRun and policy constraints.`;
5473
6951
 
5474
6952
  // src/remediation/pipeline-runtime/progress.ts
5475
6953
  function createProgressEmitter(options) {
5476
- return (stage, detail, extra) => {
5477
- if (!options.onProgress) return;
5478
- options.onProgress({
5479
- stage,
5480
- detail,
5481
- at: (/* @__PURE__ */ new Date()).toISOString(),
5482
- provider: extra?.provider,
5483
- model: extra?.model
5484
- });
5485
- };
5486
- }
5487
-
5488
- // src/remediation/tools/lookup-cve.ts
5489
- import { z as z9 } from "zod";
5490
-
5491
- // src/intelligence/sources/cisa-kev.ts
5492
- var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
5493
- async function fetchCisaKevFeed() {
5494
- try {
5495
- const res = await httpClient({ url: CISA_KEV_URL });
5496
- if (!res.ok) return void 0;
5497
- return res.data;
5498
- } catch {
5499
- return void 0;
5500
- }
5501
- }
5502
- function findKevEntry(feed, cveId) {
5503
- if (!feed?.vulnerabilities?.length) return void 0;
5504
- const normalized = cveId.toUpperCase();
5505
- return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
5506
- }
5507
- async function enrichWithCisaKev(details) {
5508
- const feed = await fetchCisaKevFeed();
5509
- const entry = findKevEntry(feed, details.id);
5510
- if (!entry) return details;
5511
- details.kev = {
5512
- knownExploited: true,
5513
- dateAdded: entry.dateAdded,
5514
- dueDate: entry.dueDate,
5515
- requiredAction: entry.requiredAction,
5516
- knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
5517
- };
5518
- if (!details.references.includes(CISA_KEV_URL)) {
5519
- details.references.push(CISA_KEV_URL);
5520
- }
5521
- return details;
5522
- }
5523
-
5524
- // src/intelligence/sources/epss.ts
5525
- async function fetchEpss(cveId) {
5526
- const { epssApi } = getIntelligenceSourceConfig();
5527
- if (!epssApi) return void 0;
5528
- try {
5529
- const url = new URL(epssApi);
5530
- url.searchParams.set("cve", cveId);
5531
- const res = await httpClient({ url: url.toString() });
5532
- if (!res.ok) return void 0;
5533
- const body = res.data;
5534
- return body.data?.[0];
5535
- } catch {
5536
- return void 0;
5537
- }
5538
- }
5539
- async function enrichWithEpss(details) {
5540
- const row = await fetchEpss(details.id);
5541
- if (!row) return details;
5542
- const score = Number.parseFloat(row.epss);
5543
- const percentile = Number.parseFloat(row.percentile);
5544
- if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
5545
- return details;
5546
- }
5547
- details.epss = {
5548
- score,
5549
- percentile,
5550
- date: row.date
6954
+ return (stage, detail, extra) => {
6955
+ if (!options.onProgress) return;
6956
+ options.onProgress({
6957
+ stage,
6958
+ detail,
6959
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6960
+ provider: extra?.provider,
6961
+ model: extra?.model
6962
+ });
5551
6963
  };
5552
- return details;
5553
6964
  }
5554
6965
 
6966
+ // src/remediation/tools/lookup-cve.ts
6967
+ import { z as z9 } from "zod";
6968
+
5555
6969
  // src/intelligence/sources/cve-services.ts
5556
6970
  function pickEnglishDescription(container) {
5557
6971
  if (!container?.descriptions?.length) return void 0;
@@ -5835,7 +7249,7 @@ var lookupCveTool = defineTool({
5835
7249
 
5836
7250
  // src/remediation/tools/check-version-match.ts
5837
7251
  import { z as z10 } from "zod";
5838
- import semver6 from "semver";
7252
+ import semver7 from "semver";
5839
7253
  var affectedPackageSchema = z10.object({
5840
7254
  name: z10.string(),
5841
7255
  ecosystem: z10.literal("npm"),
@@ -5864,10 +7278,10 @@ var checkVersionMatchTool = defineTool({
5864
7278
  (p) => p.name === affected.name
5865
7279
  );
5866
7280
  for (const installed of matches) {
5867
- if (!semver6.valid(installed.version)) continue;
7281
+ if (!semver7.valid(installed.version)) continue;
5868
7282
  let isVulnerable = false;
5869
7283
  try {
5870
- isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
7284
+ isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
5871
7285
  includePrerelease: false
5872
7286
  });
5873
7287
  } catch {
@@ -6007,6 +7421,8 @@ function createRuntimeToolsForRun(options) {
6007
7421
  installPreferOffline: constraints.installPreferOffline,
6008
7422
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6009
7423
  workspace: constraints.workspace,
7424
+ typecheck: input.typecheck ?? options.typecheck ?? constraints.typecheck,
7425
+ breakingChangeRefactor: input.breakingChangeRefactor ?? options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor,
6010
7426
  dryRun: preview ? true : input.dryRun
6011
7427
  })
6012
7428
  };
@@ -6031,6 +7447,8 @@ function createRuntimeToolsForRun(options) {
6031
7447
  installPreferOffline: constraints.installPreferOffline,
6032
7448
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6033
7449
  workspace: constraints.workspace,
7450
+ proofOfFix: input.proofOfFix ?? options.proofOfFix,
7451
+ requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
6034
7452
  dryRun: preview ? true : input.dryRun
6035
7453
  })
6036
7454
  };
@@ -6185,7 +7603,7 @@ async function runRemediationPipeline(cveId, options = {}) {
6185
7603
  }
6186
7604
 
6187
7605
  // src/api/change-request/index.ts
6188
- import { execa as execa9 } from "execa";
7606
+ import { execa as execa10 } from "execa";
6189
7607
  import { randomBytes } from "crypto";
6190
7608
  function sanitizeBranchToken(value) {
6191
7609
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -6214,22 +7632,68 @@ function resolveProvider2(options) {
6214
7632
  return options.provider ?? "github";
6215
7633
  }
6216
7634
  function resolveGrouping(options) {
6217
- if (options.grouping && options.grouping !== "all") {
7635
+ const allowed = ["all", "per-cve", "per-package", "by-severity"];
7636
+ if (options.grouping && !allowed.includes(options.grouping)) {
6218
7637
  throw new Error(
6219
- "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
7638
+ `Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
6220
7639
  );
6221
7640
  }
6222
- return "all";
7641
+ return options.grouping ?? "all";
7642
+ }
7643
+ function groupReports(reports, grouping) {
7644
+ if (reports.length === 0) return [];
7645
+ if (grouping === "all") {
7646
+ return [reports];
7647
+ }
7648
+ if (grouping === "per-cve") {
7649
+ const map = /* @__PURE__ */ new Map();
7650
+ for (const report of reports) {
7651
+ const key = report.cveId.toUpperCase();
7652
+ const list = map.get(key) ?? [];
7653
+ list.push(report);
7654
+ map.set(key, list);
7655
+ }
7656
+ return Array.from(map.values());
7657
+ }
7658
+ if (grouping === "per-package") {
7659
+ const map = /* @__PURE__ */ new Map();
7660
+ for (const report of reports) {
7661
+ const pkgs = new Set(report.results.map((r) => r.packageName));
7662
+ if (pkgs.size === 0) {
7663
+ const list = map.get("misc") ?? [];
7664
+ list.push(report);
7665
+ map.set("misc", list);
7666
+ } else {
7667
+ for (const pkg of pkgs) {
7668
+ const list = map.get(pkg) ?? [];
7669
+ list.push(report);
7670
+ map.set(pkg, list);
7671
+ }
7672
+ }
7673
+ }
7674
+ return Array.from(map.values());
7675
+ }
7676
+ if (grouping === "by-severity") {
7677
+ const map = /* @__PURE__ */ new Map();
7678
+ for (const report of reports) {
7679
+ const sev = report.cveDetails?.severity ?? "UNKNOWN";
7680
+ const list = map.get(sev) ?? [];
7681
+ list.push(report);
7682
+ map.set(sev, list);
7683
+ }
7684
+ return Array.from(map.values());
7685
+ }
7686
+ return [reports];
6223
7687
  }
6224
7688
  async function detectCurrentBranch(cwd) {
6225
- const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7689
+ const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6226
7690
  cwd,
6227
7691
  stdio: "pipe"
6228
7692
  });
6229
7693
  return result.stdout.trim();
6230
7694
  }
6231
7695
  async function ensureRepoHasChanges(cwd) {
6232
- const diff = await execa9("git", ["status", "--porcelain"], {
7696
+ const diff = await execa10("git", ["status", "--porcelain"], {
6233
7697
  cwd,
6234
7698
  stdio: "pipe"
6235
7699
  });
@@ -6258,7 +7722,7 @@ async function createGitHubRequest(params) {
6258
7722
  if (params.draft) {
6259
7723
  args.push("--draft");
6260
7724
  }
6261
- const result = await execa9("gh", args, {
7725
+ const result = await execa10("gh", args, {
6262
7726
  cwd: params.cwd,
6263
7727
  stdio: "pipe"
6264
7728
  });
@@ -6283,7 +7747,7 @@ async function createGitLabRequest(params) {
6283
7747
  if (params.draft) {
6284
7748
  args.push("--draft");
6285
7749
  }
6286
- const result = await execa9("glab", args, {
7750
+ const result = await execa10("glab", args, {
6287
7751
  cwd: params.cwd,
6288
7752
  stdio: "pipe"
6289
7753
  });
@@ -6293,64 +7757,68 @@ async function createChangeRequestsForReports(params) {
6293
7757
  const { cwd, options, reports } = params;
6294
7758
  const provider = resolveProvider2(options);
6295
7759
  const grouping = resolveGrouping(options);
6296
- const plan = buildPlan(reports);
6297
- const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
6298
- const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
6299
- const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
6300
- const body = bodyFooter ? `${plan.body}
7760
+ const groups = groupReports(reports, grouping);
7761
+ if (groups.length === 0) {
7762
+ return [];
7763
+ }
7764
+ const results = [];
7765
+ const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
7766
+ const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
7767
+ const branchPrefix = options.branchPrefix ?? "autoremediator";
7768
+ const pushRemote = options.pushRemote ?? "origin";
7769
+ if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
7770
+ throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
7771
+ }
7772
+ if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
7773
+ throw new Error(`Invalid repository format: ${options.repository}`);
7774
+ }
7775
+ for (const group of groups) {
7776
+ const plan = buildPlan(group);
7777
+ const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
7778
+ const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
7779
+ const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
7780
+ const body = bodyFooter ? `${plan.body}
6301
7781
 
6302
7782
  ${bodyFooter}` : plan.body;
6303
- try {
6304
- const hasChanges = await ensureRepoHasChanges(cwd);
6305
7783
  if (!hasChanges) {
6306
- return [
6307
- {
6308
- provider,
6309
- grouping,
6310
- repository: options.repository,
6311
- branchName: "",
6312
- title,
6313
- body,
6314
- created: false,
6315
- cveIds: plan.cveIds,
6316
- packageNames: plan.packageNames,
6317
- error: "No repository changes to include in change request."
6318
- }
6319
- ];
7784
+ results.push({
7785
+ provider,
7786
+ grouping,
7787
+ repository: options.repository,
7788
+ branchName: "",
7789
+ title,
7790
+ body,
7791
+ created: false,
7792
+ cveIds: plan.cveIds,
7793
+ packageNames: plan.packageNames,
7794
+ error: "No repository changes to include in change request."
7795
+ });
7796
+ continue;
6320
7797
  }
6321
- const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
6322
- const branchPrefix = options.branchPrefix ?? "autoremediator";
6323
7798
  const branchName = toBranchName(branchPrefix, plan.cveIds);
6324
- const pushRemote = options.pushRemote ?? "origin";
6325
- if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
6326
- throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
6327
- }
6328
- if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
6329
- throw new Error(`Invalid repository format: ${options.repository}`);
6330
- }
6331
- await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
6332
- await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
6333
- await execa9("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
6334
- await execa9("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
6335
- const url = provider === "github" ? await createGitHubRequest({
6336
- cwd,
6337
- repository: options.repository,
6338
- head: branchName,
6339
- base: baseBranch,
6340
- title,
6341
- body,
6342
- draft: options.draft
6343
- }) : await createGitLabRequest({
6344
- cwd,
6345
- repository: options.repository,
6346
- source: branchName,
6347
- target: baseBranch,
6348
- title,
6349
- body,
6350
- draft: options.draft
6351
- });
6352
- return [
6353
- {
7799
+ try {
7800
+ await execa10("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
7801
+ await execa10("git", ["add", "-A"], { cwd, stdio: "pipe" });
7802
+ await execa10("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
7803
+ await execa10("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
7804
+ const url = provider === "github" ? await createGitHubRequest({
7805
+ cwd,
7806
+ repository: options.repository,
7807
+ head: branchName,
7808
+ base: baseBranch,
7809
+ title,
7810
+ body,
7811
+ draft: options.draft
7812
+ }) : await createGitLabRequest({
7813
+ cwd,
7814
+ repository: options.repository,
7815
+ source: branchName,
7816
+ target: baseBranch,
7817
+ title,
7818
+ body,
7819
+ draft: options.draft
7820
+ });
7821
+ results.push({
6354
7822
  provider,
6355
7823
  grouping,
6356
7824
  repository: options.repository,
@@ -6362,11 +7830,13 @@ ${bodyFooter}` : plan.body;
6362
7830
  url,
6363
7831
  cveIds: plan.cveIds,
6364
7832
  packageNames: plan.packageNames
7833
+ });
7834
+ if (groups.length > 1) {
7835
+ await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
7836
+ });
6365
7837
  }
6366
- ];
6367
- } catch (error) {
6368
- return [
6369
- {
7838
+ } catch (error) {
7839
+ results.push({
6370
7840
  provider,
6371
7841
  grouping,
6372
7842
  repository: options.repository,
@@ -6378,9 +7848,10 @@ ${bodyFooter}` : plan.body;
6378
7848
  cveIds: plan.cveIds,
6379
7849
  packageNames: plan.packageNames,
6380
7850
  error: error instanceof Error ? error.message : String(error)
6381
- }
6382
- ];
7851
+ });
7852
+ }
6383
7853
  }
7854
+ return results;
6384
7855
  }
6385
7856
 
6386
7857
  // src/api/context.ts
@@ -6408,13 +7879,17 @@ function resolveConstraints(options, cwd) {
6408
7879
  installMode: options.constraints?.installMode ?? policy.constraints?.installMode ?? "deterministic",
6409
7880
  installPreferOffline: options.constraints?.installPreferOffline ?? policy.constraints?.installPreferOffline,
6410
7881
  enforceFrozenLockfile: options.constraints?.enforceFrozenLockfile ?? policy.constraints?.enforceFrozenLockfile,
6411
- workspace: options.constraints?.workspace ?? policy.constraints?.workspace
7882
+ workspace: options.constraints?.workspace ?? policy.constraints?.workspace,
7883
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? policy.constraints?.typecheck,
7884
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? policy.constraints?.requireProofOfFix,
7885
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? policy.constraints?.enableBreakingChangeRefactor,
7886
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? policy.constraints?.supplyChainPolicy
6412
7887
  };
6413
7888
  }
6414
7889
 
6415
7890
  // src/platform/evidence.ts
6416
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6417
- import { join as join18 } from "path";
7891
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync6 } from "fs";
7892
+ import { join as join21 } from "path";
6418
7893
  function createEvidenceLog(cwd, cveIds, context = {}) {
6419
7894
  return {
6420
7895
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -6446,10 +7921,10 @@ function finalizeEvidence(log) {
6446
7921
  return log;
6447
7922
  }
6448
7923
  function writeEvidenceLog(cwd, log) {
6449
- const dir = join18(cwd, ".autoremediator", "evidence");
7924
+ const dir = join21(cwd, ".autoremediator", "evidence");
6450
7925
  mkdirSync3(dir, { recursive: true });
6451
- const filePath = join18(dir, `${log.runId}.json`);
6452
- writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
7926
+ const filePath = join21(dir, `${log.runId}.json`);
7927
+ writeFileSync6(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
6453
7928
  return filePath;
6454
7929
  }
6455
7930
 
@@ -6587,10 +8062,15 @@ function writeRemediateEvidence(cwd, evidence) {
6587
8062
  }
6588
8063
 
6589
8064
  // src/api/remediate/validators.ts
8065
+ var CVE_ID_REGEX = /^CVE-\d{4}-\d{1,7}$/i;
8066
+ var GHSA_ID_REGEX = /^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/i;
8067
+ function isVulnerabilityId(id) {
8068
+ return CVE_ID_REGEX.test(id) || GHSA_ID_REGEX.test(id);
8069
+ }
6590
8070
  function assertValidCveId(cveId) {
6591
- if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
8071
+ if (!isVulnerabilityId(cveId)) {
6592
8072
  throw new Error(
6593
- `Invalid CVE ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337).`
8073
+ `Invalid vulnerability ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337) or GHSA-xxxx-xxxx-xxxx.`
6594
8074
  );
6595
8075
  }
6596
8076
  }
@@ -6686,13 +8166,14 @@ async function planRemediation(cveId, options = {}) {
6686
8166
  }
6687
8167
 
6688
8168
  // src/scanner/parse-input.ts
6689
- import { extname, resolve as resolve3 } from "path";
6690
- import { readFileSync as readFileSync15 } from "fs";
6691
- import { execa as execa10 } from "execa";
8169
+ import { extname, resolve as resolve5 } from "path";
8170
+ import { readFileSync as readFileSync19 } from "fs";
8171
+ import { execa as execa11 } from "execa";
6692
8172
 
6693
8173
  // src/scanner/adapters/npm-audit.ts
6694
- import { readFileSync as readFileSync12 } from "fs";
8174
+ import { readFileSync as readFileSync16 } from "fs";
6695
8175
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
8176
+ var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6696
8177
  function normalizeSeverity(raw) {
6697
8178
  if (!raw) return "UNKNOWN";
6698
8179
  const up = raw.toUpperCase();
@@ -6707,7 +8188,8 @@ function parseNpmAuditJsonFromString(content) {
6707
8188
  for (const vuln of Object.values(report.vulnerabilities ?? {})) {
6708
8189
  for (const viaEntry of vuln.via ?? []) {
6709
8190
  const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
6710
- const matches = text.match(CVE_REGEX) ?? [];
8191
+ const cveMatches = text.match(CVE_REGEX) ?? [];
8192
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
6711
8193
  for (const match of matches) {
6712
8194
  const cveId = match.toUpperCase();
6713
8195
  const key = `${cveId}:${vuln.name}`;
@@ -6725,13 +8207,14 @@ function parseNpmAuditJsonFromString(content) {
6725
8207
  return findings;
6726
8208
  }
6727
8209
  function parseNpmAuditJsonFile(filePath) {
6728
- const content = readFileSync12(filePath, "utf8");
8210
+ const content = readFileSync16(filePath, "utf8");
6729
8211
  return parseNpmAuditJsonFromString(content);
6730
8212
  }
6731
8213
 
6732
8214
  // src/scanner/adapters/yarn-audit.ts
6733
- import { readFileSync as readFileSync13 } from "fs";
8215
+ import { readFileSync as readFileSync17 } from "fs";
6734
8216
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
8217
+ var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6735
8218
  function normalizeSeverity2(raw) {
6736
8219
  if (!raw) return "UNKNOWN";
6737
8220
  const up = raw.toUpperCase();
@@ -6756,7 +8239,8 @@ function parseYarnAuditJsonFromString(content) {
6756
8239
  const packageName = advisory?.module_name;
6757
8240
  const severity = normalizeSeverity2(advisory?.severity);
6758
8241
  const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
6759
- const matches = text.match(CVE_REGEX2) ?? [];
8242
+ const cveMatches = text.match(CVE_REGEX2) ?? [];
8243
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
6760
8244
  for (const match of matches) {
6761
8245
  const cveId = match.toUpperCase();
6762
8246
  const key = `${cveId}:${packageName ?? ""}`;
@@ -6773,12 +8257,12 @@ function parseYarnAuditJsonFromString(content) {
6773
8257
  return findings;
6774
8258
  }
6775
8259
  function parseYarnAuditJsonFile(filePath) {
6776
- const content = readFileSync13(filePath, "utf8");
8260
+ const content = readFileSync17(filePath, "utf8");
6777
8261
  return parseYarnAuditJsonFromString(content);
6778
8262
  }
6779
8263
 
6780
8264
  // src/scanner/adapters/sarif.ts
6781
- import { readFileSync as readFileSync14 } from "fs";
8265
+ import { readFileSync as readFileSync18 } from "fs";
6782
8266
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
6783
8267
  function extractPackageName(result) {
6784
8268
  const pkg = result.properties?.["packageName"];
@@ -6816,7 +8300,7 @@ function parseSarifFromString(content) {
6816
8300
  return findings;
6817
8301
  }
6818
8302
  function parseSarifFile(filePath) {
6819
- const content = readFileSync14(filePath, "utf8");
8303
+ const content = readFileSync18(filePath, "utf8");
6820
8304
  return parseSarifFromString(content);
6821
8305
  }
6822
8306
 
@@ -6825,7 +8309,7 @@ function parseScanInput(filePath, format) {
6825
8309
  if (filePath.includes("\0")) {
6826
8310
  throw new Error("Invalid scan input path: path contains null bytes");
6827
8311
  }
6828
- const resolvedPath = resolve3(filePath);
8312
+ const resolvedPath = resolve5(filePath);
6829
8313
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
6830
8314
  if (resolved === "npm-audit") {
6831
8315
  return parseNpmAuditJsonFile(resolvedPath);
@@ -6847,7 +8331,7 @@ async function parseScanInputFromAudit(params) {
6847
8331
  }
6848
8332
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
6849
8333
  const [cmd, ...args] = command;
6850
- const result = await execa10(cmd, args, {
8334
+ const result = await execa11(cmd, args, {
6851
8335
  cwd: params.cwd,
6852
8336
  stdio: "pipe",
6853
8337
  reject: false
@@ -6901,7 +8385,7 @@ function inferFormat(filePath) {
6901
8385
  const ext = extname(filePath).toLowerCase();
6902
8386
  if (ext === ".sarif") return "sarif";
6903
8387
  try {
6904
- const content = readFileSync15(filePath, "utf8");
8388
+ const content = readFileSync19(filePath, "utf8");
6905
8389
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
6906
8390
  if (firstLine) {
6907
8391
  const parsed = JSON.parse(firstLine);
@@ -7312,14 +8796,14 @@ async function remediatePortfolio(targets, options = {}) {
7312
8796
  }
7313
8797
 
7314
8798
  // src/api/update-outdated/index.ts
7315
- import { join as join19 } from "path";
7316
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
7317
- import { execa as execa11 } from "execa";
8799
+ import { join as join22 } from "path";
8800
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
8801
+ import { execa as execa12 } from "execa";
7318
8802
  async function applyBump(params) {
7319
- const pkgPath = join19(params.cwd, "package.json");
8803
+ const pkgPath = join22(params.cwd, "package.json");
7320
8804
  let pkgJson;
7321
8805
  try {
7322
- pkgJson = JSON.parse(readFileSync16(pkgPath, "utf8"));
8806
+ pkgJson = JSON.parse(readFileSync20(pkgPath, "utf8"));
7323
8807
  } catch {
7324
8808
  return {
7325
8809
  applied: false,
@@ -7341,13 +8825,13 @@ async function applyBump(params) {
7341
8825
  const newRange = `${prefix}${params.toVersion}`;
7342
8826
  return withRepoLock(params.cwd, async () => {
7343
8827
  pkgJson[depField][params.packageName] = newRange;
7344
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8828
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7345
8829
  try {
7346
8830
  const [installCmd, ...installArgs] = params.installCommand;
7347
- await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8831
+ await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
7348
8832
  } catch (err) {
7349
8833
  pkgJson[depField][params.packageName] = currentRange;
7350
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8834
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7351
8835
  const message = err instanceof Error ? err.message : String(err);
7352
8836
  return {
7353
8837
  applied: false,
@@ -7357,13 +8841,13 @@ async function applyBump(params) {
7357
8841
  if (params.runTests) {
7358
8842
  try {
7359
8843
  const [testCmd, ...testArgs] = params.testCommand;
7360
- await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8844
+ await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
7361
8845
  } catch (err) {
7362
8846
  pkgJson[depField][params.packageName] = currentRange;
7363
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8847
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7364
8848
  try {
7365
8849
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
7366
- await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8850
+ await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
7367
8851
  } catch {
7368
8852
  }
7369
8853
  const message = err instanceof Error ? err.message : String(err);
@@ -7564,131 +9048,169 @@ async function checkReachability(options) {
7564
9048
  return assessPackageReachability(cwd, options.packageName, options.symbol);
7565
9049
  }
7566
9050
 
7567
- // src/remediation/evaluate-package.ts
7568
- import semver7 from "semver";
7569
- async function evaluatePackage(packageName, options = {}) {
7570
- const version = options.version?.trim();
7571
- const enrich = options.enrichIntelligence !== false;
7572
- let versions = [];
7573
- let latestVersion;
7574
- try {
7575
- versions = await fetchPackageVersions(packageName);
7576
- if (versions.length > 0) {
7577
- const valid = versions.filter((v) => semver7.valid(v)).sort((a, b) => semver7.compare(a, b));
7578
- latestVersion = valid[valid.length - 1];
7579
- }
7580
- } catch {
7581
- }
7582
- let rawVulns = [];
7583
- try {
7584
- rawVulns = await queryOsvPackage(packageName, version);
7585
- } catch {
7586
- rawVulns = [];
7587
- }
7588
- let kevFeed = null;
7589
- if (enrich) {
9051
+ // src/scanner/delta-scan.ts
9052
+ import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
9053
+ import { join as join23 } from "path";
9054
+ import { execa as execa13 } from "execa";
9055
+ import semver9 from "semver";
9056
+
9057
+ // src/scanner/lockfile-diff.ts
9058
+ import YAML2 from "yaml";
9059
+ import semver8 from "semver";
9060
+ function parseLockfileDependencies(filename, content) {
9061
+ const result = /* @__PURE__ */ new Map();
9062
+ const add = (name, ver) => {
9063
+ if (!name || !ver) return;
9064
+ const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
9065
+ let set = result.get(name);
9066
+ if (!set) {
9067
+ set = /* @__PURE__ */ new Set();
9068
+ result.set(name, set);
9069
+ }
9070
+ set.add(cleanVer);
9071
+ };
9072
+ if (filename.endsWith("package-lock.json")) {
7590
9073
  try {
7591
- kevFeed = await fetchCisaKevFeed();
7592
- } catch {
7593
- kevFeed = null;
7594
- }
7595
- }
7596
- const entries = [];
7597
- for (const raw of rawVulns) {
7598
- const cveDetails = parseOsvVuln(raw);
7599
- const affected = cveDetails.affectedPackages.find(
7600
- (p) => p.name.toLowerCase() === packageName.toLowerCase()
7601
- );
7602
- if (version && affected?.vulnerableRange) {
7603
- const cleanVer = semver7.clean(version) || version;
7604
- if (semver7.valid(cleanVer) && !semver7.satisfies(cleanVer, affected.vulnerableRange)) {
7605
- continue;
9074
+ const data = JSON.parse(content);
9075
+ if (data.packages && typeof data.packages === "object") {
9076
+ for (const [key, pkg] of Object.entries(data.packages)) {
9077
+ if (!key || key === "" || typeof pkg !== "object" || !pkg) continue;
9078
+ const idx = key.lastIndexOf("node_modules/");
9079
+ if (idx !== -1) {
9080
+ const rawName = key.slice(idx + "node_modules/".length);
9081
+ const ver = pkg.version;
9082
+ add(rawName, ver);
9083
+ }
9084
+ }
9085
+ }
9086
+ if (data.dependencies && typeof data.dependencies === "object") {
9087
+ const walkDeps = (deps) => {
9088
+ for (const [name, info] of Object.entries(deps)) {
9089
+ if (!info || typeof info !== "object") continue;
9090
+ const depObj = info;
9091
+ add(name, depObj.version);
9092
+ if (depObj.dependencies && typeof depObj.dependencies === "object") {
9093
+ walkDeps(depObj.dependencies);
9094
+ }
9095
+ }
9096
+ };
9097
+ walkDeps(data.dependencies);
7606
9098
  }
9099
+ } catch {
7607
9100
  }
7608
- let inCisaKev = false;
7609
- let epssScore;
7610
- let epssPercentile;
7611
- if (enrich && cveDetails.id.startsWith("CVE-")) {
7612
- if (kevFeed) {
7613
- inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
9101
+ } else if (filename.endsWith("pnpm-lock.yaml")) {
9102
+ try {
9103
+ const data = YAML2.parse(content);
9104
+ if (data?.packages && typeof data.packages === "object") {
9105
+ for (const [key, val] of Object.entries(data.packages)) {
9106
+ let rawKey = key.replace(/^\//, "");
9107
+ const parenIdx = rawKey.indexOf("(");
9108
+ if (parenIdx !== -1) {
9109
+ rawKey = rawKey.slice(0, parenIdx);
9110
+ }
9111
+ const lastAt = rawKey.lastIndexOf("@");
9112
+ if (lastAt > 0) {
9113
+ const name = val?.name ?? rawKey.slice(0, lastAt);
9114
+ const version = val?.version ?? rawKey.slice(lastAt + 1);
9115
+ add(name, version);
9116
+ }
9117
+ }
7614
9118
  }
7615
- try {
7616
- const epss = await fetchEpss(cveDetails.id);
7617
- if (epss) {
7618
- epssScore = Number.parseFloat(epss.epss);
7619
- epssPercentile = Number.parseFloat(epss.percentile);
9119
+ if (data?.snapshots && typeof data.snapshots === "object") {
9120
+ for (const key of Object.keys(data.snapshots)) {
9121
+ let rawKey = key.replace(/^\//, "");
9122
+ const parenIdx = rawKey.indexOf("(");
9123
+ if (parenIdx !== -1) {
9124
+ rawKey = rawKey.slice(0, parenIdx);
9125
+ }
9126
+ const lastAt = rawKey.lastIndexOf("@");
9127
+ if (lastAt > 0) {
9128
+ const name = rawKey.slice(0, lastAt);
9129
+ const version = rawKey.slice(lastAt + 1);
9130
+ add(name, version);
9131
+ }
7620
9132
  }
7621
- } catch {
7622
9133
  }
9134
+ } catch {
7623
9135
  }
7624
- let safeUpgradeVersion;
7625
- if (affected?.firstPatchedVersion) {
7626
- try {
7627
- const safe = await findSafeUpgradeVersion(
7628
- packageName,
7629
- version || "0.0.0",
7630
- affected.firstPatchedVersion
7631
- );
7632
- safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
7633
- } catch {
7634
- safeUpgradeVersion = affected.firstPatchedVersion;
9136
+ } else if (filename.endsWith("yarn.lock")) {
9137
+ const lines = content.split("\n");
9138
+ let currentPackages = [];
9139
+ for (const line of lines) {
9140
+ if (line.startsWith("#") || line.trim() === "") continue;
9141
+ if (!line.startsWith(" ")) {
9142
+ currentPackages = [];
9143
+ const parts = line.split(",").map((p) => p.trim().replace(/^"/, "").replace(/":?$/, ""));
9144
+ for (const part of parts) {
9145
+ const lastAt = part.lastIndexOf("@");
9146
+ if (lastAt > 0) {
9147
+ currentPackages.push(part.slice(0, lastAt));
9148
+ }
9149
+ }
9150
+ } else {
9151
+ const match = line.match(/^\s+version\s+"?([^"\s]+)"?/);
9152
+ if (match && currentPackages.length > 0) {
9153
+ const ver = match[1];
9154
+ for (const pkg of currentPackages) {
9155
+ add(pkg, ver);
9156
+ }
9157
+ currentPackages = [];
9158
+ }
7635
9159
  }
7636
9160
  }
7637
- entries.push({
7638
- cveId: cveDetails.id,
7639
- severity: cveDetails.severity,
7640
- summary: cveDetails.summary,
7641
- vulnerableRange: affected?.vulnerableRange,
7642
- firstPatchedVersion: affected?.firstPatchedVersion,
7643
- safeUpgradeVersion,
7644
- epssScore,
7645
- epssPercentile,
7646
- inCisaKev,
7647
- references: cveDetails.references
7648
- });
7649
9161
  }
7650
- const isVulnerable = entries.length > 0;
7651
- let verdict = "safe";
7652
- if (isVulnerable) {
7653
- const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
7654
- const hasHighOrCritical = entries.some(
7655
- (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
7656
- );
7657
- if (hasKevOrHighEpss) {
7658
- verdict = "actively-exploited";
7659
- } else if (hasHighOrCritical) {
7660
- verdict = "vulnerable";
7661
- } else {
7662
- verdict = "caution";
9162
+ return result;
9163
+ }
9164
+ function computeLockfileDiff(filename, oldContent, newContent) {
9165
+ const changes = [];
9166
+ const oldDeps = oldContent ? parseLockfileDependencies(filename, oldContent) : /* @__PURE__ */ new Map();
9167
+ const newDeps = newContent ? parseLockfileDependencies(filename, newContent) : /* @__PURE__ */ new Map();
9168
+ const allPkgs = /* @__PURE__ */ new Set([...oldDeps.keys(), ...newDeps.keys()]);
9169
+ for (const pkg of allPkgs) {
9170
+ const oldVers = oldDeps.get(pkg) ?? /* @__PURE__ */ new Set();
9171
+ const newVers = newDeps.get(pkg) ?? /* @__PURE__ */ new Set();
9172
+ for (const nVer of newVers) {
9173
+ if (!oldVers.has(nVer)) {
9174
+ if (oldVers.size === 0) {
9175
+ changes.push({
9176
+ packageName: pkg,
9177
+ changeType: "added",
9178
+ newVersion: nVer,
9179
+ dependencyType: "transitive",
9180
+ manifestPath: filename
9181
+ });
9182
+ } else {
9183
+ const [firstOld] = oldVers;
9184
+ const cleanOld = semver8.clean(firstOld) || semver8.coerce(firstOld)?.version;
9185
+ const cleanNew = semver8.clean(nVer) || semver8.coerce(nVer)?.version;
9186
+ const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
9187
+ changes.push({
9188
+ packageName: pkg,
9189
+ changeType: isUpgrade ? "upgraded" : "downgraded",
9190
+ oldVersion: firstOld,
9191
+ newVersion: nVer,
9192
+ dependencyType: "transitive",
9193
+ manifestPath: filename
9194
+ });
9195
+ }
9196
+ }
9197
+ }
9198
+ if (newVers.size === 0 && oldVers.size > 0) {
9199
+ for (const oVer of oldVers) {
9200
+ changes.push({
9201
+ packageName: pkg,
9202
+ changeType: "removed",
9203
+ oldVersion: oVer,
9204
+ dependencyType: "transitive",
9205
+ manifestPath: filename
9206
+ });
9207
+ }
7663
9208
  }
7664
9209
  }
7665
- let recommendedVersion;
7666
- if (isVulnerable) {
7667
- const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver7.valid(v))).sort((a, b) => semver7.compare(a, b));
7668
- recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
7669
- } else {
7670
- recommendedVersion = version || latestVersion;
7671
- }
7672
- const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
7673
- return {
7674
- schemaVersion: "1.0",
7675
- packageName,
7676
- evaluatedVersion: version,
7677
- isVulnerable,
7678
- verdict,
7679
- summary,
7680
- vulnerabilities: entries,
7681
- recommendedVersion,
7682
- latestVersion,
7683
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
7684
- };
9210
+ return changes;
7685
9211
  }
7686
9212
 
7687
9213
  // src/scanner/delta-scan.ts
7688
- import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
7689
- import { join as join20 } from "path";
7690
- import { execa as execa12 } from "execa";
7691
- import semver8 from "semver";
7692
9214
  var DEP_SECTIONS = [
7693
9215
  "dependencies",
7694
9216
  "devDependencies",
@@ -7698,7 +9220,7 @@ var DEP_SECTIONS = [
7698
9220
  async function getChangedManifestPaths(cwd, baseRef) {
7699
9221
  const manifests = /* @__PURE__ */ new Set();
7700
9222
  try {
7701
- const { stdout: diffFiles } = await execa12(
9223
+ const { stdout: diffFiles } = await execa13(
7702
9224
  "git",
7703
9225
  ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
7704
9226
  { cwd, reject: false }
@@ -7709,7 +9231,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7709
9231
  manifests.add(trimmed);
7710
9232
  }
7711
9233
  }
7712
- const { stdout: untrackedFiles } = await execa12(
9234
+ const { stdout: untrackedFiles } = await execa13(
7713
9235
  "git",
7714
9236
  ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
7715
9237
  { cwd, reject: false }
@@ -7721,7 +9243,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7721
9243
  }
7722
9244
  }
7723
9245
  } catch {
7724
- if (existsSync12(join20(cwd, "package.json"))) {
9246
+ if (existsSync16(join23(cwd, "package.json"))) {
7725
9247
  manifests.add("package.json");
7726
9248
  }
7727
9249
  }
@@ -7729,7 +9251,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7729
9251
  }
7730
9252
  async function getOldManifestContent(cwd, baseRef, relPath) {
7731
9253
  try {
7732
- const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
9254
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
7733
9255
  cwd,
7734
9256
  reject: false
7735
9257
  });
@@ -7740,10 +9262,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
7740
9262
  }
7741
9263
  }
7742
9264
  function getNewManifestContent(cwd, relPath) {
7743
- const fullPath = join20(cwd, relPath);
7744
- if (!existsSync12(fullPath)) return null;
9265
+ const fullPath = join23(cwd, relPath);
9266
+ if (!existsSync16(fullPath)) return null;
7745
9267
  try {
7746
- return JSON.parse(readFileSync17(fullPath, "utf8"));
9268
+ return JSON.parse(readFileSync21(fullPath, "utf8"));
7747
9269
  } catch {
7748
9270
  return null;
7749
9271
  }
@@ -7764,9 +9286,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7764
9286
  manifestPath: relPath
7765
9287
  });
7766
9288
  } else if (oldVer !== newVer) {
7767
- const cleanOld = semver8.clean(oldVer) || semver8.coerce(oldVer)?.version;
7768
- const cleanNew = semver8.clean(newVer) || semver8.coerce(newVer)?.version;
7769
- const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
9289
+ const cleanOld = semver9.clean(oldVer) || semver9.coerce(oldVer)?.version;
9290
+ const cleanNew = semver9.clean(newVer) || semver9.coerce(newVer)?.version;
9291
+ const isUpgrade = cleanOld && cleanNew && semver9.valid(cleanOld) && semver9.valid(cleanNew) ? semver9.gt(cleanNew, cleanOld) : true;
7770
9292
  changes.push({
7771
9293
  packageName: pkgName,
7772
9294
  changeType: isUpgrade ? "upgraded" : "downgraded",
@@ -7791,22 +9313,89 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7791
9313
  }
7792
9314
  return changes;
7793
9315
  }
9316
+ var LOCKFILE_PATTERNS = [
9317
+ "package-lock.json",
9318
+ "**/package-lock.json",
9319
+ "pnpm-lock.yaml",
9320
+ "**/pnpm-lock.yaml",
9321
+ "yarn.lock",
9322
+ "**/yarn.lock"
9323
+ ];
9324
+ async function getChangedLockfilePaths(cwd, baseRef) {
9325
+ const lockfiles = /* @__PURE__ */ new Set();
9326
+ try {
9327
+ const { stdout: diffFiles } = await execa13(
9328
+ "git",
9329
+ ["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
9330
+ { cwd, reject: false }
9331
+ );
9332
+ for (const f of diffFiles.split("\n")) {
9333
+ const trimmed = f.trim();
9334
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
9335
+ lockfiles.add(trimmed);
9336
+ }
9337
+ }
9338
+ const { stdout: untrackedFiles } = await execa13(
9339
+ "git",
9340
+ ["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
9341
+ { cwd, reject: false }
9342
+ );
9343
+ for (const f of untrackedFiles.split("\n")) {
9344
+ const trimmed = f.trim();
9345
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
9346
+ lockfiles.add(trimmed);
9347
+ }
9348
+ }
9349
+ } catch {
9350
+ }
9351
+ return Array.from(lockfiles);
9352
+ }
9353
+ async function getFileContentAtRef(cwd, baseRef, relPath) {
9354
+ try {
9355
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
9356
+ cwd,
9357
+ reject: false
9358
+ });
9359
+ return stdout || null;
9360
+ } catch {
9361
+ return null;
9362
+ }
9363
+ }
7794
9364
  async function scanDelta(options = {}) {
7795
9365
  const cwd = options.cwd ?? process.cwd();
7796
9366
  const baseRef = options.baseRef ?? "HEAD";
7797
9367
  const changedManifests = await getChangedManifestPaths(cwd, baseRef);
9368
+ const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
7798
9369
  const dependencyChanges = [];
9370
+ const directPkgs = /* @__PURE__ */ new Set();
7799
9371
  for (const manifestRel of changedManifests) {
7800
9372
  const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
7801
9373
  const newPkg = getNewManifestContent(cwd, manifestRel);
7802
9374
  const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
9375
+ for (const d of diffs) {
9376
+ directPkgs.add(d.packageName);
9377
+ }
7803
9378
  dependencyChanges.push(...diffs);
7804
9379
  }
9380
+ for (const lockfileRel of changedLockfiles) {
9381
+ const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
9382
+ const fullPath = join23(cwd, lockfileRel);
9383
+ const newContent = existsSync16(fullPath) ? readFileSync21(fullPath, "utf8") : null;
9384
+ const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
9385
+ for (const change of lockDiffs) {
9386
+ if (!directPkgs.has(change.packageName)) {
9387
+ dependencyChanges.push(change);
9388
+ }
9389
+ }
9390
+ if (!changedManifests.includes(lockfileRel)) {
9391
+ changedManifests.push(lockfileRel);
9392
+ }
9393
+ }
7805
9394
  const introducedFindings = [];
7806
9395
  const resolvedFindings = [];
7807
9396
  for (const change of dependencyChanges) {
7808
9397
  if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
7809
- const cleanVer = semver8.coerce(change.newVersion)?.version ?? change.newVersion;
9398
+ const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
7810
9399
  const evaluation = await evaluatePackage(change.packageName, {
7811
9400
  version: cleanVer,
7812
9401
  enrichIntelligence: true
@@ -7826,12 +9415,12 @@ async function scanDelta(options = {}) {
7826
9415
  }
7827
9416
  }
7828
9417
  if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
7829
- const cleanOldVer = semver8.coerce(change.oldVersion)?.version ?? change.oldVersion;
9418
+ const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
7830
9419
  const oldEval = await evaluatePackage(change.packageName, {
7831
9420
  version: cleanOldVer,
7832
9421
  enrichIntelligence: true
7833
9422
  });
7834
- const cleanNewVer = change.newVersion ? semver8.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
9423
+ const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
7835
9424
  const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
7836
9425
  version: cleanNewVer,
7837
9426
  enrichIntelligence: false
@@ -7949,6 +9538,7 @@ export {
7949
9538
  listPatchArtifacts,
7950
9539
  planRemediation,
7951
9540
  pollJob,
9541
+ reconcilePatchArtifacts,
7952
9542
  remediate,
7953
9543
  remediateFromScan,
7954
9544
  remediatePortfolio,