autoremediator 0.19.0 → 0.20.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 (185) hide show
  1. package/dist/api/change-request/index.d.ts.map +1 -1
  2. package/dist/api/options-schema.d.ts.map +1 -1
  3. package/dist/api/patches/reconcile.d.ts.map +1 -1
  4. package/dist/api/scan-execution.d.ts.map +1 -1
  5. package/dist/api/vex/index.d.ts +6 -2
  6. package/dist/api/vex/index.d.ts.map +1 -1
  7. package/dist/{chunk-7BORIBPY.js → chunk-ZGLGT4XQ.js} +2589 -885
  8. package/dist/cli/program.d.ts.map +1 -1
  9. package/dist/cli/runners.d.ts.map +1 -1
  10. package/dist/cli/triage.d.ts +9 -1
  11. package/dist/cli/triage.d.ts.map +1 -1
  12. package/dist/cli/types.d.ts.map +1 -1
  13. package/dist/cli.js +74 -33
  14. package/dist/index.js +2464 -882
  15. package/dist/index.js.map +1 -1
  16. package/dist/intelligence/snapshot.d.ts +2 -0
  17. package/dist/intelligence/snapshot.d.ts.map +1 -0
  18. package/dist/intelligence/sources/registry.d.ts +1 -0
  19. package/dist/intelligence/sources/registry.d.ts.map +1 -1
  20. package/dist/intelligence/sources/snapshot.d.ts +1 -1
  21. package/dist/intelligence/sources/snapshot.d.ts.map +1 -1
  22. package/dist/mcp/server.js +1 -1
  23. package/dist/openapi/handlers/routes.d.ts.map +1 -1
  24. package/dist/openapi/handlers/validators.d.ts.map +1 -1
  25. package/dist/openapi/http-utils.d.ts.map +1 -1
  26. package/dist/openapi/server.js +20 -7
  27. package/dist/platform/jobs.d.ts +39 -1
  28. package/dist/platform/jobs.d.ts.map +1 -1
  29. package/dist/platform/policy.d.ts +10 -0
  30. package/dist/platform/policy.d.ts.map +1 -1
  31. package/dist/platform/rollback.d.ts +11 -0
  32. package/dist/platform/rollback.d.ts.map +1 -1
  33. package/dist/platform/types.d.ts +5 -0
  34. package/dist/platform/types.d.ts.map +1 -1
  35. package/dist/remediation/local/disposition.d.ts +5 -2
  36. package/dist/remediation/local/disposition.d.ts.map +1 -1
  37. package/dist/remediation/local/fallback.d.ts.map +1 -1
  38. package/dist/remediation/local/options.d.ts.map +1 -1
  39. package/dist/remediation/local/primary-strategy.d.ts +4 -0
  40. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  41. package/dist/remediation/local/run.d.ts.map +1 -1
  42. package/dist/remediation/pipeline-runtime/tool-wrappers.d.ts.map +1 -1
  43. package/dist/remediation/proof-of-fix/runner.d.ts +52 -0
  44. package/dist/remediation/proof-of-fix/runner.d.ts.map +1 -1
  45. package/dist/remediation/refactor/call-site-finder.d.ts +21 -0
  46. package/dist/remediation/refactor/call-site-finder.d.ts.map +1 -1
  47. package/dist/remediation/refactor/refactor-agent.d.ts +18 -0
  48. package/dist/remediation/refactor/refactor-agent.d.ts.map +1 -1
  49. package/dist/remediation/tools/apply-package-override/index.d.ts +4 -0
  50. package/dist/remediation/tools/apply-package-override/index.d.ts.map +1 -1
  51. package/dist/remediation/tools/apply-patch-file/index.d.ts +2 -1
  52. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  53. package/dist/remediation/tools/apply-version-bump.d.ts +4 -0
  54. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  55. package/dist/remediation/tools/check-inventory.d.ts +1 -0
  56. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  57. package/dist/remediation/tools/fetch-package-source.d.ts.map +1 -1
  58. package/dist/remediation/tools/find-fixed-version.d.ts.map +1 -1
  59. package/dist/remediation/tools/generate-patch/helpers.d.ts +3 -3
  60. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  61. package/package.json +6 -4
  62. package/dist/api/change-request/index.test.d.ts +0 -2
  63. package/dist/api/change-request/index.test.d.ts.map +0 -1
  64. package/dist/api/delta-scan/delta-scan.test.d.ts +0 -2
  65. package/dist/api/delta-scan/delta-scan.test.d.ts.map +0 -1
  66. package/dist/api/evaluate/evaluate.test.d.ts +0 -2
  67. package/dist/api/evaluate/evaluate.test.d.ts.map +0 -1
  68. package/dist/api/index.test.d.ts +0 -2
  69. package/dist/api/index.test.d.ts.map +0 -1
  70. package/dist/api/jobs/index.test.d.ts +0 -2
  71. package/dist/api/jobs/index.test.d.ts.map +0 -1
  72. package/dist/api/patches/reconcile.test.d.ts +0 -2
  73. package/dist/api/patches/reconcile.test.d.ts.map +0 -1
  74. package/dist/api/patches.test.d.ts +0 -2
  75. package/dist/api/patches.test.d.ts.map +0 -1
  76. package/dist/api/portfolio/portfolio.test.d.ts +0 -2
  77. package/dist/api/portfolio/portfolio.test.d.ts.map +0 -1
  78. package/dist/api/portfolio/risk-score.test.d.ts +0 -2
  79. package/dist/api/portfolio/risk-score.test.d.ts.map +0 -1
  80. package/dist/api/reachability/reachability.test.d.ts +0 -2
  81. package/dist/api/reachability/reachability.test.d.ts.map +0 -1
  82. package/dist/api/update-outdated/index.test.d.ts +0 -2
  83. package/dist/api/update-outdated/index.test.d.ts.map +0 -1
  84. package/dist/api/vex/index.test.d.ts +0 -2
  85. package/dist/api/vex/index.test.d.ts.map +0 -1
  86. package/dist/cli/index.test.d.ts +0 -2
  87. package/dist/cli/index.test.d.ts.map +0 -1
  88. package/dist/intelligence/sources/certcc.test.d.ts +0 -2
  89. package/dist/intelligence/sources/certcc.test.d.ts.map +0 -1
  90. package/dist/intelligence/sources/cisa-kev.test.d.ts +0 -2
  91. package/dist/intelligence/sources/cisa-kev.test.d.ts.map +0 -1
  92. package/dist/intelligence/sources/cve-services.test.d.ts +0 -2
  93. package/dist/intelligence/sources/cve-services.test.d.ts.map +0 -1
  94. package/dist/intelligence/sources/deps-dev.test.d.ts +0 -2
  95. package/dist/intelligence/sources/deps-dev.test.d.ts.map +0 -1
  96. package/dist/intelligence/sources/epss.test.d.ts +0 -2
  97. package/dist/intelligence/sources/epss.test.d.ts.map +0 -1
  98. package/dist/intelligence/sources/external-feeds.test.d.ts +0 -2
  99. package/dist/intelligence/sources/external-feeds.test.d.ts.map +0 -1
  100. package/dist/intelligence/sources/gitlab-advisory.test.d.ts +0 -2
  101. package/dist/intelligence/sources/gitlab-advisory.test.d.ts.map +0 -1
  102. package/dist/intelligence/sources/nvd.test.d.ts +0 -2
  103. package/dist/intelligence/sources/nvd.test.d.ts.map +0 -1
  104. package/dist/intelligence/sources/ossf-scorecard.test.d.ts +0 -2
  105. package/dist/intelligence/sources/ossf-scorecard.test.d.ts.map +0 -1
  106. package/dist/intelligence/sources/osv.test.d.ts +0 -2
  107. package/dist/intelligence/sources/osv.test.d.ts.map +0 -1
  108. package/dist/intelligence/sources/registry.test.d.ts +0 -2
  109. package/dist/intelligence/sources/registry.test.d.ts.map +0 -1
  110. package/dist/intelligence/sources/snapshot.test.d.ts +0 -2
  111. package/dist/intelligence/sources/snapshot.test.d.ts.map +0 -1
  112. package/dist/mcp/server.test.d.ts +0 -2
  113. package/dist/mcp/server.test.d.ts.map +0 -1
  114. package/dist/openapi/server.test.d.ts +0 -2
  115. package/dist/openapi/server.test.d.ts.map +0 -1
  116. package/dist/platform/architecture-guardrails.test.d.ts +0 -2
  117. package/dist/platform/architecture-guardrails.test.d.ts.map +0 -1
  118. package/dist/platform/audit-logger.test.d.ts +0 -2
  119. package/dist/platform/audit-logger.test.d.ts.map +0 -1
  120. package/dist/platform/escalation.test.d.ts +0 -2
  121. package/dist/platform/escalation.test.d.ts.map +0 -1
  122. package/dist/platform/evidence.test.d.ts +0 -2
  123. package/dist/platform/evidence.test.d.ts.map +0 -1
  124. package/dist/platform/package-manager/index.test.d.ts +0 -2
  125. package/dist/platform/package-manager/index.test.d.ts.map +0 -1
  126. package/dist/platform/package-manager/list-parser.test.d.ts +0 -2
  127. package/dist/platform/package-manager/list-parser.test.d.ts.map +0 -1
  128. package/dist/platform/package-manager.test.d.ts +0 -2
  129. package/dist/platform/package-manager.test.d.ts.map +0 -1
  130. package/dist/platform/policy.test.d.ts +0 -2
  131. package/dist/platform/policy.test.d.ts.map +0 -1
  132. package/dist/platform/repo-lock.test.d.ts +0 -2
  133. package/dist/platform/repo-lock.test.d.ts.map +0 -1
  134. package/dist/platform/rollback.test.d.ts +0 -2
  135. package/dist/platform/rollback.test.d.ts.map +0 -1
  136. package/dist/platform/safe-exec.test.d.ts +0 -2
  137. package/dist/platform/safe-exec.test.d.ts.map +0 -1
  138. package/dist/platform/sla.test.d.ts +0 -2
  139. package/dist/platform/sla.test.d.ts.map +0 -1
  140. package/dist/platform/supply-chain.test.d.ts +0 -2
  141. package/dist/platform/supply-chain.test.d.ts.map +0 -1
  142. package/dist/platform/workspace.test.d.ts +0 -2
  143. package/dist/platform/workspace.test.d.ts.map +0 -1
  144. package/dist/remediation/local/consensus-gate.test.d.ts +0 -2
  145. package/dist/remediation/local/consensus-gate.test.d.ts.map +0 -1
  146. package/dist/remediation/local/disposition.test.d.ts +0 -2
  147. package/dist/remediation/local/disposition.test.d.ts.map +0 -1
  148. package/dist/remediation/local/run.secops.test.d.ts +0 -2
  149. package/dist/remediation/local/run.secops.test.d.ts.map +0 -1
  150. package/dist/remediation/local/sbom.test.d.ts +0 -2
  151. package/dist/remediation/local/sbom.test.d.ts.map +0 -1
  152. package/dist/remediation/local/vulnerability-match.test.d.ts +0 -2
  153. package/dist/remediation/local/vulnerability-match.test.d.ts.map +0 -1
  154. package/dist/remediation/pipeline.test.d.ts +0 -2
  155. package/dist/remediation/pipeline.test.d.ts.map +0 -1
  156. package/dist/remediation/proof-of-fix/runner.test.d.ts +0 -2
  157. package/dist/remediation/proof-of-fix/runner.test.d.ts.map +0 -1
  158. package/dist/remediation/refactor/refactor.test.d.ts +0 -2
  159. package/dist/remediation/refactor/refactor.test.d.ts.map +0 -1
  160. package/dist/remediation/tools/apply-package-override.test.d.ts +0 -2
  161. package/dist/remediation/tools/apply-package-override.test.d.ts.map +0 -1
  162. package/dist/remediation/tools/apply-patch-file.test.d.ts +0 -2
  163. package/dist/remediation/tools/apply-patch-file.test.d.ts.map +0 -1
  164. package/dist/remediation/tools/apply-version-bump.test.d.ts +0 -2
  165. package/dist/remediation/tools/apply-version-bump.test.d.ts.map +0 -1
  166. package/dist/remediation/tools/check-exploit-signal.test.d.ts +0 -2
  167. package/dist/remediation/tools/check-exploit-signal.test.d.ts.map +0 -1
  168. package/dist/remediation/tools/check-reachability.test.d.ts +0 -2
  169. package/dist/remediation/tools/check-reachability.test.d.ts.map +0 -1
  170. package/dist/remediation/tools/check-suppression.test.d.ts +0 -2
  171. package/dist/remediation/tools/check-suppression.test.d.ts.map +0 -1
  172. package/dist/remediation/tools/generate-patch/helpers.test.d.ts +0 -2
  173. package/dist/remediation/tools/generate-patch/helpers.test.d.ts.map +0 -1
  174. package/dist/remediation/tools/lookup-cve.test.d.ts +0 -2
  175. package/dist/remediation/tools/lookup-cve.test.d.ts.map +0 -1
  176. package/dist/scanner/adapters/npm-audit.test.d.ts +0 -2
  177. package/dist/scanner/adapters/npm-audit.test.d.ts.map +0 -1
  178. package/dist/scanner/adapters/sarif.test.d.ts +0 -2
  179. package/dist/scanner/adapters/sarif.test.d.ts.map +0 -1
  180. package/dist/scanner/adapters/yarn-audit.test.d.ts +0 -2
  181. package/dist/scanner/adapters/yarn-audit.test.d.ts.map +0 -1
  182. package/dist/scanner/lockfile-diff.test.d.ts +0 -2
  183. package/dist/scanner/lockfile-diff.test.d.ts.map +0 -1
  184. package/dist/scanner/parse-input.test.d.ts +0 -2
  185. package/dist/scanner/parse-input.test.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -827,6 +827,8 @@ function toSarifOutput(report) {
827
827
  }
828
828
 
829
829
  // src/api/vex/index.ts
830
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
831
+ import { dirname } from "path";
830
832
  import { randomUUID } from "crypto";
831
833
  function mapReportToVulnerability(report) {
832
834
  const hasResolved = report.results.some((r) => r.applied && !r.unresolvedReason);
@@ -904,22 +906,22 @@ function toCycloneDxVex(report, options = {}) {
904
906
  }
905
907
 
906
908
  // src/api/patches/inspection.ts
907
- import { existsSync as existsSync5 } from "fs";
909
+ import { existsSync as existsSync6 } from "fs";
908
910
  import { readdir, readFile as readFile2, stat } from "fs/promises";
909
911
  import { join as join5 } from "path";
910
912
 
911
913
  // src/remediation/strategies/patch-utils.ts
912
- import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
914
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
913
915
  import { join as join4 } from "path";
914
916
  import { execa as execa2 } from "execa";
915
917
 
916
918
  // src/platform/package-manager/index.ts
917
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
919
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
918
920
  import { join as join3 } from "path";
919
921
  import { execa } from "execa";
920
922
 
921
923
  // src/platform/package-manager/list-parser.ts
922
- import { readFileSync } from "fs";
924
+ import { readFileSync as readFileSync2 } from "fs";
923
925
  import { join } from "path";
924
926
  function parsePackageManagerListOutput(pm, stdout) {
925
927
  const versions = /* @__PURE__ */ new Map();
@@ -992,7 +994,7 @@ function resolveDenoInventory(cwd) {
992
994
  const versions = /* @__PURE__ */ new Map();
993
995
  let raw;
994
996
  try {
995
- raw = readFileSync(join(cwd, "deno.lock"), "utf8");
997
+ raw = readFileSync2(join(cwd, "deno.lock"), "utf8");
996
998
  } catch {
997
999
  return versions;
998
1000
  }
@@ -1032,7 +1034,7 @@ function extractNameVersion(spec, versions) {
1032
1034
  }
1033
1035
 
1034
1036
  // src/platform/package-manager/driver.ts
1035
- import { existsSync } from "fs";
1037
+ import { existsSync as existsSync2 } from "fs";
1036
1038
  import { join as join2 } from "path";
1037
1039
 
1038
1040
  // src/platform/package-manager/index.ts
@@ -1046,10 +1048,10 @@ async function getYarnMajorVersion(cwd) {
1046
1048
  }
1047
1049
  }
1048
1050
  function detectPackageManager(cwd) {
1049
- if (existsSync2(join3(cwd, "pnpm-lock.yaml"))) return "pnpm";
1050
- if (existsSync2(join3(cwd, "yarn.lock"))) return "yarn";
1051
- if (existsSync2(join3(cwd, "bun.lockb")) || existsSync2(join3(cwd, "bun.lock"))) return "bun";
1052
- if (existsSync2(join3(cwd, "deno.lock"))) return "deno";
1051
+ if (existsSync3(join3(cwd, "pnpm-lock.yaml"))) return "pnpm";
1052
+ if (existsSync3(join3(cwd, "yarn.lock"))) return "yarn";
1053
+ if (existsSync3(join3(cwd, "bun.lockb")) || existsSync3(join3(cwd, "bun.lock"))) return "bun";
1054
+ if (existsSync3(join3(cwd, "deno.lock"))) return "deno";
1053
1055
  return "npm";
1054
1056
  }
1055
1057
  function withWorkspace(command, pm, workspace) {
@@ -1110,10 +1112,10 @@ function resolveTestCommand(pm, constraints) {
1110
1112
  function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
1111
1113
  const pkgJsonPath = join3(cwd, "package.json");
1112
1114
  let hasTypecheckScript = false;
1113
- const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
1114
- if (existsSync2(pkgJsonPath)) {
1115
+ const hasTsConfig = existsSync3(join3(cwd, "tsconfig.json"));
1116
+ if (existsSync3(pkgJsonPath)) {
1115
1117
  try {
1116
- const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
1118
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
1117
1119
  if (pkg.scripts?.typecheck) {
1118
1120
  hasTypecheckScript = true;
1119
1121
  }
@@ -1248,7 +1250,7 @@ function validatePatchDiff(patchContent) {
1248
1250
  }
1249
1251
 
1250
1252
  // src/api/patches/helpers.ts
1251
- import { existsSync as existsSync4 } from "fs";
1253
+ import { existsSync as existsSync5 } from "fs";
1252
1254
  import { readFile } from "fs/promises";
1253
1255
  import { isAbsolute, resolve, sep } from "path";
1254
1256
  var DEFAULT_PATCHES_DIR = "./patches";
@@ -1267,7 +1269,7 @@ function resolveArtifactPath(cwd, patchFilePath) {
1267
1269
  return resolved;
1268
1270
  }
1269
1271
  async function readManifest(manifestFilePath) {
1270
- if (!existsSync4(manifestFilePath)) {
1272
+ if (!existsSync5(manifestFilePath)) {
1271
1273
  return void 0;
1272
1274
  }
1273
1275
  try {
@@ -1312,7 +1314,7 @@ function toSummary(inspection) {
1312
1314
  async function listPatchArtifacts(options = {}) {
1313
1315
  const cwd = options.cwd ?? process.cwd();
1314
1316
  const patchesDirPath = resolvePatchesDir(cwd, options.patchesDir);
1315
- if (!existsSync5(patchesDirPath)) {
1317
+ if (!existsSync6(patchesDirPath)) {
1316
1318
  return [];
1317
1319
  }
1318
1320
  const entries = await readdir(patchesDirPath, { withFileTypes: true });
@@ -1330,10 +1332,10 @@ async function inspectPatchArtifact(patchFilePath, options = {}) {
1330
1332
  const resolvedPatchPath = resolveArtifactPath(cwd, patchFilePath);
1331
1333
  const patchFileName = resolvedPatchPath.split("/").pop() ?? resolvedPatchPath;
1332
1334
  const manifestFilePath = `${resolvedPatchPath}.json`;
1333
- if (!existsSync5(resolvedPatchPath)) {
1335
+ if (!existsSync6(resolvedPatchPath)) {
1334
1336
  return {
1335
1337
  patchFilePath: resolvedPatchPath,
1336
- manifestFilePath: existsSync5(manifestFilePath) ? manifestFilePath : void 0,
1338
+ manifestFilePath: existsSync6(manifestFilePath) ? manifestFilePath : void 0,
1337
1339
  patchFileName,
1338
1340
  exists: false,
1339
1341
  diffValid: false,
@@ -1377,14 +1379,14 @@ function defineTool(config) {
1377
1379
 
1378
1380
  // src/remediation/tools/check-inventory.ts
1379
1381
  import { z } from "zod";
1380
- import { readFileSync as readFileSync6 } from "fs";
1382
+ import { readFileSync as readFileSync7 } from "fs";
1381
1383
  import { join as join8 } from "path";
1382
1384
  import { execa as execa3 } from "execa";
1383
1385
 
1384
1386
  // src/platform/policy.ts
1385
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1386
- import { join as join6 } from "path";
1387
- import { parse as yamlParse } from "yaml";
1387
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1388
+ import { dirname as dirname2, join as join6 } from "path";
1389
+ import { parse as yamlParse, stringify as yamlStringify } from "yaml";
1388
1390
  var DEFAULT_POLICY = {
1389
1391
  allowMajorBumps: false,
1390
1392
  denyPackages: [],
@@ -1414,14 +1416,28 @@ var DEFAULT_POLICY = {
1414
1416
  escalationGraph: void 0
1415
1417
  };
1416
1418
  function loadPolicy(cwd, explicitPath) {
1417
- const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
1418
- if (!existsSync6(candidate)) return DEFAULT_POLICY;
1419
+ let content;
1420
+ if (explicitPath && (explicitPath.trim().startsWith("{") || explicitPath.trim().startsWith("---"))) {
1421
+ content = explicitPath;
1422
+ } else {
1423
+ const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
1424
+ if (!existsSync7(candidate)) return DEFAULT_POLICY;
1425
+ try {
1426
+ content = readFileSync5(candidate, "utf8");
1427
+ } catch {
1428
+ return DEFAULT_POLICY;
1429
+ }
1430
+ }
1419
1431
  try {
1420
- const parsed = yamlParse(readFileSync4(candidate, "utf8"));
1432
+ const parsed = yamlParse(content);
1421
1433
  return {
1422
1434
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
1423
- denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
1424
- allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
1435
+ denyPackages: Array.isArray(parsed.denyPackages) ? parsed.denyPackages.filter(
1436
+ (p) => typeof p === "string" && p.trim().length > 0
1437
+ ) : DEFAULT_POLICY.denyPackages,
1438
+ allowPackages: Array.isArray(parsed.allowPackages) ? parsed.allowPackages.filter(
1439
+ (p) => typeof p === "string" && p.trim().length > 0
1440
+ ) : DEFAULT_POLICY.allowPackages,
1425
1441
  constraints: {
1426
1442
  directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
1427
1443
  preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false,
@@ -1465,20 +1481,49 @@ function loadPolicy(cwd, explicitPath) {
1465
1481
  return DEFAULT_POLICY;
1466
1482
  }
1467
1483
  }
1484
+ function isPackageInList(list, packageName) {
1485
+ if (!list || !Array.isArray(list) || typeof packageName !== "string" || !packageName.trim()) {
1486
+ return false;
1487
+ }
1488
+ const target = packageName.trim();
1489
+ return list.some((pattern) => {
1490
+ if (typeof pattern !== "string") return false;
1491
+ const cleanPattern = pattern.trim();
1492
+ if (!cleanPattern) return false;
1493
+ if (cleanPattern === target) return true;
1494
+ if (cleanPattern === "*") return true;
1495
+ if (cleanPattern.endsWith("/*")) {
1496
+ const prefix = cleanPattern.slice(0, -1);
1497
+ return target.startsWith(prefix);
1498
+ }
1499
+ if (cleanPattern.endsWith("*")) {
1500
+ const prefix = cleanPattern.slice(0, -1);
1501
+ return target.startsWith(prefix);
1502
+ }
1503
+ return false;
1504
+ });
1505
+ }
1468
1506
  function isPackageAllowed(policy, packageName) {
1469
- if (policy.denyPackages.includes(packageName)) return false;
1470
- if (policy.allowPackages.length > 0 && !policy.allowPackages.includes(packageName)) {
1507
+ if (!policy || typeof policy !== "object" || typeof packageName !== "string") {
1508
+ return true;
1509
+ }
1510
+ if (isPackageInList(policy.denyPackages, packageName)) return false;
1511
+ const allowList = Array.isArray(policy.allowPackages) ? policy.allowPackages.filter((p) => typeof p === "string" && p.trim().length > 0) : [];
1512
+ if (allowList.length > 0 && !isPackageInList(allowList, packageName)) {
1471
1513
  return false;
1472
1514
  }
1473
1515
  return true;
1474
1516
  }
1475
1517
  function isActiveSuppression(suppression) {
1518
+ if (!suppression || typeof suppression !== "object") return false;
1476
1519
  if (!suppression.expiresAt) return true;
1477
- return new Date(suppression.expiresAt) > /* @__PURE__ */ new Date();
1520
+ const expires = new Date(suppression.expiresAt).getTime();
1521
+ if (isNaN(expires)) return true;
1522
+ return expires > Date.now();
1478
1523
  }
1479
1524
  function loadSuppressionsFile(filePath) {
1480
1525
  try {
1481
- const content = readFileSync4(filePath, "utf8");
1526
+ const content = readFileSync5(filePath, "utf8");
1482
1527
  const parsed = yamlParse(content);
1483
1528
  return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
1484
1529
  } catch {
@@ -1486,6 +1531,9 @@ function loadSuppressionsFile(filePath) {
1486
1531
  }
1487
1532
  }
1488
1533
  function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1534
+ if (typeof severity !== "string" || !slaPolicy || typeof slaPolicy !== "object") {
1535
+ return null;
1536
+ }
1489
1537
  const severityKey = severity.toLowerCase();
1490
1538
  const deadlineHours = slaPolicy[severityKey];
1491
1539
  if (typeof deadlineHours !== "number") return null;
@@ -1504,18 +1552,21 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1504
1552
  };
1505
1553
  }
1506
1554
 
1555
+ // src/remediation/tools/check-inventory.ts
1556
+ import semver from "semver";
1557
+
1507
1558
  // src/platform/workspace.ts
1508
- import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1559
+ import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6, statSync } from "fs";
1509
1560
  import { join as join7, resolve as resolve2, relative } from "path";
1510
1561
  import yaml from "yaml";
1511
1562
  function findWorkspaceRoot(startDir) {
1512
1563
  let curr = resolve2(startDir);
1513
1564
  while (true) {
1514
- if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
1565
+ if (existsSync8(join7(curr, "pnpm-workspace.yaml"))) return curr;
1515
1566
  const pkgPath = join7(curr, "package.json");
1516
- if (existsSync7(pkgPath)) {
1567
+ if (existsSync8(pkgPath)) {
1517
1568
  try {
1518
- const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
1569
+ const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
1519
1570
  if (parsed.workspaces) return curr;
1520
1571
  } catch {
1521
1572
  }
@@ -1531,9 +1582,9 @@ function findWorkspacePackages(cwd) {
1531
1582
  const packages = [];
1532
1583
  const packageGlobs = [];
1533
1584
  const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
1534
- if (existsSync7(pnpmWorkspaceFile)) {
1585
+ if (existsSync8(pnpmWorkspaceFile)) {
1535
1586
  try {
1536
- const content = readFileSync5(pnpmWorkspaceFile, "utf8");
1587
+ const content = readFileSync6(pnpmWorkspaceFile, "utf8");
1537
1588
  const parsed = yaml.parse(content);
1538
1589
  if (Array.isArray(parsed?.packages)) {
1539
1590
  packageGlobs.push(...parsed.packages);
@@ -1542,9 +1593,9 @@ function findWorkspacePackages(cwd) {
1542
1593
  }
1543
1594
  }
1544
1595
  const rootPkgFile = join7(rootCwd, "package.json");
1545
- if (existsSync7(rootPkgFile)) {
1596
+ if (existsSync8(rootPkgFile)) {
1546
1597
  try {
1547
- const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
1598
+ const rootPkg = JSON.parse(readFileSync6(rootPkgFile, "utf8"));
1548
1599
  if (Array.isArray(rootPkg.workspaces)) {
1549
1600
  packageGlobs.push(...rootPkg.workspaces);
1550
1601
  } else if (Array.isArray(rootPkg.workspaces?.packages)) {
@@ -1554,9 +1605,9 @@ function findWorkspacePackages(cwd) {
1554
1605
  }
1555
1606
  }
1556
1607
  const denoJsonFile = join7(rootCwd, "deno.json");
1557
- if (existsSync7(denoJsonFile)) {
1608
+ if (existsSync8(denoJsonFile)) {
1558
1609
  try {
1559
- const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
1610
+ const denoJson = JSON.parse(readFileSync6(denoJsonFile, "utf8"));
1560
1611
  if (Array.isArray(denoJson.workspace)) {
1561
1612
  packageGlobs.push(...denoJson.workspace);
1562
1613
  }
@@ -1572,7 +1623,7 @@ function findWorkspacePackages(cwd) {
1572
1623
  if (!cleanGlob || cleanGlob.startsWith("!")) continue;
1573
1624
  if (cleanGlob.endsWith("/*")) {
1574
1625
  const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
1575
- if (!existsSync7(parentDir)) continue;
1626
+ if (!existsSync8(parentDir)) continue;
1576
1627
  try {
1577
1628
  const entries = readdirSync(parentDir);
1578
1629
  for (const entry of entries) {
@@ -1589,7 +1640,7 @@ function findWorkspacePackages(cwd) {
1589
1640
  }
1590
1641
  } else {
1591
1642
  const directDir = join7(rootCwd, cleanGlob);
1592
- if (existsSync7(directDir)) {
1643
+ if (existsSync8(directDir)) {
1593
1644
  try {
1594
1645
  if (statSync(directDir).isDirectory()) {
1595
1646
  addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
@@ -1606,9 +1657,9 @@ function addWorkspacePackage(rootCwd, dir, out, seen) {
1606
1657
  if (seen.has(norm)) return;
1607
1658
  seen.add(norm);
1608
1659
  const manifestPath = join7(dir, "package.json");
1609
- if (!existsSync7(manifestPath)) return;
1660
+ if (!existsSync8(manifestPath)) return;
1610
1661
  try {
1611
- const content = readFileSync5(manifestPath, "utf8");
1662
+ const content = readFileSync6(manifestPath, "utf8");
1612
1663
  const manifest = JSON.parse(content);
1613
1664
  out.push({
1614
1665
  name: manifest.name ?? relative(rootCwd, dir),
@@ -1639,6 +1690,37 @@ function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
1639
1690
  }
1640
1691
 
1641
1692
  // src/remediation/tools/check-inventory.ts
1693
+ function resolveInventoryVersion(rawVersion) {
1694
+ const trimmed = rawVersion.trim();
1695
+ if (semver.valid(trimmed)) {
1696
+ return semver.clean(trimmed) ?? trimmed;
1697
+ }
1698
+ let spec = trimmed;
1699
+ if (spec.startsWith("npm:") && spec.includes("@")) {
1700
+ spec = spec.slice(spec.lastIndexOf("@") + 1);
1701
+ }
1702
+ if (spec.startsWith("workspace:")) {
1703
+ spec = spec.slice("workspace:".length).trim();
1704
+ }
1705
+ if (semver.valid(spec)) {
1706
+ return semver.clean(spec) ?? spec;
1707
+ }
1708
+ try {
1709
+ const min = semver.minVersion(spec);
1710
+ if (min?.version && semver.valid(min.version)) {
1711
+ return min.version;
1712
+ }
1713
+ } catch {
1714
+ }
1715
+ try {
1716
+ const coerced = semver.coerce(spec);
1717
+ if (coerced?.version && semver.valid(coerced.version)) {
1718
+ return coerced.version;
1719
+ }
1720
+ } catch {
1721
+ }
1722
+ return semver.clean(trimmed) ?? trimmed;
1723
+ }
1642
1724
  var checkInventoryTool = defineTool({
1643
1725
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
1644
1726
  parameters: z.object({
@@ -1655,7 +1737,7 @@ var checkInventoryTool = defineTool({
1655
1737
  }) => {
1656
1738
  let pkgJson;
1657
1739
  try {
1658
- pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1740
+ pkgJson = JSON.parse(readFileSync7(join8(cwd, "package.json"), "utf8"));
1659
1741
  } catch {
1660
1742
  return {
1661
1743
  packages: [],
@@ -1698,11 +1780,12 @@ var checkInventoryTool = defineTool({
1698
1780
  if (packages.length === 0) {
1699
1781
  const allDeps = {
1700
1782
  ...pkgJson.dependencies,
1701
- ...pkgJson.devDependencies
1783
+ ...pkgJson.devDependencies,
1784
+ ...pkgJson.peerDependencies
1702
1785
  };
1703
- for (const [name, version] of Object.entries(allDeps)) {
1704
- const cleaned = version.replace(/^[\^~>=<]+/, "").trim();
1705
- packages.push({ name, version: cleaned, type: "direct" });
1786
+ for (const [name, rawVersion] of Object.entries(allDeps)) {
1787
+ const version = resolveInventoryVersion(rawVersion);
1788
+ packages.push({ name, version, type: "direct" });
1706
1789
  }
1707
1790
  }
1708
1791
  return { packages };
@@ -1812,13 +1895,12 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
1812
1895
  }
1813
1896
 
1814
1897
  // src/api/patches/reconcile.ts
1815
- import { existsSync as existsSync8 } from "fs";
1898
+ import { existsSync as existsSync9 } from "fs";
1816
1899
  import { readFile as readFile3, rm, writeFile } from "fs/promises";
1817
1900
  import { join as join10 } from "path";
1818
- import { execa as execa5 } from "execa";
1819
1901
 
1820
1902
  // src/remediation/evaluate-package.ts
1821
- import semver2 from "semver";
1903
+ import semver3 from "semver";
1822
1904
 
1823
1905
  // src/platform/http-client.ts
1824
1906
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -2179,10 +2261,10 @@ async function enrichWithCisaKev(details) {
2179
2261
  }
2180
2262
 
2181
2263
  // src/intelligence/sources/registry.ts
2182
- import { readFileSync as readFileSync7 } from "fs";
2264
+ import { readFileSync as readFileSync8 } from "fs";
2183
2265
  import { join as join9 } from "path";
2184
2266
  import { execa as execa4 } from "execa";
2185
- import semver from "semver";
2267
+ import semver2 from "semver";
2186
2268
  var NPM_REGISTRY = "https://registry.npmjs.org";
2187
2269
  async function fetchPackageVersions(packageName) {
2188
2270
  const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
@@ -2218,15 +2300,15 @@ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPat
2218
2300
  majorOnlyFixAvailable: false
2219
2301
  };
2220
2302
  }
2221
- const installed = semver.parse(installedVersion);
2222
- const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2303
+ const installed = semver2.parse(installedVersion);
2304
+ const candidates = versions.filter((v) => semver2.valid(v) && semver2.gte(v, firstPatchedVersion)).filter((v) => {
2223
2305
  if (!vulnerableRange) return true;
2224
2306
  try {
2225
- return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2307
+ return !semver2.satisfies(v, vulnerableRange, { includePrerelease: false });
2226
2308
  } catch {
2227
2309
  return true;
2228
2310
  }
2229
- }).sort(semver.compare);
2311
+ }).sort(semver2.compare);
2230
2312
  if (!candidates.length) {
2231
2313
  return {
2232
2314
  candidates: {},
@@ -2266,13 +2348,14 @@ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPat
2266
2348
  };
2267
2349
  }
2268
2350
  function classifyUpgradeLevel(installedVersion, candidateVersion) {
2269
- const installed = semver.parse(installedVersion);
2270
- const candidate = semver.parse(candidateVersion);
2351
+ const installed = semver2.parse(installedVersion) ?? semver2.coerce(installedVersion);
2352
+ const candidate = semver2.parse(candidateVersion) ?? semver2.coerce(candidateVersion);
2271
2353
  if (!installed || !candidate) return void 0;
2354
+ if (!semver2.gte(candidate.version, installed.version)) return void 0;
2272
2355
  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";
2356
+ if (candidate.major === installed.major && candidate.minor > installed.minor) return "minor";
2357
+ if (candidate.major === installed.major && candidate.minor === installed.minor) {
2358
+ if (candidate.patch >= installed.patch) return "patch";
2276
2359
  }
2277
2360
  return void 0;
2278
2361
  }
@@ -2356,7 +2439,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
2356
2439
  }
2357
2440
  let directDeps;
2358
2441
  try {
2359
- const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
2442
+ const pkgRaw = JSON.parse(readFileSync8(join9(cwd, "package.json"), "utf8"));
2360
2443
  directDeps = /* @__PURE__ */ new Set([
2361
2444
  ...Object.keys(pkgRaw.dependencies ?? {}),
2362
2445
  ...Object.keys(pkgRaw.devDependencies ?? {})
@@ -2372,7 +2455,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
2372
2455
  if (!currentVersion || !latestVersion) continue;
2373
2456
  const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2374
2457
  if (!options.includeTransitive && dependencyScope === "transitive") continue;
2375
- const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2458
+ const isMajorBump = semver2.valid(currentVersion) !== null && semver2.valid(latestVersion) !== null && semver2.major(latestVersion) > semver2.major(currentVersion);
2376
2459
  result.set(name, {
2377
2460
  currentVersion,
2378
2461
  wantedVersion,
@@ -2393,7 +2476,7 @@ async function evaluatePackage(packageName, options = {}) {
2393
2476
  try {
2394
2477
  versions = await fetchPackageVersions(packageName);
2395
2478
  if (versions.length > 0) {
2396
- const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
2479
+ const valid = versions.filter((v) => semver3.valid(v)).sort((a, b) => semver3.compare(a, b));
2397
2480
  latestVersion = valid[valid.length - 1];
2398
2481
  }
2399
2482
  } catch {
@@ -2419,8 +2502,8 @@ async function evaluatePackage(packageName, options = {}) {
2419
2502
  (p) => p.name.toLowerCase() === packageName.toLowerCase()
2420
2503
  );
2421
2504
  if (version && affected?.vulnerableRange) {
2422
- const cleanVer = semver2.clean(version) || version;
2423
- if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
2505
+ const cleanVer = semver3.clean(version) || version;
2506
+ if (semver3.valid(cleanVer) && !semver3.satisfies(cleanVer, affected.vulnerableRange)) {
2424
2507
  continue;
2425
2508
  }
2426
2509
  }
@@ -2483,7 +2566,7 @@ async function evaluatePackage(packageName, options = {}) {
2483
2566
  }
2484
2567
  let recommendedVersion;
2485
2568
  if (isVulnerable) {
2486
- const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
2569
+ const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver3.valid(v))).sort((a, b) => semver3.compare(a, b));
2487
2570
  recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
2488
2571
  } else {
2489
2572
  recommendedVersion = version || latestVersion;
@@ -2503,6 +2586,169 @@ async function evaluatePackage(packageName, options = {}) {
2503
2586
  };
2504
2587
  }
2505
2588
 
2589
+ // src/platform/safe-exec.ts
2590
+ import { execa as execa5 } from "execa";
2591
+ var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2592
+ "NODE_OPTIONS",
2593
+ "NODE_PATH",
2594
+ "LD_PRELOAD",
2595
+ "LD_LIBRARY_PATH",
2596
+ "DYLD_INSERT_LIBRARIES",
2597
+ "DYLD_LIBRARY_PATH",
2598
+ "PERL5OPT",
2599
+ "PERL5LIB",
2600
+ "PYTHONWARNINGS",
2601
+ "PYTHONPATH",
2602
+ "RUBYOPT",
2603
+ "RUBYLIB",
2604
+ "BASH_ENV",
2605
+ "ENV"
2606
+ ]);
2607
+ function sanitizeEnvironment(env = process.env, allowNodeOptions = false) {
2608
+ const sanitized = {};
2609
+ for (const [key, value] of Object.entries(env)) {
2610
+ if (value === void 0) continue;
2611
+ if (key.includes("\0") || typeof value === "string" && value.includes("\0")) continue;
2612
+ if (DANGEROUS_ENV_VARS.has(key)) {
2613
+ if (key === "NODE_OPTIONS" && allowNodeOptions) {
2614
+ sanitized[key] = value;
2615
+ }
2616
+ continue;
2617
+ }
2618
+ sanitized[key] = value;
2619
+ }
2620
+ return sanitized;
2621
+ }
2622
+ function validateCommandArguments(file, args = []) {
2623
+ if (file.includes("\0")) {
2624
+ throw new Error("Command execution blocked: executable path contains null bytes");
2625
+ }
2626
+ for (const arg of args) {
2627
+ if (arg.includes("\0")) {
2628
+ throw new Error(`Command execution blocked: argument contains null bytes: ${arg}`);
2629
+ }
2630
+ }
2631
+ }
2632
+ function safeExeca(file, args = [], options = {}) {
2633
+ validateCommandArguments(file, args);
2634
+ const timeout = options.timeoutMs ?? (typeof options.timeout === "number" ? options.timeout : 6e4);
2635
+ const sanitizedEnv = sanitizeEnvironment(
2636
+ options.env ?? process.env,
2637
+ options.allowNodeOptions
2638
+ );
2639
+ return execa5(file, args, {
2640
+ ...options,
2641
+ timeout,
2642
+ env: sanitizedEnv
2643
+ });
2644
+ }
2645
+
2646
+ // src/remediation/tools/apply-package-override/helpers.ts
2647
+ async function collectDependencyTrace(cwd, pm, packageName, constraints) {
2648
+ try {
2649
+ const whyCommand = resolveWhyCommand(pm, packageName, constraints);
2650
+ if (whyCommand.length === 0) return void 0;
2651
+ const [whyCmd, ...whyArgs] = whyCommand;
2652
+ const result = await safeExeca(whyCmd, whyArgs, {
2653
+ cwd,
2654
+ stdio: "pipe",
2655
+ reject: false
2656
+ });
2657
+ const output = [result.stdout, result.stderr].filter(Boolean).join("\n").split("\n").map((line) => line.trim()).filter(Boolean);
2658
+ if (output.length === 0) return void 0;
2659
+ return output.slice(0, 3).join(" | ");
2660
+ } catch {
2661
+ return void 0;
2662
+ }
2663
+ }
2664
+ function describeOverrideField(packageManager) {
2665
+ if (packageManager === "npm" || packageManager === "bun") return "overrides";
2666
+ if (packageManager === "pnpm") return "pnpm.overrides";
2667
+ if (packageManager === "deno") return "overrides";
2668
+ return "resolutions";
2669
+ }
2670
+ function getOverrideValue(pkgJson, packageManager, packageName) {
2671
+ if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
2672
+ return pkgJson.overrides?.[packageName];
2673
+ }
2674
+ if (packageManager === "pnpm") return pkgJson.pnpm?.overrides?.[packageName];
2675
+ return pkgJson.resolutions?.[packageName];
2676
+ }
2677
+ function setOverrideValue(pkgJson, packageManager, packageName, version) {
2678
+ if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
2679
+ pkgJson.overrides = { ...pkgJson.overrides, [packageName]: version };
2680
+ return;
2681
+ }
2682
+ if (packageManager === "pnpm") {
2683
+ pkgJson.pnpm = {
2684
+ ...pkgJson.pnpm,
2685
+ overrides: {
2686
+ ...pkgJson.pnpm?.overrides,
2687
+ [packageName]: version
2688
+ }
2689
+ };
2690
+ return;
2691
+ }
2692
+ pkgJson.resolutions = { ...pkgJson.resolutions, [packageName]: version };
2693
+ }
2694
+ function restoreOverrideValue(pkgJson, packageManager, packageName, previousValue) {
2695
+ if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
2696
+ pkgJson.overrides = restoreRecord(pkgJson.overrides, packageName, previousValue);
2697
+ return;
2698
+ }
2699
+ if (packageManager === "pnpm") {
2700
+ pkgJson.pnpm = {
2701
+ ...pkgJson.pnpm,
2702
+ overrides: restoreRecord(pkgJson.pnpm?.overrides, packageName, previousValue)
2703
+ };
2704
+ if (!pkgJson.pnpm.overrides) {
2705
+ delete pkgJson.pnpm.overrides;
2706
+ }
2707
+ if (Object.keys(pkgJson.pnpm).length === 0) {
2708
+ delete pkgJson.pnpm;
2709
+ }
2710
+ return;
2711
+ }
2712
+ pkgJson.resolutions = restoreRecord(pkgJson.resolutions, packageName, previousValue);
2713
+ }
2714
+ function getDenoJsonImportValue(denoJson, packageName) {
2715
+ const imports = denoJson.imports ?? {};
2716
+ if (imports[packageName] !== void 0) {
2717
+ return { key: packageName, value: imports[packageName] };
2718
+ }
2719
+ const npmKey = `npm:${packageName}`;
2720
+ if (imports[npmKey] !== void 0) {
2721
+ return { key: npmKey, value: imports[npmKey] };
2722
+ }
2723
+ return void 0;
2724
+ }
2725
+ function setDenoJsonImportValue(denoJson, packageName, version) {
2726
+ const existing = getDenoJsonImportValue(denoJson, packageName);
2727
+ const key = existing?.key ?? `npm:${packageName}`;
2728
+ denoJson.imports = { ...denoJson.imports, [key]: `npm:${packageName}@${version}` };
2729
+ }
2730
+ function restoreDenoJsonImportValue(denoJson, packageName, previousEntry) {
2731
+ const existing = getDenoJsonImportValue(denoJson, packageName);
2732
+ if (!existing) return;
2733
+ const imports = { ...denoJson.imports };
2734
+ if (previousEntry === void 0) {
2735
+ delete imports[existing.key];
2736
+ } else {
2737
+ delete imports[existing.key];
2738
+ imports[previousEntry.key] = previousEntry.value;
2739
+ }
2740
+ denoJson.imports = Object.keys(imports).length > 0 ? imports : void 0;
2741
+ }
2742
+ function restoreRecord(record, key, previousValue) {
2743
+ const nextRecord = { ...record };
2744
+ if (previousValue === void 0) {
2745
+ delete nextRecord[key];
2746
+ } else {
2747
+ nextRecord[key] = previousValue;
2748
+ }
2749
+ return Object.keys(nextRecord).length > 0 ? nextRecord : void 0;
2750
+ }
2751
+
2506
2752
  // src/api/patches/reconcile.ts
2507
2753
  async function reconcilePatchArtifacts(options = {}) {
2508
2754
  const cwd = options.cwd ?? process.cwd();
@@ -2556,7 +2802,8 @@ async function reconcilePatchArtifacts(options = {}) {
2556
2802
  (v) => v.safeUpgradeVersion
2557
2803
  )?.safeUpgradeVersion;
2558
2804
  }
2559
- } catch {
2805
+ } catch (evalError) {
2806
+ const _ignored = evalError;
2560
2807
  }
2561
2808
  if (!safeUpgradeVersion) {
2562
2809
  results.push({
@@ -2581,10 +2828,23 @@ async function reconcilePatchArtifacts(options = {}) {
2581
2828
  });
2582
2829
  continue;
2583
2830
  }
2831
+ const pkgJsonPath = join10(cwd, "package.json");
2832
+ const manifestPath = inspection.manifestFilePath ?? `${summary.patchFilePath}.json`;
2833
+ let originalPkgJsonContent;
2834
+ let originalPatchContent;
2835
+ let originalManifestContent;
2584
2836
  try {
2585
- const pkgJsonPath = join10(cwd, "package.json");
2586
- if (existsSync8(pkgJsonPath)) {
2587
- const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
2837
+ if (existsSync9(pkgJsonPath)) {
2838
+ originalPkgJsonContent = await readFile3(pkgJsonPath, "utf8");
2839
+ }
2840
+ if (existsSync9(summary.patchFilePath)) {
2841
+ originalPatchContent = await readFile3(summary.patchFilePath, "utf8");
2842
+ }
2843
+ if (existsSync9(manifestPath)) {
2844
+ originalManifestContent = await readFile3(manifestPath, "utf8");
2845
+ }
2846
+ if (originalPkgJsonContent) {
2847
+ const pkgJson = JSON.parse(originalPkgJsonContent);
2588
2848
  let updated = false;
2589
2849
  const depSections = [
2590
2850
  "dependencies",
@@ -2592,16 +2852,24 @@ async function reconcilePatchArtifacts(options = {}) {
2592
2852
  "peerDependencies",
2593
2853
  "optionalDependencies"
2594
2854
  ];
2855
+ let foundInDirectDeps = false;
2595
2856
  for (const depSection of depSections) {
2596
2857
  if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
2597
2858
  pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
2598
2859
  updated = true;
2860
+ foundInDirectDeps = true;
2599
2861
  }
2600
2862
  }
2863
+ const existingOverride = getOverrideValue(pkgJson, pm, packageName);
2864
+ if (existingOverride || !foundInDirectDeps) {
2865
+ setOverrideValue(pkgJson, pm, packageName, safeUpgradeVersion);
2866
+ updated = true;
2867
+ }
2601
2868
  if (pkgJson.pnpm?.patchedDependencies) {
2602
- for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
2869
+ const patched = pkgJson.pnpm.patchedDependencies;
2870
+ for (const key of Object.keys(patched)) {
2603
2871
  if (key.startsWith(`${packageName}@`)) {
2604
- delete pkgJson.pnpm.patchedDependencies[key];
2872
+ delete patched[key];
2605
2873
  updated = true;
2606
2874
  }
2607
2875
  }
@@ -2611,14 +2879,60 @@ async function reconcilePatchArtifacts(options = {}) {
2611
2879
  }
2612
2880
  }
2613
2881
  await rm(summary.patchFilePath, { force: true });
2614
- if (inspection.manifestFilePath) {
2615
- await rm(inspection.manifestFilePath, { force: true });
2882
+ if (existsSync9(manifestPath)) {
2883
+ await rm(manifestPath, { force: true });
2616
2884
  }
2617
2885
  const installCmd = resolveInstallCommand(pm);
2618
- await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
2886
+ try {
2887
+ await safeExeca(installCmd[0], installCmd.slice(1), { cwd, stdio: "pipe" });
2888
+ } catch (installErr) {
2889
+ if (originalPkgJsonContent) {
2890
+ await writeFile(pkgJsonPath, originalPkgJsonContent, "utf8");
2891
+ }
2892
+ if (originalPatchContent) {
2893
+ await writeFile(summary.patchFilePath, originalPatchContent, "utf8");
2894
+ }
2895
+ if (originalManifestContent) {
2896
+ await writeFile(manifestPath, originalManifestContent, "utf8");
2897
+ }
2898
+ const msg = installErr instanceof Error ? installErr.message : String(installErr);
2899
+ results.push({
2900
+ patchFilePath: summary.patchFilePath,
2901
+ packageName,
2902
+ cveId,
2903
+ installedVersion: vulnerableVersion,
2904
+ safeUpstreamVersion: safeUpgradeVersion,
2905
+ status: "error",
2906
+ reason: `Package manager installation failed (${installCmd.join(" ")}): ${msg}. Restored original package.json and patch artifacts.`
2907
+ });
2908
+ continue;
2909
+ }
2619
2910
  if (options.runTests) {
2620
2911
  const testCmd = resolveTestCommand(pm);
2621
- await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
2912
+ try {
2913
+ await safeExeca(testCmd[0], testCmd.slice(1), { cwd, stdio: "pipe" });
2914
+ } catch (testErr) {
2915
+ if (originalPkgJsonContent) {
2916
+ await writeFile(pkgJsonPath, originalPkgJsonContent, "utf8");
2917
+ }
2918
+ if (originalPatchContent) {
2919
+ await writeFile(summary.patchFilePath, originalPatchContent, "utf8");
2920
+ }
2921
+ if (originalManifestContent) {
2922
+ await writeFile(manifestPath, originalManifestContent, "utf8");
2923
+ }
2924
+ const msg = testErr instanceof Error ? testErr.message : String(testErr);
2925
+ results.push({
2926
+ patchFilePath: summary.patchFilePath,
2927
+ packageName,
2928
+ cveId,
2929
+ installedVersion: vulnerableVersion,
2930
+ safeUpstreamVersion: safeUpgradeVersion,
2931
+ status: "error",
2932
+ reason: `Validation tests failed post-upgrade (${testCmd.join(" ")}): ${msg}. Restored original package.json and patch artifacts.`
2933
+ });
2934
+ continue;
2935
+ }
2622
2936
  }
2623
2937
  results.push({
2624
2938
  patchFilePath: summary.patchFilePath,
@@ -2653,7 +2967,7 @@ async function reconcilePatchArtifacts(options = {}) {
2653
2967
  }
2654
2968
 
2655
2969
  // src/platform/idempotency.ts
2656
- import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
2970
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2657
2971
  import { join as join11 } from "path";
2658
2972
  var DEFAULT_INDEX = {
2659
2973
  schemaVersion: "1.0",
@@ -2667,9 +2981,9 @@ function entryKey(idempotencyKey, cveId) {
2667
2981
  }
2668
2982
  function loadIndex(cwd) {
2669
2983
  const filePath = indexFilePath(cwd);
2670
- if (!existsSync9(filePath)) return DEFAULT_INDEX;
2984
+ if (!existsSync10(filePath)) return DEFAULT_INDEX;
2671
2985
  try {
2672
- const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
2986
+ const parsed = JSON.parse(readFileSync9(filePath, "utf8"));
2673
2987
  if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
2674
2988
  return parsed;
2675
2989
  }
@@ -2680,8 +2994,8 @@ function loadIndex(cwd) {
2680
2994
  }
2681
2995
  function saveIndex(cwd, index) {
2682
2996
  const filePath = indexFilePath(cwd);
2683
- mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
2684
- writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2997
+ mkdirSync4(join11(cwd, ".autoremediator", "state"), { recursive: true });
2998
+ writeFileSync4(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2685
2999
  }
2686
3000
  function readIdempotentReport(cwd, idempotencyKey, cveId) {
2687
3001
  const index = loadIndex(cwd);
@@ -2704,7 +3018,7 @@ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
2704
3018
  import { generateText as generateText2 } from "ai";
2705
3019
 
2706
3020
  // src/remediation/local/run.ts
2707
- import semver6 from "semver";
3021
+ import semver7 from "semver";
2708
3022
 
2709
3023
  // src/intelligence/sources/github-advisory.ts
2710
3024
  var GITHUB_API_BASE = "https://api.github.com/advisories";
@@ -2843,9 +3157,9 @@ async function enrichWithNvd(details) {
2843
3157
  }
2844
3158
 
2845
3159
  // src/intelligence/sources/snapshot.ts
2846
- import { readFileSync as readFileSync9 } from "fs";
3160
+ import { readFileSync as readFileSync10 } from "fs";
2847
3161
  function loadIntelligenceSnapshot(filePath) {
2848
- const raw = readFileSync9(filePath, "utf8");
3162
+ const raw = readFileSync10(filePath, "utf8");
2849
3163
  const parsed = JSON.parse(raw);
2850
3164
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2851
3165
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -2853,11 +3167,37 @@ function loadIntelligenceSnapshot(filePath) {
2853
3167
  return parsed;
2854
3168
  }
2855
3169
  function lookupSnapshotCve(snapshot, cveId) {
2856
- return snapshot[cveId.toUpperCase()] ?? snapshot[cveId] ?? null;
3170
+ const store = snapshot.cves && typeof snapshot.cves === "object" && !Array.isArray(snapshot.cves) ? snapshot.cves : snapshot;
3171
+ const normalizedId = cveId.toUpperCase();
3172
+ const rawEntry = store[normalizedId] ?? store[cveId] ?? store[cveId.toLowerCase()];
3173
+ if (!rawEntry || typeof rawEntry !== "object") return null;
3174
+ if (typeof rawEntry.id === "string" && Array.isArray(rawEntry.affectedPackages) && typeof rawEntry.summary === "string") {
3175
+ return rawEntry;
3176
+ }
3177
+ const affectedPackages = Array.isArray(rawEntry.affectedPackages) ? rawEntry.affectedPackages : rawEntry.packageName ? [
3178
+ {
3179
+ name: rawEntry.packageName,
3180
+ ecosystem: "npm",
3181
+ vulnerableRange: rawEntry.vulnerableRange ?? "",
3182
+ firstPatchedVersion: rawEntry.firstPatchedVersion,
3183
+ source: "offline-snapshot"
3184
+ }
3185
+ ] : [];
3186
+ const details = {
3187
+ id: rawEntry.id ?? rawEntry.cveId ?? normalizedId,
3188
+ summary: rawEntry.summary ?? `Offline intelligence details for ${normalizedId}`,
3189
+ severity: rawEntry.severity ?? "UNKNOWN",
3190
+ references: Array.isArray(rawEntry.references) ? rawEntry.references : [],
3191
+ affectedPackages,
3192
+ cvssScore: typeof rawEntry.cvssScore === "number" ? rawEntry.cvssScore : void 0,
3193
+ epss: rawEntry.epss ?? (typeof rawEntry.epssScore === "number" ? { score: rawEntry.epssScore, percentile: rawEntry.epssScore } : void 0),
3194
+ kev: rawEntry.kev ?? (rawEntry.cisaKev !== void 0 ? { knownExploited: Boolean(rawEntry.cisaKev) } : void 0)
3195
+ };
3196
+ return details;
2857
3197
  }
2858
3198
 
2859
3199
  // src/remediation/tools/check-reachability.ts
2860
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
3200
+ import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync11, statSync as statSync2 } from "fs";
2861
3201
  import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
2862
3202
  import { parseSync } from "oxc-parser";
2863
3203
  import { z as z2 } from "zod";
@@ -2875,9 +3215,9 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
2875
3215
  function loadTsconfigPaths(cwd) {
2876
3216
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
2877
3217
  const fullPath = join12(cwd, configName);
2878
- if (!existsSync10(fullPath)) continue;
3218
+ if (!existsSync11(fullPath)) continue;
2879
3219
  try {
2880
- const raw = readFileSync10(fullPath, "utf8");
3220
+ const raw = readFileSync11(fullPath, "utf8");
2881
3221
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
2882
3222
  const parsed = JSON.parse(cleaned);
2883
3223
  const compilerOptions = parsed.compilerOptions;
@@ -2971,9 +3311,9 @@ function toRootPackageName(importSource) {
2971
3311
  function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
2972
3312
  for (const directPkg of importedDirectPackages) {
2973
3313
  const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
2974
- if (!existsSync10(manifestPath)) continue;
3314
+ if (!existsSync11(manifestPath)) continue;
2975
3315
  try {
2976
- const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
3316
+ const manifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
2977
3317
  if (manifest.dependencies && manifest.dependencies[targetPackage]) {
2978
3318
  return {
2979
3319
  directDependency: directPkg,
@@ -3146,7 +3486,7 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
3146
3486
  scanned++;
3147
3487
  let content;
3148
3488
  try {
3149
- content = readFileSync10(filePath, "utf8");
3489
+ content = readFileSync11(filePath, "utf8");
3150
3490
  } catch {
3151
3491
  continue;
3152
3492
  }
@@ -3390,85 +3730,295 @@ function buildSbom(packages, vulnerableNames, results) {
3390
3730
 
3391
3731
  // src/remediation/tools/apply-version-bump.ts
3392
3732
  import { z as z4 } from "zod";
3393
- import { join as join15 } from "path";
3394
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
3733
+ import { join as join16 } from "path";
3734
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
3735
+ import { rm as rm3 } from "fs/promises";
3736
+ import semver4 from "semver";
3395
3737
 
3396
- // src/platform/safe-exec.ts
3397
- import { execa as execa6 } from "execa";
3398
- var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
3399
- "NODE_OPTIONS",
3400
- "NODE_PATH",
3401
- "LD_PRELOAD",
3402
- "LD_LIBRARY_PATH",
3403
- "DYLD_INSERT_LIBRARIES",
3404
- "DYLD_LIBRARY_PATH",
3405
- "PERL5OPT",
3406
- "PERL5LIB",
3407
- "PYTHONWARNINGS",
3408
- "PYTHONPATH",
3409
- "RUBYOPT",
3410
- "RUBYLIB",
3411
- "BASH_ENV",
3412
- "ENV"
3413
- ]);
3414
- function sanitizeEnvironment(env = process.env, allowNodeOptions = false) {
3415
- const sanitized = {};
3416
- for (const [key, value] of Object.entries(env)) {
3417
- if (value === void 0) continue;
3418
- if (key.includes("\0") || typeof value === "string" && value.includes("\0")) continue;
3419
- if (DANGEROUS_ENV_VARS.has(key)) {
3420
- if (key === "NODE_OPTIONS" && allowNodeOptions) {
3421
- sanitized[key] = value;
3422
- }
3423
- continue;
3424
- }
3425
- sanitized[key] = value;
3738
+ // src/remediation/proof-of-fix/runner.ts
3739
+ import { existsSync as existsSync12 } from "fs";
3740
+ import { mkdir, writeFile as writeFile2 } from "fs/promises";
3741
+ import { isAbsolute as isAbsolute3, join as join13 } from "path";
3742
+ function sanitizePathLeakage(text, paths = []) {
3743
+ let sanitized = text;
3744
+ for (const p of paths) {
3745
+ if (!p || p.trim().length === 0) continue;
3746
+ sanitized = sanitized.split(p).join("<path>");
3426
3747
  }
3427
3748
  return sanitized;
3428
3749
  }
3429
- function validateCommandArguments(file, args = []) {
3430
- if (file.includes("\0")) {
3431
- throw new Error("Command execution blocked: executable path contains null bytes");
3750
+ function stripStackTraceFrames(text) {
3751
+ return text.split("\n").filter((line) => !/^\s*at\s+/.test(line)).join("\n");
3752
+ }
3753
+ function isHarnessFailure(context) {
3754
+ const { exitCode, output, errorMsg = "", signal } = context;
3755
+ const errorMsgEmbedsPath = errorMsg.includes("Command failed") || context.testFilePath && errorMsg.includes(context.testFilePath) || errorMsg.includes("/") || errorMsg.includes("\\");
3756
+ const filteredErrorMsg = errorMsgEmbedsPath ? "" : errorMsg;
3757
+ const combined = filteredErrorMsg ? `${output}
3758
+ ${filteredErrorMsg}` : output;
3759
+ if (signal !== void 0) {
3760
+ return true;
3432
3761
  }
3433
- for (const arg of args) {
3434
- if (arg.includes("\0")) {
3435
- throw new Error(`Command execution blocked: argument contains null bytes: ${arg}`);
3762
+ if (typeof exitCode === "number" && (exitCode === 126 || exitCode === 127 || exitCode > 128)) {
3763
+ return true;
3764
+ }
3765
+ if (combined.includes("ENOENT") || combined.includes("no such file or directory")) {
3766
+ return true;
3767
+ }
3768
+ if (combined.includes("bad option:") || combined.includes("bad option") || combined.includes("Unrecognized option") || combined.includes("illegal option") || combined.includes("invalid option")) {
3769
+ return true;
3770
+ }
3771
+ if (combined.includes("SyntaxError") || combined.includes("Unexpected token") || combined.includes("Cannot use import statement outside a module")) {
3772
+ return true;
3773
+ }
3774
+ if (combined.includes("Cannot find module") || combined.includes("Cannot find package") || combined.includes("MODULE_NOT_FOUND") || combined.includes("ERR_MODULE_NOT_FOUND") || combined.includes("ERR_REQUIRE_ESM") || combined.includes("ERR_UNKNOWN_FILE_EXTENSION") || combined.includes("ERR_PACKAGE_PATH_NOT_EXPORTED") || combined.includes("ERR_PACKAGE_IMPORT_NOT_DEFINED") || combined.includes("ERR_UNSUPPORTED_DIR_IMPORT") || combined.includes("ERR_UNSUPPORTED_ESM_URL_SCHEME") || combined.includes("ERR_INVALID_MODULE_SPECIFIER")) {
3775
+ return true;
3776
+ }
3777
+ if (combined.includes("Allocation failed - JavaScript heap out of memory") || combined.includes("FATAL ERROR:") || combined.includes("UnhandledPromiseRejection:") || combined.includes("ERR_UNHANDLED_REJECTION") || combined.includes("(Use `node --trace-uncaught") || /^\s*(?!(?:AssertionError)\b)[A-Z]\w*Error:\s/m.test(combined)) {
3778
+ return true;
3779
+ }
3780
+ return false;
3781
+ }
3782
+ function isAssertionFailure(output, exitCode) {
3783
+ if (exitCode !== 1) {
3784
+ return false;
3785
+ }
3786
+ const nonStackOutput = stripStackTraceFrames(output);
3787
+ const hasUnhandledCrash = /^\s*(?:(?!(?:AssertionError)\b)[A-Z]\w*Error|Error|UnhandledPromiseRejection):\s/m.test(
3788
+ nonStackOutput
3789
+ ) || /^\s*FATAL ERROR:/m.test(nonStackOutput) || /\(Use `node --trace-uncaught/m.test(nonStackOutput);
3790
+ if (hasUnhandledCrash) {
3791
+ return false;
3792
+ }
3793
+ return /^\s*AssertionError(?:\s*\[ERR_ASSERTION\])?(?::|$)/m.test(nonStackOutput) || /^\s*(?:Error\s*)?\[ERR_ASSERTION\](?::|$)/m.test(nonStackOutput) || /^\s*ERR_ASSERTION(?::|\s*$)/m.test(nonStackOutput) || /^\s*EXPLOIT_REPRODUCED(?::|\s*$)/m.test(nonStackOutput) || /^\s*Assertion failed(?::|$)/im.test(nonStackOutput);
3794
+ }
3795
+ async function prepareReproductionTestFile(params) {
3796
+ const {
3797
+ cwd,
3798
+ packageName,
3799
+ vulnerableVersion,
3800
+ patchesDir = "./patches",
3801
+ proofOfFix,
3802
+ reproductionTest
3803
+ } = params;
3804
+ const patchesDirPath = isAbsolute3(patchesDir) ? patchesDir : join13(cwd, patchesDir);
3805
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
3806
+ const defaultTestPath = join13(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
3807
+ if (typeof proofOfFix === "string" && proofOfFix.trim().length > 0) {
3808
+ const resolvedPath = isAbsolute3(proofOfFix) ? proofOfFix : join13(cwd, proofOfFix);
3809
+ if (existsSync12(resolvedPath)) {
3810
+ return { testFilePath: resolvedPath, isCreated: false };
3436
3811
  }
3812
+ await mkdir(patchesDirPath, { recursive: true });
3813
+ await writeFile2(defaultTestPath, proofOfFix, "utf8");
3814
+ return { testFilePath: defaultTestPath, isCreated: true };
3437
3815
  }
3816
+ if (reproductionTest && reproductionTest.trim().length > 0) {
3817
+ const resolvedPath = isAbsolute3(reproductionTest) ? reproductionTest : join13(cwd, reproductionTest);
3818
+ if (existsSync12(resolvedPath)) {
3819
+ return { testFilePath: resolvedPath, isCreated: false };
3820
+ }
3821
+ await mkdir(patchesDirPath, { recursive: true });
3822
+ await writeFile2(defaultTestPath, reproductionTest, "utf8");
3823
+ return { testFilePath: defaultTestPath, isCreated: true };
3824
+ }
3825
+ if (proofOfFix === true && existsSync12(defaultTestPath)) {
3826
+ return { testFilePath: defaultTestPath, isCreated: false };
3827
+ }
3828
+ return { testFilePath: void 0, isCreated: false };
3438
3829
  }
3439
- function safeExeca(file, args = [], options = {}) {
3440
- validateCommandArguments(file, args);
3441
- const timeout = options.timeoutMs ?? (typeof options.timeout === "number" ? options.timeout : 6e4);
3442
- const sanitizedEnv = sanitizeEnvironment(
3443
- options.env ?? process.env,
3444
- options.allowNodeOptions
3445
- );
3446
- return execa6(file, args, {
3447
- ...options,
3448
- timeout,
3449
- env: sanitizedEnv
3450
- });
3830
+ async function runProofOfFixPhase(options) {
3831
+ const { stage, testFilePath, cwd, timeoutMs = 15e3 } = options;
3832
+ const started = Date.now();
3833
+ const isTs = testFilePath.endsWith(".ts");
3834
+ const nodeArgs = isTs ? ["--experimental-strip-types", testFilePath] : [testFilePath];
3835
+ try {
3836
+ const result = await safeExeca("node", nodeArgs, {
3837
+ cwd,
3838
+ timeoutMs,
3839
+ stdio: "pipe"
3840
+ });
3841
+ const durationMs = Date.now() - started;
3842
+ const stdout = String(result.stdout ?? "");
3843
+ const stderr = String(result.stderr ?? "");
3844
+ const output = `${stdout}
3845
+ ${stderr}`.trim();
3846
+ if (stage === "negative") {
3847
+ return {
3848
+ executed: true,
3849
+ passed: false,
3850
+ exitCode: 0,
3851
+ output,
3852
+ durationMs,
3853
+ error: "Exploit reproduction test passed on vulnerable target (failed to reproduce vulnerability)."
3854
+ };
3855
+ }
3856
+ return {
3857
+ executed: true,
3858
+ passed: true,
3859
+ exitCode: 0,
3860
+ output,
3861
+ durationMs
3862
+ };
3863
+ } catch (err) {
3864
+ const durationMs = Date.now() - started;
3865
+ const execaErr = err;
3866
+ const exitCode = typeof execaErr.exitCode === "number" ? execaErr.exitCode : 1;
3867
+ const errorMsg = execaErr.message || String(err);
3868
+ const rawStdout = String(execaErr.stdout ?? "").trim();
3869
+ const rawStderr = String(execaErr.stderr ?? "").trim();
3870
+ const processOutput = [rawStdout, rawStderr].filter(Boolean).join("\n").trim();
3871
+ const output = processOutput || errorMsg;
3872
+ if (stage === "negative") {
3873
+ if (execaErr.timedOut || errorMsg.includes("timed out") || output.includes("timed out") || execaErr.code === "ETIMEDOUT") {
3874
+ return {
3875
+ executed: true,
3876
+ passed: false,
3877
+ harnessFailure: true,
3878
+ exitCode,
3879
+ output,
3880
+ durationMs,
3881
+ error: "Exploit reproduction test timed out (execution hung)."
3882
+ };
3883
+ }
3884
+ if (isHarnessFailure({
3885
+ exitCode,
3886
+ output,
3887
+ errorMsg,
3888
+ testFilePath,
3889
+ signal: execaErr.signal
3890
+ })) {
3891
+ return {
3892
+ executed: false,
3893
+ passed: false,
3894
+ harnessFailure: true,
3895
+ exitCode,
3896
+ output,
3897
+ durationMs,
3898
+ error: `Reproduction test failed to execute (harness error): ${errorMsg}`
3899
+ };
3900
+ }
3901
+ const knownPaths = [
3902
+ cwd,
3903
+ testFilePath,
3904
+ isAbsolute3(testFilePath) ? testFilePath : join13(cwd, testFilePath)
3905
+ ];
3906
+ if (rawStderr) {
3907
+ const stderrSanitized = sanitizePathLeakage(stripStackTraceFrames(rawStderr), knownPaths);
3908
+ if (isAssertionFailure(stderrSanitized, exitCode)) {
3909
+ return {
3910
+ executed: true,
3911
+ passed: true,
3912
+ exitCode,
3913
+ output,
3914
+ durationMs
3915
+ };
3916
+ }
3917
+ const hasStderrCrash = /^\s*(?:(?!(?:AssertionError)\b)[A-Z]\w*Error|Error|UnhandledPromiseRejection|UnhandledPromiseRejectionWarning):\s/m.test(
3918
+ stderrSanitized
3919
+ ) || /^\s*FATAL ERROR:/m.test(stderrSanitized) || stderrSanitized.includes("Allocation failed - JavaScript heap out of memory") || /\(Use `node --trace-uncaught/m.test(rawStderr) || /\bERR_UNHANDLED_REJECTION\b/.test(rawStderr) || /^\s*at\s+/m.test(rawStderr) || /Node\.js v\d+/m.test(rawStderr);
3920
+ if (hasStderrCrash) {
3921
+ return {
3922
+ executed: false,
3923
+ passed: false,
3924
+ harnessFailure: true,
3925
+ exitCode,
3926
+ output,
3927
+ durationMs,
3928
+ error: `Reproduction test failed with runtime error instead of exploit assertion: ${errorMsg}`
3929
+ };
3930
+ }
3931
+ }
3932
+ if (rawStdout) {
3933
+ const stdoutSanitized = sanitizePathLeakage(stripStackTraceFrames(rawStdout), knownPaths);
3934
+ if (isAssertionFailure(stdoutSanitized, exitCode)) {
3935
+ return {
3936
+ executed: true,
3937
+ passed: true,
3938
+ exitCode,
3939
+ output,
3940
+ durationMs
3941
+ };
3942
+ }
3943
+ }
3944
+ if (errorMsg) {
3945
+ const errorMsgSanitized = sanitizePathLeakage(stripStackTraceFrames(errorMsg), knownPaths);
3946
+ if (isAssertionFailure(errorMsgSanitized, exitCode)) {
3947
+ return {
3948
+ executed: true,
3949
+ passed: true,
3950
+ exitCode,
3951
+ output,
3952
+ durationMs
3953
+ };
3954
+ }
3955
+ }
3956
+ return {
3957
+ executed: false,
3958
+ passed: false,
3959
+ harnessFailure: true,
3960
+ exitCode,
3961
+ output,
3962
+ durationMs,
3963
+ error: `Reproduction test failed with runtime error instead of exploit assertion: ${errorMsg}`
3964
+ };
3965
+ }
3966
+ return {
3967
+ executed: true,
3968
+ passed: false,
3969
+ exitCode,
3970
+ output,
3971
+ durationMs,
3972
+ error: `Reproduction test failed on patched target: ${errorMsg}`
3973
+ };
3974
+ }
3975
+ }
3976
+ function assembleProofOfFixVerification(params) {
3977
+ const { reproductionTestPath, negativePhase, positivePhase, regressionPhase } = params;
3978
+ const regPhase = regressionPhase ?? {
3979
+ executed: false,
3980
+ passed: true
3981
+ };
3982
+ let status = "verified";
3983
+ let failureReason;
3984
+ if (!negativePhase.passed) {
3985
+ status = "failed";
3986
+ failureReason = negativePhase.error || "negative-phase-failed";
3987
+ } else if (!positivePhase.passed) {
3988
+ status = "failed";
3989
+ failureReason = positivePhase.error || "positive-phase-failed";
3990
+ } else if (regPhase.executed && !regPhase.passed) {
3991
+ status = "failed";
3992
+ failureReason = regPhase.error || "regression-phase-failed";
3993
+ }
3994
+ const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
3995
+ return {
3996
+ status,
3997
+ reproductionTestPath,
3998
+ negativePhase,
3999
+ positivePhase,
4000
+ regressionPhase: regPhase,
4001
+ durationMs,
4002
+ failureReason
4003
+ };
3451
4004
  }
3452
-
3453
- // src/remediation/tools/apply-version-bump.ts
3454
- import semver3 from "semver";
3455
4005
 
3456
4006
  // src/platform/repo-lock.ts
3457
- import { mkdir, rm as rm2 } from "fs/promises";
3458
- import { join as join13 } from "path";
4007
+ import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
4008
+ import { join as join14 } from "path";
3459
4009
  async function sleep(ms) {
3460
- await new Promise((resolve6) => setTimeout(resolve6, ms));
4010
+ await new Promise((resolve5) => setTimeout(resolve5, ms));
3461
4011
  }
3462
4012
  async function acquireRepoLock(cwd, options = {}) {
3463
4013
  const timeoutMs = options.timeoutMs ?? 15e3;
3464
4014
  const retryDelayMs = options.retryDelayMs ?? 125;
3465
- const lockRoot = join13(cwd, ".autoremediator", "locks");
3466
- const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
4015
+ const lockRoot = join14(cwd, ".autoremediator", "locks");
4016
+ const lockPath = join14(cwd, ".autoremediator", "locks", "remediation.lock");
3467
4017
  const startedAt = Date.now();
3468
- await mkdir(lockRoot, { recursive: true });
4018
+ await mkdir2(lockRoot, { recursive: true });
3469
4019
  while (true) {
3470
4020
  try {
3471
- await mkdir(lockPath, { recursive: false });
4021
+ await mkdir2(lockPath, { recursive: false });
3472
4022
  return {
3473
4023
  lockPath,
3474
4024
  release: async () => {
@@ -3493,8 +4043,8 @@ async function withRepoLock(cwd, fn, options) {
3493
4043
  }
3494
4044
 
3495
4045
  // 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";
4046
+ import { readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
4047
+ import { join as join15 } from "path";
3498
4048
  import { parseSync as parseSync2 } from "oxc-parser";
3499
4049
  var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
3500
4050
  var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
@@ -3516,7 +4066,7 @@ function* walkFiles(dir) {
3516
4066
  }
3517
4067
  for (const entry of entries) {
3518
4068
  if (IGNORED_DIRS2.has(entry)) continue;
3519
- const full = join14(dir, entry);
4069
+ const full = join15(dir, entry);
3520
4070
  let stat2;
3521
4071
  try {
3522
4072
  stat2 = statSync3(full);
@@ -3538,7 +4088,7 @@ function findPackageCallSites(cwd, packageName) {
3538
4088
  for (const filePath of walkFiles(cwd)) {
3539
4089
  let content;
3540
4090
  try {
3541
- content = readFileSync11(filePath, "utf8");
4091
+ content = readFileSync12(filePath, "utf8");
3542
4092
  } catch {
3543
4093
  continue;
3544
4094
  }
@@ -3546,6 +4096,94 @@ function findPackageCallSites(cwd, packageName) {
3546
4096
  continue;
3547
4097
  }
3548
4098
  try {
4099
+ let getLineCol2 = function(offset) {
4100
+ let low = 0;
4101
+ let high = lineStarts.length - 1;
4102
+ while (low <= high) {
4103
+ const mid = Math.floor((low + high) / 2);
4104
+ if (lineStarts[mid] <= offset) {
4105
+ if (mid === lineStarts.length - 1 || lineStarts[mid + 1] > offset) {
4106
+ return { line: mid + 1, column: offset - lineStarts[mid] + 1 };
4107
+ }
4108
+ low = mid + 1;
4109
+ } else {
4110
+ high = mid - 1;
4111
+ }
4112
+ }
4113
+ return { line: 1, column: offset + 1 };
4114
+ }, walk2 = function(node, parentType) {
4115
+ if (!node || typeof node !== "object") return;
4116
+ if (node.type === "CallExpression") {
4117
+ let localSymbol = "";
4118
+ let memberName;
4119
+ if (node.callee?.type === "Identifier" && localNames.has(node.callee.name)) {
4120
+ localSymbol = node.callee.name;
4121
+ } else if (node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && localNames.has(node.callee.object.name)) {
4122
+ localSymbol = node.callee.object.name;
4123
+ memberName = node.callee.property?.name;
4124
+ }
4125
+ if (localSymbol) {
4126
+ const { line, column } = getLineCol2(node.start);
4127
+ referencedLinesSet.add(line);
4128
+ const argumentSpans = (node.arguments || []).map((arg) => ({
4129
+ start: arg.start,
4130
+ end: arg.end,
4131
+ text: content.slice(arg.start, arg.end)
4132
+ }));
4133
+ astCallSites.push({
4134
+ start: node.start,
4135
+ end: node.end,
4136
+ line,
4137
+ column,
4138
+ calleeText: content.slice(node.callee.start, node.callee.end),
4139
+ expressionType: "call",
4140
+ localSymbol,
4141
+ memberName,
4142
+ argumentCount: (node.arguments || []).length,
4143
+ argumentSpans
4144
+ });
4145
+ }
4146
+ } else if (node.type === "MemberExpression") {
4147
+ if (node.object?.type === "Identifier" && localNames.has(node.object.name)) {
4148
+ const { line, column } = getLineCol2(node.start);
4149
+ referencedLinesSet.add(line);
4150
+ if (parentType !== "CallExpression") {
4151
+ astCallSites.push({
4152
+ start: node.start,
4153
+ end: node.end,
4154
+ line,
4155
+ column,
4156
+ calleeText: content.slice(node.start, node.end),
4157
+ expressionType: "property-access",
4158
+ localSymbol: node.object.name,
4159
+ memberName: node.property?.name,
4160
+ argumentCount: 0,
4161
+ argumentSpans: []
4162
+ });
4163
+ }
4164
+ }
4165
+ } else if (node.type === "Identifier") {
4166
+ if (localNames.has(node.name)) {
4167
+ const isImportDecl = Boolean(parentType?.startsWith("Import"));
4168
+ if (!isImportDecl) {
4169
+ const { line } = getLineCol2(node.start);
4170
+ referencedLinesSet.add(line);
4171
+ }
4172
+ }
4173
+ }
4174
+ for (const key of Object.keys(node)) {
4175
+ if (key === "parent") continue;
4176
+ const child = node[key];
4177
+ if (Array.isArray(child)) {
4178
+ for (const item of child) {
4179
+ walk2(item, node.type);
4180
+ }
4181
+ } else if (child && typeof child === "object") {
4182
+ walk2(child, node.type);
4183
+ }
4184
+ }
4185
+ };
4186
+ var getLineCol = getLineCol2, walk = walk2;
3549
4187
  const parseResult = parseSync2(filePath, content);
3550
4188
  if (!parseResult.program) continue;
3551
4189
  const program = parseResult.program;
@@ -3554,7 +4192,7 @@ function findPackageCallSites(cwd, packageName) {
3554
4192
  const localNames = /* @__PURE__ */ new Set();
3555
4193
  for (const statement of program.body) {
3556
4194
  if (statement.type === "ImportDeclaration" && statement.source?.value) {
3557
- const importSource = statement.source.value;
4195
+ const importSource = String(statement.source.value);
3558
4196
  if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
3559
4197
  importSources.push(importSource);
3560
4198
  for (const spec of statement.specifiers || []) {
@@ -3566,47 +4204,90 @@ function findPackageCallSites(cwd, packageName) {
3566
4204
  localName,
3567
4205
  importedName: "default",
3568
4206
  isDefault: true,
3569
- isNamespace: false
4207
+ isNamespace: false,
4208
+ kind: "default",
4209
+ start: spec.local?.start,
4210
+ end: spec.local?.end
3570
4211
  });
3571
4212
  } else if (spec.type === "ImportNamespaceSpecifier") {
3572
4213
  imports.push({
3573
4214
  localName,
3574
4215
  importedName: "*",
3575
4216
  isDefault: false,
3576
- isNamespace: true
4217
+ isNamespace: true,
4218
+ kind: "namespace",
4219
+ start: spec.local?.start,
4220
+ end: spec.local?.end
3577
4221
  });
3578
4222
  } else if (spec.type === "ImportSpecifier") {
3579
4223
  imports.push({
3580
4224
  localName,
3581
4225
  importedName: spec.imported?.name || localName,
3582
4226
  isDefault: false,
3583
- isNamespace: false
4227
+ isNamespace: false,
4228
+ kind: "named",
4229
+ start: spec.local?.start,
4230
+ end: spec.local?.end
3584
4231
  });
3585
4232
  }
3586
4233
  }
3587
4234
  }
3588
4235
  }
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;
4236
+ if (statement.type === "VariableDeclaration") {
4237
+ for (const decl of statement.declarations || []) {
4238
+ if (decl.init?.type === "CallExpression" && decl.init.callee?.type === "Identifier" && decl.init.callee.name === "require" && decl.init.arguments?.[0]?.value) {
4239
+ const importSource = String(decl.init.arguments[0].value);
4240
+ if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
4241
+ importSources.push(importSource);
4242
+ if (decl.id?.type === "Identifier") {
4243
+ const localName = decl.id.name;
4244
+ localNames.add(localName);
4245
+ imports.push({
4246
+ localName,
4247
+ importedName: "default",
4248
+ isDefault: true,
4249
+ isNamespace: false,
4250
+ kind: "require-default"
4251
+ });
4252
+ } else if (decl.id?.type === "ObjectPattern") {
4253
+ for (const prop of decl.id.properties || []) {
4254
+ const localName = prop.value?.name || prop.key?.name;
4255
+ if (localName) {
4256
+ localNames.add(localName);
4257
+ imports.push({
4258
+ localName,
4259
+ importedName: prop.key?.name || localName,
4260
+ isDefault: false,
4261
+ isNamespace: false,
4262
+ kind: "require-destructured"
4263
+ });
4264
+ }
4265
+ }
4266
+ }
4267
+ }
3599
4268
  }
3600
4269
  }
3601
- });
3602
- callSites.push({
3603
- filePath,
3604
- sourceContent: content,
3605
- importSources,
3606
- imports,
3607
- referencedLines
3608
- });
4270
+ }
3609
4271
  }
4272
+ if (imports.length === 0 && importSources.length === 0) {
4273
+ continue;
4274
+ }
4275
+ const lineStarts = [0];
4276
+ for (let i = 0; i < content.length; i++) {
4277
+ if (content[i] === "\n") lineStarts.push(i + 1);
4278
+ }
4279
+ const referencedLinesSet = /* @__PURE__ */ new Set();
4280
+ const astCallSites = [];
4281
+ walk2(program);
4282
+ const referencedLines = Array.from(referencedLinesSet).sort((a, b) => a - b);
4283
+ callSites.push({
4284
+ filePath,
4285
+ sourceContent: content,
4286
+ importSources,
4287
+ imports,
4288
+ referencedLines,
4289
+ callSites: astCallSites
4290
+ });
3610
4291
  } catch {
3611
4292
  }
3612
4293
  }
@@ -3614,7 +4295,17 @@ function findPackageCallSites(cwd, packageName) {
3614
4295
  }
3615
4296
 
3616
4297
  // src/remediation/refactor/refactor-agent.ts
3617
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
4298
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
4299
+ import { parseSync as parseSync3 } from "oxc-parser";
4300
+ function applyEdits(source, edits) {
4301
+ if (edits.length === 0) return source;
4302
+ const sorted = [...edits].sort((a, b) => b.start - a.start);
4303
+ let result = source;
4304
+ for (const edit of sorted) {
4305
+ result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
4306
+ }
4307
+ return result;
4308
+ }
3618
4309
  function parseCompilerDiagnostics(diagnosticError) {
3619
4310
  const diagnostics = [];
3620
4311
  const lines = diagnosticError.split("\n");
@@ -3649,173 +4340,332 @@ function parseCompilerDiagnostics(diagnosticError) {
3649
4340
  function revertRefactorSnapshots(snapshots) {
3650
4341
  for (const [filePath, content] of Object.entries(snapshots)) {
3651
4342
  try {
3652
- writeFileSync3(filePath, content, "utf8");
4343
+ writeFileSync5(filePath, content, "utf8");
3653
4344
  } catch {
3654
4345
  }
3655
4346
  }
3656
4347
  }
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(");
4348
+ function transformCallArguments(content, options) {
4349
+ const parseResult = parseSync3(options.filePath ?? "file.ts", content);
4350
+ if (!parseResult.program) {
4351
+ return { modified: false, newContent: content };
4352
+ }
4353
+ const edits = [];
4354
+ function walk(node) {
4355
+ if (!node || typeof node !== "object") return;
4356
+ if (node.type === "CallExpression") {
4357
+ const calleeText = content.slice(node.callee.start, node.callee.end);
4358
+ const matchesCallee = calleeText === options.callee || calleeText.endsWith(`.${options.callee}`);
4359
+ if (matchesCallee) {
4360
+ const args = node.arguments || [];
4361
+ if (options.dropIndex !== void 0) {
4362
+ const idx = options.dropIndex;
4363
+ if (idx >= 0 && idx < args.length) {
4364
+ if (args.length === 1) {
4365
+ edits.push({
4366
+ start: args[0].start,
4367
+ end: args[0].end,
4368
+ replacement: ""
4369
+ });
4370
+ } else if (idx < args.length - 1) {
4371
+ edits.push({
4372
+ start: args[idx].start,
4373
+ end: args[idx + 1].start,
4374
+ replacement: ""
4375
+ });
4376
+ } else {
4377
+ edits.push({
4378
+ start: args[idx - 1].end,
4379
+ end: args[idx].end,
4380
+ replacement: ""
4381
+ });
4382
+ }
4383
+ }
4384
+ }
4385
+ if (options.reorder) {
4386
+ const [i, j] = options.reorder;
4387
+ if (i >= 0 && j >= 0 && i < args.length && j < args.length && i !== j) {
4388
+ const argIText = content.slice(args[i].start, args[i].end);
4389
+ const argJText = content.slice(args[j].start, args[j].end);
4390
+ edits.push({
4391
+ start: args[i].start,
4392
+ end: args[i].end,
4393
+ replacement: argJText
4394
+ });
4395
+ edits.push({
4396
+ start: args[j].start,
4397
+ end: args[j].end,
4398
+ replacement: argIText
4399
+ });
4400
+ }
4401
+ }
3696
4402
  }
3697
4403
  }
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(");
4404
+ for (const key of Object.keys(node)) {
4405
+ if (key === "parent") continue;
4406
+ const child = node[key];
4407
+ if (Array.isArray(child)) {
4408
+ for (const item of child) walk(item);
4409
+ } else if (child && typeof child === "object") {
4410
+ walk(child);
3707
4411
  }
3708
4412
  }
3709
- ]
3710
- };
3711
- function escapeRegex(str) {
3712
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4413
+ }
4414
+ walk(parseResult.program);
4415
+ if (edits.length === 0) {
4416
+ return { modified: false, newContent: content };
4417
+ }
4418
+ return { modified: true, newContent: applyEdits(content, edits) };
3713
4419
  }
3714
- function transformFileContent(content, packageName, diagnosticError, filePath) {
4420
+ function transformFileContent(content, packageName, diagnosticError, filePath, argumentTransforms) {
4421
+ let currentContent = content;
3715
4422
  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;
4423
+ const parseResult = parseSync3(filePath ?? "file.ts", currentContent);
4424
+ if (!parseResult.program) {
4425
+ return { modified: false, newContent: content };
4426
+ }
4427
+ const edits = [];
4428
+ const body = parseResult.program.body || [];
4429
+ let defaultLocalName;
4430
+ let namespaceLocalName;
4431
+ let defaultImportDecl;
4432
+ let subpathImportDecl;
4433
+ let subpathName;
4434
+ for (const statement of body) {
4435
+ if (statement.type === "ImportDeclaration" && statement.source?.value) {
4436
+ const specText = String(statement.source.value);
4437
+ if (specText === packageName) {
4438
+ for (const spec of statement.specifiers || []) {
4439
+ if (spec.type === "ImportDefaultSpecifier") {
4440
+ defaultLocalName = spec.local?.name;
4441
+ defaultImportDecl = statement;
4442
+ } else if (spec.type === "ImportNamespaceSpecifier") {
4443
+ namespaceLocalName = spec.local?.name;
4444
+ }
4445
+ }
4446
+ } else if (specText.startsWith(`${packageName}/`)) {
4447
+ subpathImportDecl = statement;
4448
+ subpathName = specText.slice(packageName.length + 1);
4449
+ for (const spec of statement.specifiers || []) {
4450
+ if (spec.type === "ImportDefaultSpecifier") {
4451
+ defaultLocalName = spec.local?.name;
4452
+ }
3725
4453
  }
3726
4454
  }
3727
4455
  }
3728
4456
  }
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
- }
4457
+ if (subpathImportDecl && (diagnosticError.includes(`Cannot find module '${packageName}/`) || diagnosticError.includes(`Cannot find module "${packageName}/`))) {
4458
+ const local = defaultLocalName || subpathName || "default";
4459
+ const newImport = `import { ${subpathName} as ${local} } from "${packageName}";`;
4460
+ edits.push({
4461
+ start: subpathImportDecl.start,
4462
+ end: subpathImportDecl.end,
4463
+ replacement: newImport
4464
+ });
4465
+ }
4466
+ if (packageName === "rimraf" && (diagnosticError.includes("sync") || diagnosticError.includes("rimrafSync") || currentContent.includes("rimraf.sync(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.sync(`))) {
4467
+ let walkRimraf2 = function(node) {
4468
+ if (!node || typeof node !== "object") return;
4469
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "sync") {
4470
+ edits.push({
4471
+ start: node.callee.start,
4472
+ end: node.callee.end,
4473
+ replacement: "rimrafSync"
4474
+ });
4475
+ }
4476
+ for (const k of Object.keys(node)) {
4477
+ if (k === "parent") continue;
4478
+ const v = node[k];
4479
+ if (Array.isArray(v)) v.forEach(walkRimraf2);
4480
+ else if (v && typeof v === "object") walkRimraf2(v);
4481
+ }
4482
+ };
4483
+ var walkRimraf = walkRimraf2;
4484
+ const targetLocal = defaultLocalName || "rimraf";
4485
+ walkRimraf2(parseResult.program);
4486
+ if (defaultImportDecl) {
4487
+ edits.push({
4488
+ start: defaultImportDecl.start,
4489
+ end: defaultImportDecl.end,
4490
+ replacement: `import { rimrafSync } from "rimraf";`
4491
+ });
4492
+ }
4493
+ }
4494
+ if (packageName === "uuid" && (diagnosticError.includes("v4") || diagnosticError.includes("uuidv4") || currentContent.includes("uuid.v4(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.v4(`))) {
4495
+ let walkUuid2 = function(node) {
4496
+ if (!node || typeof node !== "object") return;
4497
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "v4") {
4498
+ edits.push({
4499
+ start: node.callee.start,
4500
+ end: node.callee.end,
4501
+ replacement: "uuidv4"
4502
+ });
4503
+ }
4504
+ for (const k of Object.keys(node)) {
4505
+ if (k === "parent") continue;
4506
+ const v = node[k];
4507
+ if (Array.isArray(v)) v.forEach(walkUuid2);
4508
+ else if (v && typeof v === "object") walkUuid2(v);
4509
+ }
4510
+ };
4511
+ var walkUuid = walkUuid2;
4512
+ const targetLocal = defaultLocalName || "uuid";
4513
+ walkUuid2(parseResult.program);
4514
+ if (defaultImportDecl) {
4515
+ edits.push({
4516
+ start: defaultImportDecl.start,
4517
+ end: defaultImportDecl.end,
4518
+ replacement: `import { v4 as uuidv4 } from "uuid";`
4519
+ });
4520
+ }
4521
+ }
4522
+ if (packageName === "glob" && (diagnosticError.includes("sync") || diagnosticError.includes("globSync") || currentContent.includes("glob.sync(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.sync(`))) {
4523
+ let walkGlob2 = function(node) {
4524
+ if (!node || typeof node !== "object") return;
4525
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "sync") {
4526
+ edits.push({
4527
+ start: node.callee.start,
4528
+ end: node.callee.end,
4529
+ replacement: "globSync"
4530
+ });
4531
+ }
4532
+ for (const k of Object.keys(node)) {
4533
+ if (k === "parent") continue;
4534
+ const v = node[k];
4535
+ if (Array.isArray(v)) v.forEach(walkGlob2);
4536
+ else if (v && typeof v === "object") walkGlob2(v);
4537
+ }
4538
+ };
4539
+ var walkGlob = walkGlob2;
4540
+ const targetLocal = defaultLocalName || "glob";
4541
+ walkGlob2(parseResult.program);
4542
+ if (defaultImportDecl) {
4543
+ edits.push({
4544
+ start: defaultImportDecl.start,
4545
+ end: defaultImportDecl.end,
4546
+ replacement: `import { globSync } from "glob";`
4547
+ });
4548
+ }
4549
+ }
4550
+ if (packageName === "mkdirp" && (diagnosticError.includes("sync") || diagnosticError.includes("mkdirpSync") || currentContent.includes("mkdirp.sync(") || defaultLocalName && currentContent.includes(`${defaultLocalName}.sync(`))) {
4551
+ let walkMkdirp2 = function(node) {
4552
+ if (!node || typeof node !== "object") return;
4553
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === targetLocal && node.callee.property?.name === "sync") {
4554
+ edits.push({
4555
+ start: node.callee.start,
4556
+ end: node.callee.end,
4557
+ replacement: "mkdirpSync"
4558
+ });
4559
+ }
4560
+ for (const k of Object.keys(node)) {
4561
+ if (k === "parent") continue;
4562
+ const v = node[k];
4563
+ if (Array.isArray(v)) v.forEach(walkMkdirp2);
4564
+ else if (v && typeof v === "object") walkMkdirp2(v);
4565
+ }
4566
+ };
4567
+ var walkMkdirp = walkMkdirp2;
4568
+ const targetLocal = defaultLocalName || "mkdirp";
4569
+ walkMkdirp2(parseResult.program);
4570
+ if (defaultImportDecl) {
4571
+ edits.push({
4572
+ start: defaultImportDecl.start,
4573
+ end: defaultImportDecl.end,
4574
+ replacement: `import { mkdirpSync } from "mkdirp";`
4575
+ });
4576
+ }
4577
+ }
4578
+ if (defaultImportDecl && defaultLocalName && (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"))) {
4579
+ edits.push({
4580
+ start: defaultImportDecl.start,
4581
+ end: defaultImportDecl.end,
4582
+ replacement: `import * as ${defaultLocalName} from "${packageName}";`
4583
+ });
3741
4584
  }
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;
4585
+ if (namespaceLocalName && (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259"))) {
4586
+ for (const statement of body) {
4587
+ if (statement.type === "ImportDeclaration" && String(statement.source?.value) === packageName) {
4588
+ edits.push({
4589
+ start: statement.start,
4590
+ end: statement.end,
4591
+ replacement: `import ${namespaceLocalName} from "${packageName}";`
4592
+ });
4593
+ }
3753
4594
  }
3754
4595
  }
3755
4596
  const diagnostics = parseCompilerDiagnostics(diagnosticError);
3756
4597
  for (const diag of diagnostics) {
3757
4598
  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;
4599
+ let getLine2 = function(offset) {
4600
+ let low = 0;
4601
+ let high = lineStarts.length - 1;
4602
+ while (low <= high) {
4603
+ const mid = Math.floor((low + high) / 2);
4604
+ if (lineStarts[mid] <= offset) {
4605
+ if (mid === lineStarts.length - 1 || lineStarts[mid + 1] > offset) {
4606
+ return mid + 1;
4607
+ }
4608
+ low = mid + 1;
4609
+ } else {
4610
+ high = mid - 1;
3770
4611
  }
3771
4612
  }
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;
4613
+ return 1;
4614
+ }, walkDiagnostics2 = function(node) {
4615
+ if (!node || typeof node !== "object") return;
4616
+ if (node.type === "MemberExpression" && node.property?.name === missing) {
4617
+ const line = getLine2(node.start);
4618
+ const lineMatches = !diag.line || diag.line === line;
4619
+ if (lineMatches) {
4620
+ edits.push({
4621
+ start: node.property.start,
4622
+ end: node.property.end,
4623
+ replacement: suggested
4624
+ });
4625
+ }
3777
4626
  }
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;
4627
+ if (node.type === "ImportSpecifier" && (node.imported?.name === missing || node.local?.name === missing)) {
4628
+ edits.push({
4629
+ start: node.local.start,
4630
+ end: node.local.end,
4631
+ replacement: suggested
4632
+ });
4633
+ }
4634
+ for (const k of Object.keys(node)) {
4635
+ if (k === "parent") continue;
4636
+ const v = node[k];
4637
+ if (Array.isArray(v)) v.forEach(walkDiagnostics2);
4638
+ else if (v && typeof v === "object") walkDiagnostics2(v);
3788
4639
  }
4640
+ };
4641
+ var getLine = getLine2, walkDiagnostics = walkDiagnostics2;
4642
+ const missing = diag.missingMember;
4643
+ const suggested = diag.suggestedSymbol;
4644
+ const lineStarts = [0];
4645
+ for (let i = 0; i < currentContent.length; i++) {
4646
+ if (currentContent[i] === "\n") lineStarts.push(i + 1);
3789
4647
  }
4648
+ walkDiagnostics2(parseResult.program);
3790
4649
  }
3791
4650
  }
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
- }
4651
+ if (edits.length > 0) {
4652
+ currentContent = applyEdits(currentContent, edits);
4653
+ modified = true;
4654
+ }
4655
+ if (argumentTransforms) {
4656
+ const argResult = transformCallArguments(currentContent, {
4657
+ ...argumentTransforms,
4658
+ filePath
4659
+ });
4660
+ if (argResult.modified) {
4661
+ currentContent = argResult.newContent;
4662
+ modified = true;
3813
4663
  }
3814
4664
  }
3815
- return { modified, newContent };
4665
+ return { modified, newContent: currentContent };
3816
4666
  }
3817
4667
  async function refactorConsumerCallSites(params) {
3818
- const { packageName, diagnosticError, callSites } = params;
4668
+ const { packageName, diagnosticError, callSites, argumentTransforms } = params;
3819
4669
  if (callSites.length === 0) {
3820
4670
  return {
3821
4671
  applied: false,
@@ -3828,16 +4678,17 @@ async function refactorConsumerCallSites(params) {
3828
4678
  const refactoredFiles = [];
3829
4679
  try {
3830
4680
  for (const site of callSites) {
3831
- const originalContent = site.sourceContent || readFileSync12(site.filePath, "utf8");
4681
+ const originalContent = site.sourceContent || readFileSync13(site.filePath, "utf8");
3832
4682
  snapshots[site.filePath] = originalContent;
3833
4683
  const { modified, newContent } = transformFileContent(
3834
4684
  originalContent,
3835
4685
  packageName,
3836
4686
  diagnosticError,
3837
- site.filePath
4687
+ site.filePath,
4688
+ argumentTransforms
3838
4689
  );
3839
4690
  if (modified && newContent !== originalContent) {
3840
- writeFileSync3(site.filePath, newContent, "utf8");
4691
+ writeFileSync5(site.filePath, newContent, "utf8");
3841
4692
  refactoredFiles.push(site.filePath);
3842
4693
  }
3843
4694
  }
@@ -3860,7 +4711,7 @@ async function refactorConsumerCallSites(params) {
3860
4711
  return {
3861
4712
  applied: false,
3862
4713
  refactoredFiles: [],
3863
- snapshots: {},
4714
+ snapshots,
3864
4715
  error: `Failed to apply breaking change refactoring: ${message}`
3865
4716
  };
3866
4717
  }
@@ -3883,7 +4734,13 @@ var applyVersionBumpTool = defineTool({
3883
4734
  installPreferOffline: z4.boolean().optional(),
3884
4735
  enforceFrozenLockfile: z4.boolean().optional(),
3885
4736
  workspace: z4.string().optional(),
3886
- breakingChangeRefactor: z4.boolean().optional().describe("If true, attempt agentic call-site refactoring when validation fails after major upgrade")
4737
+ breakingChangeRefactor: z4.boolean().optional().describe(
4738
+ "If true, attempt agentic call-site refactoring when validation fails after major upgrade"
4739
+ ),
4740
+ reproductionTest: z4.string().optional().describe("Optional reproduction test script verifying fix"),
4741
+ proofOfFix: z4.union([z4.boolean(), z4.string()]).optional().describe("If true or script path/content, enforce dual-stage exploit reproduction"),
4742
+ requireProofOfFix: z4.boolean().optional().describe("If true, fail and rollback version bump if proof-of-fix verification fails"),
4743
+ patchesDir: z4.string().optional().default("./patches").describe("Directory containing patch/reproduction artifacts")
3887
4744
  }),
3888
4745
  execute: async ({
3889
4746
  cwd,
@@ -3899,10 +4756,14 @@ var applyVersionBumpTool = defineTool({
3899
4756
  installPreferOffline,
3900
4757
  enforceFrozenLockfile,
3901
4758
  workspace,
3902
- breakingChangeRefactor
4759
+ breakingChangeRefactor,
4760
+ reproductionTest,
4761
+ proofOfFix,
4762
+ requireProofOfFix,
4763
+ patchesDir
3903
4764
  }) => {
3904
4765
  const pm = packageManager ?? detectPackageManager(cwd);
3905
- let pkgPath = join15(cwd, "package.json");
4766
+ let pkgPath = join16(cwd, "package.json");
3906
4767
  const loadedPolicy = loadPolicy(cwd, policy);
3907
4768
  const commandConstraints = {
3908
4769
  ...loadedPolicy.constraints,
@@ -3924,7 +4785,7 @@ var applyVersionBumpTool = defineTool({
3924
4785
  message: `Policy blocked changes for package "${packageName}".`
3925
4786
  };
3926
4787
  }
3927
- const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
4788
+ const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
3928
4789
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
3929
4790
  return {
3930
4791
  packageName,
@@ -3937,10 +4798,10 @@ var applyVersionBumpTool = defineTool({
3937
4798
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
3938
4799
  };
3939
4800
  }
3940
- pkgPath = join15(cwd, "package.json");
4801
+ pkgPath = join16(cwd, "package.json");
3941
4802
  let pkgJson;
3942
4803
  try {
3943
- pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
4804
+ pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
3944
4805
  } catch {
3945
4806
  return {
3946
4807
  packageName,
@@ -4000,8 +4861,57 @@ var applyVersionBumpTool = defineTool({
4000
4861
  };
4001
4862
  }
4002
4863
  return withRepoLock(cwd, async () => {
4864
+ const prepared = await prepareReproductionTestFile({
4865
+ cwd,
4866
+ packageName,
4867
+ vulnerableVersion: fromVersion,
4868
+ patchesDir,
4869
+ proofOfFix,
4870
+ reproductionTest
4871
+ });
4872
+ const reproTestPath = prepared.testFilePath;
4873
+ const isProofActive = Boolean(reproTestPath || proofOfFix || requireProofOfFix);
4874
+ let negativePhaseResult;
4875
+ if (isProofActive) {
4876
+ if (!reproTestPath) {
4877
+ if (requireProofOfFix) {
4878
+ return {
4879
+ packageName,
4880
+ strategy: "none",
4881
+ fromVersion,
4882
+ toVersion,
4883
+ applied: false,
4884
+ dryRun,
4885
+ unresolvedReason: "policy-blocked",
4886
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied."
4887
+ };
4888
+ }
4889
+ } else {
4890
+ negativePhaseResult = await runProofOfFixPhase({
4891
+ stage: "negative",
4892
+ testFilePath: reproTestPath,
4893
+ cwd
4894
+ });
4895
+ if (!negativePhaseResult.passed) {
4896
+ if (prepared.isCreated && reproTestPath) {
4897
+ await rm3(reproTestPath, { force: true }).catch(() => {
4898
+ });
4899
+ }
4900
+ return {
4901
+ packageName,
4902
+ strategy: "version-bump",
4903
+ fromVersion,
4904
+ toVersion,
4905
+ applied: false,
4906
+ dryRun: false,
4907
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
4908
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
4909
+ };
4910
+ }
4911
+ }
4912
+ }
4003
4913
  pkgJson[depField][packageName] = newRange;
4004
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4914
+ writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4005
4915
  try {
4006
4916
  const [installCmd, ...installArgs] = effectiveInstallCommand;
4007
4917
  await safeExeca(installCmd, installArgs, {
@@ -4010,7 +4920,7 @@ var applyVersionBumpTool = defineTool({
4010
4920
  });
4011
4921
  } catch (err) {
4012
4922
  pkgJson[depField][packageName] = currentRange;
4013
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4923
+ writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4014
4924
  const message = err instanceof Error ? err.message : String(err);
4015
4925
  return {
4016
4926
  packageName,
@@ -4082,7 +4992,7 @@ var applyVersionBumpTool = defineTool({
4082
4992
  }
4083
4993
  if (!validationResult.passed) {
4084
4994
  pkgJson[depField][packageName] = currentRange;
4085
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4995
+ writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4086
4996
  try {
4087
4997
  const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
4088
4998
  await safeExeca(rollbackCmd, rollbackArgs, {
@@ -4104,6 +5014,49 @@ var applyVersionBumpTool = defineTool({
4104
5014
  message: `${prefix2} after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${failureMessage}`
4105
5015
  };
4106
5016
  }
5017
+ let proofOfFixVerification;
5018
+ if (reproTestPath && negativePhaseResult?.passed) {
5019
+ const positivePhaseResult = await runProofOfFixPhase({
5020
+ stage: "positive",
5021
+ testFilePath: reproTestPath,
5022
+ cwd
5023
+ });
5024
+ if (!positivePhaseResult.passed) {
5025
+ pkgJson[depField][packageName] = currentRange;
5026
+ writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5027
+ try {
5028
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
5029
+ await safeExeca(rollbackCmd, rollbackArgs, {
5030
+ cwd,
5031
+ stdio: "pipe"
5032
+ });
5033
+ } catch {
5034
+ }
5035
+ if (prepared.isCreated && reproTestPath) {
5036
+ await rm3(reproTestPath, { force: true }).catch(() => {
5037
+ });
5038
+ }
5039
+ return {
5040
+ packageName,
5041
+ strategy: "version-bump",
5042
+ fromVersion,
5043
+ toVersion,
5044
+ applied: false,
5045
+ dryRun: false,
5046
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
5047
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
5048
+ };
5049
+ }
5050
+ proofOfFixVerification = assembleProofOfFixVerification({
5051
+ reproductionTestPath: reproTestPath,
5052
+ negativePhase: negativePhaseResult,
5053
+ positivePhase: positivePhaseResult,
5054
+ regressionPhase: {
5055
+ executed: runTests,
5056
+ passed: validationResult.passed
5057
+ }
5058
+ });
5059
+ }
4107
5060
  if (refactoredFilesList && refactoredFilesList.length > 0) {
4108
5061
  return {
4109
5062
  packageName,
@@ -4113,6 +5066,7 @@ var applyVersionBumpTool = defineTool({
4113
5066
  applied: true,
4114
5067
  dryRun: false,
4115
5068
  refactoredFiles: refactoredFilesList,
5069
+ proofOfFix: proofOfFixVerification,
4116
5070
  message: `Upgraded "${packageName}" from ${fromVersion} to ${toVersion} and refactored ${refactoredFilesList.length} call-site file(s) across breaking API changes.`
4117
5071
  };
4118
5072
  }
@@ -4136,6 +5090,7 @@ var applyVersionBumpTool = defineTool({
4136
5090
  toVersion,
4137
5091
  applied: true,
4138
5092
  dryRun: false,
5093
+ proofOfFix: proofOfFixVerification,
4139
5094
  message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
4140
5095
  };
4141
5096
  });
@@ -4144,117 +5099,10 @@ var applyVersionBumpTool = defineTool({
4144
5099
 
4145
5100
  // src/remediation/tools/apply-package-override/index.ts
4146
5101
  import { z as z5 } from "zod";
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";
4150
-
4151
- // src/remediation/tools/apply-package-override/helpers.ts
4152
- async function collectDependencyTrace(cwd, pm, packageName, constraints) {
4153
- try {
4154
- const whyCommand = resolveWhyCommand(pm, packageName, constraints);
4155
- if (whyCommand.length === 0) return void 0;
4156
- const [whyCmd, ...whyArgs] = whyCommand;
4157
- const result = await safeExeca(whyCmd, whyArgs, {
4158
- cwd,
4159
- stdio: "pipe",
4160
- reject: false
4161
- });
4162
- const output = [result.stdout, result.stderr].filter(Boolean).join("\n").split("\n").map((line) => line.trim()).filter(Boolean);
4163
- if (output.length === 0) return void 0;
4164
- return output.slice(0, 3).join(" | ");
4165
- } catch {
4166
- return void 0;
4167
- }
4168
- }
4169
- function describeOverrideField(packageManager) {
4170
- if (packageManager === "npm" || packageManager === "bun") return "overrides";
4171
- if (packageManager === "pnpm") return "pnpm.overrides";
4172
- if (packageManager === "deno") return "overrides";
4173
- return "resolutions";
4174
- }
4175
- function getOverrideValue(pkgJson, packageManager, packageName) {
4176
- if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
4177
- return pkgJson.overrides?.[packageName];
4178
- }
4179
- if (packageManager === "pnpm") return pkgJson.pnpm?.overrides?.[packageName];
4180
- return pkgJson.resolutions?.[packageName];
4181
- }
4182
- function setOverrideValue(pkgJson, packageManager, packageName, version) {
4183
- if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
4184
- pkgJson.overrides = { ...pkgJson.overrides, [packageName]: version };
4185
- return;
4186
- }
4187
- if (packageManager === "pnpm") {
4188
- pkgJson.pnpm = {
4189
- ...pkgJson.pnpm,
4190
- overrides: {
4191
- ...pkgJson.pnpm?.overrides,
4192
- [packageName]: version
4193
- }
4194
- };
4195
- return;
4196
- }
4197
- pkgJson.resolutions = { ...pkgJson.resolutions, [packageName]: version };
4198
- }
4199
- function restoreOverrideValue(pkgJson, packageManager, packageName, previousValue) {
4200
- if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno") {
4201
- pkgJson.overrides = restoreRecord(pkgJson.overrides, packageName, previousValue);
4202
- return;
4203
- }
4204
- if (packageManager === "pnpm") {
4205
- pkgJson.pnpm = {
4206
- ...pkgJson.pnpm,
4207
- overrides: restoreRecord(pkgJson.pnpm?.overrides, packageName, previousValue)
4208
- };
4209
- if (!pkgJson.pnpm.overrides) {
4210
- delete pkgJson.pnpm.overrides;
4211
- }
4212
- if (Object.keys(pkgJson.pnpm).length === 0) {
4213
- delete pkgJson.pnpm;
4214
- }
4215
- return;
4216
- }
4217
- pkgJson.resolutions = restoreRecord(pkgJson.resolutions, packageName, previousValue);
4218
- }
4219
- function getDenoJsonImportValue(denoJson, packageName) {
4220
- const imports = denoJson.imports ?? {};
4221
- if (imports[packageName] !== void 0) {
4222
- return { key: packageName, value: imports[packageName] };
4223
- }
4224
- const npmKey = `npm:${packageName}`;
4225
- if (imports[npmKey] !== void 0) {
4226
- return { key: npmKey, value: imports[npmKey] };
4227
- }
4228
- return void 0;
4229
- }
4230
- function setDenoJsonImportValue(denoJson, packageName, version) {
4231
- const existing = getDenoJsonImportValue(denoJson, packageName);
4232
- const key = existing?.key ?? `npm:${packageName}`;
4233
- denoJson.imports = { ...denoJson.imports, [key]: `npm:${packageName}@${version}` };
4234
- }
4235
- function restoreDenoJsonImportValue(denoJson, packageName, previousEntry) {
4236
- const existing = getDenoJsonImportValue(denoJson, packageName);
4237
- if (!existing) return;
4238
- const imports = { ...denoJson.imports };
4239
- if (previousEntry === void 0) {
4240
- delete imports[existing.key];
4241
- } else {
4242
- delete imports[existing.key];
4243
- imports[previousEntry.key] = previousEntry.value;
4244
- }
4245
- denoJson.imports = Object.keys(imports).length > 0 ? imports : void 0;
4246
- }
4247
- function restoreRecord(record, key, previousValue) {
4248
- const nextRecord = { ...record };
4249
- if (previousValue === void 0) {
4250
- delete nextRecord[key];
4251
- } else {
4252
- nextRecord[key] = previousValue;
4253
- }
4254
- return Object.keys(nextRecord).length > 0 ? nextRecord : void 0;
4255
- }
4256
-
4257
- // src/remediation/tools/apply-package-override/index.ts
5102
+ import { join as join17 } from "path";
5103
+ import { existsSync as existsSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
5104
+ import { rm as rm4 } from "fs/promises";
5105
+ import semver5 from "semver";
4258
5106
  var applyPackageOverrideTool = defineTool({
4259
5107
  description: "Apply a package-manager-native package.json override for a vulnerable transitive dependency and reinstall. Uses npm overrides, pnpm.overrides, yarn resolutions, bun overrides, or deno.json imports.",
4260
5108
  parameters: z5.object({
@@ -4270,7 +5118,11 @@ var applyPackageOverrideTool = defineTool({
4270
5118
  installMode: z5.enum(["standard", "prefer-offline", "deterministic"]).optional(),
4271
5119
  installPreferOffline: z5.boolean().optional(),
4272
5120
  enforceFrozenLockfile: z5.boolean().optional(),
4273
- workspace: z5.string().optional()
5121
+ workspace: z5.string().optional(),
5122
+ reproductionTest: z5.string().optional().describe("Optional reproduction test script verifying fix"),
5123
+ proofOfFix: z5.union([z5.boolean(), z5.string()]).optional().describe("If true or script path/content, enforce dual-stage exploit reproduction"),
5124
+ requireProofOfFix: z5.boolean().optional().describe("If true, fail and rollback override if proof-of-fix verification fails"),
5125
+ patchesDir: z5.string().optional().default("./patches").describe("Directory containing patch/reproduction artifacts")
4274
5126
  }),
4275
5127
  execute: async ({
4276
5128
  cwd,
@@ -4285,12 +5137,16 @@ var applyPackageOverrideTool = defineTool({
4285
5137
  installMode,
4286
5138
  installPreferOffline,
4287
5139
  enforceFrozenLockfile,
4288
- workspace
5140
+ workspace,
5141
+ reproductionTest,
5142
+ proofOfFix,
5143
+ requireProofOfFix,
5144
+ patchesDir
4289
5145
  }) => {
4290
5146
  const pm = packageManager ?? detectPackageManager(cwd);
4291
- const pkgPath = join16(cwd, "package.json");
4292
- const denoJsonPath = join16(cwd, "deno.json");
4293
- const isDenoNative = pm === "deno" && !existsSync12(pkgPath);
5147
+ const pkgPath = join17(cwd, "package.json");
5148
+ const denoJsonPath = join17(cwd, "deno.json");
5149
+ const isDenoNative = pm === "deno" && !existsSync13(pkgPath);
4294
5150
  const loadedPolicy = loadPolicy(cwd, policy);
4295
5151
  const commandConstraints = {
4296
5152
  ...loadedPolicy.constraints,
@@ -4316,7 +5172,7 @@ var applyPackageOverrideTool = defineTool({
4316
5172
  message: `Policy blocked changes for package "${packageName}".`
4317
5173
  };
4318
5174
  }
4319
- const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
5175
+ const isMajorBump = semver5.valid(fromVersion) && semver5.valid(toVersion) && semver5.major(toVersion) > semver5.major(fromVersion);
4320
5176
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
4321
5177
  return {
4322
5178
  packageName,
@@ -4332,7 +5188,7 @@ var applyPackageOverrideTool = defineTool({
4332
5188
  if (isDenoNative) {
4333
5189
  let denoJson;
4334
5190
  try {
4335
- denoJson = JSON.parse(readFileSync14(denoJsonPath, "utf8"));
5191
+ denoJson = JSON.parse(readFileSync15(denoJsonPath, "utf8"));
4336
5192
  } catch {
4337
5193
  return {
4338
5194
  packageName,
@@ -4377,14 +5233,63 @@ var applyPackageOverrideTool = defineTool({
4377
5233
  };
4378
5234
  }
4379
5235
  return withRepoLock(cwd, async () => {
5236
+ const prepared = await prepareReproductionTestFile({
5237
+ cwd,
5238
+ packageName,
5239
+ vulnerableVersion: fromVersion,
5240
+ patchesDir,
5241
+ proofOfFix,
5242
+ reproductionTest
5243
+ });
5244
+ const reproTestPath = prepared.testFilePath;
5245
+ const isProofActive = Boolean(reproTestPath || proofOfFix || requireProofOfFix);
5246
+ let negativePhaseResult;
5247
+ if (isProofActive) {
5248
+ if (!reproTestPath) {
5249
+ if (requireProofOfFix) {
5250
+ return {
5251
+ packageName,
5252
+ strategy: "none",
5253
+ fromVersion,
5254
+ toVersion,
5255
+ applied: false,
5256
+ dryRun,
5257
+ unresolvedReason: "policy-blocked",
5258
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied."
5259
+ };
5260
+ }
5261
+ } else {
5262
+ negativePhaseResult = await runProofOfFixPhase({
5263
+ stage: "negative",
5264
+ testFilePath: reproTestPath,
5265
+ cwd
5266
+ });
5267
+ if (!negativePhaseResult.passed) {
5268
+ if (prepared.isCreated && reproTestPath) {
5269
+ await rm4(reproTestPath, { force: true }).catch(() => {
5270
+ });
5271
+ }
5272
+ return {
5273
+ packageName,
5274
+ strategy: "override",
5275
+ fromVersion,
5276
+ toVersion,
5277
+ applied: false,
5278
+ dryRun: false,
5279
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
5280
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
5281
+ };
5282
+ }
5283
+ }
5284
+ }
4380
5285
  setDenoJsonImportValue(denoJson, overrideSelector, toVersion);
4381
- writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
5286
+ writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4382
5287
  try {
4383
5288
  const [installCmd, ...installArgs] = installCommand;
4384
5289
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
4385
5290
  } catch (err) {
4386
5291
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
4387
- writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
5292
+ writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4388
5293
  const message = err instanceof Error ? err.message : String(err);
4389
5294
  return {
4390
5295
  packageName,
@@ -4403,7 +5308,7 @@ var applyPackageOverrideTool = defineTool({
4403
5308
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
4404
5309
  } catch (err) {
4405
5310
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
4406
- writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
5311
+ writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4407
5312
  try {
4408
5313
  const [rollbackCmd, ...rollbackArgs] = installCommand;
4409
5314
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -4422,6 +5327,46 @@ var applyPackageOverrideTool = defineTool({
4422
5327
  };
4423
5328
  }
4424
5329
  }
5330
+ let proofOfFixVerification;
5331
+ if (reproTestPath && negativePhaseResult?.passed) {
5332
+ const positivePhaseResult = await runProofOfFixPhase({
5333
+ stage: "positive",
5334
+ testFilePath: reproTestPath,
5335
+ cwd
5336
+ });
5337
+ if (!positivePhaseResult.passed) {
5338
+ restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
5339
+ writeFileSync7(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
5340
+ try {
5341
+ const [rollbackCmd, ...rollbackArgs] = installCommand;
5342
+ await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
5343
+ } catch {
5344
+ }
5345
+ if (prepared.isCreated && reproTestPath) {
5346
+ await rm4(reproTestPath, { force: true }).catch(() => {
5347
+ });
5348
+ }
5349
+ return {
5350
+ packageName,
5351
+ strategy: "override",
5352
+ fromVersion,
5353
+ toVersion,
5354
+ applied: false,
5355
+ dryRun: false,
5356
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
5357
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
5358
+ };
5359
+ }
5360
+ proofOfFixVerification = assembleProofOfFixVerification({
5361
+ reproductionTestPath: reproTestPath,
5362
+ negativePhase: negativePhaseResult,
5363
+ positivePhase: positivePhaseResult,
5364
+ regressionPhase: {
5365
+ executed: runTests,
5366
+ passed: true
5367
+ }
5368
+ });
5369
+ }
4425
5370
  return {
4426
5371
  packageName,
4427
5372
  strategy: "override",
@@ -4429,13 +5374,14 @@ var applyPackageOverrideTool = defineTool({
4429
5374
  toVersion,
4430
5375
  applied: true,
4431
5376
  dryRun: false,
5377
+ proofOfFix: proofOfFixVerification,
4432
5378
  message: `Successfully updated deno.json imports["${existingEntry.key}"] for "${overrideSelector}" from ${fromVersion} to ${toVersion}, then ran ${installCommand.join(" ")}${runTests ? ` and passed ${testCommand.join(" ")}` : ""}.${dependencyTraceSuffix2}`
4433
5379
  };
4434
5380
  });
4435
5381
  }
4436
5382
  let pkgJson;
4437
5383
  try {
4438
- pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
5384
+ pkgJson = JSON.parse(readFileSync15(pkgPath, "utf8"));
4439
5385
  } catch {
4440
5386
  return {
4441
5387
  packageName,
@@ -4464,14 +5410,63 @@ var applyPackageOverrideTool = defineTool({
4464
5410
  };
4465
5411
  }
4466
5412
  return withRepoLock(cwd, async () => {
5413
+ const prepared = await prepareReproductionTestFile({
5414
+ cwd,
5415
+ packageName,
5416
+ vulnerableVersion: fromVersion,
5417
+ patchesDir,
5418
+ proofOfFix,
5419
+ reproductionTest
5420
+ });
5421
+ const reproTestPath = prepared.testFilePath;
5422
+ const isProofActive = Boolean(reproTestPath || proofOfFix || requireProofOfFix);
5423
+ let negativePhaseResult;
5424
+ if (isProofActive) {
5425
+ if (!reproTestPath) {
5426
+ if (requireProofOfFix) {
5427
+ return {
5428
+ packageName,
5429
+ strategy: "none",
5430
+ fromVersion,
5431
+ toVersion,
5432
+ applied: false,
5433
+ dryRun,
5434
+ unresolvedReason: "policy-blocked",
5435
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied."
5436
+ };
5437
+ }
5438
+ } else {
5439
+ negativePhaseResult = await runProofOfFixPhase({
5440
+ stage: "negative",
5441
+ testFilePath: reproTestPath,
5442
+ cwd
5443
+ });
5444
+ if (!negativePhaseResult.passed) {
5445
+ if (prepared.isCreated && reproTestPath) {
5446
+ await rm4(reproTestPath, { force: true }).catch(() => {
5447
+ });
5448
+ }
5449
+ return {
5450
+ packageName,
5451
+ strategy: "override",
5452
+ fromVersion,
5453
+ toVersion,
5454
+ applied: false,
5455
+ dryRun: false,
5456
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
5457
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
5458
+ };
5459
+ }
5460
+ }
5461
+ }
4467
5462
  setOverrideValue(pkgJson, pm, overrideSelector, toVersion);
4468
- writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5463
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4469
5464
  try {
4470
5465
  const [installCmd, ...installArgs] = installCommand;
4471
5466
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
4472
5467
  } catch (err) {
4473
5468
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
4474
- writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5469
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4475
5470
  const message = err instanceof Error ? err.message : String(err);
4476
5471
  return {
4477
5472
  packageName,
@@ -4490,7 +5485,7 @@ var applyPackageOverrideTool = defineTool({
4490
5485
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
4491
5486
  } catch (err) {
4492
5487
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
4493
- writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5488
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4494
5489
  try {
4495
5490
  const [rollbackCmd, ...rollbackArgs] = installCommand;
4496
5491
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -4509,6 +5504,46 @@ var applyPackageOverrideTool = defineTool({
4509
5504
  };
4510
5505
  }
4511
5506
  }
5507
+ let proofOfFixVerification;
5508
+ if (reproTestPath && negativePhaseResult?.passed) {
5509
+ const positivePhaseResult = await runProofOfFixPhase({
5510
+ stage: "positive",
5511
+ testFilePath: reproTestPath,
5512
+ cwd
5513
+ });
5514
+ if (!positivePhaseResult.passed) {
5515
+ restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
5516
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5517
+ try {
5518
+ const [rollbackCmd, ...rollbackArgs] = installCommand;
5519
+ await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
5520
+ } catch {
5521
+ }
5522
+ if (prepared.isCreated && reproTestPath) {
5523
+ await rm4(reproTestPath, { force: true }).catch(() => {
5524
+ });
5525
+ }
5526
+ return {
5527
+ packageName,
5528
+ strategy: "override",
5529
+ fromVersion,
5530
+ toVersion,
5531
+ applied: false,
5532
+ dryRun: false,
5533
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
5534
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
5535
+ };
5536
+ }
5537
+ proofOfFixVerification = assembleProofOfFixVerification({
5538
+ reproductionTestPath: reproTestPath,
5539
+ negativePhase: negativePhaseResult,
5540
+ positivePhase: positivePhaseResult,
5541
+ regressionPhase: {
5542
+ executed: runTests,
5543
+ passed: true
5544
+ }
5545
+ });
5546
+ }
4512
5547
  let dedupeNote = "";
4513
5548
  if (dedupeCommand.length > 0) {
4514
5549
  try {
@@ -4526,6 +5561,7 @@ var applyPackageOverrideTool = defineTool({
4526
5561
  toVersion,
4527
5562
  applied: true,
4528
5563
  dryRun: false,
5564
+ proofOfFix: proofOfFixVerification,
4529
5565
  message: `Successfully applied ${overrideLabel} for "${overrideSelector}" from ${fromVersion} to ${toVersion}, then ran ${installCommand.join(" ")}${runTests ? ` and passed ${testCommand.join(" ")}` : ""}.${dedupeNote}${dependencyTraceSuffix}`
4530
5566
  };
4531
5567
  });
@@ -4608,7 +5644,19 @@ async function verifyPackageTrust(packageName, targetVersion, policy) {
4608
5644
 
4609
5645
  // src/remediation/local/primary-strategy.ts
4610
5646
  async function resolvePrimaryResult(params) {
4611
- const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
5647
+ const {
5648
+ vulnerable,
5649
+ cwd,
5650
+ packageManager,
5651
+ dryRun,
5652
+ policy,
5653
+ runTests,
5654
+ constraints,
5655
+ proofOfFix,
5656
+ requireProofOfFix = constraints.requireProofOfFix,
5657
+ reproductionTest,
5658
+ patchesDir
5659
+ } = params;
4612
5660
  const pkg = vulnerable.installed;
4613
5661
  const firstPatchedVersion = vulnerable.affected.firstPatchedVersion;
4614
5662
  if (pkg.type === "transitive") {
@@ -4708,7 +5756,11 @@ async function resolvePrimaryResult(params) {
4708
5756
  installMode: constraints.installMode,
4709
5757
  installPreferOffline: constraints.installPreferOffline,
4710
5758
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
4711
- workspace: constraints.workspace
5759
+ workspace: constraints.workspace,
5760
+ proofOfFix,
5761
+ requireProofOfFix,
5762
+ reproductionTest,
5763
+ patchesDir
4712
5764
  });
4713
5765
  return {
4714
5766
  steps: 2,
@@ -4785,7 +5837,11 @@ async function resolvePrimaryResult(params) {
4785
5837
  installPreferOffline: constraints.installPreferOffline,
4786
5838
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
4787
5839
  workspace: constraints.workspace,
4788
- breakingChangeRefactor: constraints.enableBreakingChangeRefactor
5840
+ breakingChangeRefactor: constraints.enableBreakingChangeRefactor,
5841
+ proofOfFix,
5842
+ requireProofOfFix,
5843
+ reproductionTest,
5844
+ patchesDir
4789
5845
  });
4790
5846
  return {
4791
5847
  steps: 2,
@@ -4795,10 +5851,10 @@ async function resolvePrimaryResult(params) {
4795
5851
 
4796
5852
  // src/remediation/tools/fetch-package-source.ts
4797
5853
  import { z as z6 } from "zod";
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";
5854
+ import { mkdir as mkdir3, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm5 } from "fs/promises";
5855
+ import { join as join18 } from "path";
4800
5856
  import { tmpdir } from "os";
4801
- import { execa as execa7 } from "execa";
5857
+ import { execa as execa6 } from "execa";
4802
5858
  var fetchPackageSourceTool = defineTool({
4803
5859
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
4804
5860
  parameters: z6.object({
@@ -4816,34 +5872,33 @@ var fetchPackageSourceTool = defineTool({
4816
5872
  if (safePatterns.length === 0) {
4817
5873
  return { success: false, error: "No valid file patterns provided." };
4818
5874
  }
4819
- const tempBaseDir = await mkdtemp(join17(tmpdir(), "autoremediator-pkg-"));
4820
- const extractDir = join17(tempBaseDir, "out");
5875
+ const tempBaseDir = await mkdtemp(join18(tmpdir(), "autoremediator-pkg-"));
5876
+ const extractDir = join18(tempBaseDir, "out");
4821
5877
  try {
4822
5878
  const scopedName = packageName.split("/").pop();
4823
5879
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
4824
- await mkdir2(tempBaseDir, { recursive: true });
4825
- const tarballPath = join17(tempBaseDir, "package.tgz");
4826
- await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
4827
- await mkdir2(extractDir, { recursive: true });
4828
- await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
5880
+ await mkdir3(tempBaseDir, { recursive: true });
5881
+ const tarballPath = join18(tempBaseDir, "package.tgz");
5882
+ await execa6("curl", ["-f", "-s", "-S", "-L", "-o", tarballPath, npmUrl]);
5883
+ await mkdir3(extractDir, { recursive: true });
5884
+ await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
4829
5885
  const extractedContents = await readdir2(extractDir);
4830
- const packageRootDir = extractedContents.includes("package") ? join17(extractDir, "package") : extractDir;
5886
+ const packageRootDir = extractedContents.includes("package") ? join18(extractDir, "package") : extractDir;
4831
5887
  const sourceCode = {};
4832
5888
  async function walkDir(dir, relativeBase) {
4833
5889
  try {
4834
5890
  const files = await readdir2(dir, { withFileTypes: true });
4835
5891
  for (const file of files) {
4836
- const fullPath = join17(dir, file.name);
4837
- const relPath = join17(relativeBase, file.name);
5892
+ const fullPath = join18(dir, file.name);
5893
+ const relPath = join18(relativeBase, file.name);
4838
5894
  if (file.isDirectory()) {
4839
- if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
4840
- file.name
4841
- )) {
5895
+ if (!["node_modules", ".git", "coverage", ".next"].includes(file.name)) {
4842
5896
  await walkDir(fullPath, relPath);
4843
5897
  }
4844
5898
  } else if (file.isFile()) {
4845
5899
  const matches = safePatterns.some((pattern) => {
4846
- const regex = new RegExp(`^${pattern.replace(/\*/g, ".*").replace(/\./g, "\\.")}$`);
5900
+ const escaped = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
5901
+ const regex = new RegExp(`^${escaped}$`);
4847
5902
  return regex.test(file.name);
4848
5903
  });
4849
5904
  if (matches) {
@@ -4872,7 +5927,8 @@ var fetchPackageSourceTool = defineTool({
4872
5927
  };
4873
5928
  } catch (err) {
4874
5929
  const message = err instanceof Error ? err.message : String(err);
4875
- if (message.includes("404") || message.includes("not found")) {
5930
+ const exitCode = typeof err.exitCode === "number" ? err.exitCode : void 0;
5931
+ if (exitCode === 22 || message.includes("404") || message.includes("not found") || message.includes("returned error: 404")) {
4876
5932
  return {
4877
5933
  success: false,
4878
5934
  error: `Package ${packageName}@${version} not found on npm registry. It may not exist or the version may be incorrect.`
@@ -4883,7 +5939,7 @@ var fetchPackageSourceTool = defineTool({
4883
5939
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
4884
5940
  };
4885
5941
  } finally {
4886
- await rm3(tempBaseDir, { recursive: true, force: true });
5942
+ await rm5(tempBaseDir, { recursive: true, force: true });
4887
5943
  }
4888
5944
  }
4889
5945
  });
@@ -4964,7 +6020,7 @@ Important:
4964
6020
  }
4965
6021
 
4966
6022
  // src/remediation/tools/generate-patch/helpers.ts
4967
- import { parseSync as parseSync3 } from "oxc-parser";
6023
+ import { parseSync as parseSync4 } from "oxc-parser";
4968
6024
  import YAML from "yaml";
4969
6025
  function parseLlmAnalysisResponse(text) {
4970
6026
  try {
@@ -4987,7 +6043,7 @@ function validatePatchedCodeSyntax(filePath, code) {
4987
6043
  const lowerPath = filePath.toLowerCase();
4988
6044
  if (/\.[cm]?[jt]sx?$/i.test(lowerPath)) {
4989
6045
  try {
4990
- const res = parseSync3(filePath, code);
6046
+ const res = parseSync4(filePath, code);
4991
6047
  if (res.errors && res.errors.length > 0) {
4992
6048
  const errorMsg = res.errors.map((e) => e.message).join("; ");
4993
6049
  return {
@@ -5039,32 +6095,179 @@ function buildGeneratedPatches(sourceFiles, fixedCodeByFile) {
5039
6095
  }
5040
6096
  return patches;
5041
6097
  }
5042
- function generateUnifiedDiff(original, fixed, filePath) {
6098
+ function splitLines(text) {
6099
+ if (text.length === 0) return [];
6100
+ const lines = text.split(/\r?\n/);
6101
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
6102
+ lines.pop();
6103
+ }
6104
+ return lines;
6105
+ }
6106
+ function myersDiff(aLines, bLines) {
6107
+ const N = aLines.length;
6108
+ const M = bLines.length;
6109
+ const max = N + M;
6110
+ if (max === 0) return [];
6111
+ const offset = max;
6112
+ const v = new Int32Array(2 * max + 1);
6113
+ const history = [];
6114
+ v[offset + 1] = 0;
6115
+ let dFound = -1;
6116
+ for (let d = 0; d <= max; d++) {
6117
+ const vCopy = new Int32Array(v);
6118
+ history.push(vCopy);
6119
+ for (let k = -d; k <= d; k += 2) {
6120
+ let x2;
6121
+ if (k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1]) {
6122
+ x2 = v[offset + k + 1];
6123
+ } else {
6124
+ x2 = v[offset + k - 1] + 1;
6125
+ }
6126
+ let y2 = x2 - k;
6127
+ while (x2 < N && y2 < M && aLines[x2] === bLines[y2]) {
6128
+ x2++;
6129
+ y2++;
6130
+ }
6131
+ v[offset + k] = x2;
6132
+ if (x2 >= N && y2 >= M) {
6133
+ dFound = d;
6134
+ break;
6135
+ }
6136
+ }
6137
+ if (dFound !== -1) break;
6138
+ }
6139
+ let x = N;
6140
+ let y = M;
6141
+ const edits = [];
6142
+ for (let d = dFound; d > 0; d--) {
6143
+ const k = x - y;
6144
+ const vPrev = history[d];
6145
+ let prevK;
6146
+ if (k === -d || k !== d && vPrev[offset + k - 1] < vPrev[offset + k + 1]) {
6147
+ prevK = k + 1;
6148
+ } else {
6149
+ prevK = k - 1;
6150
+ }
6151
+ const prevX = vPrev[offset + prevK];
6152
+ const prevY = prevX - prevK;
6153
+ while (x > prevX && y > prevY) {
6154
+ edits.push({ type: "common", text: aLines[x - 1] });
6155
+ x--;
6156
+ y--;
6157
+ }
6158
+ if (x === prevX) {
6159
+ edits.push({ type: "insert", text: bLines[y - 1] });
6160
+ y--;
6161
+ } else {
6162
+ edits.push({ type: "delete", text: aLines[x - 1] });
6163
+ x--;
6164
+ }
6165
+ }
6166
+ while (x > 0 && y > 0) {
6167
+ edits.push({ type: "common", text: aLines[x - 1] });
6168
+ x--;
6169
+ y--;
6170
+ }
6171
+ edits.reverse();
6172
+ return edits;
6173
+ }
6174
+ function generateUnifiedDiff(original, fixed, filePath, context = 3) {
5043
6175
  if (original === fixed) {
5044
6176
  return null;
5045
6177
  }
5046
- const originalLines = original.split("\n");
5047
- const fixedLines = fixed.split("\n");
5048
- const diff = [];
5049
- diff.push(`--- a/${filePath}`);
5050
- diff.push(`+++ b/${filePath}`);
5051
- diff.push(`@@ -1,${originalLines.length} +1,${fixedLines.length} @@`);
5052
- const maxLen = Math.max(originalLines.length, fixedLines.length);
5053
- for (let i = 0; i < maxLen; i++) {
5054
- const origLine = originalLines[i] || "";
5055
- const fixedLine = fixedLines[i] || "";
5056
- if (origLine !== fixedLine) {
5057
- if (origLine) {
5058
- diff.push(`-${origLine}`);
5059
- }
5060
- if (fixedLine) {
5061
- diff.push(`+${fixedLine}`);
5062
- }
5063
- } else if (origLine) {
5064
- diff.push(` ${origLine}`);
6178
+ const aLines = splitLines(original);
6179
+ const bLines = splitLines(fixed);
6180
+ let prefixCount = 0;
6181
+ while (prefixCount < aLines.length && prefixCount < bLines.length && aLines[prefixCount] === bLines[prefixCount]) {
6182
+ prefixCount++;
6183
+ }
6184
+ let suffixCount = 0;
6185
+ while (suffixCount < aLines.length - prefixCount && suffixCount < bLines.length - prefixCount && aLines[aLines.length - 1 - suffixCount] === bLines[bLines.length - 1 - suffixCount]) {
6186
+ suffixCount++;
6187
+ }
6188
+ const prefixEdits = [];
6189
+ for (let i = 0; i < prefixCount; i++) {
6190
+ prefixEdits.push({ type: "common", text: aLines[i] });
6191
+ }
6192
+ const suffixEdits = [];
6193
+ for (let i = aLines.length - suffixCount; i < aLines.length; i++) {
6194
+ suffixEdits.push({ type: "common", text: aLines[i] });
6195
+ }
6196
+ const aMiddle = aLines.slice(prefixCount, aLines.length - suffixCount);
6197
+ const bMiddle = bLines.slice(prefixCount, bLines.length - suffixCount);
6198
+ const middleEdits = myersDiff(aMiddle, bMiddle);
6199
+ const edits = [...prefixEdits, ...middleEdits, ...suffixEdits];
6200
+ const changeIndices = [];
6201
+ for (let i = 0; i < edits.length; i++) {
6202
+ if (edits[i].type !== "common") {
6203
+ changeIndices.push(i);
6204
+ }
6205
+ }
6206
+ if (changeIndices.length === 0) {
6207
+ return null;
6208
+ }
6209
+ const groups = [];
6210
+ let currentGroup = [changeIndices[0]];
6211
+ for (let i = 1; i < changeIndices.length; i++) {
6212
+ const prev = changeIndices[i - 1];
6213
+ const curr = changeIndices[i];
6214
+ if (curr - prev - 1 <= 2 * context) {
6215
+ currentGroup.push(curr);
6216
+ } else {
6217
+ groups.push(currentGroup);
6218
+ currentGroup = [curr];
5065
6219
  }
5066
6220
  }
5067
- return diff.join("\n");
6221
+ groups.push(currentGroup);
6222
+ const hunkStrings = [];
6223
+ for (const group of groups) {
6224
+ const firstChange = group[0];
6225
+ const lastChange = group[group.length - 1];
6226
+ const startIndex = Math.max(0, firstChange - context);
6227
+ const endIndex = Math.min(edits.length, lastChange + context + 1);
6228
+ const hunkEdits = edits.slice(startIndex, endIndex);
6229
+ let curOrig = 1;
6230
+ let curFixed = 1;
6231
+ for (let i = 0; i < startIndex; i++) {
6232
+ if (edits[i].type === "common") {
6233
+ curOrig++;
6234
+ curFixed++;
6235
+ } else if (edits[i].type === "delete") {
6236
+ curOrig++;
6237
+ } else if (edits[i].type === "insert") {
6238
+ curFixed++;
6239
+ }
6240
+ }
6241
+ let origStart = curOrig;
6242
+ let fixedStart = curFixed;
6243
+ let origCount = 0;
6244
+ let fixedCount = 0;
6245
+ for (const edit of hunkEdits) {
6246
+ if (edit.type === "common") {
6247
+ origCount++;
6248
+ fixedCount++;
6249
+ } else if (edit.type === "delete") {
6250
+ origCount++;
6251
+ } else if (edit.type === "insert") {
6252
+ fixedCount++;
6253
+ }
6254
+ }
6255
+ if (origCount === 0) origStart = Math.max(0, origStart - 1);
6256
+ if (fixedCount === 0) fixedStart = Math.max(0, fixedStart - 1);
6257
+ const lines = [];
6258
+ lines.push(`@@ -${origStart},${origCount} +${fixedStart},${fixedCount} @@`);
6259
+ for (const edit of hunkEdits) {
6260
+ if (edit.type === "common") {
6261
+ lines.push(` ${edit.text}`);
6262
+ } else if (edit.type === "delete") {
6263
+ lines.push(`-${edit.text}`);
6264
+ } else if (edit.type === "insert") {
6265
+ lines.push(`+${edit.text}`);
6266
+ }
6267
+ }
6268
+ hunkStrings.push(lines.join("\n"));
6269
+ }
6270
+ return [`--- a/${filePath}`, `+++ b/${filePath}`, ...hunkStrings].join("\n") + "\n";
5068
6271
  }
5069
6272
 
5070
6273
  // src/remediation/tools/generate-patch/index.ts
@@ -5258,126 +6461,209 @@ var generatePatchTool = defineTool({
5258
6461
 
5259
6462
  // src/remediation/tools/apply-patch-file/index.ts
5260
6463
  import { z as z8 } from "zod";
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";
6464
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
6465
+ import { join as join21 } from "path";
6466
+ import { execa as execa8 } from "execa";
5265
6467
 
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];
6468
+ // src/platform/rollback.ts
6469
+ import { execFileSync, execSync } from "child_process";
6470
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync16, rmSync, statSync as statSync4, writeFileSync as writeFileSync8 } from "fs";
6471
+ import { isAbsolute as isAbsolute4, join as join19, relative as relative2 } from "path";
6472
+ var MANIFEST_PATTERNS = [
6473
+ /package\.json$/,
6474
+ /.*lock.*/,
6475
+ /deno\.jsonc?$/,
6476
+ /\.patch$/,
6477
+ /\.autoremediator\.ya?ml$/,
6478
+ /tsconfig.*\.json$/
6479
+ ];
6480
+ var IGNORED_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".pnpm-store"]);
6481
+ var activeSnapshots = /* @__PURE__ */ new Set();
6482
+ function recordFileTouch(filePath) {
6483
+ for (const snapshot of activeSnapshots) {
6484
+ const absPath = isAbsolute4(filePath) ? filePath : join19(snapshot.cwd, filePath);
6485
+ if (!absPath.startsWith(snapshot.cwd)) continue;
6486
+ const relPath = relative2(snapshot.cwd, absPath);
6487
+ if (!snapshot.fileSnapshots.has(relPath)) {
6488
+ if (existsSync14(absPath)) {
6489
+ try {
6490
+ snapshot.fileSnapshots.set(relPath, readFileSync16(absPath, "utf8"));
6491
+ } catch {
6492
+ try {
6493
+ snapshot.fileSnapshots.set(relPath, readFileSync16(absPath).toString("base64"));
6494
+ } catch {
6495
+ snapshot.fileSnapshots.set(relPath, null);
6496
+ }
6497
+ }
6498
+ } else {
6499
+ snapshot.fileSnapshots.set(relPath, null);
6500
+ }
6501
+ }
6502
+ }
6503
+ }
6504
+ function discoverProjectFiles(dir, maxDepth = 4, currentDepth = 0) {
6505
+ const discovered = [];
6506
+ if (currentDepth > maxDepth) return discovered;
6507
+ let entries;
5272
6508
  try {
5273
- const result = await safeExeca("node", nodeArgs, {
6509
+ entries = readdirSync4(dir);
6510
+ } catch {
6511
+ return discovered;
6512
+ }
6513
+ for (const entry of entries) {
6514
+ if (IGNORED_DIRS3.has(entry)) continue;
6515
+ const fullPath = join19(dir, entry);
6516
+ let stat2;
6517
+ try {
6518
+ stat2 = statSync4(fullPath);
6519
+ } catch {
6520
+ continue;
6521
+ }
6522
+ if (stat2.isDirectory()) {
6523
+ discovered.push(...discoverProjectFiles(fullPath, maxDepth, currentDepth + 1));
6524
+ } else if (stat2.isFile()) {
6525
+ const matchesPattern = MANIFEST_PATTERNS.some((pattern) => pattern.test(entry));
6526
+ if (matchesPattern) {
6527
+ discovered.push(fullPath);
6528
+ }
6529
+ }
6530
+ }
6531
+ return discovered;
6532
+ }
6533
+ async function isGitRepository(cwd) {
6534
+ try {
6535
+ const res = await safeExeca("git", ["rev-parse", "--is-inside-work-tree"], {
5274
6536
  cwd,
5275
- timeoutMs,
5276
6537
  stdio: "pipe"
5277
6538
  });
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
- };
6539
+ return String(res.stdout).trim() === "true";
6540
+ } catch {
6541
+ return false;
6542
+ }
6543
+ }
6544
+ function parsePorcelainLines(stdout) {
6545
+ return stdout.split(/\r?\n/).map((l) => l.replace(/\r$/, "")).filter((l) => l.length >= 3 && l.slice(0, 2).trim().length > 0);
6546
+ }
6547
+ function parseGitPorcelainLine(line) {
6548
+ if (!line || line.length < 3) return null;
6549
+ const statusCode = line.slice(0, 2).trim();
6550
+ let filePath = line.slice(3).trim();
6551
+ if (filePath.includes(" -> ")) {
6552
+ filePath = filePath.split(" -> ").pop().trim();
6553
+ }
6554
+ if (filePath.startsWith('"') && filePath.endsWith('"')) {
6555
+ filePath = filePath.slice(1, -1).replace(/\\"/g, '"');
6556
+ }
6557
+ return { statusCode, filePath };
6558
+ }
6559
+ async function getGitStatusPorcelain(cwd) {
6560
+ try {
6561
+ const res = await safeExeca("git", ["status", "--porcelain=v1", "-uall"], {
6562
+ cwd,
6563
+ stdio: "pipe"
6564
+ });
6565
+ return parsePorcelainLines(String(res.stdout));
6566
+ } catch {
6567
+ return [];
6568
+ }
6569
+ }
6570
+ async function createProjectSnapshot(cwd, explicitFiles) {
6571
+ const isGit = await isGitRepository(cwd);
6572
+ const gitStatusBefore = isGit ? await getGitStatusPorcelain(cwd) : void 0;
6573
+ const fileSnapshots = /* @__PURE__ */ new Map();
6574
+ const targetFiles = explicitFiles ? explicitFiles.map((f) => f.startsWith(cwd) ? f : join19(cwd, f)) : discoverProjectFiles(cwd);
6575
+ for (const fullPath of targetFiles) {
6576
+ const relPath = relative2(cwd, fullPath);
6577
+ if (existsSync14(fullPath)) {
6578
+ try {
6579
+ fileSnapshots.set(relPath, readFileSync16(fullPath, "utf8"));
6580
+ } catch {
6581
+ try {
6582
+ fileSnapshots.set(relPath, readFileSync16(fullPath).toString("base64"));
6583
+ } catch {
6584
+ }
6585
+ }
6586
+ } else {
6587
+ fileSnapshots.set(relPath, null);
5290
6588
  }
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
- };
6589
+ }
6590
+ const snapshot = {
6591
+ cwd,
6592
+ isGit,
6593
+ gitStatusBefore,
6594
+ fileSnapshots
6595
+ };
6596
+ return snapshot;
6597
+ }
6598
+ function restoreProjectSnapshotSync(snapshot) {
6599
+ for (const [relPath, previousContent] of snapshot.fileSnapshots.entries()) {
6600
+ const fullPath = join19(snapshot.cwd, relPath);
6601
+ if (previousContent === null) {
6602
+ if (existsSync14(fullPath)) {
6603
+ try {
6604
+ rmSync(fullPath, { force: true, recursive: true });
6605
+ } catch {
6606
+ }
5314
6607
  }
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
- };
6608
+ } else {
6609
+ try {
6610
+ writeFileSync8(fullPath, previousContent, "utf8");
6611
+ } catch {
5325
6612
  }
5326
- return {
5327
- executed: true,
5328
- passed: true,
5329
- exitCode,
5330
- output,
5331
- durationMs
5332
- };
5333
6613
  }
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
6614
  }
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";
6615
+ const currentFiles = discoverProjectFiles(snapshot.cwd);
6616
+ for (const fullPath of currentFiles) {
6617
+ const relPath = relative2(snapshot.cwd, fullPath);
6618
+ if (!snapshot.fileSnapshots.has(relPath)) {
6619
+ try {
6620
+ rmSync(fullPath, { force: true, recursive: true });
6621
+ } catch {
6622
+ }
6623
+ }
6624
+ }
6625
+ if (snapshot.isGit) {
6626
+ try {
6627
+ const stdout = execSync("git status --porcelain=v1 -uall", {
6628
+ cwd: snapshot.cwd,
6629
+ stdio: ["pipe", "pipe", "ignore"],
6630
+ encoding: "utf8"
6631
+ });
6632
+ const currentStatus = parsePorcelainLines(stdout);
6633
+ const beforeSet = new Set(snapshot.gitStatusBefore ?? []);
6634
+ for (const line of currentStatus) {
6635
+ if (beforeSet.has(line)) continue;
6636
+ const parsed = parseGitPorcelainLine(line);
6637
+ if (!parsed) continue;
6638
+ const { statusCode, filePath } = parsed;
6639
+ const fullPath = join19(snapshot.cwd, filePath);
6640
+ if (statusCode === "??" && existsSync14(fullPath)) {
6641
+ try {
6642
+ rmSync(fullPath, { force: true, recursive: true });
6643
+ } catch {
6644
+ }
6645
+ } else if (statusCode === "M" || statusCode === "MM" || statusCode === "D" || statusCode.includes("M") || statusCode.includes("D") || statusCode.includes("T")) {
6646
+ try {
6647
+ execFileSync("git", ["checkout", "--", filePath], {
6648
+ cwd: snapshot.cwd,
6649
+ stdio: "ignore"
6650
+ });
6651
+ } catch {
6652
+ }
6653
+ }
6654
+ }
6655
+ } catch {
6656
+ }
5361
6657
  }
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
6658
  }
5373
6659
 
5374
6660
  // src/remediation/tools/apply-patch-file/helpers.ts
5375
- import { existsSync as existsSync13 } from "fs";
5376
- import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
6661
+ import { existsSync as existsSync15 } from "fs";
6662
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm6, writeFile as writeFile3 } from "fs/promises";
5377
6663
  import { createHash } from "crypto";
5378
6664
  import { tmpdir as tmpdir2 } from "os";
5379
- import { join as join18 } from "path";
5380
- import { execa as execa8 } from "execa";
6665
+ import { join as join20 } from "path";
6666
+ import { execa as execa7 } from "execa";
5381
6667
  async function resolvePatchMode(packageManager, cwd) {
5382
6668
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
5383
6669
  return "patch-package";
@@ -5408,10 +6694,10 @@ function computePatchIntegrity(patchContent) {
5408
6694
  return `sha256:${hex}`;
5409
6695
  }
5410
6696
  async function writePatchManifest(manifestFilePath, artifact) {
5411
- await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
6697
+ await writeFile3(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
5412
6698
  }
5413
6699
  async function configurePatchPackagePostinstall(cwd, packageManager) {
5414
- const pkgJsonPath = join18(cwd, "package.json");
6700
+ const pkgJsonPath = join20(cwd, "package.json");
5415
6701
  let pkgJson;
5416
6702
  try {
5417
6703
  pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
@@ -5426,7 +6712,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
5426
6712
  try {
5427
6713
  const commands = getPackageManagerCommands(packageManager);
5428
6714
  const [cmd, ...args] = commands.installDev("patch-package");
5429
- await execa8(cmd, args, {
6715
+ await execa7(cmd, args, {
5430
6716
  cwd,
5431
6717
  stdio: "pipe"
5432
6718
  });
@@ -5447,11 +6733,11 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
5447
6733
  } else if (!currentPostinstall) {
5448
6734
  pkgJson.scripts.postinstall = patchApplyCmd;
5449
6735
  }
5450
- await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
6736
+ await writeFile3(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5451
6737
  return { success: true };
5452
6738
  }
5453
6739
  async function capturePackageJsonSnapshot(cwd) {
5454
- const path = join18(cwd, "package.json");
6740
+ const path = join20(cwd, "package.json");
5455
6741
  try {
5456
6742
  const content = await readFile5(path, "utf8");
5457
6743
  return { path, content };
@@ -5469,19 +6755,19 @@ async function cleanupPatchArtifacts(params) {
5469
6755
  installCommand,
5470
6756
  rerunInstall
5471
6757
  } = params;
5472
- await rm4(patchFilePath, { force: true }).catch(() => void 0);
6758
+ await rm6(patchFilePath, { force: true }).catch(() => void 0);
5473
6759
  if (manifestFilePath) {
5474
- await rm4(manifestFilePath, { force: true }).catch(() => void 0);
6760
+ await rm6(manifestFilePath, { force: true }).catch(() => void 0);
5475
6761
  }
5476
6762
  if (patchMode === "patch-package" && packageJsonSnapshot) {
5477
- await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
6763
+ await writeFile3(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
5478
6764
  () => void 0
5479
6765
  );
5480
6766
  }
5481
6767
  if (!rerunInstall) return;
5482
6768
  try {
5483
6769
  const [installCmd, ...installArgs] = installCommand;
5484
- await execa8(installCmd, installArgs, {
6770
+ await execa7(installCmd, installArgs, {
5485
6771
  cwd,
5486
6772
  stdio: "pipe"
5487
6773
  });
@@ -5495,7 +6781,7 @@ async function applyNativePatch(params) {
5495
6781
  const createArgs = ["patch", packageSpec];
5496
6782
  let patchDir;
5497
6783
  try {
5498
- const createResult = await execa8(createCommand, createArgs, {
6784
+ const createResult = await execa7(createCommand, createArgs, {
5499
6785
  cwd,
5500
6786
  stdio: "pipe"
5501
6787
  });
@@ -5523,17 +6809,17 @@ ${createResult.stderr}`);
5523
6809
  error: `Could not determine native patch directory for ${packageSpec}.`
5524
6810
  };
5525
6811
  }
5526
- const tempPatchDir = await mkdtemp2(join18(tmpdir2(), "autoremediator-native-patch-"));
5527
- const tempPatchFile = join18(tempPatchDir, "change.patch");
6812
+ const tempPatchDir = await mkdtemp2(join20(tmpdir2(), "autoremediator-native-patch-"));
6813
+ const tempPatchFile = join20(tempPatchDir, "change.patch");
5528
6814
  try {
5529
- await writeFile2(tempPatchFile, patchContent, "utf8");
5530
- await execa8("patch", ["-p1", "-i", tempPatchFile], {
6815
+ await writeFile3(tempPatchFile, patchContent, "utf8");
6816
+ await execa7("patch", ["-p1", "-i", tempPatchFile], {
5531
6817
  cwd: patchDir,
5532
6818
  stdio: "pipe"
5533
6819
  });
5534
6820
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
5535
6821
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
5536
- await execa8(commitCommand, commitArgs, {
6822
+ await execa7(commitCommand, commitArgs, {
5537
6823
  cwd,
5538
6824
  stdio: "pipe"
5539
6825
  });
@@ -5548,19 +6834,19 @@ ${createResult.stderr}`);
5548
6834
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
5549
6835
  };
5550
6836
  } finally {
5551
- await rm4(tempPatchDir, { recursive: true, force: true });
6837
+ await rm6(tempPatchDir, { recursive: true, force: true });
5552
6838
  }
5553
6839
  return { success: true };
5554
6840
  }
5555
6841
  function extractPatchDirectory(output) {
5556
6842
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5557
6843
  for (const line of lines) {
5558
- if (existsSync13(line)) {
6844
+ if (existsSync15(line)) {
5559
6845
  return line;
5560
6846
  }
5561
6847
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
5562
6848
  for (const token of tokens) {
5563
- if (token.startsWith("/") && existsSync13(token)) {
6849
+ if (token.startsWith("/") && existsSync15(token)) {
5564
6850
  return token;
5565
6851
  }
5566
6852
  }
@@ -5570,7 +6856,7 @@ function extractPatchDirectory(output) {
5570
6856
  async function validatePatchWithTests(cwd, testCommand) {
5571
6857
  try {
5572
6858
  const [cmd, ...args] = testCommand;
5573
- const result = await execa8(cmd, args, {
6859
+ const result = await execa7(cmd, args, {
5574
6860
  cwd,
5575
6861
  timeout: 6e4,
5576
6862
  stdio: "pipe"
@@ -5664,6 +6950,18 @@ var applyPatchFileTool = defineTool({
5664
6950
  try {
5665
6951
  const pm = packageManager ?? detectPackageManager(cwd);
5666
6952
  const loadedPolicy = loadPolicy(cwd, policy);
6953
+ if (!isPackageAllowed(loadedPolicy, packageName)) {
6954
+ return {
6955
+ success: false,
6956
+ packageName,
6957
+ vulnerableVersion,
6958
+ applied: false,
6959
+ dryRun,
6960
+ unresolvedReason: "policy-blocked",
6961
+ message: `Policy denyPackages blocked changes for package "${packageName}".`,
6962
+ error: `Policy denyPackages blocked changes for package "${packageName}".`
6963
+ };
6964
+ }
5667
6965
  const commandConstraints = {
5668
6966
  ...loadedPolicy.constraints,
5669
6967
  installMode: installMode ?? loadedPolicy.constraints?.installMode,
@@ -5709,7 +7007,7 @@ var applyPatchFileTool = defineTool({
5709
7007
  };
5710
7008
  }
5711
7009
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
5712
- const patchFilePath = join19(cwd, patchesDir, patchFileName);
7010
+ const patchFilePath = join21(cwd, patchesDir, patchFileName);
5713
7011
  const manifestFilePath = `${patchFilePath}.json`;
5714
7012
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
5715
7013
  const baseArtifact = {
@@ -5748,9 +7046,11 @@ var applyPatchFileTool = defineTool({
5748
7046
  }
5749
7047
  return withRepoLock(cwd, async () => {
5750
7048
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
5751
- const patchesDirPath = join19(cwd, patchesDir);
5752
- await mkdir3(patchesDirPath, { recursive: true });
5753
- await writeFile3(patchFilePath, selectedPatch, "utf8");
7049
+ const patchesDirPath = join21(cwd, patchesDir);
7050
+ await mkdir4(patchesDirPath, { recursive: true });
7051
+ recordFileTouch(patchFilePath);
7052
+ recordFileTouch(manifestFilePath);
7053
+ await writeFile4(patchFilePath, selectedPatch, "utf8");
5754
7054
  validationPhases.push({
5755
7055
  phase: "patch-write",
5756
7056
  passed: true,
@@ -5773,21 +7073,19 @@ var applyPatchFileTool = defineTool({
5773
7073
  await writePatchManifest(manifestFilePath, artifact);
5774
7074
  let reproductionTestPath;
5775
7075
  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);
7076
+ const preparedRepro = await prepareReproductionTestFile({
7077
+ cwd,
7078
+ packageName,
7079
+ vulnerableVersion,
7080
+ patchesDir: patchesDirPath,
7081
+ proofOfFix,
7082
+ reproductionTest
7083
+ });
7084
+ reproductionTestPath = preparedRepro.testFilePath;
7085
+ const hasReproductionScript = Boolean(reproductionTestPath);
7086
+ const isProofOfFixActive = Boolean(
7087
+ hasReproductionScript || proofOfFix || requireProofOfFix
7088
+ );
5791
7089
  if (isProofOfFixActive) {
5792
7090
  if (!reproductionTestPath) {
5793
7091
  if (requireProofOfFix) {
@@ -5806,6 +7104,7 @@ var applyPatchFileTool = defineTool({
5806
7104
  vulnerableVersion,
5807
7105
  applied: false,
5808
7106
  dryRun: false,
7107
+ unresolvedReason: "policy-blocked",
5809
7108
  message: "Proof-of-fix verification required, but no reproduction test or script was supplied.",
5810
7109
  patchFilePath,
5811
7110
  manifestFilePath,
@@ -5833,7 +7132,7 @@ var applyPatchFileTool = defineTool({
5833
7132
  message: negativePhaseResult.passed ? "Reproduction test reproduced vulnerability on unpatched code." : void 0,
5834
7133
  error: negativePhaseResult.passed ? void 0 : negativePhaseResult.error
5835
7134
  });
5836
- if (!negativePhaseResult.passed && requireProofOfFix) {
7135
+ if (!negativePhaseResult.passed) {
5837
7136
  await cleanupPatchArtifacts({
5838
7137
  cwd,
5839
7138
  patchFilePath,
@@ -5849,6 +7148,7 @@ var applyPatchFileTool = defineTool({
5849
7148
  vulnerableVersion,
5850
7149
  applied: false,
5851
7150
  dryRun: false,
7151
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
5852
7152
  message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`,
5853
7153
  patchFilePath,
5854
7154
  manifestFilePath,
@@ -5913,7 +7213,7 @@ var applyPatchFileTool = defineTool({
5913
7213
  if (patchMode === "patch-package") {
5914
7214
  try {
5915
7215
  const [installCmd, ...installArgs] = installCommand;
5916
- await execa9(installCmd, installArgs, {
7216
+ await execa8(installCmd, installArgs, {
5917
7217
  cwd,
5918
7218
  stdio: "pipe"
5919
7219
  });
@@ -6007,66 +7307,49 @@ var applyPatchFileTool = defineTool({
6007
7307
  });
6008
7308
  }
6009
7309
  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
7310
+ if (reproductionTestPath && isProofOfFixActive) {
7311
+ positivePhaseResult = await runProofOfFixPhase({
7312
+ stage: "positive",
7313
+ testFilePath: reproductionTestPath,
7314
+ cwd
7315
+ });
7316
+ validationPhases.push({
7317
+ phase: "proof-of-fix-positive",
7318
+ passed: positivePhaseResult.passed,
7319
+ message: positivePhaseResult.passed ? "Reproduction test passed on patched code (vulnerability mitigated)." : void 0,
7320
+ error: positivePhaseResult.passed ? void 0 : positivePhaseResult.error
7321
+ });
7322
+ if (!positivePhaseResult.passed) {
7323
+ await cleanupPatchArtifacts({
7324
+ cwd,
7325
+ patchFilePath,
7326
+ manifestFilePath,
7327
+ patchMode,
7328
+ packageJsonSnapshot,
7329
+ installCommand,
7330
+ rerunInstall: patchMode === "patch-package"
6022
7331
  });
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,
7332
+ return {
7333
+ success: false,
7334
+ packageName,
7335
+ vulnerableVersion,
7336
+ applied: false,
7337
+ dryRun: false,
7338
+ unresolvedReason: requireProofOfFix ? "policy-blocked" : "proof-of-fix-failed",
7339
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`,
7340
+ patchFilePath,
7341
+ manifestFilePath,
7342
+ patchPath: patchFilePath,
7343
+ patchMode,
7344
+ postinstallConfigured: false,
7345
+ patchArtifact: {
7346
+ ...artifact,
6037
7347
  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
- }
7348
+ validationPhases
7349
+ },
7350
+ validationPhases,
7351
+ error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
7352
+ };
6070
7353
  }
6071
7354
  }
6072
7355
  let proofOfFixVerification;
@@ -6198,12 +7481,29 @@ function resolvePatchProvider(provider) {
6198
7481
  function shouldAttemptPatchFallback(result, preferVersionBump) {
6199
7482
  if (preferVersionBump) return false;
6200
7483
  if (result.applied || result.dryRun) return false;
6201
- return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "transitive-dependency";
7484
+ return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "transitive-dependency" || result.unresolvedReason === "proof-of-fix-failed";
6202
7485
  }
6203
7486
  async function tryLocalPatchFallback(params) {
6204
7487
  const usage = [];
6205
7488
  let steps = 0;
6206
7489
  let consensusVerdict;
7490
+ const loadedPolicy = loadPolicy(params.cwd, params.policy);
7491
+ if (!isPackageAllowed(loadedPolicy, params.packageName)) {
7492
+ return {
7493
+ steps: 0,
7494
+ usage,
7495
+ result: {
7496
+ packageName: params.packageName,
7497
+ strategy: "none",
7498
+ fromVersion: params.vulnerableVersion,
7499
+ applied: false,
7500
+ dryRun: params.dryRun,
7501
+ dependencyScope: params.dependencyScope,
7502
+ unresolvedReason: "policy-blocked",
7503
+ message: `Policy denyPackages blocked patch fallback for package "${params.packageName}".`
7504
+ }
7505
+ };
7506
+ }
6207
7507
  const sourceResult = await fetchPackageSourceTool.execute({
6208
7508
  packageName: params.packageName,
6209
7509
  version: params.vulnerableVersion
@@ -6382,7 +7682,7 @@ async function tryLocalPatchFallback(params) {
6382
7682
  riskLevel: patchResult.riskLevel,
6383
7683
  vulnerableRange: params.vulnerableRange,
6384
7684
  consensusVerdict,
6385
- unresolvedReason: !applyResult.applied && !applyResult.dryRun ? applyResult.validation?.passed === false ? "patch-validation-failed" : "patch-apply-failed" : void 0,
7685
+ unresolvedReason: !applyResult.applied && !applyResult.dryRun ? applyResult.unresolvedReason ?? (applyResult.validation?.passed === false ? "patch-validation-failed" : "patch-apply-failed") : void 0,
6386
7686
  message: applyResult.message ?? applyResult.error ?? "Patch-file strategy finished.",
6387
7687
  validation: typeof applyResult.validation?.passed === "boolean" ? {
6388
7688
  passed: applyResult.validation.passed,
@@ -6448,7 +7748,7 @@ function resolveLocalRunOptions(options) {
6448
7748
  }
6449
7749
 
6450
7750
  // src/remediation/local/vulnerability-match.ts
6451
- import semver5 from "semver";
7751
+ import semver6 from "semver";
6452
7752
  function findVulnerablePackages(cveDetails, installedPackages) {
6453
7753
  const vulnerablePackages = [];
6454
7754
  for (const affected of cveDetails.affectedPackages) {
@@ -6457,10 +7757,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
6457
7757
  if (affected.ecosystem !== "npm") continue;
6458
7758
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
6459
7759
  for (const installed of matches) {
6460
- if (!semver5.valid(installed.version)) continue;
7760
+ if (!semver6.valid(installed.version)) continue;
6461
7761
  let isVulnerable = false;
6462
7762
  try {
6463
- isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
7763
+ isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
6464
7764
  includePrerelease: false
6465
7765
  });
6466
7766
  } catch {
@@ -6555,6 +7855,15 @@ function applyDispositionAndContainment(result, context) {
6555
7855
  dispositionReason: disposition.dispositionReason
6556
7856
  };
6557
7857
  if (context.containmentMode && withDisposition.disposition === "escalate" && withDisposition.applied) {
7858
+ if (context.snapshot) {
7859
+ restoreProjectSnapshotSync(context.snapshot);
7860
+ }
7861
+ if (context.onContainmentRollback) {
7862
+ try {
7863
+ context.onContainmentRollback();
7864
+ } catch {
7865
+ }
7866
+ }
6558
7867
  return {
6559
7868
  ...withDisposition,
6560
7869
  applied: false,
@@ -6586,35 +7895,7 @@ function computeEscalationAction(reason, graph) {
6586
7895
  // src/remediation/local/run.ts
6587
7896
  async function runLocalRemediationPipeline(cveId, options = {}) {
6588
7897
  const resolved = resolveLocalRunOptions(options);
6589
- const {
6590
- cwd,
6591
- packageManager,
6592
- dryRun,
6593
- runTests,
6594
- policy,
6595
- patchesDir,
6596
- constraints,
6597
- llmProvider,
6598
- providerSafetyProfile,
6599
- requireConsensusForHighRisk,
6600
- consensusProvider,
6601
- consensusModel,
6602
- patchConfidenceThresholds,
6603
- dynamicModelRouting,
6604
- dynamicRoutingThresholdChars,
6605
- exploitSignalOverride,
6606
- suppressions,
6607
- suppressionsFile,
6608
- slaCheck,
6609
- slaPolicy,
6610
- skipUnreachable,
6611
- regressionCheck,
6612
- escalationGraph,
6613
- offlineIntelligence,
6614
- intelligenceSnapshotPath,
6615
- proofOfFix,
6616
- requireProofOfFix
6617
- } = resolved;
7898
+ const { cwd, packageManager, dryRun, constraints } = resolved;
6618
7899
  const correlation = {
6619
7900
  requestId: options.requestId,
6620
7901
  sessionId: options.sessionId,
@@ -6628,10 +7909,10 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6628
7909
  const normalizedId = cveId.toUpperCase();
6629
7910
  let osvDetails = null;
6630
7911
  let ghPackages = [];
6631
- if (offlineIntelligence) {
6632
- if (intelligenceSnapshotPath) {
7912
+ if (resolved.offlineIntelligence) {
7913
+ if (resolved.intelligenceSnapshotPath) {
6633
7914
  osvDetails = lookupSnapshotCve(
6634
- loadIntelligenceSnapshot(intelligenceSnapshotPath),
7915
+ loadIntelligenceSnapshot(resolved.intelligenceSnapshotPath),
6635
7916
  normalizedId
6636
7917
  );
6637
7918
  }
@@ -6649,7 +7930,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6649
7930
  vulnerablePackages,
6650
7931
  results: collectedResults,
6651
7932
  agentSteps,
6652
- summary: offlineIntelligence ? `Offline mode: ${normalizedId} not found in intelligence snapshot.` : `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`,
7933
+ summary: resolved.offlineIntelligence ? `Offline mode: ${normalizedId} not found in intelligence snapshot.` : `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`,
6653
7934
  correlation
6654
7935
  };
6655
7936
  }
@@ -6663,16 +7944,10 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6663
7944
  if (ghPackages.length > 0) {
6664
7945
  cveDetails = mergeGhDataIntoCveDetails(cveDetails, ghPackages);
6665
7946
  }
6666
- if (!offlineIntelligence) {
7947
+ if (!resolved.offlineIntelligence) {
6667
7948
  cveDetails = await enrichWithNvd(cveDetails);
6668
7949
  }
6669
- const preflight = await runSecOpsPreflight(normalizedId, cveDetails, {
6670
- suppressions,
6671
- suppressionsFile,
6672
- exploitSignalOverride,
6673
- slaCheck,
6674
- slaPolicy
6675
- });
7950
+ const preflight = await runSecOpsPreflight(normalizedId, cveDetails, resolved);
6676
7951
  if (preflight.suppressed) {
6677
7952
  return {
6678
7953
  cveId,
@@ -6721,35 +7996,57 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6721
7996
  const installedPackages = inventory.packages ?? [];
6722
7997
  vulnerablePackages = findVulnerablePackages(cveDetails, installedPackages);
6723
7998
  agentSteps += 1;
6724
- const finalizePatchResult = (result) => {
6725
- const withDisposition = applyDispositionAndContainment(result, {
6726
- exploitSignalTriggered,
6727
- slaBreaches,
6728
- severity: cveDetails?.severity,
6729
- policy: options.dispositionPolicy,
6730
- containmentMode: options.containmentMode
6731
- });
6732
- if (withDisposition.unresolvedReason) {
6733
- withDisposition.escalationAction = computeEscalationAction(
6734
- withDisposition.unresolvedReason,
6735
- escalationGraph
6736
- );
6737
- }
6738
- return withDisposition;
6739
- };
7999
+ const effectivePolicy = loadPolicy(cwd, options.policy);
8000
+ const containmentMode = options.containmentMode ?? effectivePolicy.containmentMode ?? false;
8001
+ const dispositionPolicy = options.dispositionPolicy ?? effectivePolicy.dispositionPolicy;
6740
8002
  for (const vulnerable of vulnerablePackages) {
6741
- if (skipUnreachable) {
6742
- const reach = assessPackageReachability(cwd, vulnerable.installed.name);
6743
- if (reach.status === "not-reachable") {
6744
- collectedResults.push({
8003
+ const snapshot = await createProjectSnapshot(cwd);
8004
+ const finalizePatchResult = (result) => {
8005
+ const withDisposition = applyDispositionAndContainment(result, {
8006
+ exploitSignalTriggered,
8007
+ slaBreaches,
8008
+ severity: cveDetails?.severity,
8009
+ policy: dispositionPolicy,
8010
+ containmentMode,
8011
+ cwd,
8012
+ snapshot
8013
+ });
8014
+ if (withDisposition.unresolvedReason) {
8015
+ withDisposition.escalationAction = computeEscalationAction(
8016
+ withDisposition.unresolvedReason,
8017
+ resolved.escalationGraph
8018
+ );
8019
+ }
8020
+ return withDisposition;
8021
+ };
8022
+ if (!isPackageAllowed(effectivePolicy, vulnerable.installed.name)) {
8023
+ collectedResults.push(
8024
+ finalizePatchResult({
6745
8025
  packageName: vulnerable.installed.name,
6746
8026
  fromVersion: vulnerable.installed.version,
6747
8027
  strategy: "none",
6748
8028
  applied: false,
6749
8029
  dryRun,
6750
- message: `Skipped: '${vulnerable.installed.name}' is not reachable from source code.`,
6751
- reachability: reach
6752
- });
8030
+ unresolvedReason: "policy-blocked",
8031
+ message: `Policy denyPackages blocked changes for package "${vulnerable.installed.name}".`
8032
+ })
8033
+ );
8034
+ continue;
8035
+ }
8036
+ if (resolved.skipUnreachable) {
8037
+ const reach = assessPackageReachability(cwd, vulnerable.installed.name);
8038
+ if (reach.status === "not-reachable") {
8039
+ collectedResults.push(
8040
+ finalizePatchResult({
8041
+ packageName: vulnerable.installed.name,
8042
+ fromVersion: vulnerable.installed.version,
8043
+ strategy: "none",
8044
+ applied: false,
8045
+ dryRun,
8046
+ message: `Skipped: '${vulnerable.installed.name}' is not reachable from source code.`,
8047
+ reachability: reach
8048
+ })
8049
+ );
6753
8050
  continue;
6754
8051
  }
6755
8052
  }
@@ -6758,9 +8055,13 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6758
8055
  cwd,
6759
8056
  packageManager,
6760
8057
  dryRun,
6761
- policy,
6762
- runTests,
6763
- constraints
8058
+ policy: resolved.policy,
8059
+ runTests: resolved.runTests,
8060
+ constraints,
8061
+ proofOfFix: resolved.proofOfFix,
8062
+ requireProofOfFix: resolved.requireProofOfFix,
8063
+ reproductionTest: options.reproductionTest,
8064
+ patchesDir: resolved.patchesDir
6764
8065
  });
6765
8066
  agentSteps += primary.steps;
6766
8067
  if (shouldAttemptPatchFallback(primary.result, constraints.preferVersionBump ?? false)) {
@@ -6773,26 +8074,26 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6773
8074
  cveSummary: cveDetails?.summary ?? normalizedId,
6774
8075
  dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive",
6775
8076
  dryRun,
6776
- runTests,
6777
- patchesDir,
6778
- llmProvider,
8077
+ runTests: resolved.runTests,
8078
+ patchesDir: resolved.patchesDir,
8079
+ llmProvider: resolved.llmProvider,
6779
8080
  model: options.model,
6780
8081
  policy: options.policy,
6781
8082
  modelPersonality: options.modelPersonality,
6782
- providerSafetyProfile,
6783
- requireConsensusForHighRisk,
6784
- consensusProvider,
6785
- consensusModel,
6786
- patchConfidenceThresholds,
6787
- dynamicModelRouting,
6788
- dynamicRoutingThresholdChars,
8083
+ providerSafetyProfile: resolved.providerSafetyProfile,
8084
+ requireConsensusForHighRisk: resolved.requireConsensusForHighRisk,
8085
+ consensusProvider: resolved.consensusProvider,
8086
+ consensusModel: resolved.consensusModel,
8087
+ patchConfidenceThresholds: resolved.patchConfidenceThresholds,
8088
+ dynamicModelRouting: resolved.dynamicModelRouting,
8089
+ dynamicRoutingThresholdChars: resolved.dynamicRoutingThresholdChars,
6789
8090
  installMode: constraints.installMode,
6790
8091
  installPreferOffline: constraints.installPreferOffline,
6791
8092
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6792
8093
  workspace: constraints.workspace,
6793
8094
  vulnerableRange: vulnerable.affected.vulnerableRange,
6794
- proofOfFix,
6795
- requireProofOfFix
8095
+ proofOfFix: resolved.proofOfFix,
8096
+ requireProofOfFix: resolved.requireProofOfFix
6796
8097
  });
6797
8098
  agentSteps += fallback.steps;
6798
8099
  collectedResults.push(finalizePatchResult(fallback.result));
@@ -6805,14 +8106,15 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6805
8106
  ...primary.result,
6806
8107
  dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
6807
8108
  };
6808
- if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
8109
+ if (resolved.regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
6809
8110
  try {
6810
- if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
8111
+ if (semver7.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
6811
8112
  includePrerelease: false
6812
8113
  })) {
6813
8114
  primaryResult.regressionDetected = true;
6814
8115
  }
6815
8116
  } catch {
8117
+ primaryResult.regressionDetected = false;
6816
8118
  }
6817
8119
  }
6818
8120
  collectedResults.push(finalizePatchResult(primaryResult));
@@ -6898,8 +8200,8 @@ function accumulateStepResults(params) {
6898
8200
  }
6899
8201
 
6900
8202
  // src/remediation/orchestration-prompt.ts
6901
- import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
6902
- import { join as join20 } from "path";
8203
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
8204
+ import { join as join22 } from "path";
6903
8205
  function buildProviderAddendum(provider, personality = "balanced") {
6904
8206
  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.";
6905
8207
  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.";
@@ -6910,13 +8212,13 @@ Provider profile:
6910
8212
  - ${personalityDirective}`;
6911
8213
  }
6912
8214
  function loadOrchestrationPrompt(ctx) {
6913
- const promptPath = join20(
8215
+ const promptPath = join22(
6914
8216
  process.cwd(),
6915
8217
  ".github",
6916
8218
  "instructions",
6917
8219
  "orchestration.instructions.md"
6918
8220
  );
6919
- if (!existsSync15(promptPath)) {
8221
+ if (!existsSync16(promptPath)) {
6920
8222
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
6921
8223
  Working directory: ${ctx.cwd}
6922
8224
  Package manager: ${ctx.packageManager}
@@ -6942,7 +8244,7 @@ Fallback sequence (when neither version bump nor override can be applied):
6942
8244
 
6943
8245
  Always respect dryRun and policy constraints.`;
6944
8246
  }
6945
- const template = readFileSync15(promptPath, "utf8");
8247
+ const template = readFileSync17(promptPath, "utf8");
6946
8248
  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(
6947
8249
  "{{directDependenciesOnly}}",
6948
8250
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -6963,6 +8265,9 @@ function createProgressEmitter(options) {
6963
8265
  };
6964
8266
  }
6965
8267
 
8268
+ // src/remediation/pipeline-runtime/tool-wrappers.ts
8269
+ import { existsSync as existsSync17 } from "fs";
8270
+
6966
8271
  // src/remediation/tools/lookup-cve.ts
6967
8272
  import { z as z9 } from "zod";
6968
8273
 
@@ -7249,7 +8554,7 @@ var lookupCveTool = defineTool({
7249
8554
 
7250
8555
  // src/remediation/tools/check-version-match.ts
7251
8556
  import { z as z10 } from "zod";
7252
- import semver7 from "semver";
8557
+ import semver8 from "semver";
7253
8558
  var affectedPackageSchema = z10.object({
7254
8559
  name: z10.string(),
7255
8560
  ecosystem: z10.literal("npm"),
@@ -7278,10 +8583,10 @@ var checkVersionMatchTool = defineTool({
7278
8583
  (p) => p.name === affected.name
7279
8584
  );
7280
8585
  for (const installed of matches) {
7281
- if (!semver7.valid(installed.version)) continue;
8586
+ if (!semver8.valid(installed.version)) continue;
7282
8587
  let isVulnerable = false;
7283
8588
  try {
7284
- isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
8589
+ isVulnerable = semver8.satisfies(installed.version, affected.vulnerableRange, {
7285
8590
  includePrerelease: false
7286
8591
  });
7287
8592
  } catch {
@@ -7301,6 +8606,7 @@ var checkVersionMatchTool = defineTool({
7301
8606
 
7302
8607
  // src/remediation/tools/find-fixed-version.ts
7303
8608
  import { z as z11 } from "zod";
8609
+ import semver9 from "semver";
7304
8610
  var findFixedVersionTool = defineTool({
7305
8611
  description: "Query the npm registry to find the safest published upgrade version for a package that is >= the first patched version. Prefer patch upgrades first, then minor, and only fall back to major when no same-major fix exists.",
7306
8612
  parameters: z11.object({
@@ -7332,9 +8638,11 @@ var findFixedVersionTool = defineTool({
7332
8638
  message: `No safe upgrade version found for "${packageName}". The patch-file path will be needed.`
7333
8639
  };
7334
8640
  }
7335
- const installedMajor = parseInt(installedVersion.split(".")[0] ?? "0", 10);
7336
- const safeMajor = parseInt(safeVersion.split(".")[0] ?? "0", 10);
7337
- const isMajorBump = safeMajor > installedMajor;
8641
+ const parsedInstalled = semver9.parse(installedVersion) ?? semver9.coerce(installedVersion);
8642
+ const parsedSafe = semver9.parse(safeVersion) ?? semver9.coerce(safeVersion);
8643
+ const isMajorBump = Boolean(
8644
+ parsedInstalled && parsedSafe && parsedSafe.major > parsedInstalled.major
8645
+ );
7338
8646
  return {
7339
8647
  safeVersion,
7340
8648
  upgradeLevel,
@@ -7423,6 +8731,10 @@ function createRuntimeToolsForRun(options) {
7423
8731
  workspace: constraints.workspace,
7424
8732
  typecheck: input.typecheck ?? options.typecheck ?? constraints.typecheck,
7425
8733
  breakingChangeRefactor: input.breakingChangeRefactor ?? options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor,
8734
+ proofOfFix: input.proofOfFix ?? options.proofOfFix,
8735
+ requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
8736
+ reproductionTest: input.reproductionTest ?? options.reproductionTest,
8737
+ patchesDir: input.patchesDir ?? options.patchesDir,
7426
8738
  dryRun: preview ? true : input.dryRun
7427
8739
  })
7428
8740
  };
@@ -7435,6 +8747,10 @@ function createRuntimeToolsForRun(options) {
7435
8747
  installPreferOffline: constraints.installPreferOffline,
7436
8748
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
7437
8749
  workspace: constraints.workspace,
8750
+ proofOfFix: input.proofOfFix ?? options.proofOfFix,
8751
+ requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
8752
+ reproductionTest: input.reproductionTest ?? options.reproductionTest,
8753
+ patchesDir: input.patchesDir ?? options.patchesDir,
7438
8754
  dryRun: preview ? true : input.dryRun
7439
8755
  })
7440
8756
  };
@@ -7449,6 +8765,8 @@ function createRuntimeToolsForRun(options) {
7449
8765
  workspace: constraints.workspace,
7450
8766
  proofOfFix: input.proofOfFix ?? options.proofOfFix,
7451
8767
  requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
8768
+ reproductionTest: input.reproductionTest ?? options.reproductionTest,
8769
+ patchesDir: input.patchesDir ?? options.patchesDir,
7452
8770
  dryRun: preview ? true : input.dryRun
7453
8771
  })
7454
8772
  };
@@ -7460,15 +8778,50 @@ function createRuntimeToolsForRun(options) {
7460
8778
  constraints
7461
8779
  });
7462
8780
  if (options.offlineIntelligence) {
7463
- const offlineMessage = options.intelligenceSnapshotPath ? `Offline mode: intelligence lookup via snapshot only. No live network calls.` : `Offline mode: intelligence lookup skipped (no snapshot provided).`;
8781
+ let snapshot = null;
8782
+ if (options.intelligenceSnapshotPath && existsSync17(options.intelligenceSnapshotPath)) {
8783
+ try {
8784
+ snapshot = loadIntelligenceSnapshot(options.intelligenceSnapshotPath);
8785
+ } catch (err) {
8786
+ const _ignored = err;
8787
+ snapshot = null;
8788
+ }
8789
+ }
7464
8790
  tools["lookup-cve"] = {
7465
8791
  ...tools["lookup-cve"],
7466
- execute: async (_input) => ({
7467
- cveId: (_input.cveId ?? "unknown").toUpperCase(),
7468
- found: false,
7469
- offlineMode: true,
7470
- message: offlineMessage
7471
- })
8792
+ execute: async (_input) => {
8793
+ const cveId = (_input.cveId ?? "unknown").toUpperCase();
8794
+ if (snapshot) {
8795
+ const details = lookupSnapshotCve(snapshot, cveId);
8796
+ if (details) {
8797
+ return {
8798
+ success: true,
8799
+ found: true,
8800
+ cveId,
8801
+ offlineMode: true,
8802
+ data: details,
8803
+ message: `Offline mode: intelligence lookup resolved from snapshot for ${cveId}.`
8804
+ };
8805
+ }
8806
+ return {
8807
+ success: false,
8808
+ found: false,
8809
+ cveId,
8810
+ offlineMode: true,
8811
+ error: `Offline mode: ${cveId} not found in intelligence snapshot.`,
8812
+ message: `Offline mode: ${cveId} not found in intelligence snapshot.`
8813
+ };
8814
+ }
8815
+ const snapshotErrorMsg = options.intelligenceSnapshotPath ? `Offline mode: intelligence snapshot file not found or invalid at ${options.intelligenceSnapshotPath}.` : `Offline mode: intelligence lookup skipped (no snapshot provided).`;
8816
+ return {
8817
+ success: false,
8818
+ found: false,
8819
+ cveId,
8820
+ offlineMode: true,
8821
+ error: snapshotErrorMsg,
8822
+ message: snapshotErrorMsg
8823
+ };
8824
+ }
7472
8825
  };
7473
8826
  }
7474
8827
  return tools;
@@ -7603,7 +8956,7 @@ async function runRemediationPipeline(cveId, options = {}) {
7603
8956
  }
7604
8957
 
7605
8958
  // src/api/change-request/index.ts
7606
- import { execa as execa10 } from "execa";
8959
+ import { execa as execa9 } from "execa";
7607
8960
  import { randomBytes } from "crypto";
7608
8961
  function sanitizeBranchToken(value) {
7609
8962
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -7686,14 +9039,14 @@ function groupReports(reports, grouping) {
7686
9039
  return [reports];
7687
9040
  }
7688
9041
  async function detectCurrentBranch(cwd) {
7689
- const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
9042
+ const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7690
9043
  cwd,
7691
9044
  stdio: "pipe"
7692
9045
  });
7693
9046
  return result.stdout.trim();
7694
9047
  }
7695
9048
  async function ensureRepoHasChanges(cwd) {
7696
- const diff = await execa10("git", ["status", "--porcelain"], {
9049
+ const diff = await execa9("git", ["status", "--porcelain"], {
7697
9050
  cwd,
7698
9051
  stdio: "pipe"
7699
9052
  });
@@ -7703,7 +9056,59 @@ function toBranchName(prefix, cveIds) {
7703
9056
  const token = sanitizeBranchToken(cveIds.join("-") || "remediation");
7704
9057
  return `${sanitizeBranchToken(prefix)}/${token}-${randomBytes(6).toString("hex")}`;
7705
9058
  }
9059
+ async function detectRepoFromRemote(cwd) {
9060
+ const result = await execa9("git", ["config", "--get", "remote.origin.url"], {
9061
+ cwd,
9062
+ stdio: "pipe"
9063
+ }).catch(() => ({ stdout: "" }));
9064
+ const url = result.stdout.trim();
9065
+ const match = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
9066
+ if (match) {
9067
+ return `${match[1]}/${match[2]}`;
9068
+ }
9069
+ return void 0;
9070
+ }
9071
+ async function ensureRepoHasStagedChanges(cwd) {
9072
+ const result = await execa9("git", ["diff", "--cached", "--name-only"], {
9073
+ cwd,
9074
+ stdio: "pipe"
9075
+ }).catch(() => ({ stdout: "" }));
9076
+ return result.stdout.trim().length > 0;
9077
+ }
7706
9078
  async function createGitHubRequest(params) {
9079
+ const token = params.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
9080
+ let repository = params.repository;
9081
+ if (!repository) {
9082
+ repository = await detectRepoFromRemote(params.cwd);
9083
+ }
9084
+ if (token && repository) {
9085
+ const [owner, repo] = repository.split("/");
9086
+ const baseUrl = params.githubApiUrl?.replace(/\/+$/, "") || "https://api.github.com";
9087
+ const endpoint = `${baseUrl}/repos/${owner}/${repo}/pulls`;
9088
+ const response = await fetch(endpoint, {
9089
+ method: "POST",
9090
+ headers: {
9091
+ authorization: `Bearer ${token}`,
9092
+ accept: "application/vnd.github+json",
9093
+ "content-type": "application/json",
9094
+ "user-agent": "autoremediator",
9095
+ "x-github-api-version": "2022-11-28"
9096
+ },
9097
+ body: JSON.stringify({
9098
+ title: params.title,
9099
+ head: params.head,
9100
+ base: params.base,
9101
+ body: params.body,
9102
+ draft: Boolean(params.draft)
9103
+ })
9104
+ });
9105
+ if (!response.ok) {
9106
+ const errText = await response.text().catch(() => "");
9107
+ throw new Error(`GitHub API create pull request failed (${response.status}): ${errText}`);
9108
+ }
9109
+ const data = await response.json();
9110
+ return data.html_url || void 0;
9111
+ }
7707
9112
  const args = [
7708
9113
  "pr",
7709
9114
  "create",
@@ -7722,7 +9127,7 @@ async function createGitHubRequest(params) {
7722
9127
  if (params.draft) {
7723
9128
  args.push("--draft");
7724
9129
  }
7725
- const result = await execa10("gh", args, {
9130
+ const result = await execa9("gh", args, {
7726
9131
  cwd: params.cwd,
7727
9132
  stdio: "pipe"
7728
9133
  });
@@ -7747,7 +9152,7 @@ async function createGitLabRequest(params) {
7747
9152
  if (params.draft) {
7748
9153
  args.push("--draft");
7749
9154
  }
7750
- const result = await execa10("glab", args, {
9155
+ const result = await execa9("glab", args, {
7751
9156
  cwd: params.cwd,
7752
9157
  stdio: "pipe"
7753
9158
  });
@@ -7762,7 +9167,7 @@ async function createChangeRequestsForReports(params) {
7762
9167
  return [];
7763
9168
  }
7764
9169
  const results = [];
7765
- const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
9170
+ const token = options.token ?? (options.tokenEnvVar ? process.env[options.tokenEnvVar] : void 0) ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
7766
9171
  const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
7767
9172
  const branchPrefix = options.branchPrefix ?? "autoremediator";
7768
9173
  const pushRemote = options.pushRemote ?? "origin";
@@ -7780,6 +9185,7 @@ async function createChangeRequestsForReports(params) {
7780
9185
  const body = bodyFooter ? `${plan.body}
7781
9186
 
7782
9187
  ${bodyFooter}` : plan.body;
9188
+ const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
7783
9189
  if (!hasChanges) {
7784
9190
  results.push({
7785
9191
  provider,
@@ -7796,11 +9202,49 @@ ${bodyFooter}` : plan.body;
7796
9202
  continue;
7797
9203
  }
7798
9204
  const branchName = toBranchName(branchPrefix, plan.cveIds);
9205
+ let branchCreated = false;
7799
9206
  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" });
9207
+ await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
9208
+ branchCreated = true;
9209
+ await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
9210
+ const hasStaged = await ensureRepoHasStagedChanges(cwd);
9211
+ if (!hasStaged) {
9212
+ results.push({
9213
+ provider,
9214
+ grouping,
9215
+ repository: options.repository,
9216
+ branchName: "",
9217
+ title,
9218
+ body,
9219
+ created: false,
9220
+ cveIds: plan.cveIds,
9221
+ packageNames: plan.packageNames,
9222
+ error: "No repository changes to include in change request."
9223
+ });
9224
+ continue;
9225
+ }
9226
+ await execa9(
9227
+ "git",
9228
+ [
9229
+ "-c",
9230
+ "user.name=autoremediator[bot]",
9231
+ "-c",
9232
+ "user.email=41898282+github-actions[bot]@users.noreply.github.com",
9233
+ "commit",
9234
+ "-m",
9235
+ title
9236
+ ],
9237
+ { cwd, stdio: "pipe" }
9238
+ );
9239
+ const pushArgs = token ? [
9240
+ "-c",
9241
+ `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`,
9242
+ "push",
9243
+ "-u",
9244
+ pushRemote,
9245
+ branchName
9246
+ ] : ["push", "-u", pushRemote, branchName];
9247
+ await execa9("git", pushArgs, { cwd, stdio: "pipe" });
7804
9248
  const url = provider === "github" ? await createGitHubRequest({
7805
9249
  cwd,
7806
9250
  repository: options.repository,
@@ -7808,7 +9252,9 @@ ${bodyFooter}` : plan.body;
7808
9252
  base: baseBranch,
7809
9253
  title,
7810
9254
  body,
7811
- draft: options.draft
9255
+ draft: options.draft,
9256
+ token,
9257
+ githubApiUrl: options.githubApiUrl
7812
9258
  }) : await createGitLabRequest({
7813
9259
  cwd,
7814
9260
  repository: options.repository,
@@ -7831,10 +9277,6 @@ ${bodyFooter}` : plan.body;
7831
9277
  cveIds: plan.cveIds,
7832
9278
  packageNames: plan.packageNames
7833
9279
  });
7834
- if (groups.length > 1) {
7835
- await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
7836
- });
7837
- }
7838
9280
  } catch (error) {
7839
9281
  results.push({
7840
9282
  provider,
@@ -7849,6 +9291,11 @@ ${bodyFooter}` : plan.body;
7849
9291
  packageNames: plan.packageNames,
7850
9292
  error: error instanceof Error ? error.message : String(error)
7851
9293
  });
9294
+ } finally {
9295
+ if (branchCreated) {
9296
+ await execa9("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
9297
+ });
9298
+ }
7852
9299
  }
7853
9300
  }
7854
9301
  return results;
@@ -7888,8 +9335,8 @@ function resolveConstraints(options, cwd) {
7888
9335
  }
7889
9336
 
7890
9337
  // src/platform/evidence.ts
7891
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync6 } from "fs";
7892
- import { join as join21 } from "path";
9338
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "fs";
9339
+ import { join as join23 } from "path";
7893
9340
  function createEvidenceLog(cwd, cveIds, context = {}) {
7894
9341
  return {
7895
9342
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -7921,10 +9368,10 @@ function finalizeEvidence(log) {
7921
9368
  return log;
7922
9369
  }
7923
9370
  function writeEvidenceLog(cwd, log) {
7924
- const dir = join21(cwd, ".autoremediator", "evidence");
7925
- mkdirSync3(dir, { recursive: true });
7926
- const filePath = join21(dir, `${log.runId}.json`);
7927
- writeFileSync6(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
9371
+ const dir = join23(cwd, ".autoremediator", "evidence");
9372
+ mkdirSync5(dir, { recursive: true });
9373
+ const filePath = join23(dir, `${log.runId}.json`);
9374
+ writeFileSync9(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
7928
9375
  return filePath;
7929
9376
  }
7930
9377
 
@@ -8166,12 +9613,12 @@ async function planRemediation(cveId, options = {}) {
8166
9613
  }
8167
9614
 
8168
9615
  // src/scanner/parse-input.ts
8169
- import { extname, resolve as resolve5 } from "path";
8170
- import { readFileSync as readFileSync19 } from "fs";
8171
- import { execa as execa11 } from "execa";
9616
+ import { extname, resolve as resolve4 } from "path";
9617
+ import { readFileSync as readFileSync21 } from "fs";
9618
+ import { execa as execa10 } from "execa";
8172
9619
 
8173
9620
  // src/scanner/adapters/npm-audit.ts
8174
- import { readFileSync as readFileSync16 } from "fs";
9621
+ import { readFileSync as readFileSync18 } from "fs";
8175
9622
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
8176
9623
  var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
8177
9624
  function normalizeSeverity(raw) {
@@ -8207,12 +9654,12 @@ function parseNpmAuditJsonFromString(content) {
8207
9654
  return findings;
8208
9655
  }
8209
9656
  function parseNpmAuditJsonFile(filePath) {
8210
- const content = readFileSync16(filePath, "utf8");
9657
+ const content = readFileSync18(filePath, "utf8");
8211
9658
  return parseNpmAuditJsonFromString(content);
8212
9659
  }
8213
9660
 
8214
9661
  // src/scanner/adapters/yarn-audit.ts
8215
- import { readFileSync as readFileSync17 } from "fs";
9662
+ import { readFileSync as readFileSync19 } from "fs";
8216
9663
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
8217
9664
  var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
8218
9665
  function normalizeSeverity2(raw) {
@@ -8257,12 +9704,12 @@ function parseYarnAuditJsonFromString(content) {
8257
9704
  return findings;
8258
9705
  }
8259
9706
  function parseYarnAuditJsonFile(filePath) {
8260
- const content = readFileSync17(filePath, "utf8");
9707
+ const content = readFileSync19(filePath, "utf8");
8261
9708
  return parseYarnAuditJsonFromString(content);
8262
9709
  }
8263
9710
 
8264
9711
  // src/scanner/adapters/sarif.ts
8265
- import { readFileSync as readFileSync18 } from "fs";
9712
+ import { readFileSync as readFileSync20 } from "fs";
8266
9713
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
8267
9714
  function extractPackageName(result) {
8268
9715
  const pkg = result.properties?.["packageName"];
@@ -8300,7 +9747,7 @@ function parseSarifFromString(content) {
8300
9747
  return findings;
8301
9748
  }
8302
9749
  function parseSarifFile(filePath) {
8303
- const content = readFileSync18(filePath, "utf8");
9750
+ const content = readFileSync20(filePath, "utf8");
8304
9751
  return parseSarifFromString(content);
8305
9752
  }
8306
9753
 
@@ -8309,7 +9756,7 @@ function parseScanInput(filePath, format) {
8309
9756
  if (filePath.includes("\0")) {
8310
9757
  throw new Error("Invalid scan input path: path contains null bytes");
8311
9758
  }
8312
- const resolvedPath = resolve5(filePath);
9759
+ const resolvedPath = resolve4(filePath);
8313
9760
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
8314
9761
  if (resolved === "npm-audit") {
8315
9762
  return parseNpmAuditJsonFile(resolvedPath);
@@ -8331,7 +9778,7 @@ async function parseScanInputFromAudit(params) {
8331
9778
  }
8332
9779
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
8333
9780
  const [cmd, ...args] = command;
8334
- const result = await execa11(cmd, args, {
9781
+ const result = await execa10(cmd, args, {
8335
9782
  cwd: params.cwd,
8336
9783
  stdio: "pipe",
8337
9784
  reject: false
@@ -8385,7 +9832,7 @@ function inferFormat(filePath) {
8385
9832
  const ext = extname(filePath).toLowerCase();
8386
9833
  if (ext === ".sarif") return "sarif";
8387
9834
  try {
8388
- const content = readFileSync19(filePath, "utf8");
9835
+ const content = readFileSync21(filePath, "utf8");
8389
9836
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
8390
9837
  if (firstLine) {
8391
9838
  const parsed = JSON.parse(firstLine);
@@ -8421,9 +9868,6 @@ async function executeScanRemediations(params) {
8421
9868
  source: params.provenance.source,
8422
9869
  constraints: params.constraints
8423
9870
  });
8424
- report.results = report.results.filter(
8425
- (result) => isPackageAllowed(params.policy, result.packageName)
8426
- );
8427
9871
  for (const result of report.results) {
8428
9872
  if (result.strategy === "patch-file") {
8429
9873
  patchCount += 1;
@@ -8796,14 +10240,14 @@ async function remediatePortfolio(targets, options = {}) {
8796
10240
  }
8797
10241
 
8798
10242
  // src/api/update-outdated/index.ts
8799
- import { join as join22 } from "path";
8800
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
8801
- import { execa as execa12 } from "execa";
10243
+ import { join as join24 } from "path";
10244
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "fs";
10245
+ import { execa as execa11 } from "execa";
8802
10246
  async function applyBump(params) {
8803
- const pkgPath = join22(params.cwd, "package.json");
10247
+ const pkgPath = join24(params.cwd, "package.json");
8804
10248
  let pkgJson;
8805
10249
  try {
8806
- pkgJson = JSON.parse(readFileSync20(pkgPath, "utf8"));
10250
+ pkgJson = JSON.parse(readFileSync22(pkgPath, "utf8"));
8807
10251
  } catch {
8808
10252
  return {
8809
10253
  applied: false,
@@ -8825,13 +10269,13 @@ async function applyBump(params) {
8825
10269
  const newRange = `${prefix}${params.toVersion}`;
8826
10270
  return withRepoLock(params.cwd, async () => {
8827
10271
  pkgJson[depField][params.packageName] = newRange;
8828
- writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
10272
+ writeFileSync10(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8829
10273
  try {
8830
10274
  const [installCmd, ...installArgs] = params.installCommand;
8831
- await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
10275
+ await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8832
10276
  } catch (err) {
8833
10277
  pkgJson[depField][params.packageName] = currentRange;
8834
- writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
10278
+ writeFileSync10(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8835
10279
  const message = err instanceof Error ? err.message : String(err);
8836
10280
  return {
8837
10281
  applied: false,
@@ -8841,13 +10285,13 @@ async function applyBump(params) {
8841
10285
  if (params.runTests) {
8842
10286
  try {
8843
10287
  const [testCmd, ...testArgs] = params.testCommand;
8844
- await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
10288
+ await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8845
10289
  } catch (err) {
8846
10290
  pkgJson[depField][params.packageName] = currentRange;
8847
- writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
10291
+ writeFileSync10(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8848
10292
  try {
8849
10293
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
8850
- await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
10294
+ await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8851
10295
  } catch {
8852
10296
  }
8853
10297
  const message = err instanceof Error ? err.message : String(err);
@@ -9049,19 +10493,19 @@ async function checkReachability(options) {
9049
10493
  }
9050
10494
 
9051
10495
  // 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";
10496
+ import { existsSync as existsSync18, readFileSync as readFileSync23 } from "fs";
10497
+ import { join as join25 } from "path";
10498
+ import { execa as execa12 } from "execa";
10499
+ import semver11 from "semver";
9056
10500
 
9057
10501
  // src/scanner/lockfile-diff.ts
9058
10502
  import YAML2 from "yaml";
9059
- import semver8 from "semver";
10503
+ import semver10 from "semver";
9060
10504
  function parseLockfileDependencies(filename, content) {
9061
10505
  const result = /* @__PURE__ */ new Map();
9062
10506
  const add = (name, ver) => {
9063
10507
  if (!name || !ver) return;
9064
- const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
10508
+ const cleanVer = semver10.clean(ver) || semver10.coerce(ver)?.version || ver;
9065
10509
  let set = result.get(name);
9066
10510
  if (!set) {
9067
10511
  set = /* @__PURE__ */ new Set();
@@ -9181,9 +10625,9 @@ function computeLockfileDiff(filename, oldContent, newContent) {
9181
10625
  });
9182
10626
  } else {
9183
10627
  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;
10628
+ const cleanOld = semver10.clean(firstOld) || semver10.coerce(firstOld)?.version;
10629
+ const cleanNew = semver10.clean(nVer) || semver10.coerce(nVer)?.version;
10630
+ const isUpgrade = cleanOld && cleanNew && semver10.valid(cleanOld) && semver10.valid(cleanNew) ? semver10.gt(cleanNew, cleanOld) : true;
9187
10631
  changes.push({
9188
10632
  packageName: pkg,
9189
10633
  changeType: isUpgrade ? "upgraded" : "downgraded",
@@ -9220,7 +10664,7 @@ var DEP_SECTIONS = [
9220
10664
  async function getChangedManifestPaths(cwd, baseRef) {
9221
10665
  const manifests = /* @__PURE__ */ new Set();
9222
10666
  try {
9223
- const { stdout: diffFiles } = await execa13(
10667
+ const { stdout: diffFiles } = await execa12(
9224
10668
  "git",
9225
10669
  ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
9226
10670
  { cwd, reject: false }
@@ -9231,7 +10675,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
9231
10675
  manifests.add(trimmed);
9232
10676
  }
9233
10677
  }
9234
- const { stdout: untrackedFiles } = await execa13(
10678
+ const { stdout: untrackedFiles } = await execa12(
9235
10679
  "git",
9236
10680
  ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
9237
10681
  { cwd, reject: false }
@@ -9243,7 +10687,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
9243
10687
  }
9244
10688
  }
9245
10689
  } catch {
9246
- if (existsSync16(join23(cwd, "package.json"))) {
10690
+ if (existsSync18(join25(cwd, "package.json"))) {
9247
10691
  manifests.add("package.json");
9248
10692
  }
9249
10693
  }
@@ -9251,7 +10695,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
9251
10695
  }
9252
10696
  async function getOldManifestContent(cwd, baseRef, relPath) {
9253
10697
  try {
9254
- const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
10698
+ const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
9255
10699
  cwd,
9256
10700
  reject: false
9257
10701
  });
@@ -9262,10 +10706,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
9262
10706
  }
9263
10707
  }
9264
10708
  function getNewManifestContent(cwd, relPath) {
9265
- const fullPath = join23(cwd, relPath);
9266
- if (!existsSync16(fullPath)) return null;
10709
+ const fullPath = join25(cwd, relPath);
10710
+ if (!existsSync18(fullPath)) return null;
9267
10711
  try {
9268
- return JSON.parse(readFileSync21(fullPath, "utf8"));
10712
+ return JSON.parse(readFileSync23(fullPath, "utf8"));
9269
10713
  } catch {
9270
10714
  return null;
9271
10715
  }
@@ -9286,9 +10730,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
9286
10730
  manifestPath: relPath
9287
10731
  });
9288
10732
  } else if (oldVer !== newVer) {
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;
10733
+ const cleanOld = semver11.clean(oldVer) || semver11.coerce(oldVer)?.version;
10734
+ const cleanNew = semver11.clean(newVer) || semver11.coerce(newVer)?.version;
10735
+ const isUpgrade = cleanOld && cleanNew && semver11.valid(cleanOld) && semver11.valid(cleanNew) ? semver11.gt(cleanNew, cleanOld) : true;
9292
10736
  changes.push({
9293
10737
  packageName: pkgName,
9294
10738
  changeType: isUpgrade ? "upgraded" : "downgraded",
@@ -9324,7 +10768,7 @@ var LOCKFILE_PATTERNS = [
9324
10768
  async function getChangedLockfilePaths(cwd, baseRef) {
9325
10769
  const lockfiles = /* @__PURE__ */ new Set();
9326
10770
  try {
9327
- const { stdout: diffFiles } = await execa13(
10771
+ const { stdout: diffFiles } = await execa12(
9328
10772
  "git",
9329
10773
  ["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
9330
10774
  { cwd, reject: false }
@@ -9335,7 +10779,7 @@ async function getChangedLockfilePaths(cwd, baseRef) {
9335
10779
  lockfiles.add(trimmed);
9336
10780
  }
9337
10781
  }
9338
- const { stdout: untrackedFiles } = await execa13(
10782
+ const { stdout: untrackedFiles } = await execa12(
9339
10783
  "git",
9340
10784
  ["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
9341
10785
  { cwd, reject: false }
@@ -9352,7 +10796,7 @@ async function getChangedLockfilePaths(cwd, baseRef) {
9352
10796
  }
9353
10797
  async function getFileContentAtRef(cwd, baseRef, relPath) {
9354
10798
  try {
9355
- const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
10799
+ const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
9356
10800
  cwd,
9357
10801
  reject: false
9358
10802
  });
@@ -9379,8 +10823,8 @@ async function scanDelta(options = {}) {
9379
10823
  }
9380
10824
  for (const lockfileRel of changedLockfiles) {
9381
10825
  const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
9382
- const fullPath = join23(cwd, lockfileRel);
9383
- const newContent = existsSync16(fullPath) ? readFileSync21(fullPath, "utf8") : null;
10826
+ const fullPath = join25(cwd, lockfileRel);
10827
+ const newContent = existsSync18(fullPath) ? readFileSync23(fullPath, "utf8") : null;
9384
10828
  const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
9385
10829
  for (const change of lockDiffs) {
9386
10830
  if (!directPkgs.has(change.packageName)) {
@@ -9395,7 +10839,7 @@ async function scanDelta(options = {}) {
9395
10839
  const resolvedFindings = [];
9396
10840
  for (const change of dependencyChanges) {
9397
10841
  if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
9398
- const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
10842
+ const cleanVer = semver11.coerce(change.newVersion)?.version ?? change.newVersion;
9399
10843
  const evaluation = await evaluatePackage(change.packageName, {
9400
10844
  version: cleanVer,
9401
10845
  enrichIntelligence: true
@@ -9415,12 +10859,12 @@ async function scanDelta(options = {}) {
9415
10859
  }
9416
10860
  }
9417
10861
  if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
9418
- const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
10862
+ const cleanOldVer = semver11.coerce(change.oldVersion)?.version ?? change.oldVersion;
9419
10863
  const oldEval = await evaluatePackage(change.packageName, {
9420
10864
  version: cleanOldVer,
9421
10865
  enrichIntelligence: true
9422
10866
  });
9423
- const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
10867
+ const cleanNewVer = change.newVersion ? semver11.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
9424
10868
  const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
9425
10869
  version: cleanNewVer,
9426
10870
  enrichIntelligence: false
@@ -9472,7 +10916,145 @@ async function scanDelta(options = {}) {
9472
10916
  import { randomUUID as randomUUID2 } from "crypto";
9473
10917
 
9474
10918
  // src/platform/jobs.ts
9475
- var jobRegistry = /* @__PURE__ */ new Map();
10919
+ var BoundedJobRegistry = class extends Map {
10920
+ maxCapacity;
10921
+ ttlMs;
10922
+ constructor(options = {}) {
10923
+ super();
10924
+ this.maxCapacity = options.maxCapacity ?? 1e3;
10925
+ this.ttlMs = options.ttlMs ?? 24 * 60 * 60 * 1e3;
10926
+ }
10927
+ getMaxCapacity() {
10928
+ return this.maxCapacity;
10929
+ }
10930
+ setMaxCapacity(capacity) {
10931
+ if (capacity <= 0) {
10932
+ throw new Error(`maxCapacity must be greater than 0, received ${capacity}`);
10933
+ }
10934
+ this.maxCapacity = capacity;
10935
+ this.enforceCapacity();
10936
+ }
10937
+ getTtl() {
10938
+ return this.ttlMs;
10939
+ }
10940
+ setTtl(ttlMs) {
10941
+ if (ttlMs <= 0) {
10942
+ throw new Error(`ttlMs must be greater than 0, received ${ttlMs}`);
10943
+ }
10944
+ this.ttlMs = ttlMs;
10945
+ }
10946
+ isExpired(job, now = Date.now()) {
10947
+ const timestampStr = job.completedAt ?? job.submittedAt;
10948
+ const timeMs = new Date(timestampStr).getTime();
10949
+ if (isNaN(timeMs)) return false;
10950
+ return now - timeMs > this.ttlMs;
10951
+ }
10952
+ evictExpired(now = Date.now()) {
10953
+ let count = 0;
10954
+ for (const [id, job] of super.entries()) {
10955
+ if (this.isExpired(job, now)) {
10956
+ super.delete(id);
10957
+ count++;
10958
+ }
10959
+ }
10960
+ return count;
10961
+ }
10962
+ prune(now = Date.now()) {
10963
+ return this.evictExpired(now);
10964
+ }
10965
+ get(jobId) {
10966
+ const job = super.get(jobId);
10967
+ if (!job) return void 0;
10968
+ if (this.isExpired(job)) {
10969
+ super.delete(jobId);
10970
+ return void 0;
10971
+ }
10972
+ return job;
10973
+ }
10974
+ has(jobId) {
10975
+ const job = super.get(jobId);
10976
+ if (!job) return false;
10977
+ if (this.isExpired(job)) {
10978
+ super.delete(jobId);
10979
+ return false;
10980
+ }
10981
+ return true;
10982
+ }
10983
+ set(jobId, job) {
10984
+ if (super.has(jobId)) {
10985
+ super.set(jobId, job);
10986
+ return this;
10987
+ }
10988
+ this.evictExpired();
10989
+ if (this.size >= this.maxCapacity) {
10990
+ this.evictOneCandidate();
10991
+ }
10992
+ super.set(jobId, job);
10993
+ return this;
10994
+ }
10995
+ evictOneCandidate() {
10996
+ let oldestTerminalId;
10997
+ let oldestTerminalTime = Infinity;
10998
+ let oldestOverallId;
10999
+ let oldestOverallTime = Infinity;
11000
+ for (const [id, job] of super.entries()) {
11001
+ const submittedMs = new Date(job.submittedAt).getTime() || 0;
11002
+ if (submittedMs < oldestOverallTime) {
11003
+ oldestOverallTime = submittedMs;
11004
+ oldestOverallId = id;
11005
+ }
11006
+ if (job.status === "done" || job.status === "failed") {
11007
+ const terminalMs = new Date(job.completedAt ?? job.submittedAt).getTime() || 0;
11008
+ if (terminalMs < oldestTerminalTime) {
11009
+ oldestTerminalTime = terminalMs;
11010
+ oldestTerminalId = id;
11011
+ }
11012
+ }
11013
+ }
11014
+ const targetId = oldestTerminalId ?? oldestOverallId;
11015
+ if (targetId) {
11016
+ super.delete(targetId);
11017
+ }
11018
+ }
11019
+ enforceCapacity() {
11020
+ this.evictExpired();
11021
+ while (this.size > this.maxCapacity) {
11022
+ this.evictOneCandidate();
11023
+ }
11024
+ }
11025
+ stats() {
11026
+ let pending = 0;
11027
+ let running = 0;
11028
+ let done = 0;
11029
+ let failed = 0;
11030
+ for (const job of super.values()) {
11031
+ switch (job.status) {
11032
+ case "pending":
11033
+ pending++;
11034
+ break;
11035
+ case "running":
11036
+ running++;
11037
+ break;
11038
+ case "done":
11039
+ done++;
11040
+ break;
11041
+ case "failed":
11042
+ failed++;
11043
+ break;
11044
+ }
11045
+ }
11046
+ return {
11047
+ size: this.size,
11048
+ maxCapacity: this.maxCapacity,
11049
+ ttlMs: this.ttlMs,
11050
+ pending,
11051
+ running,
11052
+ done,
11053
+ failed
11054
+ };
11055
+ }
11056
+ };
11057
+ var jobRegistry = new BoundedJobRegistry();
9476
11058
 
9477
11059
  // src/api/jobs/index.ts
9478
11060
  function createJob() {