autoremediator 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +20 -27
  2. package/dist/api/change-request/index.d.ts +2 -1
  3. package/dist/api/change-request/index.d.ts.map +1 -1
  4. package/dist/api/context.d.ts.map +1 -1
  5. package/dist/api/index.d.ts +2 -2
  6. package/dist/api/index.d.ts.map +1 -1
  7. package/dist/api/options-schema.d.ts +3 -0
  8. package/dist/api/options-schema.d.ts.map +1 -1
  9. package/dist/api/patches/index.d.ts +1 -0
  10. package/dist/api/patches/index.d.ts.map +1 -1
  11. package/dist/api/patches/reconcile.d.ts +7 -0
  12. package/dist/api/patches/reconcile.d.ts.map +1 -0
  13. package/dist/api/patches/reconcile.test.d.ts +2 -0
  14. package/dist/api/patches/reconcile.test.d.ts.map +1 -0
  15. package/dist/api/remediate/validators.d.ts +3 -0
  16. package/dist/api/remediate/validators.d.ts.map +1 -1
  17. package/dist/{chunk-U4LIJIGD.js → chunk-7BORIBPY.js} +2476 -885
  18. package/dist/cli/program.d.ts.map +1 -1
  19. package/dist/cli/runners.d.ts +5 -0
  20. package/dist/cli/runners.d.ts.map +1 -1
  21. package/dist/cli/triage.d.ts +7 -0
  22. package/dist/cli/triage.d.ts.map +1 -0
  23. package/dist/cli/types.d.ts +5 -0
  24. package/dist/cli/types.d.ts.map +1 -1
  25. package/dist/cli.js +233 -6
  26. package/dist/index.js +2479 -889
  27. package/dist/index.js.map +1 -1
  28. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  29. package/dist/mcp/server.js +1 -1
  30. package/dist/openapi/server.js +1 -1
  31. package/dist/platform/evidence.d.ts +2 -0
  32. package/dist/platform/evidence.d.ts.map +1 -1
  33. package/dist/platform/package-manager/index.d.ts +1 -0
  34. package/dist/platform/package-manager/index.d.ts.map +1 -1
  35. package/dist/platform/supply-chain.d.ts +10 -0
  36. package/dist/platform/supply-chain.d.ts.map +1 -0
  37. package/dist/platform/supply-chain.test.d.ts +2 -0
  38. package/dist/platform/supply-chain.test.d.ts.map +1 -0
  39. package/dist/platform/types.d.ts +69 -6
  40. package/dist/platform/types.d.ts.map +1 -1
  41. package/dist/platform/workspace.d.ts +28 -0
  42. package/dist/platform/workspace.d.ts.map +1 -0
  43. package/dist/platform/workspace.test.d.ts +2 -0
  44. package/dist/platform/workspace.test.d.ts.map +1 -0
  45. package/dist/remediation/local/fallback.d.ts +2 -0
  46. package/dist/remediation/local/fallback.d.ts.map +1 -1
  47. package/dist/remediation/local/options.d.ts +3 -0
  48. package/dist/remediation/local/options.d.ts.map +1 -1
  49. package/dist/remediation/local/primary-strategy.d.ts +2 -9
  50. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  51. package/dist/remediation/local/run.d.ts.map +1 -1
  52. package/dist/remediation/pipeline-runtime/tool-wrappers.d.ts.map +1 -1
  53. package/dist/remediation/proof-of-fix/runner.d.ts +16 -0
  54. package/dist/remediation/proof-of-fix/runner.d.ts.map +1 -0
  55. package/dist/remediation/proof-of-fix/runner.test.d.ts +2 -0
  56. package/dist/remediation/proof-of-fix/runner.test.d.ts.map +1 -0
  57. package/dist/remediation/refactor/call-site-finder.d.ts +15 -0
  58. package/dist/remediation/refactor/call-site-finder.d.ts.map +1 -0
  59. package/dist/remediation/refactor/refactor-agent.d.ts +34 -0
  60. package/dist/remediation/refactor/refactor-agent.d.ts.map +1 -0
  61. package/dist/remediation/refactor/refactor.test.d.ts +2 -0
  62. package/dist/remediation/refactor/refactor.test.d.ts.map +1 -0
  63. package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
  64. package/dist/remediation/tools/apply-patch-file/index.d.ts +5 -1
  65. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  66. package/dist/remediation/tools/apply-version-bump.d.ts +2 -0
  67. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  68. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  69. package/dist/remediation/tools/check-reachability.d.ts +6 -1
  70. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  71. package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
  72. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  73. package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
  74. package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
  75. package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
  76. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  77. package/dist/scanner/delta-scan.d.ts.map +1 -1
  78. package/dist/scanner/lockfile-diff.d.ts +10 -0
  79. package/dist/scanner/lockfile-diff.d.ts.map +1 -0
  80. package/dist/scanner/lockfile-diff.test.d.ts +2 -0
  81. package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
  82. package/package.json +8 -8
@@ -65,7 +65,10 @@ var OPTION_DESCRIPTIONS = {
65
65
  campaignMode: "Enable portfolio campaign mode to risk-rank targets before execution",
66
66
  escalationGraph: "Optional mapping from unresolved reasons to intended escalation actions",
67
67
  offlineIntelligence: "Skip all network calls to intelligence sources. Only local inventory and npm-registry version resolution proceed.",
68
- intelligenceSnapshotPath: "Path to a local JSON snapshot of pre-fetched CVE intelligence (keyed by CVE ID) used when offline mode is active."
68
+ intelligenceSnapshotPath: "Path to a local JSON snapshot of pre-fetched CVE intelligence (keyed by CVE ID) used when offline mode is active.",
69
+ proofOfFix: "If true, enforce dual-stage exploit reproduction proof-of-fix verification (assert failure pre-patch, assert pass post-patch)",
70
+ requireProofOfFix: "If true, reject patches and roll back if proof-of-fix verification fails",
71
+ breakingChangeRefactor: "If true, attempt agentic call-site refactoring when major version upgrades introduce breaking API changes"
69
72
  };
70
73
  function createConstraintSchemaProperties() {
71
74
  return {
@@ -87,7 +90,12 @@ function createConstraintSchemaProperties() {
87
90
  type: "boolean",
88
91
  description: OPTION_DESCRIPTIONS.enforceFrozenLockfile
89
92
  },
90
- workspace: { type: "string", description: OPTION_DESCRIPTIONS.workspace }
93
+ workspace: { type: "string", description: OPTION_DESCRIPTIONS.workspace },
94
+ requireProofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.requireProofOfFix },
95
+ enableBreakingChangeRefactor: {
96
+ type: "boolean",
97
+ description: OPTION_DESCRIPTIONS.breakingChangeRefactor
98
+ }
91
99
  };
92
100
  }
93
101
  function createRemediateOptionSchemaProperties(options) {
@@ -306,6 +314,14 @@ function createRemediateOptionSchemaProperties(options) {
306
314
  "validation-failed": {
307
315
  type: "string",
308
316
  enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
317
+ },
318
+ "proof-of-fix-failed": {
319
+ type: "string",
320
+ enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
321
+ },
322
+ "breaking-change-unresolved": {
323
+ type: "string",
324
+ enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
309
325
  }
310
326
  },
311
327
  additionalProperties: false
@@ -314,6 +330,12 @@ function createRemediateOptionSchemaProperties(options) {
314
330
  intelligenceSnapshotPath: {
315
331
  type: "string",
316
332
  description: OPTION_DESCRIPTIONS.intelligenceSnapshotPath
333
+ },
334
+ proofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.proofOfFix },
335
+ requireProofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.requireProofOfFix },
336
+ breakingChangeRefactor: {
337
+ type: "boolean",
338
+ description: OPTION_DESCRIPTIONS.breakingChangeRefactor
317
339
  }
318
340
  };
319
341
  }
@@ -463,12 +485,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
463
485
  import { join as join5 } from "path";
464
486
 
465
487
  // src/remediation/strategies/patch-utils.ts
466
- import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
488
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
467
489
  import { join as join4 } from "path";
468
490
  import { execa as execa2 } from "execa";
469
491
 
470
492
  // src/platform/package-manager/index.ts
471
- import { existsSync as existsSync2 } from "fs";
493
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
472
494
  import { join as join3 } from "path";
473
495
  import { execa } from "execa";
474
496
 
@@ -661,6 +683,32 @@ function resolveTestCommand(pm, constraints) {
661
683
  const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
662
684
  return withWorkspace(base, pm, constraints?.workspace);
663
685
  }
686
+ function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
687
+ const pkgJsonPath = join3(cwd, "package.json");
688
+ let hasTypecheckScript = false;
689
+ const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
690
+ if (existsSync2(pkgJsonPath)) {
691
+ try {
692
+ const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
693
+ if (pkg.scripts?.typecheck) {
694
+ hasTypecheckScript = true;
695
+ }
696
+ } catch {
697
+ }
698
+ }
699
+ if (hasTypecheckScript) {
700
+ const base = [pm, "run", "typecheck"];
701
+ return withWorkspace(base, pm, constraints?.workspace);
702
+ }
703
+ if (hasTsConfig) {
704
+ if (pm === "deno") {
705
+ return withWorkspace(["deno", "check"], pm, constraints?.workspace);
706
+ }
707
+ const base = ["npx", "tsc", "--noEmit"];
708
+ return withWorkspace(base, pm, constraints?.workspace);
709
+ }
710
+ return [];
711
+ }
664
712
  function resolveAuditCommand(pm, constraints) {
665
713
  if (pm === "deno") {
666
714
  throw new Error(
@@ -905,12 +953,12 @@ function defineTool(config) {
905
953
 
906
954
  // src/remediation/tools/check-inventory.ts
907
955
  import { z } from "zod";
908
- import { readFileSync as readFileSync4 } from "fs";
909
- import { join as join7 } from "path";
956
+ import { readFileSync as readFileSync6 } from "fs";
957
+ import { join as join8 } from "path";
910
958
  import { execa as execa3 } from "execa";
911
959
 
912
960
  // src/platform/policy.ts
913
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
961
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
914
962
  import { join as join6 } from "path";
915
963
  import { parse as yamlParse } from "yaml";
916
964
  var DEFAULT_POLICY = {
@@ -945,7 +993,7 @@ function loadPolicy(cwd, explicitPath) {
945
993
  const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
946
994
  if (!existsSync6(candidate)) return DEFAULT_POLICY;
947
995
  try {
948
- const parsed = yamlParse(readFileSync3(candidate, "utf8"));
996
+ const parsed = yamlParse(readFileSync4(candidate, "utf8"));
949
997
  return {
950
998
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
951
999
  denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
@@ -1006,7 +1054,7 @@ function isActiveSuppression(suppression) {
1006
1054
  }
1007
1055
  function loadSuppressionsFile(filePath) {
1008
1056
  try {
1009
- const content = readFileSync3(filePath, "utf8");
1057
+ const content = readFileSync4(filePath, "utf8");
1010
1058
  const parsed = yamlParse(content);
1011
1059
  return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
1012
1060
  } catch {
@@ -1032,6 +1080,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1032
1080
  };
1033
1081
  }
1034
1082
 
1083
+ // src/platform/workspace.ts
1084
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1085
+ import { join as join7, resolve as resolve2, relative } from "path";
1086
+ import yaml from "yaml";
1087
+ function findWorkspaceRoot(startDir) {
1088
+ let curr = resolve2(startDir);
1089
+ while (true) {
1090
+ if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
1091
+ const pkgPath = join7(curr, "package.json");
1092
+ if (existsSync7(pkgPath)) {
1093
+ try {
1094
+ const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
1095
+ if (parsed.workspaces) return curr;
1096
+ } catch {
1097
+ }
1098
+ }
1099
+ const parent = resolve2(curr, "..");
1100
+ if (parent === curr) break;
1101
+ curr = parent;
1102
+ }
1103
+ return null;
1104
+ }
1105
+ function findWorkspacePackages(cwd) {
1106
+ const rootCwd = findWorkspaceRoot(cwd) ?? resolve2(cwd);
1107
+ const packages = [];
1108
+ const packageGlobs = [];
1109
+ const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
1110
+ if (existsSync7(pnpmWorkspaceFile)) {
1111
+ try {
1112
+ const content = readFileSync5(pnpmWorkspaceFile, "utf8");
1113
+ const parsed = yaml.parse(content);
1114
+ if (Array.isArray(parsed?.packages)) {
1115
+ packageGlobs.push(...parsed.packages);
1116
+ }
1117
+ } catch {
1118
+ }
1119
+ }
1120
+ const rootPkgFile = join7(rootCwd, "package.json");
1121
+ if (existsSync7(rootPkgFile)) {
1122
+ try {
1123
+ const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
1124
+ if (Array.isArray(rootPkg.workspaces)) {
1125
+ packageGlobs.push(...rootPkg.workspaces);
1126
+ } else if (Array.isArray(rootPkg.workspaces?.packages)) {
1127
+ packageGlobs.push(...rootPkg.workspaces.packages);
1128
+ }
1129
+ } catch {
1130
+ }
1131
+ }
1132
+ const denoJsonFile = join7(rootCwd, "deno.json");
1133
+ if (existsSync7(denoJsonFile)) {
1134
+ try {
1135
+ const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
1136
+ if (Array.isArray(denoJson.workspace)) {
1137
+ packageGlobs.push(...denoJson.workspace);
1138
+ }
1139
+ } catch {
1140
+ }
1141
+ }
1142
+ if (packageGlobs.length === 0) {
1143
+ return packages;
1144
+ }
1145
+ const seenDirs = /* @__PURE__ */ new Set();
1146
+ for (const glob of packageGlobs) {
1147
+ const cleanGlob = glob.trim().replace(/\/+$/, "");
1148
+ if (!cleanGlob || cleanGlob.startsWith("!")) continue;
1149
+ if (cleanGlob.endsWith("/*")) {
1150
+ const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
1151
+ if (!existsSync7(parentDir)) continue;
1152
+ try {
1153
+ const entries = readdirSync(parentDir);
1154
+ for (const entry of entries) {
1155
+ if (entry.startsWith(".") || entry === "node_modules") continue;
1156
+ const subDir = join7(parentDir, entry);
1157
+ try {
1158
+ if (statSync(subDir).isDirectory()) {
1159
+ addWorkspacePackage(rootCwd, subDir, packages, seenDirs);
1160
+ }
1161
+ } catch {
1162
+ }
1163
+ }
1164
+ } catch {
1165
+ }
1166
+ } else {
1167
+ const directDir = join7(rootCwd, cleanGlob);
1168
+ if (existsSync7(directDir)) {
1169
+ try {
1170
+ if (statSync(directDir).isDirectory()) {
1171
+ addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
1172
+ }
1173
+ } catch {
1174
+ }
1175
+ }
1176
+ }
1177
+ }
1178
+ return packages;
1179
+ }
1180
+ function addWorkspacePackage(rootCwd, dir, out, seen) {
1181
+ const norm = resolve2(dir);
1182
+ if (seen.has(norm)) return;
1183
+ seen.add(norm);
1184
+ const manifestPath = join7(dir, "package.json");
1185
+ if (!existsSync7(manifestPath)) return;
1186
+ try {
1187
+ const content = readFileSync5(manifestPath, "utf8");
1188
+ const manifest = JSON.parse(content);
1189
+ out.push({
1190
+ name: manifest.name ?? relative(rootCwd, dir),
1191
+ relativeDir: relative(rootCwd, dir),
1192
+ absoluteDir: norm,
1193
+ manifestPath,
1194
+ manifest
1195
+ });
1196
+ } catch {
1197
+ }
1198
+ }
1199
+ function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
1200
+ const workspaces = findWorkspacePackages(cwd);
1201
+ if (workspaces.length === 0) return null;
1202
+ const matchesFilter = (wp) => {
1203
+ if (!filterWorkspace) return true;
1204
+ const filter = filterWorkspace.trim();
1205
+ return wp.name === filter || wp.relativeDir === filter || wp.relativeDir.replace(/\\/g, "/") === filter.replace(/\\/g, "/");
1206
+ };
1207
+ for (const wp of workspaces) {
1208
+ if (!matchesFilter(wp)) continue;
1209
+ const isDeclared = Boolean(wp.manifest.dependencies?.[dependencyName]) || Boolean(wp.manifest.devDependencies?.[dependencyName]) || Boolean(wp.manifest.peerDependencies?.[dependencyName]);
1210
+ if (isDeclared) {
1211
+ return wp;
1212
+ }
1213
+ }
1214
+ return null;
1215
+ }
1216
+
1035
1217
  // src/remediation/tools/check-inventory.ts
1036
1218
  var checkInventoryTool = defineTool({
1037
1219
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
@@ -1049,7 +1231,7 @@ var checkInventoryTool = defineTool({
1049
1231
  }) => {
1050
1232
  let pkgJson;
1051
1233
  try {
1052
- pkgJson = JSON.parse(readFileSync4(join7(cwd, "package.json"), "utf8"));
1234
+ pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1053
1235
  } catch {
1054
1236
  return {
1055
1237
  packages: [],
@@ -1081,7 +1263,8 @@ var checkInventoryTool = defineTool({
1081
1263
  }
1082
1264
  const packages = [];
1083
1265
  for (const [name, version] of installedVersions.entries()) {
1084
- const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]);
1266
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, name, workspace);
1267
+ const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]) || Boolean(declaringWorkspace);
1085
1268
  packages.push({
1086
1269
  name,
1087
1270
  version,
@@ -1204,173 +1387,8 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
1204
1387
  };
1205
1388
  }
1206
1389
 
1207
- // src/platform/config.ts
1208
- function resolveProvider(options = {}) {
1209
- const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1210
- if (raw !== "remote" && raw !== "local") {
1211
- throw new Error(
1212
- `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1213
- );
1214
- }
1215
- return raw;
1216
- }
1217
- function resolveModelName(provider, options = {}, routing = {}) {
1218
- if (options.model) return options.model;
1219
- if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1220
- const cwd = options.cwd ?? process.cwd();
1221
- const policy = loadPolicy(cwd, options.policy);
1222
- const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1223
- if (providerEnvModel) return providerEnvModel;
1224
- const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1225
- if (policyPinnedModel) return policyPinnedModel;
1226
- const defaults = {
1227
- remote: "remote-default",
1228
- local: "local"
1229
- };
1230
- const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
1231
- const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
1232
- if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
1233
- return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
1234
- }
1235
- return defaults[provider];
1236
- }
1237
- async function loadRemoteFactory() {
1238
- const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
1239
- const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
1240
- if (!moduleName) {
1241
- throw new Error(
1242
- "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
1243
- );
1244
- }
1245
- if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
1246
- throw new Error(
1247
- `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
1248
- );
1249
- }
1250
- const loaded = await import(moduleName);
1251
- const factory = loaded[exportName];
1252
- if (typeof factory !== "function") {
1253
- throw new Error(
1254
- `Remote client factory "${exportName}" was not found in module "${moduleName}".`
1255
- );
1256
- }
1257
- return factory;
1258
- }
1259
- async function createModel(options = {}, routing = {}) {
1260
- const provider = resolveProvider(options);
1261
- if (provider === "local") {
1262
- throw new Error(
1263
- "Local provider does not create a language model. Use the deterministic pipeline path instead."
1264
- );
1265
- }
1266
- const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
1267
- if (!apiKey) {
1268
- throw new Error(
1269
- "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
1270
- );
1271
- }
1272
- const modelName = resolveModelName(provider, options, routing);
1273
- const createRemoteClient = await loadRemoteFactory();
1274
- const remoteClient = createRemoteClient({ apiKey });
1275
- return remoteClient(modelName);
1276
- }
1277
- function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
1278
- const relaxed = {
1279
- remote: { low: 0.65, medium: 0.7, high: 0.8 },
1280
- local: { low: 0.65, medium: 0.7, high: 0.8 }
1281
- };
1282
- const strict = {
1283
- remote: { low: 0.8, medium: 0.85, high: 0.9 },
1284
- local: { low: 0.8, medium: 0.85, high: 0.9 }
1285
- };
1286
- const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
1287
- const override = overrides?.[riskLevel];
1288
- if (typeof override === "number" && Number.isFinite(override)) {
1289
- return Math.max(0, Math.min(1, override));
1290
- }
1291
- return baseThreshold;
1292
- }
1293
- function estimateModelCostUsd(params) {
1294
- const inputTokens = params.promptChars / 4;
1295
- const outputTokens = params.completionChars / 4;
1296
- const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
1297
- return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
1298
- }
1299
- function getNvdConfig() {
1300
- return {
1301
- apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
1302
- };
1303
- }
1304
- function getGitHubToken() {
1305
- return process.env.GITHUB_TOKEN;
1306
- }
1307
- function getIntelligenceSourceConfig() {
1308
- return {
1309
- gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
1310
- certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
1311
- epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
1312
- cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
1313
- depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
1314
- scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
1315
- vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1316
- commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1317
- commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
1318
- };
1319
- }
1320
-
1321
- // src/platform/idempotency.ts
1322
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1323
- import { join as join8 } from "path";
1324
- var DEFAULT_INDEX = {
1325
- schemaVersion: "1.0",
1326
- entries: {}
1327
- };
1328
- function indexFilePath(cwd) {
1329
- return join8(cwd, ".autoremediator", "state", "idempotency.json");
1330
- }
1331
- function entryKey(idempotencyKey, cveId) {
1332
- return `${idempotencyKey}::${cveId.toUpperCase()}`;
1333
- }
1334
- function loadIndex(cwd) {
1335
- const filePath = indexFilePath(cwd);
1336
- if (!existsSync7(filePath)) return DEFAULT_INDEX;
1337
- try {
1338
- const parsed = JSON.parse(readFileSync5(filePath, "utf8"));
1339
- if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
1340
- return parsed;
1341
- }
1342
- return DEFAULT_INDEX;
1343
- } catch {
1344
- return DEFAULT_INDEX;
1345
- }
1346
- }
1347
- function saveIndex(cwd, index) {
1348
- const filePath = indexFilePath(cwd);
1349
- mkdirSync2(join8(cwd, ".autoremediator", "state"), { recursive: true });
1350
- writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
1351
- }
1352
- function readIdempotentReport(cwd, idempotencyKey, cveId) {
1353
- const index = loadIndex(cwd);
1354
- const key = entryKey(idempotencyKey, cveId);
1355
- return index.entries[key]?.report;
1356
- }
1357
- function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
1358
- const index = loadIndex(cwd);
1359
- const key = entryKey(idempotencyKey, cveId);
1360
- index.entries[key] = {
1361
- key: idempotencyKey,
1362
- cveId: cveId.toUpperCase(),
1363
- report,
1364
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
1365
- };
1366
- saveIndex(cwd, index);
1367
- }
1368
-
1369
- // src/remediation/pipeline.ts
1370
- import { generateText as generateText2 } from "ai";
1371
-
1372
- // src/remediation/local/run.ts
1373
- import semver5 from "semver";
1390
+ // src/remediation/evaluate-package.ts
1391
+ import semver2 from "semver";
1374
1392
 
1375
1393
  // src/platform/http-client.ts
1376
1394
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -1552,16 +1570,744 @@ async function lookupCveOsv(cveId) {
1552
1570
  return parseOsvVuln(vuln);
1553
1571
  }
1554
1572
 
1555
- // src/intelligence/sources/github-advisory.ts
1556
- var GITHUB_API_BASE = "https://api.github.com/advisories";
1557
- async function fetchGhAdvisories(cveId) {
1558
- const token = getGitHubToken();
1559
- const headers = {
1560
- "X-GitHub-Api-Version": "2022-11-28",
1561
- "User-Agent": "autoremediator"
1562
- };
1563
- if (token) {
1564
- headers["Authorization"] = `token ${token}`;
1573
+ // src/platform/config.ts
1574
+ function resolveProvider(options = {}) {
1575
+ const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1576
+ if (raw !== "remote" && raw !== "local") {
1577
+ throw new Error(
1578
+ `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1579
+ );
1580
+ }
1581
+ return raw;
1582
+ }
1583
+ function resolveModelName(provider, options = {}, routing = {}) {
1584
+ if (options.model) return options.model;
1585
+ if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1586
+ const cwd = options.cwd ?? process.cwd();
1587
+ const policy = loadPolicy(cwd, options.policy);
1588
+ const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1589
+ if (providerEnvModel) return providerEnvModel;
1590
+ const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1591
+ if (policyPinnedModel) return policyPinnedModel;
1592
+ const defaults = {
1593
+ remote: "remote-default",
1594
+ local: "local"
1595
+ };
1596
+ const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
1597
+ const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
1598
+ if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
1599
+ return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
1600
+ }
1601
+ return defaults[provider];
1602
+ }
1603
+ async function loadRemoteFactory() {
1604
+ const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
1605
+ const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
1606
+ if (!moduleName) {
1607
+ throw new Error(
1608
+ "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
1609
+ );
1610
+ }
1611
+ if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
1612
+ throw new Error(
1613
+ `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
1614
+ );
1615
+ }
1616
+ const loaded = await import(moduleName);
1617
+ const factory = loaded[exportName];
1618
+ if (typeof factory !== "function") {
1619
+ throw new Error(
1620
+ `Remote client factory "${exportName}" was not found in module "${moduleName}".`
1621
+ );
1622
+ }
1623
+ return factory;
1624
+ }
1625
+ async function createModel(options = {}, routing = {}) {
1626
+ const provider = resolveProvider(options);
1627
+ if (provider === "local") {
1628
+ throw new Error(
1629
+ "Local provider does not create a language model. Use the deterministic pipeline path instead."
1630
+ );
1631
+ }
1632
+ const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
1633
+ if (!apiKey) {
1634
+ throw new Error(
1635
+ "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
1636
+ );
1637
+ }
1638
+ const modelName = resolveModelName(provider, options, routing);
1639
+ const createRemoteClient = await loadRemoteFactory();
1640
+ const remoteClient = createRemoteClient({ apiKey });
1641
+ return remoteClient(modelName);
1642
+ }
1643
+ function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
1644
+ const relaxed = {
1645
+ remote: { low: 0.65, medium: 0.7, high: 0.8 },
1646
+ local: { low: 0.65, medium: 0.7, high: 0.8 }
1647
+ };
1648
+ const strict = {
1649
+ remote: { low: 0.8, medium: 0.85, high: 0.9 },
1650
+ local: { low: 0.8, medium: 0.85, high: 0.9 }
1651
+ };
1652
+ const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
1653
+ const override = overrides?.[riskLevel];
1654
+ if (typeof override === "number" && Number.isFinite(override)) {
1655
+ return Math.max(0, Math.min(1, override));
1656
+ }
1657
+ return baseThreshold;
1658
+ }
1659
+ function estimateModelCostUsd(params) {
1660
+ const inputTokens = params.promptChars / 4;
1661
+ const outputTokens = params.completionChars / 4;
1662
+ const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
1663
+ return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
1664
+ }
1665
+ function getNvdConfig() {
1666
+ return {
1667
+ apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
1668
+ };
1669
+ }
1670
+ function getGitHubToken() {
1671
+ return process.env.GITHUB_TOKEN;
1672
+ }
1673
+ function getIntelligenceSourceConfig() {
1674
+ return {
1675
+ gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
1676
+ certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
1677
+ epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
1678
+ cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
1679
+ depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
1680
+ scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
1681
+ vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1682
+ commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1683
+ commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
1684
+ };
1685
+ }
1686
+
1687
+ // src/intelligence/sources/epss.ts
1688
+ async function fetchEpss(cveId) {
1689
+ const { epssApi } = getIntelligenceSourceConfig();
1690
+ if (!epssApi) return void 0;
1691
+ try {
1692
+ const url = new URL(epssApi);
1693
+ url.searchParams.set("cve", cveId);
1694
+ const res = await httpClient({ url: url.toString() });
1695
+ if (!res.ok) return void 0;
1696
+ const body = res.data;
1697
+ return body.data?.[0];
1698
+ } catch {
1699
+ return void 0;
1700
+ }
1701
+ }
1702
+ async function enrichWithEpss(details) {
1703
+ const row = await fetchEpss(details.id);
1704
+ if (!row) return details;
1705
+ const score = Number.parseFloat(row.epss);
1706
+ const percentile = Number.parseFloat(row.percentile);
1707
+ if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
1708
+ return details;
1709
+ }
1710
+ details.epss = {
1711
+ score,
1712
+ percentile,
1713
+ date: row.date
1714
+ };
1715
+ return details;
1716
+ }
1717
+
1718
+ // src/intelligence/sources/cisa-kev.ts
1719
+ var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
1720
+ async function fetchCisaKevFeed() {
1721
+ try {
1722
+ const res = await httpClient({ url: CISA_KEV_URL });
1723
+ if (!res.ok) return void 0;
1724
+ return res.data;
1725
+ } catch {
1726
+ return void 0;
1727
+ }
1728
+ }
1729
+ function findKevEntry(feed, cveId) {
1730
+ if (!feed?.vulnerabilities?.length) return void 0;
1731
+ const normalized = cveId.toUpperCase();
1732
+ return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
1733
+ }
1734
+ async function enrichWithCisaKev(details) {
1735
+ const feed = await fetchCisaKevFeed();
1736
+ const entry = findKevEntry(feed, details.id);
1737
+ if (!entry) return details;
1738
+ details.kev = {
1739
+ knownExploited: true,
1740
+ dateAdded: entry.dateAdded,
1741
+ dueDate: entry.dueDate,
1742
+ requiredAction: entry.requiredAction,
1743
+ knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
1744
+ };
1745
+ if (!details.references.includes(CISA_KEV_URL)) {
1746
+ details.references.push(CISA_KEV_URL);
1747
+ }
1748
+ return details;
1749
+ }
1750
+
1751
+ // src/intelligence/sources/registry.ts
1752
+ import { readFileSync as readFileSync7 } from "fs";
1753
+ import { join as join9 } from "path";
1754
+ import { execa as execa4 } from "execa";
1755
+ import semver from "semver";
1756
+ var NPM_REGISTRY = "https://registry.npmjs.org";
1757
+ async function fetchPackageVersions(packageName) {
1758
+ const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
1759
+ try {
1760
+ const res = await httpClient({ url });
1761
+ if (res.status === 404) return [];
1762
+ if (!res.ok) {
1763
+ throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
1764
+ }
1765
+ const data = res.data;
1766
+ return Object.keys(data.versions);
1767
+ } catch (err) {
1768
+ if (err instanceof Error && err.message.includes("registry error")) {
1769
+ throw err;
1770
+ }
1771
+ return [];
1772
+ }
1773
+ }
1774
+ async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
1775
+ const resolution = await resolveSafeUpgradeVersion(
1776
+ packageName,
1777
+ installedVersion,
1778
+ firstPatchedVersion,
1779
+ vulnerableRange
1780
+ );
1781
+ return resolution.safeVersion;
1782
+ }
1783
+ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
1784
+ const versions = await fetchPackageVersions(packageName);
1785
+ if (!versions.length) {
1786
+ return {
1787
+ candidates: {},
1788
+ majorOnlyFixAvailable: false
1789
+ };
1790
+ }
1791
+ const installed = semver.parse(installedVersion);
1792
+ const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
1793
+ if (!vulnerableRange) return true;
1794
+ try {
1795
+ return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
1796
+ } catch {
1797
+ return true;
1798
+ }
1799
+ }).sort(semver.compare);
1800
+ if (!candidates.length) {
1801
+ return {
1802
+ candidates: {},
1803
+ majorOnlyFixAvailable: false
1804
+ };
1805
+ }
1806
+ const categorizedCandidates = {};
1807
+ for (const candidate of candidates) {
1808
+ const level = classifyUpgradeLevel(installedVersion, candidate);
1809
+ if (!level) continue;
1810
+ if (!categorizedCandidates[level]) {
1811
+ categorizedCandidates[level] = candidate;
1812
+ }
1813
+ }
1814
+ const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
1815
+ if (!safeVersion) {
1816
+ return {
1817
+ candidates: categorizedCandidates,
1818
+ majorOnlyFixAvailable: false
1819
+ };
1820
+ }
1821
+ const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
1822
+ const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
1823
+ if (!installed || !upgradeLevel) {
1824
+ return {
1825
+ safeVersion,
1826
+ upgradeLevel,
1827
+ candidates: categorizedCandidates,
1828
+ majorOnlyFixAvailable
1829
+ };
1830
+ }
1831
+ return {
1832
+ safeVersion,
1833
+ upgradeLevel,
1834
+ candidates: categorizedCandidates,
1835
+ majorOnlyFixAvailable
1836
+ };
1837
+ }
1838
+ function classifyUpgradeLevel(installedVersion, candidateVersion) {
1839
+ const installed = semver.parse(installedVersion);
1840
+ const candidate = semver.parse(candidateVersion);
1841
+ if (!installed || !candidate) return void 0;
1842
+ if (candidate.major > installed.major) return "major";
1843
+ if (candidate.minor > installed.minor) return "minor";
1844
+ if (candidate.patch > installed.patch || candidate.version === installed.version) {
1845
+ return "patch";
1846
+ }
1847
+ return void 0;
1848
+ }
1849
+ async function queryOutdatedPackages(cwd, options = {}) {
1850
+ const pm = options.packageManager ?? detectPackageManager(cwd);
1851
+ let stdout;
1852
+ try {
1853
+ const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
1854
+ const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
1855
+ const finalArgs = yarnArgs ?? args;
1856
+ const result2 = await execa4(pm, finalArgs, {
1857
+ cwd,
1858
+ reject: false,
1859
+ stdio: "pipe"
1860
+ });
1861
+ if (result2.exitCode !== 0 && result2.exitCode !== 1) {
1862
+ const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
1863
+ if (!result2.stdout) {
1864
+ throw new Error(
1865
+ `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
1866
+ );
1867
+ }
1868
+ }
1869
+ stdout = typeof result2.stdout === "string" ? result2.stdout : "";
1870
+ } catch (err) {
1871
+ if (err instanceof Error && !err.message.includes("outdated --json failed")) {
1872
+ throw new Error(`Failed to query outdated packages: ${err.message}`);
1873
+ }
1874
+ throw err;
1875
+ }
1876
+ if (!stdout.trim()) {
1877
+ return /* @__PURE__ */ new Map();
1878
+ }
1879
+ const rawEntries = {};
1880
+ if (pm === "yarn") {
1881
+ for (const line of stdout.split("\n")) {
1882
+ const trimmed = line.trim();
1883
+ if (!trimmed) continue;
1884
+ try {
1885
+ const parsed = JSON.parse(trimmed);
1886
+ if (parsed.type === "table" && parsed.data) {
1887
+ const tableData = parsed.data;
1888
+ const head = tableData.head ?? [];
1889
+ const currentIdx = head.indexOf("Current");
1890
+ const wantedIdx = head.indexOf("Wanted");
1891
+ const latestIdx = head.indexOf("Latest");
1892
+ for (const row of tableData.body ?? []) {
1893
+ const name = row[0];
1894
+ if (!name) continue;
1895
+ rawEntries[name] = {
1896
+ current: row[currentIdx] ?? "",
1897
+ wanted: row[wantedIdx] ?? "",
1898
+ latest: row[latestIdx] ?? "",
1899
+ type: row[head.indexOf("Package Type")] ?? "dependencies"
1900
+ };
1901
+ }
1902
+ }
1903
+ } catch {
1904
+ }
1905
+ }
1906
+ } else {
1907
+ try {
1908
+ const parsed = JSON.parse(stdout);
1909
+ if (Array.isArray(parsed)) {
1910
+ for (const entry of parsed) {
1911
+ const name = entry.name ?? entry.packageName;
1912
+ if (!name) continue;
1913
+ rawEntries[name] = {
1914
+ current: entry.current,
1915
+ wanted: entry.wanted,
1916
+ latest: entry.latest,
1917
+ type: entry.dependencyType
1918
+ };
1919
+ }
1920
+ } else {
1921
+ Object.assign(rawEntries, parsed);
1922
+ }
1923
+ } catch {
1924
+ throw new Error(`Could not parse ${pm} outdated --json output.`);
1925
+ }
1926
+ }
1927
+ let directDeps;
1928
+ try {
1929
+ const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
1930
+ directDeps = /* @__PURE__ */ new Set([
1931
+ ...Object.keys(pkgRaw.dependencies ?? {}),
1932
+ ...Object.keys(pkgRaw.devDependencies ?? {})
1933
+ ]);
1934
+ } catch {
1935
+ directDeps = /* @__PURE__ */ new Set();
1936
+ }
1937
+ const result = /* @__PURE__ */ new Map();
1938
+ for (const [name, entry] of Object.entries(rawEntries)) {
1939
+ const currentVersion = entry.current ?? "";
1940
+ const wantedVersion = entry.wanted ?? currentVersion;
1941
+ const latestVersion = entry.latest ?? currentVersion;
1942
+ if (!currentVersion || !latestVersion) continue;
1943
+ const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
1944
+ if (!options.includeTransitive && dependencyScope === "transitive") continue;
1945
+ const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
1946
+ result.set(name, {
1947
+ currentVersion,
1948
+ wantedVersion,
1949
+ latestVersion,
1950
+ isMajorBump,
1951
+ dependencyScope
1952
+ });
1953
+ }
1954
+ return result;
1955
+ }
1956
+
1957
+ // src/remediation/evaluate-package.ts
1958
+ async function evaluatePackage(packageName, options = {}) {
1959
+ const version = options.version?.trim();
1960
+ const enrich = options.enrichIntelligence !== false;
1961
+ let versions = [];
1962
+ let latestVersion;
1963
+ try {
1964
+ versions = await fetchPackageVersions(packageName);
1965
+ if (versions.length > 0) {
1966
+ const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
1967
+ latestVersion = valid[valid.length - 1];
1968
+ }
1969
+ } catch {
1970
+ }
1971
+ let rawVulns = [];
1972
+ try {
1973
+ rawVulns = await queryOsvPackage(packageName, version);
1974
+ } catch {
1975
+ rawVulns = [];
1976
+ }
1977
+ let kevFeed = null;
1978
+ if (enrich) {
1979
+ try {
1980
+ kevFeed = await fetchCisaKevFeed();
1981
+ } catch {
1982
+ kevFeed = null;
1983
+ }
1984
+ }
1985
+ const entries = [];
1986
+ for (const raw of rawVulns) {
1987
+ const cveDetails = parseOsvVuln(raw);
1988
+ const affected = cveDetails.affectedPackages.find(
1989
+ (p) => p.name.toLowerCase() === packageName.toLowerCase()
1990
+ );
1991
+ if (version && affected?.vulnerableRange) {
1992
+ const cleanVer = semver2.clean(version) || version;
1993
+ if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
1994
+ continue;
1995
+ }
1996
+ }
1997
+ let inCisaKev = false;
1998
+ let epssScore;
1999
+ let epssPercentile;
2000
+ if (enrich && cveDetails.id.startsWith("CVE-")) {
2001
+ if (kevFeed) {
2002
+ inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
2003
+ }
2004
+ try {
2005
+ const epss = await fetchEpss(cveDetails.id);
2006
+ if (epss) {
2007
+ epssScore = Number.parseFloat(epss.epss);
2008
+ epssPercentile = Number.parseFloat(epss.percentile);
2009
+ }
2010
+ } catch {
2011
+ }
2012
+ }
2013
+ let safeUpgradeVersion;
2014
+ if (affected?.firstPatchedVersion) {
2015
+ try {
2016
+ const safe = await findSafeUpgradeVersion(
2017
+ packageName,
2018
+ version || "0.0.0",
2019
+ affected.firstPatchedVersion
2020
+ );
2021
+ safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
2022
+ } catch {
2023
+ safeUpgradeVersion = affected.firstPatchedVersion;
2024
+ }
2025
+ }
2026
+ entries.push({
2027
+ cveId: cveDetails.id,
2028
+ severity: cveDetails.severity,
2029
+ summary: cveDetails.summary,
2030
+ vulnerableRange: affected?.vulnerableRange,
2031
+ firstPatchedVersion: affected?.firstPatchedVersion,
2032
+ safeUpgradeVersion,
2033
+ epssScore,
2034
+ epssPercentile,
2035
+ inCisaKev,
2036
+ references: cveDetails.references
2037
+ });
2038
+ }
2039
+ const isVulnerable = entries.length > 0;
2040
+ let verdict = "safe";
2041
+ if (isVulnerable) {
2042
+ const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
2043
+ const hasHighOrCritical = entries.some(
2044
+ (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
2045
+ );
2046
+ if (hasKevOrHighEpss) {
2047
+ verdict = "actively-exploited";
2048
+ } else if (hasHighOrCritical) {
2049
+ verdict = "vulnerable";
2050
+ } else {
2051
+ verdict = "caution";
2052
+ }
2053
+ }
2054
+ let recommendedVersion;
2055
+ if (isVulnerable) {
2056
+ const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
2057
+ recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
2058
+ } else {
2059
+ recommendedVersion = version || latestVersion;
2060
+ }
2061
+ const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
2062
+ return {
2063
+ schemaVersion: "1.0",
2064
+ packageName,
2065
+ evaluatedVersion: version,
2066
+ isVulnerable,
2067
+ verdict,
2068
+ summary,
2069
+ vulnerabilities: entries,
2070
+ recommendedVersion,
2071
+ latestVersion,
2072
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2073
+ };
2074
+ }
2075
+
2076
+ // src/api/patches/reconcile.ts
2077
+ import { existsSync as existsSync8 } from "fs";
2078
+ import { readFile as readFile3, rm, writeFile } from "fs/promises";
2079
+ import { join as join10 } from "path";
2080
+ import { execa as execa5 } from "execa";
2081
+ async function reconcilePatchArtifacts(options = {}) {
2082
+ const cwd = options.cwd ?? process.cwd();
2083
+ const pm = options.packageManager ?? detectPackageManager(cwd);
2084
+ const summaries = await listPatchArtifacts(options);
2085
+ const results = [];
2086
+ for (const summary of summaries) {
2087
+ const inspection = await inspectPatchArtifact(summary.patchFilePath, options);
2088
+ let packageName = inspection.packageName;
2089
+ let vulnerableVersion = inspection.vulnerableVersion;
2090
+ const cveId = inspection.cveId;
2091
+ if (!packageName && summary.patchFileName) {
2092
+ const basename = summary.patchFileName.replace(/\.patch$/, "");
2093
+ const plusIdx = basename.lastIndexOf("+");
2094
+ if (plusIdx > 0) {
2095
+ let rawPkg = basename.slice(0, plusIdx);
2096
+ vulnerableVersion = basename.slice(plusIdx + 1);
2097
+ if (rawPkg.includes("+")) {
2098
+ rawPkg = "@" + rawPkg.replace("+", "/");
2099
+ }
2100
+ packageName = rawPkg;
2101
+ } else {
2102
+ packageName = basename;
2103
+ }
2104
+ }
2105
+ if (!packageName) {
2106
+ results.push({
2107
+ patchFilePath: summary.patchFilePath,
2108
+ packageName: "unknown",
2109
+ cveId,
2110
+ status: "patch-retained",
2111
+ reason: "Cannot determine package name from patch artifact."
2112
+ });
2113
+ continue;
2114
+ }
2115
+ let safeUpgradeVersion;
2116
+ try {
2117
+ const evaluation = await evaluatePackage(packageName, {
2118
+ version: vulnerableVersion,
2119
+ enrichIntelligence: false
2120
+ });
2121
+ if (cveId) {
2122
+ const vuln = evaluation.vulnerabilities.find(
2123
+ (v) => v.cveId.toLowerCase() === cveId.toLowerCase()
2124
+ );
2125
+ if (vuln?.safeUpgradeVersion) {
2126
+ safeUpgradeVersion = vuln.safeUpgradeVersion;
2127
+ }
2128
+ } else {
2129
+ safeUpgradeVersion = evaluation.vulnerabilities.find(
2130
+ (v) => v.safeUpgradeVersion
2131
+ )?.safeUpgradeVersion;
2132
+ }
2133
+ } catch {
2134
+ }
2135
+ if (!safeUpgradeVersion) {
2136
+ results.push({
2137
+ patchFilePath: summary.patchFilePath,
2138
+ packageName,
2139
+ cveId,
2140
+ installedVersion: vulnerableVersion,
2141
+ status: "patch-retained",
2142
+ reason: "No safe upstream release exists yet; patch remains necessary."
2143
+ });
2144
+ continue;
2145
+ }
2146
+ if (!options.autoApply) {
2147
+ results.push({
2148
+ patchFilePath: summary.patchFilePath,
2149
+ packageName,
2150
+ cveId,
2151
+ installedVersion: vulnerableVersion,
2152
+ safeUpstreamVersion: safeUpgradeVersion,
2153
+ status: "safe-version-available",
2154
+ reason: `Safe upstream version ${safeUpgradeVersion} is available. Re-run with autoApply: true or --auto-apply to upgrade.`
2155
+ });
2156
+ continue;
2157
+ }
2158
+ try {
2159
+ const pkgJsonPath = join10(cwd, "package.json");
2160
+ if (existsSync8(pkgJsonPath)) {
2161
+ const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
2162
+ let updated = false;
2163
+ const depSections = [
2164
+ "dependencies",
2165
+ "devDependencies",
2166
+ "peerDependencies",
2167
+ "optionalDependencies"
2168
+ ];
2169
+ for (const depSection of depSections) {
2170
+ if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
2171
+ pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
2172
+ updated = true;
2173
+ }
2174
+ }
2175
+ if (pkgJson.pnpm?.patchedDependencies) {
2176
+ for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
2177
+ if (key.startsWith(`${packageName}@`)) {
2178
+ delete pkgJson.pnpm.patchedDependencies[key];
2179
+ updated = true;
2180
+ }
2181
+ }
2182
+ }
2183
+ if (updated) {
2184
+ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2185
+ }
2186
+ }
2187
+ await rm(summary.patchFilePath, { force: true });
2188
+ if (inspection.manifestFilePath) {
2189
+ await rm(inspection.manifestFilePath, { force: true });
2190
+ }
2191
+ const installCmd = resolveInstallCommand(pm);
2192
+ await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
2193
+ if (options.runTests) {
2194
+ const testCmd = resolveTestCommand(pm);
2195
+ await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
2196
+ }
2197
+ results.push({
2198
+ patchFilePath: summary.patchFilePath,
2199
+ packageName,
2200
+ cveId,
2201
+ installedVersion: vulnerableVersion,
2202
+ safeUpstreamVersion: safeUpgradeVersion,
2203
+ status: "upgraded",
2204
+ reason: `Successfully upgraded to ${safeUpgradeVersion} and retired patch artifact.`
2205
+ });
2206
+ } catch (err) {
2207
+ results.push({
2208
+ patchFilePath: summary.patchFilePath,
2209
+ packageName,
2210
+ cveId,
2211
+ installedVersion: vulnerableVersion,
2212
+ safeUpstreamVersion: safeUpgradeVersion,
2213
+ status: "error",
2214
+ reason: err instanceof Error ? err.message : String(err)
2215
+ });
2216
+ }
2217
+ }
2218
+ const reconciledCount = results.filter((r) => r.status === "upgraded").length;
2219
+ const retainedCount = results.filter(
2220
+ (r) => r.status === "patch-retained" || r.status === "safe-version-available"
2221
+ ).length;
2222
+ return {
2223
+ reconciled: results,
2224
+ reconciledCount,
2225
+ retainedCount
2226
+ };
2227
+ }
2228
+
2229
+ // src/platform/idempotency.ts
2230
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
2231
+ import { join as join11 } from "path";
2232
+ var DEFAULT_INDEX = {
2233
+ schemaVersion: "1.0",
2234
+ entries: {}
2235
+ };
2236
+ function indexFilePath(cwd) {
2237
+ return join11(cwd, ".autoremediator", "state", "idempotency.json");
2238
+ }
2239
+ function entryKey(idempotencyKey, cveId) {
2240
+ return `${idempotencyKey}::${cveId.toUpperCase()}`;
2241
+ }
2242
+ function loadIndex(cwd) {
2243
+ const filePath = indexFilePath(cwd);
2244
+ if (!existsSync9(filePath)) return DEFAULT_INDEX;
2245
+ try {
2246
+ const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
2247
+ if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
2248
+ return parsed;
2249
+ }
2250
+ return DEFAULT_INDEX;
2251
+ } catch {
2252
+ return DEFAULT_INDEX;
2253
+ }
2254
+ }
2255
+ function saveIndex(cwd, index) {
2256
+ const filePath = indexFilePath(cwd);
2257
+ mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
2258
+ writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2259
+ }
2260
+ function readIdempotentReport(cwd, idempotencyKey, cveId) {
2261
+ const index = loadIndex(cwd);
2262
+ const key = entryKey(idempotencyKey, cveId);
2263
+ return index.entries[key]?.report;
2264
+ }
2265
+ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
2266
+ const index = loadIndex(cwd);
2267
+ const key = entryKey(idempotencyKey, cveId);
2268
+ index.entries[key] = {
2269
+ key: idempotencyKey,
2270
+ cveId: cveId.toUpperCase(),
2271
+ report,
2272
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
2273
+ };
2274
+ saveIndex(cwd, index);
2275
+ }
2276
+
2277
+ // src/remediation/pipeline.ts
2278
+ import { generateText as generateText2 } from "ai";
2279
+
2280
+ // src/remediation/local/run.ts
2281
+ import semver6 from "semver";
2282
+
2283
+ // src/intelligence/sources/github-advisory.ts
2284
+ var GITHUB_API_BASE = "https://api.github.com/advisories";
2285
+ async function fetchGhAdvisories(cveId) {
2286
+ const token = getGitHubToken();
2287
+ const headers = {
2288
+ "X-GitHub-Api-Version": "2022-11-28",
2289
+ "User-Agent": "autoremediator"
2290
+ };
2291
+ if (token) {
2292
+ headers["Authorization"] = `token ${token}`;
2293
+ }
2294
+ const normalized = cveId.trim();
2295
+ if (normalized.toUpperCase().startsWith("GHSA-")) {
2296
+ const url2 = `${GITHUB_API_BASE}/${encodeURIComponent(normalized)}`;
2297
+ try {
2298
+ const res = await httpClient({ url: url2, headers });
2299
+ if (!res.ok) {
2300
+ if (res.status === 404) return [];
2301
+ throw new Error(`HTTP ${res.status}: ${res.text}`);
2302
+ }
2303
+ return res.data ? [res.data] : [];
2304
+ } catch (err) {
2305
+ const errorMsg = err instanceof Error ? err.message : String(err);
2306
+ console.warn(
2307
+ `[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
2308
+ );
2309
+ return [];
2310
+ }
1565
2311
  }
1566
2312
  const url = new URL(GITHUB_API_BASE);
1567
2313
  url.searchParams.set("cve_id", cveId);
@@ -1671,9 +2417,9 @@ async function enrichWithNvd(details) {
1671
2417
  }
1672
2418
 
1673
2419
  // src/intelligence/sources/snapshot.ts
1674
- import { readFileSync as readFileSync6 } from "fs";
2420
+ import { readFileSync as readFileSync9 } from "fs";
1675
2421
  function loadIntelligenceSnapshot(filePath) {
1676
- const raw = readFileSync6(filePath, "utf8");
2422
+ const raw = readFileSync9(filePath, "utf8");
1677
2423
  const parsed = JSON.parse(raw);
1678
2424
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1679
2425
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -1685,8 +2431,8 @@ function lookupSnapshotCve(snapshot, cveId) {
1685
2431
  }
1686
2432
 
1687
2433
  // src/remediation/tools/check-reachability.ts
1688
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync } from "fs";
1689
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "path";
2434
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
2435
+ import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
1690
2436
  import { parseSync } from "oxc-parser";
1691
2437
  import { z as z2 } from "zod";
1692
2438
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
@@ -1702,16 +2448,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
1702
2448
  ]);
1703
2449
  function loadTsconfigPaths(cwd) {
1704
2450
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
1705
- const fullPath = join9(cwd, configName);
1706
- if (!existsSync8(fullPath)) continue;
2451
+ const fullPath = join12(cwd, configName);
2452
+ if (!existsSync10(fullPath)) continue;
1707
2453
  try {
1708
- const raw = readFileSync7(fullPath, "utf8");
2454
+ const raw = readFileSync10(fullPath, "utf8");
1709
2455
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
1710
2456
  const parsed = JSON.parse(cleaned);
1711
2457
  const compilerOptions = parsed.compilerOptions;
1712
2458
  if (compilerOptions?.paths) {
1713
2459
  return {
1714
- baseUrl: compilerOptions.baseUrl ? resolve2(cwd, compilerOptions.baseUrl) : cwd,
2460
+ baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
1715
2461
  paths: compilerOptions.paths
1716
2462
  };
1717
2463
  }
@@ -1726,14 +2472,14 @@ function resolveAliasedPath(importSource, config) {
1726
2472
  }
1727
2473
  for (const [pattern, replacements] of Object.entries(config.paths)) {
1728
2474
  if (pattern === importSource && replacements[0]) {
1729
- return resolve2(config.baseUrl, replacements[0]);
2475
+ return resolve3(config.baseUrl, replacements[0]);
1730
2476
  }
1731
2477
  if (pattern.endsWith("/*")) {
1732
2478
  const prefix = pattern.slice(0, -2);
1733
2479
  if (importSource.startsWith(prefix)) {
1734
2480
  const suffix = importSource.slice(prefix.length);
1735
2481
  const replacement = replacements[0]?.slice(0, -2) ?? "";
1736
- return resolve2(config.baseUrl, replacement + suffix);
2482
+ return resolve3(config.baseUrl, replacement + suffix);
1737
2483
  }
1738
2484
  }
1739
2485
  }
@@ -1742,16 +2488,16 @@ function resolveAliasedPath(importSource, config) {
1742
2488
  function* walkSourceFiles(dir) {
1743
2489
  let entries;
1744
2490
  try {
1745
- entries = readdirSync(dir);
2491
+ entries = readdirSync2(dir);
1746
2492
  } catch {
1747
2493
  return;
1748
2494
  }
1749
2495
  for (const entry of entries) {
1750
2496
  if (IGNORED_DIRS.has(entry)) continue;
1751
- const full = join9(dir, entry);
2497
+ const full = join12(dir, entry);
1752
2498
  let stat2;
1753
2499
  try {
1754
- stat2 = statSync(full);
2500
+ stat2 = statSync2(full);
1755
2501
  } catch {
1756
2502
  continue;
1757
2503
  }
@@ -1789,7 +2535,31 @@ function walkAst(node, visitor) {
1789
2535
  }
1790
2536
  }
1791
2537
  }
1792
- function extractPackageReferences(filename, content, packageName, tsconfig) {
2538
+ function toRootPackageName(importSource) {
2539
+ if (importSource.startsWith("@")) {
2540
+ const parts = importSource.split("/");
2541
+ return parts.slice(0, 2).join("/");
2542
+ }
2543
+ return importSource.split("/")[0] ?? importSource;
2544
+ }
2545
+ function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
2546
+ for (const directPkg of importedDirectPackages) {
2547
+ const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
2548
+ if (!existsSync10(manifestPath)) continue;
2549
+ try {
2550
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
2551
+ if (manifest.dependencies && manifest.dependencies[targetPackage]) {
2552
+ return {
2553
+ directDependency: directPkg,
2554
+ version: manifest.dependencies[targetPackage]
2555
+ };
2556
+ }
2557
+ } catch {
2558
+ }
2559
+ }
2560
+ return null;
2561
+ }
2562
+ function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
1793
2563
  const results = [];
1794
2564
  let parseResult;
1795
2565
  try {
@@ -1801,6 +2571,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1801
2571
  walkAst(parseResult.program, (node) => {
1802
2572
  if (node.type === "ImportDeclaration") {
1803
2573
  const source = node.source;
2574
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2575
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2576
+ }
1804
2577
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1805
2578
  const symbols = [];
1806
2579
  const importedIdentifiers = /* @__PURE__ */ new Map();
@@ -1835,6 +2608,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1835
2608
  }
1836
2609
  if (node.type === "ImportExpression") {
1837
2610
  const source = node.source;
2611
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2612
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2613
+ }
1838
2614
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1839
2615
  results.push({
1840
2616
  matchType: "dynamic-import",
@@ -1846,6 +2622,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1846
2622
  }
1847
2623
  if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
1848
2624
  const firstArg = node.arguments[0];
2625
+ if (firstArg?.value && typeof firstArg.value === "string" && !firstArg.value.startsWith(".") && !isAbsolute2(firstArg.value)) {
2626
+ outImportedDirectPackages?.add(toRootPackageName(firstArg.value));
2627
+ }
1849
2628
  if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
1850
2629
  results.push({
1851
2630
  matchType: "require",
@@ -1857,6 +2636,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1857
2636
  }
1858
2637
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
1859
2638
  const source = node.source;
2639
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2640
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2641
+ }
1860
2642
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1861
2643
  const symbols = [];
1862
2644
  if (node.type === "ExportAllDeclaration") {
@@ -1933,15 +2715,22 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
1933
2715
  let hasReExport = false;
1934
2716
  let totalInvocations = 0;
1935
2717
  let hasPackageImport = false;
2718
+ const importedDirectPackages = /* @__PURE__ */ new Set();
1936
2719
  for (const filePath of walkSourceFiles(cwd)) {
1937
2720
  scanned++;
1938
2721
  let content;
1939
2722
  try {
1940
- content = readFileSync7(filePath, "utf8");
2723
+ content = readFileSync10(filePath, "utf8");
1941
2724
  } catch {
1942
2725
  continue;
1943
2726
  }
1944
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
2727
+ const refs = extractPackageReferences(
2728
+ filePath,
2729
+ content,
2730
+ packageName,
2731
+ tsconfig,
2732
+ importedDirectPackages
2733
+ );
1945
2734
  if (refs.length > 0) {
1946
2735
  hasPackageImport = true;
1947
2736
  }
@@ -1984,6 +2773,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
1984
2773
  };
1985
2774
  }
1986
2775
  if (!hasPackageImport) {
2776
+ const transitivePath = findTransitiveDependencyPath(cwd, packageName, importedDirectPackages);
2777
+ if (transitivePath) {
2778
+ return {
2779
+ packageName,
2780
+ status: "reachable",
2781
+ reason: `Package '${packageName}' is a transitive dependency of imported package '${transitivePath.directDependency}'`,
2782
+ reachabilityBasis: "transitive-path-found",
2783
+ evidence: []
2784
+ };
2785
+ }
1987
2786
  return {
1988
2787
  packageName,
1989
2788
  status: "not-reachable",
@@ -2128,254 +2927,48 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
2128
2927
  opts.slaPolicy
2129
2928
  );
2130
2929
  if (breach) {
2131
- slaBreaches = [breach];
2132
- }
2133
- }
2134
- return { suppressed: false, exploitSignalTriggered, slaBreaches };
2135
- }
2136
-
2137
- // src/remediation/local/sbom.ts
2138
- function buildSbom(packages, vulnerableNames, results) {
2139
- const statusByPackage = /* @__PURE__ */ new Map();
2140
- for (const result of results) {
2141
- if (!vulnerableNames.has(result.packageName)) continue;
2142
- if (result.suppressedBy) {
2143
- statusByPackage.set(result.packageName, "suppressed");
2144
- } else if (!result.applied && result.strategy === "none") {
2145
- statusByPackage.set(result.packageName, "skipped");
2146
- } else if (result.applied) {
2147
- statusByPackage.set(result.packageName, "patched");
2148
- } else {
2149
- statusByPackage.set(result.packageName, "unpatched");
2150
- }
2151
- }
2152
- return packages.map((pkg) => {
2153
- const isVulnerable = vulnerableNames.has(pkg.name);
2154
- const entry = {
2155
- name: pkg.name,
2156
- version: pkg.version,
2157
- scope: pkg.type === "direct" ? "direct" : "transitive"
2158
- };
2159
- if (isVulnerable) {
2160
- entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2161
- }
2162
- return entry;
2163
- });
2164
- }
2165
-
2166
- // src/intelligence/sources/registry.ts
2167
- import { readFileSync as readFileSync8 } from "fs";
2168
- import { join as join10 } from "path";
2169
- import { execa as execa4 } from "execa";
2170
- import semver from "semver";
2171
- var NPM_REGISTRY = "https://registry.npmjs.org";
2172
- async function fetchPackageVersions(packageName) {
2173
- const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2174
- try {
2175
- const res = await httpClient({ url });
2176
- if (res.status === 404) return [];
2177
- if (!res.ok) {
2178
- throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2179
- }
2180
- const data = res.data;
2181
- return Object.keys(data.versions);
2182
- } catch (err) {
2183
- if (err instanceof Error && err.message.includes("registry error")) {
2184
- throw err;
2185
- }
2186
- return [];
2187
- }
2188
- }
2189
- async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2190
- const resolution = await resolveSafeUpgradeVersion(
2191
- packageName,
2192
- installedVersion,
2193
- firstPatchedVersion,
2194
- vulnerableRange
2195
- );
2196
- return resolution.safeVersion;
2197
- }
2198
- async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2199
- const versions = await fetchPackageVersions(packageName);
2200
- if (!versions.length) {
2201
- return {
2202
- candidates: {},
2203
- majorOnlyFixAvailable: false
2204
- };
2205
- }
2206
- const installed = semver.parse(installedVersion);
2207
- const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2208
- if (!vulnerableRange) return true;
2209
- try {
2210
- return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2211
- } catch {
2212
- return true;
2213
- }
2214
- }).sort(semver.compare);
2215
- if (!candidates.length) {
2216
- return {
2217
- candidates: {},
2218
- majorOnlyFixAvailable: false
2219
- };
2220
- }
2221
- const categorizedCandidates = {};
2222
- for (const candidate of candidates) {
2223
- const level = classifyUpgradeLevel(installedVersion, candidate);
2224
- if (!level) continue;
2225
- if (!categorizedCandidates[level]) {
2226
- categorizedCandidates[level] = candidate;
2227
- }
2228
- }
2229
- const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2230
- if (!safeVersion) {
2231
- return {
2232
- candidates: categorizedCandidates,
2233
- majorOnlyFixAvailable: false
2234
- };
2235
- }
2236
- const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2237
- const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2238
- if (!installed || !upgradeLevel) {
2239
- return {
2240
- safeVersion,
2241
- upgradeLevel,
2242
- candidates: categorizedCandidates,
2243
- majorOnlyFixAvailable
2244
- };
2245
- }
2246
- return {
2247
- safeVersion,
2248
- upgradeLevel,
2249
- candidates: categorizedCandidates,
2250
- majorOnlyFixAvailable
2251
- };
2252
- }
2253
- function classifyUpgradeLevel(installedVersion, candidateVersion) {
2254
- const installed = semver.parse(installedVersion);
2255
- const candidate = semver.parse(candidateVersion);
2256
- if (!installed || !candidate) return void 0;
2257
- if (candidate.major > installed.major) return "major";
2258
- if (candidate.minor > installed.minor) return "minor";
2259
- if (candidate.patch > installed.patch || candidate.version === installed.version) {
2260
- return "patch";
2261
- }
2262
- return void 0;
2263
- }
2264
- async function queryOutdatedPackages(cwd, options = {}) {
2265
- const pm = options.packageManager ?? detectPackageManager(cwd);
2266
- let stdout;
2267
- try {
2268
- const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2269
- const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2270
- const finalArgs = yarnArgs ?? args;
2271
- const result2 = await execa4(pm, finalArgs, {
2272
- cwd,
2273
- reject: false,
2274
- stdio: "pipe"
2275
- });
2276
- if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2277
- const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2278
- if (!result2.stdout) {
2279
- throw new Error(
2280
- `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2281
- );
2282
- }
2283
- }
2284
- stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2285
- } catch (err) {
2286
- if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2287
- throw new Error(`Failed to query outdated packages: ${err.message}`);
2288
- }
2289
- throw err;
2290
- }
2291
- if (!stdout.trim()) {
2292
- return /* @__PURE__ */ new Map();
2293
- }
2294
- const rawEntries = {};
2295
- if (pm === "yarn") {
2296
- for (const line of stdout.split("\n")) {
2297
- const trimmed = line.trim();
2298
- if (!trimmed) continue;
2299
- try {
2300
- const parsed = JSON.parse(trimmed);
2301
- if (parsed.type === "table" && parsed.data) {
2302
- const tableData = parsed.data;
2303
- const head = tableData.head ?? [];
2304
- const currentIdx = head.indexOf("Current");
2305
- const wantedIdx = head.indexOf("Wanted");
2306
- const latestIdx = head.indexOf("Latest");
2307
- for (const row of tableData.body ?? []) {
2308
- const name = row[0];
2309
- if (!name) continue;
2310
- rawEntries[name] = {
2311
- current: row[currentIdx] ?? "",
2312
- wanted: row[wantedIdx] ?? "",
2313
- latest: row[latestIdx] ?? "",
2314
- type: row[head.indexOf("Package Type")] ?? "dependencies"
2315
- };
2316
- }
2317
- }
2318
- } catch {
2319
- }
2320
- }
2321
- } else {
2322
- try {
2323
- const parsed = JSON.parse(stdout);
2324
- if (Array.isArray(parsed)) {
2325
- for (const entry of parsed) {
2326
- const name = entry.name ?? entry.packageName;
2327
- if (!name) continue;
2328
- rawEntries[name] = {
2329
- current: entry.current,
2330
- wanted: entry.wanted,
2331
- latest: entry.latest,
2332
- type: entry.dependencyType
2333
- };
2334
- }
2335
- } else {
2336
- Object.assign(rawEntries, parsed);
2337
- }
2338
- } catch {
2339
- throw new Error(`Could not parse ${pm} outdated --json output.`);
2930
+ slaBreaches = [breach];
2340
2931
  }
2341
2932
  }
2342
- let directDeps;
2343
- try {
2344
- const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
2345
- directDeps = /* @__PURE__ */ new Set([
2346
- ...Object.keys(pkgRaw.dependencies ?? {}),
2347
- ...Object.keys(pkgRaw.devDependencies ?? {})
2348
- ]);
2349
- } catch {
2350
- directDeps = /* @__PURE__ */ new Set();
2351
- }
2352
- const result = /* @__PURE__ */ new Map();
2353
- for (const [name, entry] of Object.entries(rawEntries)) {
2354
- const currentVersion = entry.current ?? "";
2355
- const wantedVersion = entry.wanted ?? currentVersion;
2356
- const latestVersion = entry.latest ?? currentVersion;
2357
- if (!currentVersion || !latestVersion) continue;
2358
- const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2359
- if (!options.includeTransitive && dependencyScope === "transitive") continue;
2360
- const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2361
- result.set(name, {
2362
- currentVersion,
2363
- wantedVersion,
2364
- latestVersion,
2365
- isMajorBump,
2366
- dependencyScope
2367
- });
2933
+ return { suppressed: false, exploitSignalTriggered, slaBreaches };
2934
+ }
2935
+
2936
+ // src/remediation/local/sbom.ts
2937
+ function buildSbom(packages, vulnerableNames, results) {
2938
+ const statusByPackage = /* @__PURE__ */ new Map();
2939
+ for (const result of results) {
2940
+ if (!vulnerableNames.has(result.packageName)) continue;
2941
+ if (result.suppressedBy) {
2942
+ statusByPackage.set(result.packageName, "suppressed");
2943
+ } else if (!result.applied && result.strategy === "none") {
2944
+ statusByPackage.set(result.packageName, "skipped");
2945
+ } else if (result.applied) {
2946
+ statusByPackage.set(result.packageName, "patched");
2947
+ } else {
2948
+ statusByPackage.set(result.packageName, "unpatched");
2949
+ }
2368
2950
  }
2369
- return result;
2951
+ return packages.map((pkg) => {
2952
+ const isVulnerable = vulnerableNames.has(pkg.name);
2953
+ const entry = {
2954
+ name: pkg.name,
2955
+ version: pkg.version,
2956
+ scope: pkg.type === "direct" ? "direct" : "transitive"
2957
+ };
2958
+ if (isVulnerable) {
2959
+ entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2960
+ }
2961
+ return entry;
2962
+ });
2370
2963
  }
2371
2964
 
2372
2965
  // src/remediation/tools/apply-version-bump.ts
2373
2966
  import { z as z4 } from "zod";
2374
- import { join as join12 } from "path";
2375
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
2967
+ import { join as join15 } from "path";
2968
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
2376
2969
 
2377
2970
  // src/platform/safe-exec.ts
2378
- import { execa as execa5 } from "execa";
2971
+ import { execa as execa6 } from "execa";
2379
2972
  var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2380
2973
  "NODE_OPTIONS",
2381
2974
  "NODE_PATH",
@@ -2424,7 +3017,7 @@ function safeExeca(file, args = [], options = {}) {
2424
3017
  options.env ?? process.env,
2425
3018
  options.allowNodeOptions
2426
3019
  );
2427
- return execa5(file, args, {
3020
+ return execa6(file, args, {
2428
3021
  ...options,
2429
3022
  timeout,
2430
3023
  env: sanitizedEnv
@@ -2432,19 +3025,19 @@ function safeExeca(file, args = [], options = {}) {
2432
3025
  }
2433
3026
 
2434
3027
  // src/remediation/tools/apply-version-bump.ts
2435
- import semver2 from "semver";
3028
+ import semver3 from "semver";
2436
3029
 
2437
3030
  // src/platform/repo-lock.ts
2438
- import { mkdir, rm } from "fs/promises";
2439
- import { join as join11 } from "path";
3031
+ import { mkdir, rm as rm2 } from "fs/promises";
3032
+ import { join as join13 } from "path";
2440
3033
  async function sleep(ms) {
2441
- await new Promise((resolve4) => setTimeout(resolve4, ms));
3034
+ await new Promise((resolve6) => setTimeout(resolve6, ms));
2442
3035
  }
2443
3036
  async function acquireRepoLock(cwd, options = {}) {
2444
3037
  const timeoutMs = options.timeoutMs ?? 15e3;
2445
3038
  const retryDelayMs = options.retryDelayMs ?? 125;
2446
- const lockRoot = join11(cwd, ".autoremediator", "locks");
2447
- const lockPath = join11(cwd, ".autoremediator", "locks", "remediation.lock");
3039
+ const lockRoot = join13(cwd, ".autoremediator", "locks");
3040
+ const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
2448
3041
  const startedAt = Date.now();
2449
3042
  await mkdir(lockRoot, { recursive: true });
2450
3043
  while (true) {
@@ -2453,7 +3046,7 @@ async function acquireRepoLock(cwd, options = {}) {
2453
3046
  return {
2454
3047
  lockPath,
2455
3048
  release: async () => {
2456
- await rm(lockPath, { recursive: true, force: true });
3049
+ await rm2(lockPath, { recursive: true, force: true });
2457
3050
  }
2458
3051
  };
2459
3052
  } catch {
@@ -2473,6 +3066,380 @@ async function withRepoLock(cwd, fn, options) {
2473
3066
  }
2474
3067
  }
2475
3068
 
3069
+ // src/remediation/refactor/call-site-finder.ts
3070
+ import { readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
3071
+ import { join as join14 } from "path";
3072
+ import { parseSync as parseSync2 } from "oxc-parser";
3073
+ var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
3074
+ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
3075
+ "node_modules",
3076
+ ".git",
3077
+ "dist",
3078
+ "build",
3079
+ "coverage",
3080
+ ".next",
3081
+ ".turbo",
3082
+ ".pnpm-store"
3083
+ ]);
3084
+ function* walkFiles(dir) {
3085
+ let entries;
3086
+ try {
3087
+ entries = readdirSync3(dir);
3088
+ } catch {
3089
+ return;
3090
+ }
3091
+ for (const entry of entries) {
3092
+ if (IGNORED_DIRS2.has(entry)) continue;
3093
+ const full = join14(dir, entry);
3094
+ let stat2;
3095
+ try {
3096
+ stat2 = statSync3(full);
3097
+ } catch {
3098
+ continue;
3099
+ }
3100
+ if (stat2.isDirectory()) {
3101
+ yield* walkFiles(full);
3102
+ } else {
3103
+ const ext = full.slice(full.lastIndexOf("."));
3104
+ if (SOURCE_EXTENSIONS2.has(ext)) {
3105
+ yield full;
3106
+ }
3107
+ }
3108
+ }
3109
+ }
3110
+ function findPackageCallSites(cwd, packageName) {
3111
+ const callSites = [];
3112
+ for (const filePath of walkFiles(cwd)) {
3113
+ let content;
3114
+ try {
3115
+ content = readFileSync11(filePath, "utf8");
3116
+ } catch {
3117
+ continue;
3118
+ }
3119
+ if (!content.includes(packageName)) {
3120
+ continue;
3121
+ }
3122
+ try {
3123
+ const parseResult = parseSync2(filePath, content);
3124
+ if (!parseResult.program) continue;
3125
+ const program = parseResult.program;
3126
+ const imports = [];
3127
+ const importSources = [];
3128
+ const localNames = /* @__PURE__ */ new Set();
3129
+ for (const statement of program.body) {
3130
+ if (statement.type === "ImportDeclaration" && statement.source?.value) {
3131
+ const importSource = statement.source.value;
3132
+ if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
3133
+ importSources.push(importSource);
3134
+ for (const spec of statement.specifiers || []) {
3135
+ const localName = spec.local?.name || "";
3136
+ if (!localName) continue;
3137
+ localNames.add(localName);
3138
+ if (spec.type === "ImportDefaultSpecifier") {
3139
+ imports.push({
3140
+ localName,
3141
+ importedName: "default",
3142
+ isDefault: true,
3143
+ isNamespace: false
3144
+ });
3145
+ } else if (spec.type === "ImportNamespaceSpecifier") {
3146
+ imports.push({
3147
+ localName,
3148
+ importedName: "*",
3149
+ isDefault: false,
3150
+ isNamespace: true
3151
+ });
3152
+ } else if (spec.type === "ImportSpecifier") {
3153
+ imports.push({
3154
+ localName,
3155
+ importedName: spec.imported?.name || localName,
3156
+ isDefault: false,
3157
+ isNamespace: false
3158
+ });
3159
+ }
3160
+ }
3161
+ }
3162
+ }
3163
+ }
3164
+ if (imports.length > 0 || importSources.length > 0) {
3165
+ const lines = content.split("\n");
3166
+ const referencedLines = [];
3167
+ lines.forEach((line, index) => {
3168
+ for (const local of localNames) {
3169
+ const regex = new RegExp(`\\b${local}\\b`);
3170
+ if (regex.test(line)) {
3171
+ referencedLines.push(index + 1);
3172
+ break;
3173
+ }
3174
+ }
3175
+ });
3176
+ callSites.push({
3177
+ filePath,
3178
+ sourceContent: content,
3179
+ importSources,
3180
+ imports,
3181
+ referencedLines
3182
+ });
3183
+ }
3184
+ } catch {
3185
+ }
3186
+ }
3187
+ return callSites;
3188
+ }
3189
+
3190
+ // src/remediation/refactor/refactor-agent.ts
3191
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
3192
+ function parseCompilerDiagnostics(diagnosticError) {
3193
+ const diagnostics = [];
3194
+ const lines = diagnosticError.split("\n");
3195
+ for (const line of lines) {
3196
+ const trimmed = line.trim();
3197
+ if (!trimmed) continue;
3198
+ const match = trimmed.match(
3199
+ /(?:([^\s:(]+)[:(](\d+)[:,](\d+)\)?:?\s*(?:-\s*)?(?:error\s+)?(TS\d+)?:?\s*(.*))/i
3200
+ );
3201
+ if (match) {
3202
+ const [, filePath, lineStr, colStr, code, message] = match;
3203
+ const diag = {
3204
+ filePath,
3205
+ line: lineStr ? parseInt(lineStr, 10) : void 0,
3206
+ column: colStr ? parseInt(colStr, 10) : void 0,
3207
+ code,
3208
+ message: message || trimmed
3209
+ };
3210
+ const suggestionMatch = diag.message.match(/Did you mean ['"]([a-zA-Z0-9_$]+)['"]\?/i);
3211
+ if (suggestionMatch) {
3212
+ diag.suggestedSymbol = suggestionMatch[1];
3213
+ }
3214
+ const missingMatch = diag.message.match(/has no exported member ['"]([a-zA-Z0-9_$]+)['"]/i) || diag.message.match(/Property ['"]([a-zA-Z0-9_$]+)['"] does not exist/i);
3215
+ if (missingMatch) {
3216
+ diag.missingMember = missingMatch[1];
3217
+ }
3218
+ diagnostics.push(diag);
3219
+ }
3220
+ }
3221
+ return diagnostics;
3222
+ }
3223
+ function revertRefactorSnapshots(snapshots) {
3224
+ for (const [filePath, content] of Object.entries(snapshots)) {
3225
+ try {
3226
+ writeFileSync3(filePath, content, "utf8");
3227
+ } catch {
3228
+ }
3229
+ }
3230
+ }
3231
+ var KNOWN_MIGRATIONS = {
3232
+ rimraf: [
3233
+ {
3234
+ detect: (content, diag) => diag.includes("sync") && content.includes("rimraf.sync("),
3235
+ transform: (content) => {
3236
+ let res = content.replace(/import\s+rimraf\s+from\s+['"]rimraf['"];?/g, "");
3237
+ res = `import { rimrafSync } from "rimraf";
3238
+ ` + res;
3239
+ return res.replace(/rimraf\.sync\(/g, "rimrafSync(");
3240
+ }
3241
+ }
3242
+ ],
3243
+ uuid: [
3244
+ {
3245
+ detect: (content, diag) => diag.includes("v4") && content.includes("uuid.v4("),
3246
+ transform: (content) => {
3247
+ let res = content.replace(/import\s+uuid\s+from\s+['"]uuid['"];?/g, "");
3248
+ res = `import { v4 as uuidv4 } from "uuid";
3249
+ ` + res;
3250
+ return res.replace(/uuid\.v4\(/g, "uuidv4(");
3251
+ }
3252
+ },
3253
+ {
3254
+ // Subpath import migration: `import v4 from "uuid/v4"` -> `import { v4 } from "uuid"`
3255
+ detect: (content) => /from\s+['"]uuid\/v4['"]/.test(content),
3256
+ transform: (content) => content.replace(
3257
+ /import\s+([a-zA-Z0-9_$]+)\s+from\s+['"]uuid\/v4['"];?/g,
3258
+ `import { v4 as $1 } from "uuid";`
3259
+ )
3260
+ }
3261
+ ],
3262
+ glob: [
3263
+ {
3264
+ detect: (content, diag) => diag.includes("sync") && content.includes("glob.sync("),
3265
+ transform: (content) => {
3266
+ let res = content.replace(/import\s+glob\s+from\s+['"]glob['"];?/g, "");
3267
+ res = `import { globSync } from "glob";
3268
+ ` + res;
3269
+ return res.replace(/glob\.sync\(/g, "globSync(");
3270
+ }
3271
+ }
3272
+ ],
3273
+ mkdirp: [
3274
+ {
3275
+ detect: (content, diag) => diag.includes("sync") && content.includes("mkdirp.sync("),
3276
+ transform: (content) => {
3277
+ let res = content.replace(/import\s+mkdirp\s+from\s+['"]mkdirp['"];?/g, "");
3278
+ res = `import { mkdirpSync } from "mkdirp";
3279
+ ` + res;
3280
+ return res.replace(/mkdirp\.sync\(/g, "mkdirpSync(");
3281
+ }
3282
+ }
3283
+ ]
3284
+ };
3285
+ function escapeRegex(str) {
3286
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3287
+ }
3288
+ function transformFileContent(content, packageName, diagnosticError, filePath) {
3289
+ let modified = false;
3290
+ let newContent = content;
3291
+ const migrations = KNOWN_MIGRATIONS[packageName];
3292
+ if (migrations) {
3293
+ for (const mig of migrations) {
3294
+ if (mig.detect(newContent, diagnosticError)) {
3295
+ const transformed = mig.transform(newContent);
3296
+ if (transformed !== newContent) {
3297
+ newContent = transformed;
3298
+ modified = true;
3299
+ }
3300
+ }
3301
+ }
3302
+ }
3303
+ 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")) {
3304
+ const defaultImportRegex = new RegExp(
3305
+ `import\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3306
+ "g"
3307
+ );
3308
+ if (defaultImportRegex.test(newContent)) {
3309
+ newContent = newContent.replace(
3310
+ defaultImportRegex,
3311
+ `import * as $1 from "${packageName}"`
3312
+ );
3313
+ modified = true;
3314
+ }
3315
+ }
3316
+ if (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259")) {
3317
+ const starImportRegex = new RegExp(
3318
+ `import\\s+\\*\\s+as\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3319
+ "g"
3320
+ );
3321
+ if (starImportRegex.test(newContent)) {
3322
+ newContent = newContent.replace(
3323
+ starImportRegex,
3324
+ `import $1 from "${packageName}"`
3325
+ );
3326
+ modified = true;
3327
+ }
3328
+ }
3329
+ const diagnostics = parseCompilerDiagnostics(diagnosticError);
3330
+ for (const diag of diagnostics) {
3331
+ if (diag.missingMember && diag.suggestedSymbol) {
3332
+ const missingMember = diag.missingMember;
3333
+ const suggestedSymbol = diag.suggestedSymbol;
3334
+ if (filePath && diag.filePath && filePath.endsWith(diag.filePath) && diag.line && diag.line > 0) {
3335
+ const fileLines = newContent.split("\n");
3336
+ const idx = diag.line - 1;
3337
+ if (idx >= 0 && idx < fileLines.length) {
3338
+ const line = fileLines[idx];
3339
+ const replaced = line.replace(new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g"), `.${suggestedSymbol}`).replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol);
3340
+ if (replaced !== line) {
3341
+ fileLines[idx] = replaced;
3342
+ newContent = fileLines.join("\n");
3343
+ modified = true;
3344
+ }
3345
+ }
3346
+ } else {
3347
+ const memberRegex = new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g");
3348
+ if (memberRegex.test(newContent)) {
3349
+ newContent = newContent.replace(memberRegex, `.${suggestedSymbol}`);
3350
+ modified = true;
3351
+ }
3352
+ const importPattern = new RegExp(
3353
+ `import\\s*\\{[^}]*\\b${escapeRegex(missingMember)}\\b[^}]*\\}\\s*from\\s*['"]${escapeRegex(packageName)}['"]`,
3354
+ "g"
3355
+ );
3356
+ if (importPattern.test(newContent)) {
3357
+ newContent = newContent.replace(
3358
+ importPattern,
3359
+ (match) => match.replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol)
3360
+ );
3361
+ modified = true;
3362
+ }
3363
+ }
3364
+ }
3365
+ }
3366
+ const lineDiags = diagnostics.filter(
3367
+ (d) => Boolean(filePath && d.filePath && filePath.endsWith(d.filePath) && typeof d.line === "number" && d.line > 0)
3368
+ ).sort((a, b) => b.line - a.line);
3369
+ const processedLines = /* @__PURE__ */ new Set();
3370
+ for (const diag of lineDiags) {
3371
+ if (processedLines.has(diag.line)) continue;
3372
+ processedLines.add(diag.line);
3373
+ const fileLines = newContent.split("\n");
3374
+ const targetLineIdx = diag.line - 1;
3375
+ if (targetLineIdx >= 0 && targetLineIdx < fileLines.length) {
3376
+ const prevLine = targetLineIdx > 0 ? fileLines[targetLineIdx - 1] : "";
3377
+ if (!prevLine?.includes("@ts-expect-error") && !fileLines[targetLineIdx]?.includes("@ts-expect-error")) {
3378
+ const indent = fileLines[targetLineIdx]?.match(/^\s*/)?.[0] || "";
3379
+ fileLines.splice(
3380
+ targetLineIdx,
3381
+ 0,
3382
+ `${indent}// @ts-expect-error [autoremediator-refactor]: breaking change in ${packageName}`
3383
+ );
3384
+ newContent = fileLines.join("\n");
3385
+ modified = true;
3386
+ }
3387
+ }
3388
+ }
3389
+ return { modified, newContent };
3390
+ }
3391
+ async function refactorConsumerCallSites(params) {
3392
+ const { packageName, diagnosticError, callSites } = params;
3393
+ if (callSites.length === 0) {
3394
+ return {
3395
+ applied: false,
3396
+ refactoredFiles: [],
3397
+ snapshots: {},
3398
+ error: `No consumer call sites found for "${packageName}".`
3399
+ };
3400
+ }
3401
+ const snapshots = {};
3402
+ const refactoredFiles = [];
3403
+ try {
3404
+ for (const site of callSites) {
3405
+ const originalContent = site.sourceContent || readFileSync12(site.filePath, "utf8");
3406
+ snapshots[site.filePath] = originalContent;
3407
+ const { modified, newContent } = transformFileContent(
3408
+ originalContent,
3409
+ packageName,
3410
+ diagnosticError,
3411
+ site.filePath
3412
+ );
3413
+ if (modified && newContent !== originalContent) {
3414
+ writeFileSync3(site.filePath, newContent, "utf8");
3415
+ refactoredFiles.push(site.filePath);
3416
+ }
3417
+ }
3418
+ if (refactoredFiles.length === 0) {
3419
+ return {
3420
+ applied: false,
3421
+ refactoredFiles: [],
3422
+ snapshots,
3423
+ error: "No automated codemod pattern matched the diagnostic error."
3424
+ };
3425
+ }
3426
+ return {
3427
+ applied: true,
3428
+ refactoredFiles,
3429
+ snapshots
3430
+ };
3431
+ } catch (err) {
3432
+ revertRefactorSnapshots(snapshots);
3433
+ const message = err instanceof Error ? err.message : String(err);
3434
+ return {
3435
+ applied: false,
3436
+ refactoredFiles: [],
3437
+ snapshots: {},
3438
+ error: `Failed to apply breaking change refactoring: ${message}`
3439
+ };
3440
+ }
3441
+ }
3442
+
2476
3443
  // src/remediation/tools/apply-version-bump.ts
2477
3444
  var applyVersionBumpTool = defineTool({
2478
3445
  description: "Update package.json to use the safe version of a vulnerable package and run the project's package manager install. In dry-run mode, only reports what would change.",
@@ -2485,10 +3452,12 @@ var applyVersionBumpTool = defineTool({
2485
3452
  dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
2486
3453
  policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
2487
3454
  runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
3455
+ typecheck: z4.boolean().default(false).describe("If true, run typecheck validation after applying the fix"),
2488
3456
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2489
3457
  installPreferOffline: z4.boolean().optional(),
2490
3458
  enforceFrozenLockfile: z4.boolean().optional(),
2491
- workspace: z4.string().optional()
3459
+ workspace: z4.string().optional(),
3460
+ breakingChangeRefactor: z4.boolean().optional().describe("If true, attempt agentic call-site refactoring when validation fails after major upgrade")
2492
3461
  }),
2493
3462
  execute: async ({
2494
3463
  cwd,
@@ -2499,13 +3468,15 @@ var applyVersionBumpTool = defineTool({
2499
3468
  dryRun,
2500
3469
  policy,
2501
3470
  runTests,
3471
+ typecheck,
2502
3472
  installMode,
2503
3473
  installPreferOffline,
2504
3474
  enforceFrozenLockfile,
2505
- workspace
3475
+ workspace,
3476
+ breakingChangeRefactor
2506
3477
  }) => {
2507
3478
  const pm = packageManager ?? detectPackageManager(cwd);
2508
- const pkgPath = join12(cwd, "package.json");
3479
+ let pkgPath = join15(cwd, "package.json");
2509
3480
  const loadedPolicy = loadPolicy(cwd, policy);
2510
3481
  const commandConstraints = {
2511
3482
  ...loadedPolicy.constraints,
@@ -2515,9 +3486,6 @@ var applyVersionBumpTool = defineTool({
2515
3486
  workspace: workspace ?? loadedPolicy.constraints?.workspace
2516
3487
  };
2517
3488
  const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
2518
- const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
2519
- const testCommand = resolveTestCommand(pm, commandConstraints);
2520
- const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
2521
3489
  if (!isPackageAllowed(loadedPolicy, packageName)) {
2522
3490
  return {
2523
3491
  packageName,
@@ -2530,7 +3498,7 @@ var applyVersionBumpTool = defineTool({
2530
3498
  message: `Policy blocked changes for package "${packageName}".`
2531
3499
  };
2532
3500
  }
2533
- const isMajorBump = semver2.valid(fromVersion) && semver2.valid(toVersion) && semver2.major(toVersion) > semver2.major(fromVersion);
3501
+ const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
2534
3502
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2535
3503
  return {
2536
3504
  packageName,
@@ -2543,9 +3511,10 @@ var applyVersionBumpTool = defineTool({
2543
3511
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
2544
3512
  };
2545
3513
  }
3514
+ pkgPath = join15(cwd, "package.json");
2546
3515
  let pkgJson;
2547
3516
  try {
2548
- pkgJson = JSON.parse(readFileSync9(pkgPath, "utf8"));
3517
+ pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
2549
3518
  } catch {
2550
3519
  return {
2551
3520
  packageName,
@@ -2557,9 +3526,20 @@ var applyVersionBumpTool = defineTool({
2557
3526
  message: `Could not read package.json at "${pkgPath}".`
2558
3527
  };
2559
3528
  }
2560
- const depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3529
+ let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
2561
3530
  (f) => pkgJson[f]?.[packageName] !== void 0
2562
3531
  );
3532
+ if (!depField) {
3533
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, packageName, workspace);
3534
+ if (declaringWorkspace) {
3535
+ pkgPath = declaringWorkspace.manifestPath;
3536
+ pkgJson = declaringWorkspace.manifest;
3537
+ depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3538
+ (f) => pkgJson[f]?.[packageName] !== void 0
3539
+ );
3540
+ commandConstraints.workspace = workspace ?? declaringWorkspace.name;
3541
+ }
3542
+ }
2563
3543
  if (!depField) {
2564
3544
  return {
2565
3545
  packageName,
@@ -2575,9 +3555,14 @@ var applyVersionBumpTool = defineTool({
2575
3555
  const prefixMatch = currentRange.match(/^([~^]?)/);
2576
3556
  const prefix = prefixMatch?.[1] ?? "";
2577
3557
  const newRange = `${prefix}${toVersion}`;
3558
+ const effectiveInstallCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
3559
+ const effectiveTestCommand = resolveTestCommand(pm, commandConstraints);
3560
+ const effectiveTypecheckCommand = resolveTypecheckCommand(pm, commandConstraints, cwd);
3561
+ const effectiveDedupeCommand = resolveDedupeCommand(pm, commandConstraints);
3562
+ const shouldTypecheck = typecheck || Boolean(commandConstraints.typecheck);
2578
3563
  if (dryRun) {
2579
- const installCmd = installCommand.join(" ");
2580
- const testCmd = testCommand.join(" ");
3564
+ const installCmd = effectiveInstallCommand.join(" ");
3565
+ const testCmd = effectiveTestCommand.join(" ");
2581
3566
  return {
2582
3567
  packageName,
2583
3568
  strategy: "version-bump",
@@ -2585,75 +3570,139 @@ var applyVersionBumpTool = defineTool({
2585
3570
  toVersion,
2586
3571
  applied: false,
2587
3572
  dryRun: true,
2588
- message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${dedupeCommand.length > 0 ? ` and ${dedupeCommand.join(" ")} (best-effort)` : ""}.`
3573
+ message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${effectiveDedupeCommand.length > 0 ? ` and ${effectiveDedupeCommand.join(" ")} (best-effort)` : ""}.`
2589
3574
  };
2590
3575
  }
2591
3576
  return withRepoLock(cwd, async () => {
2592
3577
  pkgJson[depField][packageName] = newRange;
2593
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3578
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2594
3579
  try {
2595
- const [installCmd, ...installArgs] = installCommand;
3580
+ const [installCmd, ...installArgs] = effectiveInstallCommand;
2596
3581
  await safeExeca(installCmd, installArgs, {
2597
3582
  cwd,
2598
3583
  stdio: "pipe"
2599
3584
  });
2600
3585
  } catch (err) {
2601
3586
  pkgJson[depField][packageName] = currentRange;
2602
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3587
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2603
3588
  const message = err instanceof Error ? err.message : String(err);
2604
3589
  return {
2605
3590
  packageName,
2606
- strategy: "version-bump",
3591
+ strategy: "version-bump",
3592
+ fromVersion,
3593
+ toVersion,
3594
+ applied: false,
3595
+ dryRun: false,
3596
+ unresolvedReason: "install-failed",
3597
+ message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3598
+ };
3599
+ }
3600
+ const shouldAttemptRefactor = Boolean(
3601
+ breakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor
3602
+ );
3603
+ async function runValidationSteps() {
3604
+ if (runTests) {
3605
+ try {
3606
+ const [testCmd, ...testArgs] = effectiveTestCommand;
3607
+ await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3608
+ } catch (err) {
3609
+ const execErr = err;
3610
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
3611
+ return {
3612
+ passed: false,
3613
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
3614
+ };
3615
+ }
3616
+ }
3617
+ if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
3618
+ try {
3619
+ const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
3620
+ await safeExeca(tcCmd, tcArgs, { cwd, stdio: "pipe" });
3621
+ } catch (err) {
3622
+ const execErr = err;
3623
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
3624
+ return {
3625
+ passed: false,
3626
+ isTypecheck: true,
3627
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
3628
+ };
3629
+ }
3630
+ }
3631
+ return { passed: true };
3632
+ }
3633
+ let validationResult = await runValidationSteps();
3634
+ let refactoredFilesList;
3635
+ if (!validationResult.passed && shouldAttemptRefactor) {
3636
+ const callSites = findPackageCallSites(cwd, packageName);
3637
+ if (callSites.length > 0) {
3638
+ const refactorAttempt = await refactorConsumerCallSites({
3639
+ cwd,
3640
+ packageName,
3641
+ fromVersion,
3642
+ toVersion,
3643
+ diagnosticError: validationResult.error || "",
3644
+ callSites
3645
+ });
3646
+ if (refactorAttempt.applied) {
3647
+ const postRefactorValidation = await runValidationSteps();
3648
+ if (postRefactorValidation.passed) {
3649
+ validationResult = { passed: true };
3650
+ refactoredFilesList = refactorAttempt.refactoredFiles;
3651
+ } else {
3652
+ revertRefactorSnapshots(refactorAttempt.snapshots);
3653
+ }
3654
+ }
3655
+ }
3656
+ }
3657
+ if (!validationResult.passed) {
3658
+ pkgJson[depField][packageName] = currentRange;
3659
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3660
+ try {
3661
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3662
+ await safeExeca(rollbackCmd, rollbackArgs, {
3663
+ cwd,
3664
+ stdio: "pipe"
3665
+ });
3666
+ } catch {
3667
+ }
3668
+ const failureMessage = validationResult.error || "Unknown validation error";
3669
+ const prefix2 = validationResult.isTypecheck ? `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")})` : `${effectiveTestCommand.join(" ")} failed`;
3670
+ return {
3671
+ packageName,
3672
+ strategy: "version-bump",
3673
+ fromVersion,
3674
+ toVersion,
3675
+ applied: false,
3676
+ dryRun: false,
3677
+ unresolvedReason: shouldAttemptRefactor ? "breaking-change-unresolved" : "validation-failed",
3678
+ message: `${prefix2} after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${failureMessage}`
3679
+ };
3680
+ }
3681
+ if (refactoredFilesList && refactoredFilesList.length > 0) {
3682
+ return {
3683
+ packageName,
3684
+ strategy: "breaking-change-refactor",
2607
3685
  fromVersion,
2608
3686
  toVersion,
2609
- applied: false,
3687
+ applied: true,
2610
3688
  dryRun: false,
2611
- unresolvedReason: "install-failed",
2612
- message: `${installCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3689
+ refactoredFiles: refactoredFilesList,
3690
+ message: `Upgraded "${packageName}" from ${fromVersion} to ${toVersion} and refactored ${refactoredFilesList.length} call-site file(s) across breaking API changes.`
2613
3691
  };
2614
3692
  }
2615
- if (runTests) {
3693
+ let dedupeNote = "";
3694
+ if (effectiveDedupeCommand.length > 0) {
2616
3695
  try {
2617
- const [testCmd, ...testArgs] = testCommand;
2618
- await safeExeca(testCmd, testArgs, {
3696
+ const [dedupeCmd, ...dedupeArgs] = effectiveDedupeCommand;
3697
+ await safeExeca(dedupeCmd, dedupeArgs, {
2619
3698
  cwd,
2620
3699
  stdio: "pipe"
2621
3700
  });
2622
3701
  } catch (err) {
2623
- pkgJson[depField][packageName] = currentRange;
2624
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2625
- try {
2626
- const [rollbackCmd, ...rollbackArgs] = installCommand;
2627
- await safeExeca(rollbackCmd, rollbackArgs, {
2628
- cwd,
2629
- stdio: "pipe"
2630
- });
2631
- } catch {
2632
- }
2633
3702
  const message = err instanceof Error ? err.message : String(err);
2634
- return {
2635
- packageName,
2636
- strategy: "version-bump",
2637
- fromVersion,
2638
- toVersion,
2639
- applied: false,
2640
- dryRun: false,
2641
- unresolvedReason: "validation-failed",
2642
- message: `${testCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
2643
- };
3703
+ dedupeNote = ` Dedupe warning: ${effectiveDedupeCommand.join(" ")} failed (${message}).`;
2644
3704
  }
2645
3705
  }
2646
- let dedupeNote = "";
2647
- try {
2648
- const [dedupeCmd, ...dedupeArgs] = dedupeCommand;
2649
- await safeExeca(dedupeCmd, dedupeArgs, {
2650
- cwd,
2651
- stdio: "pipe"
2652
- });
2653
- } catch (err) {
2654
- const message = err instanceof Error ? err.message : String(err);
2655
- dedupeNote = ` Dedupe warning: ${dedupeCommand.join(" ")} failed (${message}).`;
2656
- }
2657
3706
  return {
2658
3707
  packageName,
2659
3708
  strategy: "version-bump",
@@ -2661,7 +3710,7 @@ var applyVersionBumpTool = defineTool({
2661
3710
  toVersion,
2662
3711
  applied: true,
2663
3712
  dryRun: false,
2664
- message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${installCommand.join(" ")}${runTests ? `, and passed ${testCommand.join(" ")}` : ""}.${dedupeNote}`
3713
+ message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
2665
3714
  };
2666
3715
  });
2667
3716
  }
@@ -2669,9 +3718,9 @@ var applyVersionBumpTool = defineTool({
2669
3718
 
2670
3719
  // src/remediation/tools/apply-package-override/index.ts
2671
3720
  import { z as z5 } from "zod";
2672
- import { join as join13 } from "path";
2673
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
2674
- import semver3 from "semver";
3721
+ import { join as join16 } from "path";
3722
+ import { existsSync as existsSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
3723
+ import semver4 from "semver";
2675
3724
 
2676
3725
  // src/remediation/tools/apply-package-override/helpers.ts
2677
3726
  async function collectDependencyTrace(cwd, pm, packageName, constraints) {
@@ -2813,9 +3862,9 @@ var applyPackageOverrideTool = defineTool({
2813
3862
  workspace
2814
3863
  }) => {
2815
3864
  const pm = packageManager ?? detectPackageManager(cwd);
2816
- const pkgPath = join13(cwd, "package.json");
2817
- const denoJsonPath = join13(cwd, "deno.json");
2818
- const isDenoNative = pm === "deno" && !existsSync9(pkgPath);
3865
+ const pkgPath = join16(cwd, "package.json");
3866
+ const denoJsonPath = join16(cwd, "deno.json");
3867
+ const isDenoNative = pm === "deno" && !existsSync12(pkgPath);
2819
3868
  const loadedPolicy = loadPolicy(cwd, policy);
2820
3869
  const commandConstraints = {
2821
3870
  ...loadedPolicy.constraints,
@@ -2841,7 +3890,7 @@ var applyPackageOverrideTool = defineTool({
2841
3890
  message: `Policy blocked changes for package "${packageName}".`
2842
3891
  };
2843
3892
  }
2844
- const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
3893
+ const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
2845
3894
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2846
3895
  return {
2847
3896
  packageName,
@@ -2857,7 +3906,7 @@ var applyPackageOverrideTool = defineTool({
2857
3906
  if (isDenoNative) {
2858
3907
  let denoJson;
2859
3908
  try {
2860
- denoJson = JSON.parse(readFileSync10(denoJsonPath, "utf8"));
3909
+ denoJson = JSON.parse(readFileSync14(denoJsonPath, "utf8"));
2861
3910
  } catch {
2862
3911
  return {
2863
3912
  packageName,
@@ -2903,13 +3952,13 @@ var applyPackageOverrideTool = defineTool({
2903
3952
  }
2904
3953
  return withRepoLock(cwd, async () => {
2905
3954
  setDenoJsonImportValue(denoJson, overrideSelector, toVersion);
2906
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3955
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
2907
3956
  try {
2908
3957
  const [installCmd, ...installArgs] = installCommand;
2909
3958
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
2910
3959
  } catch (err) {
2911
3960
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
2912
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3961
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
2913
3962
  const message = err instanceof Error ? err.message : String(err);
2914
3963
  return {
2915
3964
  packageName,
@@ -2928,7 +3977,7 @@ var applyPackageOverrideTool = defineTool({
2928
3977
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
2929
3978
  } catch (err) {
2930
3979
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
2931
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3980
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
2932
3981
  try {
2933
3982
  const [rollbackCmd, ...rollbackArgs] = installCommand;
2934
3983
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -2960,7 +4009,7 @@ var applyPackageOverrideTool = defineTool({
2960
4009
  }
2961
4010
  let pkgJson;
2962
4011
  try {
2963
- pkgJson = JSON.parse(readFileSync10(pkgPath, "utf8"));
4012
+ pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
2964
4013
  } catch {
2965
4014
  return {
2966
4015
  packageName,
@@ -2990,13 +4039,13 @@ var applyPackageOverrideTool = defineTool({
2990
4039
  }
2991
4040
  return withRepoLock(cwd, async () => {
2992
4041
  setOverrideValue(pkgJson, pm, overrideSelector, toVersion);
2993
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4042
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2994
4043
  try {
2995
4044
  const [installCmd, ...installArgs] = installCommand;
2996
4045
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
2997
4046
  } catch (err) {
2998
4047
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
2999
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4048
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3000
4049
  const message = err instanceof Error ? err.message : String(err);
3001
4050
  return {
3002
4051
  packageName,
@@ -3015,7 +4064,7 @@ var applyPackageOverrideTool = defineTool({
3015
4064
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3016
4065
  } catch (err) {
3017
4066
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
3018
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4067
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3019
4068
  try {
3020
4069
  const [rollbackCmd, ...rollbackArgs] = installCommand;
3021
4070
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -3057,6 +4106,80 @@ var applyPackageOverrideTool = defineTool({
3057
4106
  }
3058
4107
  });
3059
4108
 
4109
+ // src/platform/supply-chain.ts
4110
+ async function verifyPackageTrust(packageName, targetVersion, policy) {
4111
+ if (!policy || !policy.minPublishAgeHours && !policy.blockInstallScripts && !policy.requireProvenance) {
4112
+ return { trusted: true };
4113
+ }
4114
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
4115
+ let packument = null;
4116
+ try {
4117
+ const res = await httpClient({
4118
+ url,
4119
+ headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" }
4120
+ });
4121
+ if (res.ok && res.data) {
4122
+ packument = res.data;
4123
+ }
4124
+ } catch (err) {
4125
+ const msg = err instanceof Error ? err.message : String(err);
4126
+ return {
4127
+ trusted: false,
4128
+ reason: `Could not verify supply-chain trust for "${packageName}@${targetVersion}": ${msg}`
4129
+ };
4130
+ }
4131
+ if (!packument) {
4132
+ return {
4133
+ trusted: false,
4134
+ reason: `Package packument for "${packageName}" not found on registry.`
4135
+ };
4136
+ }
4137
+ let publishAgeHours;
4138
+ if (packument.time?.[targetVersion]) {
4139
+ const publishedAt = new Date(packument.time[targetVersion]).getTime();
4140
+ publishAgeHours = Math.max(0, (Date.now() - publishedAt) / (1e3 * 60 * 60));
4141
+ if (policy.minPublishAgeHours && publishAgeHours < policy.minPublishAgeHours) {
4142
+ return {
4143
+ trusted: false,
4144
+ publishAgeHours,
4145
+ reason: `Target release "${packageName}@${targetVersion}" was published only ${publishAgeHours.toFixed(1)}h ago (minimum required age is ${policy.minPublishAgeHours}h).`
4146
+ };
4147
+ }
4148
+ }
4149
+ const versionData = packument.versions?.[targetVersion];
4150
+ const scripts = versionData?.scripts ?? {};
4151
+ const installScriptKeys = ["preinstall", "install", "postinstall"].filter(
4152
+ (k) => Boolean(scripts[k])
4153
+ );
4154
+ const hasInstallScripts = installScriptKeys.length > 0;
4155
+ if (policy.blockInstallScripts && hasInstallScripts) {
4156
+ return {
4157
+ trusted: false,
4158
+ publishAgeHours,
4159
+ hasInstallScripts: true,
4160
+ reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
4161
+ };
4162
+ }
4163
+ const hasProvenance = Boolean(
4164
+ versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url
4165
+ );
4166
+ if (policy.requireProvenance && !hasProvenance) {
4167
+ return {
4168
+ trusted: false,
4169
+ publishAgeHours,
4170
+ hasInstallScripts,
4171
+ hasProvenance: false,
4172
+ reason: `Target release "${packageName}@${targetVersion}" does not have cryptographic build provenance attestations.`
4173
+ };
4174
+ }
4175
+ return {
4176
+ trusted: true,
4177
+ publishAgeHours,
4178
+ hasInstallScripts,
4179
+ hasProvenance
4180
+ };
4181
+ }
4182
+
3060
4183
  // src/remediation/local/primary-strategy.ts
3061
4184
  async function resolvePrimaryResult(params) {
3062
4185
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
@@ -3127,6 +4250,26 @@ async function resolvePrimaryResult(params) {
3127
4250
  }
3128
4251
  };
3129
4252
  }
4253
+ const trust2 = await verifyPackageTrust(
4254
+ pkg.name,
4255
+ safeUpgrade2.safeVersion,
4256
+ constraints.supplyChainPolicy
4257
+ );
4258
+ if (!trust2.trusted) {
4259
+ return {
4260
+ steps: 1,
4261
+ result: {
4262
+ packageName: pkg.name,
4263
+ strategy: "none",
4264
+ fromVersion: pkg.version,
4265
+ applied: false,
4266
+ dryRun,
4267
+ unresolvedReason: "policy-blocked",
4268
+ vulnerableRange: vulnerable.affected.vulnerableRange,
4269
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade2.safeVersion}": ${trust2.reason}`
4270
+ }
4271
+ };
4272
+ }
3130
4273
  const overrideResult = await applyPackageOverrideTool.execute({
3131
4274
  cwd,
3132
4275
  packageManager,
@@ -3182,6 +4325,26 @@ async function resolvePrimaryResult(params) {
3182
4325
  }
3183
4326
  };
3184
4327
  }
4328
+ const trust = await verifyPackageTrust(
4329
+ pkg.name,
4330
+ safeUpgrade.safeVersion,
4331
+ constraints.supplyChainPolicy
4332
+ );
4333
+ if (!trust.trusted) {
4334
+ return {
4335
+ steps: 1,
4336
+ result: {
4337
+ packageName: pkg.name,
4338
+ strategy: "none",
4339
+ fromVersion: pkg.version,
4340
+ applied: false,
4341
+ dryRun,
4342
+ unresolvedReason: "policy-blocked",
4343
+ vulnerableRange: vulnerable.affected.vulnerableRange,
4344
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade.safeVersion}": ${trust.reason}`
4345
+ }
4346
+ };
4347
+ }
3185
4348
  const applyResult = await applyVersionBumpTool.execute({
3186
4349
  cwd,
3187
4350
  packageManager,
@@ -3191,10 +4354,12 @@ async function resolvePrimaryResult(params) {
3191
4354
  dryRun,
3192
4355
  policy,
3193
4356
  runTests,
4357
+ typecheck: constraints.typecheck,
3194
4358
  installMode: constraints.installMode,
3195
4359
  installPreferOffline: constraints.installPreferOffline,
3196
4360
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
3197
- workspace: constraints.workspace
4361
+ workspace: constraints.workspace,
4362
+ breakingChangeRefactor: constraints.enableBreakingChangeRefactor
3198
4363
  });
3199
4364
  return {
3200
4365
  steps: 2,
@@ -3204,10 +4369,10 @@ async function resolvePrimaryResult(params) {
3204
4369
 
3205
4370
  // src/remediation/tools/fetch-package-source.ts
3206
4371
  import { z as z6 } from "zod";
3207
- import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
3208
- import { join as join14 } from "path";
4372
+ import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
4373
+ import { join as join17 } from "path";
3209
4374
  import { tmpdir } from "os";
3210
- import { execa as execa6 } from "execa";
4375
+ import { execa as execa7 } from "execa";
3211
4376
  var fetchPackageSourceTool = defineTool({
3212
4377
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
3213
4378
  parameters: z6.object({
@@ -3225,25 +4390,25 @@ var fetchPackageSourceTool = defineTool({
3225
4390
  if (safePatterns.length === 0) {
3226
4391
  return { success: false, error: "No valid file patterns provided." };
3227
4392
  }
3228
- const tempBaseDir = await mkdtemp(join14(tmpdir(), "autoremediator-pkg-"));
3229
- const extractDir = join14(tempBaseDir, "out");
4393
+ const tempBaseDir = await mkdtemp(join17(tmpdir(), "autoremediator-pkg-"));
4394
+ const extractDir = join17(tempBaseDir, "out");
3230
4395
  try {
3231
4396
  const scopedName = packageName.split("/").pop();
3232
4397
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3233
4398
  await mkdir2(tempBaseDir, { recursive: true });
3234
- const tarballPath = join14(tempBaseDir, "package.tgz");
3235
- await execa6("curl", ["-L", "-o", tarballPath, npmUrl]);
4399
+ const tarballPath = join17(tempBaseDir, "package.tgz");
4400
+ await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3236
4401
  await mkdir2(extractDir, { recursive: true });
3237
- await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
4402
+ await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3238
4403
  const extractedContents = await readdir2(extractDir);
3239
- const packageRootDir = extractedContents.includes("package") ? join14(extractDir, "package") : extractDir;
4404
+ const packageRootDir = extractedContents.includes("package") ? join17(extractDir, "package") : extractDir;
3240
4405
  const sourceCode = {};
3241
4406
  async function walkDir(dir, relativeBase) {
3242
4407
  try {
3243
4408
  const files = await readdir2(dir, { withFileTypes: true });
3244
4409
  for (const file of files) {
3245
- const fullPath = join14(dir, file.name);
3246
- const relPath = join14(relativeBase, file.name);
4410
+ const fullPath = join17(dir, file.name);
4411
+ const relPath = join17(relativeBase, file.name);
3247
4412
  if (file.isDirectory()) {
3248
4413
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3249
4414
  file.name
@@ -3257,7 +4422,7 @@ var fetchPackageSourceTool = defineTool({
3257
4422
  });
3258
4423
  if (matches) {
3259
4424
  try {
3260
- const content = await readFile3(fullPath, "utf8");
4425
+ const content = await readFile4(fullPath, "utf8");
3261
4426
  sourceCode[relPath] = content;
3262
4427
  } catch {
3263
4428
  }
@@ -3292,7 +4457,7 @@ var fetchPackageSourceTool = defineTool({
3292
4457
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
3293
4458
  };
3294
4459
  } finally {
3295
- await rm2(tempBaseDir, { recursive: true, force: true });
4460
+ await rm3(tempBaseDir, { recursive: true, force: true });
3296
4461
  }
3297
4462
  }
3298
4463
  });
@@ -3350,6 +4515,7 @@ Analyze the source code to:
3350
4515
  2. Explain the root cause of the security issue
3351
4516
  3. Propose a secure fix that addresses the vulnerability
3352
4517
  4. Provide the complete fixed version of affected files
4518
+ 5. Optionally provide a standalone Node.js reproduction/regression test script
3353
4519
 
3354
4520
  ## Response Format
3355
4521
  Respond ONLY with valid JSON (no markdown, no extra text):
@@ -3360,7 +4526,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
3360
4526
  "path/to/other.ts": "Complete fixed source code for this file"
3361
4527
  },
3362
4528
  "confidence": 0.95,
3363
- "riskLevel": "medium"
4529
+ "riskLevel": "medium",
4530
+ "reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
3364
4531
  }
3365
4532
 
3366
4533
  Important:
@@ -3371,7 +4538,7 @@ Important:
3371
4538
  }
3372
4539
 
3373
4540
  // src/remediation/tools/generate-patch/helpers.ts
3374
- import { parseSync as parseSync2 } from "oxc-parser";
4541
+ import { parseSync as parseSync3 } from "oxc-parser";
3375
4542
  import YAML from "yaml";
3376
4543
  function parseLlmAnalysisResponse(text) {
3377
4544
  try {
@@ -3394,7 +4561,7 @@ function validatePatchedCodeSyntax(filePath, code) {
3394
4561
  const lowerPath = filePath.toLowerCase();
3395
4562
  if (/\.[cm]?[jt]sx?$/i.test(lowerPath)) {
3396
4563
  try {
3397
- const res = parseSync2(filePath, code);
4564
+ const res = parseSync3(filePath, code);
3398
4565
  if (res.errors && res.errors.length > 0) {
3399
4566
  const errorMsg = res.errors.map((e) => e.message).join("; ");
3400
4567
  return {
@@ -3640,6 +4807,7 @@ var generatePatchTool = defineTool({
3640
4807
  success: true,
3641
4808
  patches,
3642
4809
  patchContent: patches[0]?.unifiedDiff,
4810
+ reproductionTest: analysis.reproductionTest,
3643
4811
  llmProvider: provider,
3644
4812
  llmModel: modelName,
3645
4813
  latencyMs,
@@ -3664,17 +4832,126 @@ var generatePatchTool = defineTool({
3664
4832
 
3665
4833
  // src/remediation/tools/apply-patch-file/index.ts
3666
4834
  import { z as z8 } from "zod";
3667
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
3668
- import { join as join16 } from "path";
3669
- import { execa as execa8 } from "execa";
4835
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
4836
+ import { existsSync as existsSync14 } from "fs";
4837
+ import { isAbsolute as isAbsolute4, join as join19 } from "path";
4838
+ import { execa as execa9 } from "execa";
4839
+
4840
+ // src/remediation/proof-of-fix/runner.ts
4841
+ async function runProofOfFixPhase(options) {
4842
+ const { stage, testFilePath, cwd, timeoutMs = 15e3 } = options;
4843
+ const started = Date.now();
4844
+ const isTs = testFilePath.endsWith(".ts");
4845
+ const nodeArgs = isTs ? ["--experimental-strip-types", testFilePath] : [testFilePath];
4846
+ try {
4847
+ const result = await safeExeca("node", nodeArgs, {
4848
+ cwd,
4849
+ timeoutMs,
4850
+ stdio: "pipe"
4851
+ });
4852
+ const durationMs = Date.now() - started;
4853
+ const output = `${result.stdout || ""}
4854
+ ${result.stderr || ""}`.trim();
4855
+ if (stage === "negative") {
4856
+ return {
4857
+ executed: true,
4858
+ passed: false,
4859
+ exitCode: 0,
4860
+ output,
4861
+ durationMs,
4862
+ error: "Exploit reproduction test passed on vulnerable target (failed to reproduce vulnerability)."
4863
+ };
4864
+ }
4865
+ return {
4866
+ executed: true,
4867
+ passed: true,
4868
+ exitCode: 0,
4869
+ output,
4870
+ durationMs
4871
+ };
4872
+ } catch (err) {
4873
+ const durationMs = Date.now() - started;
4874
+ const execaErr = err;
4875
+ const exitCode = typeof execaErr.exitCode === "number" ? execaErr.exitCode : 1;
4876
+ const errorMsg = execaErr.message || String(err);
4877
+ const output = [execaErr.stdout, execaErr.stderr, errorMsg].filter(Boolean).join("\n").trim();
4878
+ if (stage === "negative") {
4879
+ if (execaErr.timedOut || errorMsg.includes("timed out")) {
4880
+ return {
4881
+ executed: true,
4882
+ passed: false,
4883
+ exitCode,
4884
+ output,
4885
+ durationMs,
4886
+ error: "Exploit reproduction test timed out (execution hung)."
4887
+ };
4888
+ }
4889
+ const isHarnessFailure = exitCode === 127 || errorMsg.includes("ENOENT") || output.includes("bad option:") || output.includes("Cannot find module") && output.includes(testFilePath);
4890
+ if (isHarnessFailure) {
4891
+ return {
4892
+ executed: false,
4893
+ passed: false,
4894
+ exitCode,
4895
+ output,
4896
+ durationMs,
4897
+ error: `Reproduction test failed to execute (harness error): ${errorMsg}`
4898
+ };
4899
+ }
4900
+ return {
4901
+ executed: true,
4902
+ passed: true,
4903
+ exitCode,
4904
+ output,
4905
+ durationMs
4906
+ };
4907
+ }
4908
+ return {
4909
+ executed: true,
4910
+ passed: false,
4911
+ exitCode,
4912
+ output,
4913
+ durationMs,
4914
+ error: `Reproduction test failed on patched target: ${errorMsg}`
4915
+ };
4916
+ }
4917
+ }
4918
+ function assembleProofOfFixVerification(params) {
4919
+ const { reproductionTestPath, negativePhase, positivePhase, regressionPhase } = params;
4920
+ const regPhase = regressionPhase ?? {
4921
+ executed: false,
4922
+ passed: true
4923
+ };
4924
+ let status = "verified";
4925
+ let failureReason;
4926
+ if (!negativePhase.passed) {
4927
+ status = "failed";
4928
+ failureReason = negativePhase.error || "negative-phase-failed";
4929
+ } else if (!positivePhase.passed) {
4930
+ status = "failed";
4931
+ failureReason = positivePhase.error || "positive-phase-failed";
4932
+ } else if (regPhase.executed && !regPhase.passed) {
4933
+ status = "failed";
4934
+ failureReason = regPhase.error || "regression-phase-failed";
4935
+ }
4936
+ const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
4937
+ return {
4938
+ status,
4939
+ reproductionTestPath,
4940
+ negativePhase,
4941
+ positivePhase,
4942
+ regressionPhase: regPhase,
4943
+ durationMs,
4944
+ failureReason
4945
+ };
4946
+ }
3670
4947
 
3671
4948
  // src/remediation/tools/apply-patch-file/helpers.ts
3672
- import { existsSync as existsSync10 } from "fs";
3673
- import { mkdtemp as mkdtemp2, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
4949
+ import { existsSync as existsSync13 } from "fs";
4950
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
3674
4951
  import { createHash } from "crypto";
3675
4952
  import { tmpdir as tmpdir2 } from "os";
3676
- import { join as join15 } from "path";
3677
- import { execa as execa7 } from "execa";
4953
+ import { join as join18 } from "path";
4954
+ import { execa as execa8 } from "execa";
3678
4955
  async function resolvePatchMode(packageManager, cwd) {
3679
4956
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
3680
4957
  return "patch-package";
@@ -3705,13 +4982,13 @@ function computePatchIntegrity(patchContent) {
3705
4982
  return `sha256:${hex}`;
3706
4983
  }
3707
4984
  async function writePatchManifest(manifestFilePath, artifact) {
3708
- await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4985
+ await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
3709
4986
  }
3710
4987
  async function configurePatchPackagePostinstall(cwd, packageManager) {
3711
- const pkgJsonPath = join15(cwd, "package.json");
4988
+ const pkgJsonPath = join18(cwd, "package.json");
3712
4989
  let pkgJson;
3713
4990
  try {
3714
- pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
4991
+ pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
3715
4992
  } catch {
3716
4993
  return {
3717
4994
  success: false,
@@ -3723,7 +5000,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
3723
5000
  try {
3724
5001
  const commands = getPackageManagerCommands(packageManager);
3725
5002
  const [cmd, ...args] = commands.installDev("patch-package");
3726
- await execa7(cmd, args, {
5003
+ await execa8(cmd, args, {
3727
5004
  cwd,
3728
5005
  stdio: "pipe"
3729
5006
  });
@@ -3744,13 +5021,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
3744
5021
  } else if (!currentPostinstall) {
3745
5022
  pkgJson.scripts.postinstall = patchApplyCmd;
3746
5023
  }
3747
- await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5024
+ await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3748
5025
  return { success: true };
3749
5026
  }
3750
5027
  async function capturePackageJsonSnapshot(cwd) {
3751
- const path = join15(cwd, "package.json");
5028
+ const path = join18(cwd, "package.json");
3752
5029
  try {
3753
- const content = await readFile4(path, "utf8");
5030
+ const content = await readFile5(path, "utf8");
3754
5031
  return { path, content };
3755
5032
  } catch {
3756
5033
  return void 0;
@@ -3766,19 +5043,19 @@ async function cleanupPatchArtifacts(params) {
3766
5043
  installCommand,
3767
5044
  rerunInstall
3768
5045
  } = params;
3769
- await rm3(patchFilePath, { force: true }).catch(() => void 0);
5046
+ await rm4(patchFilePath, { force: true }).catch(() => void 0);
3770
5047
  if (manifestFilePath) {
3771
- await rm3(manifestFilePath, { force: true }).catch(() => void 0);
5048
+ await rm4(manifestFilePath, { force: true }).catch(() => void 0);
3772
5049
  }
3773
5050
  if (patchMode === "patch-package" && packageJsonSnapshot) {
3774
- await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
5051
+ await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
3775
5052
  () => void 0
3776
5053
  );
3777
5054
  }
3778
5055
  if (!rerunInstall) return;
3779
5056
  try {
3780
5057
  const [installCmd, ...installArgs] = installCommand;
3781
- await execa7(installCmd, installArgs, {
5058
+ await execa8(installCmd, installArgs, {
3782
5059
  cwd,
3783
5060
  stdio: "pipe"
3784
5061
  });
@@ -3792,7 +5069,7 @@ async function applyNativePatch(params) {
3792
5069
  const createArgs = ["patch", packageSpec];
3793
5070
  let patchDir;
3794
5071
  try {
3795
- const createResult = await execa7(createCommand, createArgs, {
5072
+ const createResult = await execa8(createCommand, createArgs, {
3796
5073
  cwd,
3797
5074
  stdio: "pipe"
3798
5075
  });
@@ -3820,17 +5097,17 @@ ${createResult.stderr}`);
3820
5097
  error: `Could not determine native patch directory for ${packageSpec}.`
3821
5098
  };
3822
5099
  }
3823
- const tempPatchDir = await mkdtemp2(join15(tmpdir2(), "autoremediator-native-patch-"));
3824
- const tempPatchFile = join15(tempPatchDir, "change.patch");
5100
+ const tempPatchDir = await mkdtemp2(join18(tmpdir2(), "autoremediator-native-patch-"));
5101
+ const tempPatchFile = join18(tempPatchDir, "change.patch");
3825
5102
  try {
3826
- await writeFile(tempPatchFile, patchContent, "utf8");
3827
- await execa7("patch", ["-p1", "-i", tempPatchFile], {
5103
+ await writeFile2(tempPatchFile, patchContent, "utf8");
5104
+ await execa8("patch", ["-p1", "-i", tempPatchFile], {
3828
5105
  cwd: patchDir,
3829
5106
  stdio: "pipe"
3830
5107
  });
3831
5108
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
3832
5109
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
3833
- await execa7(commitCommand, commitArgs, {
5110
+ await execa8(commitCommand, commitArgs, {
3834
5111
  cwd,
3835
5112
  stdio: "pipe"
3836
5113
  });
@@ -3845,19 +5122,19 @@ ${createResult.stderr}`);
3845
5122
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
3846
5123
  };
3847
5124
  } finally {
3848
- await rm3(tempPatchDir, { recursive: true, force: true });
5125
+ await rm4(tempPatchDir, { recursive: true, force: true });
3849
5126
  }
3850
5127
  return { success: true };
3851
5128
  }
3852
5129
  function extractPatchDirectory(output) {
3853
5130
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
3854
5131
  for (const line of lines) {
3855
- if (existsSync10(line)) {
5132
+ if (existsSync13(line)) {
3856
5133
  return line;
3857
5134
  }
3858
5135
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
3859
5136
  for (const token of tokens) {
3860
- if (token.startsWith("/") && existsSync10(token)) {
5137
+ if (token.startsWith("/") && existsSync13(token)) {
3861
5138
  return token;
3862
5139
  }
3863
5140
  }
@@ -3867,7 +5144,7 @@ function extractPatchDirectory(output) {
3867
5144
  async function validatePatchWithTests(cwd, testCommand) {
3868
5145
  try {
3869
5146
  const [cmd, ...args] = testCommand;
3870
- const result = await execa7(cmd, args, {
5147
+ const result = await execa8(cmd, args, {
3871
5148
  cwd,
3872
5149
  timeout: 6e4,
3873
5150
  stdio: "pipe"
@@ -3927,6 +5204,11 @@ var applyPatchFileTool = defineTool({
3927
5204
  enforceFrozenLockfile: z8.boolean().optional(),
3928
5205
  workspace: z8.string().optional(),
3929
5206
  validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
5207
+ reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
5208
+ proofOfFix: z8.union([z8.boolean(), z8.string()]).optional().describe(
5209
+ "If true or script path/content, enforce dual-stage exploit reproduction (assert failure pre-patch, assert pass post-patch)"
5210
+ ),
5211
+ requireProofOfFix: z8.boolean().optional().describe("If true, fail and roll back patch if proof-of-fix verification fails"),
3930
5212
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
3931
5213
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
3932
5214
  message: "Either patchContent or patches must be provided"
@@ -3948,6 +5230,9 @@ var applyPatchFileTool = defineTool({
3948
5230
  workspace,
3949
5231
  riskLevel,
3950
5232
  validateWithTests,
5233
+ reproductionTest,
5234
+ proofOfFix,
5235
+ requireProofOfFix,
3951
5236
  dryRun
3952
5237
  }) => {
3953
5238
  try {
@@ -3998,7 +5283,7 @@ var applyPatchFileTool = defineTool({
3998
5283
  };
3999
5284
  }
4000
5285
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
4001
- const patchFilePath = join16(cwd, patchesDir, patchFileName);
5286
+ const patchFilePath = join19(cwd, patchesDir, patchFileName);
4002
5287
  const manifestFilePath = `${patchFilePath}.json`;
4003
5288
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4004
5289
  const baseArtifact = {
@@ -4037,9 +5322,9 @@ var applyPatchFileTool = defineTool({
4037
5322
  }
4038
5323
  return withRepoLock(cwd, async () => {
4039
5324
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
4040
- const patchesDirPath = join16(cwd, patchesDir);
5325
+ const patchesDirPath = join19(cwd, patchesDir);
4041
5326
  await mkdir3(patchesDirPath, { recursive: true });
4042
- await writeFile2(patchFilePath, selectedPatch, "utf8");
5327
+ await writeFile3(patchFilePath, selectedPatch, "utf8");
4043
5328
  validationPhases.push({
4044
5329
  phase: "patch-write",
4045
5330
  passed: true,
@@ -4060,6 +5345,101 @@ var applyPatchFileTool = defineTool({
4060
5345
  });
4061
5346
  artifact.validationPhases = validationPhases;
4062
5347
  await writePatchManifest(manifestFilePath, artifact);
5348
+ let reproductionTestPath;
5349
+ let negativePhaseResult;
5350
+ if (typeof proofOfFix === "string" && proofOfFix.trim().length > 0) {
5351
+ const resolvedPath = isAbsolute4(proofOfFix) ? proofOfFix : join19(cwd, proofOfFix);
5352
+ if (existsSync14(resolvedPath)) {
5353
+ reproductionTestPath = resolvedPath;
5354
+ } else {
5355
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5356
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5357
+ await writeFile3(reproductionTestPath, proofOfFix, "utf8");
5358
+ }
5359
+ } else if (reproductionTest) {
5360
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5361
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5362
+ await writeFile3(reproductionTestPath, reproductionTest, "utf8");
5363
+ }
5364
+ const isProofOfFixActive = Boolean(proofOfFix || requireProofOfFix);
5365
+ if (isProofOfFixActive) {
5366
+ if (!reproductionTestPath) {
5367
+ if (requireProofOfFix) {
5368
+ await cleanupPatchArtifacts({
5369
+ cwd,
5370
+ patchFilePath,
5371
+ manifestFilePath,
5372
+ patchMode,
5373
+ packageJsonSnapshot,
5374
+ installCommand,
5375
+ rerunInstall: false
5376
+ });
5377
+ return {
5378
+ success: false,
5379
+ packageName,
5380
+ vulnerableVersion,
5381
+ applied: false,
5382
+ dryRun: false,
5383
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied.",
5384
+ patchFilePath,
5385
+ manifestFilePath,
5386
+ patchPath: patchFilePath,
5387
+ patchMode,
5388
+ postinstallConfigured: false,
5389
+ patchArtifact: {
5390
+ ...artifact,
5391
+ applied: false,
5392
+ validationPhases
5393
+ },
5394
+ validationPhases,
5395
+ error: "Proof-of-fix verification required, but no reproduction test or script was supplied."
5396
+ };
5397
+ }
5398
+ } else {
5399
+ negativePhaseResult = await runProofOfFixPhase({
5400
+ stage: "negative",
5401
+ testFilePath: reproductionTestPath,
5402
+ cwd
5403
+ });
5404
+ validationPhases.push({
5405
+ phase: "proof-of-fix-negative",
5406
+ passed: negativePhaseResult.passed,
5407
+ message: negativePhaseResult.passed ? "Reproduction test reproduced vulnerability on unpatched code." : void 0,
5408
+ error: negativePhaseResult.passed ? void 0 : negativePhaseResult.error
5409
+ });
5410
+ if (!negativePhaseResult.passed && requireProofOfFix) {
5411
+ await cleanupPatchArtifacts({
5412
+ cwd,
5413
+ patchFilePath,
5414
+ manifestFilePath,
5415
+ patchMode,
5416
+ packageJsonSnapshot,
5417
+ installCommand,
5418
+ rerunInstall: false
5419
+ });
5420
+ return {
5421
+ success: false,
5422
+ packageName,
5423
+ vulnerableVersion,
5424
+ applied: false,
5425
+ dryRun: false,
5426
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`,
5427
+ patchFilePath,
5428
+ manifestFilePath,
5429
+ patchPath: patchFilePath,
5430
+ patchMode,
5431
+ postinstallConfigured: false,
5432
+ patchArtifact: {
5433
+ ...artifact,
5434
+ applied: false,
5435
+ validationPhases
5436
+ },
5437
+ validationPhases,
5438
+ error: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
5439
+ };
5440
+ }
5441
+ }
5442
+ }
4063
5443
  const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
4064
5444
  cwd,
4065
5445
  packageName,
@@ -4107,7 +5487,7 @@ var applyPatchFileTool = defineTool({
4107
5487
  if (patchMode === "patch-package") {
4108
5488
  try {
4109
5489
  const [installCmd, ...installArgs] = installCommand;
4110
- await execa8(installCmd, installArgs, {
5490
+ await execa9(installCmd, installArgs, {
4111
5491
  cwd,
4112
5492
  stdio: "pipe"
4113
5493
  });
@@ -4200,11 +5580,90 @@ var applyPatchFileTool = defineTool({
4200
5580
  message: "Patch validation tests passed."
4201
5581
  });
4202
5582
  }
5583
+ let positivePhaseResult;
5584
+ if (reproductionTestPath) {
5585
+ if (isProofOfFixActive) {
5586
+ positivePhaseResult = await runProofOfFixPhase({
5587
+ stage: "positive",
5588
+ testFilePath: reproductionTestPath,
5589
+ cwd
5590
+ });
5591
+ validationPhases.push({
5592
+ phase: "proof-of-fix-positive",
5593
+ passed: positivePhaseResult.passed,
5594
+ message: positivePhaseResult.passed ? "Reproduction test passed on patched code (vulnerability mitigated)." : void 0,
5595
+ error: positivePhaseResult.passed ? void 0 : positivePhaseResult.error
5596
+ });
5597
+ if (!positivePhaseResult.passed && (requireProofOfFix || validateWithTests)) {
5598
+ await cleanupPatchArtifacts({
5599
+ cwd,
5600
+ patchFilePath,
5601
+ manifestFilePath,
5602
+ patchMode,
5603
+ packageJsonSnapshot,
5604
+ installCommand,
5605
+ rerunInstall: patchMode === "patch-package"
5606
+ });
5607
+ return {
5608
+ success: false,
5609
+ packageName,
5610
+ vulnerableVersion,
5611
+ applied: false,
5612
+ dryRun: false,
5613
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`,
5614
+ patchFilePath,
5615
+ manifestFilePath,
5616
+ patchPath: patchFilePath,
5617
+ patchMode,
5618
+ postinstallConfigured: false,
5619
+ patchArtifact: {
5620
+ ...artifact,
5621
+ applied: false,
5622
+ validationPhases
5623
+ },
5624
+ validationPhases,
5625
+ error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
5626
+ };
5627
+ }
5628
+ } else {
5629
+ try {
5630
+ const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
5631
+ validationPhases.push({
5632
+ phase: "reproduction-test",
5633
+ passed: reproRun.exitCode === 0,
5634
+ message: "Reproduction test verified fix successfully."
5635
+ });
5636
+ } catch (err) {
5637
+ const reproErr = err instanceof Error ? err.message : String(err);
5638
+ validationPhases.push({
5639
+ phase: "reproduction-test",
5640
+ passed: false,
5641
+ error: `Reproduction test failed: ${reproErr}`
5642
+ });
5643
+ }
5644
+ }
5645
+ }
5646
+ let proofOfFixVerification;
5647
+ if (negativePhaseResult && positivePhaseResult) {
5648
+ proofOfFixVerification = assembleProofOfFixVerification({
5649
+ reproductionTestPath,
5650
+ negativePhase: negativePhaseResult,
5651
+ positivePhase: positivePhaseResult,
5652
+ regressionPhase: validationResult ? {
5653
+ executed: true,
5654
+ passed: validationResult.passed,
5655
+ error: validationResult.error
5656
+ } : void 0
5657
+ });
5658
+ }
4203
5659
  const finalArtifact = {
4204
5660
  ...artifact,
4205
5661
  applied: true,
4206
5662
  dryRun: false,
4207
- validationPhases
5663
+ reproductionTest,
5664
+ reproductionTestPath,
5665
+ validationPhases,
5666
+ proofOfFix: proofOfFixVerification
4208
5667
  };
4209
5668
  await writePatchManifest(manifestFilePath, finalArtifact);
4210
5669
  return {
@@ -4221,7 +5680,8 @@ var applyPatchFileTool = defineTool({
4221
5680
  postinstallConfigured: patchMode === "patch-package",
4222
5681
  patchArtifact: finalArtifact,
4223
5682
  validation: validationResult,
4224
- validationPhases
5683
+ validationPhases,
5684
+ proofOfFix: proofOfFixVerification
4225
5685
  };
4226
5686
  });
4227
5687
  } catch (err) {
@@ -4465,6 +5925,9 @@ async function tryLocalPatchFallback(params) {
4465
5925
  workspace: params.workspace,
4466
5926
  riskLevel: patchResult.riskLevel,
4467
5927
  validateWithTests: params.runTests,
5928
+ reproductionTest: patchResult.reproductionTest,
5929
+ proofOfFix: params.proofOfFix,
5930
+ requireProofOfFix: params.requireProofOfFix,
4468
5931
  dryRun: params.dryRun
4469
5932
  });
4470
5933
  steps += 1;
@@ -4513,8 +5976,14 @@ function resolveLocalRunOptions(options) {
4513
5976
  const runTests = options.runTests ?? false;
4514
5977
  const policy = options.policy ?? "";
4515
5978
  const patchesDir = options.patchesDir || "./patches";
4516
- const constraints = options.constraints ?? {};
4517
5979
  const loadedPolicy = loadPolicy(cwd, options.policy);
5980
+ const constraints = {
5981
+ ...options.constraints,
5982
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? loadedPolicy.constraints?.typecheck,
5983
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? loadedPolicy.constraints?.supplyChainPolicy,
5984
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor,
5985
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? loadedPolicy.constraints?.requireProofOfFix
5986
+ };
4518
5987
  const llmProvider = resolveProvider(options);
4519
5988
  return {
4520
5989
  cwd,
@@ -4545,12 +6014,15 @@ function resolveLocalRunOptions(options) {
4545
6014
  regressionCheck: options.regressionCheck ?? false,
4546
6015
  escalationGraph: options.escalationGraph ?? loadedPolicy.escalationGraph,
4547
6016
  offlineIntelligence: options.offlineIntelligence ?? false,
4548
- intelligenceSnapshotPath: options.intelligenceSnapshotPath
6017
+ intelligenceSnapshotPath: options.intelligenceSnapshotPath,
6018
+ proofOfFix: options.proofOfFix,
6019
+ requireProofOfFix: options.requireProofOfFix ?? constraints.requireProofOfFix,
6020
+ breakingChangeRefactor: options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor
4549
6021
  };
4550
6022
  }
4551
6023
 
4552
6024
  // src/remediation/local/vulnerability-match.ts
4553
- import semver4 from "semver";
6025
+ import semver5 from "semver";
4554
6026
  function findVulnerablePackages(cveDetails, installedPackages) {
4555
6027
  const vulnerablePackages = [];
4556
6028
  for (const affected of cveDetails.affectedPackages) {
@@ -4559,10 +6031,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
4559
6031
  if (affected.ecosystem !== "npm") continue;
4560
6032
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
4561
6033
  for (const installed of matches) {
4562
- if (!semver4.valid(installed.version)) continue;
6034
+ if (!semver5.valid(installed.version)) continue;
4563
6035
  let isVulnerable = false;
4564
6036
  try {
4565
- isVulnerable = semver4.satisfies(installed.version, affected.vulnerableRange, {
6037
+ isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4566
6038
  includePrerelease: false
4567
6039
  });
4568
6040
  } catch {
@@ -4713,7 +6185,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
4713
6185
  regressionCheck,
4714
6186
  escalationGraph,
4715
6187
  offlineIntelligence,
4716
- intelligenceSnapshotPath
6188
+ intelligenceSnapshotPath,
6189
+ proofOfFix,
6190
+ requireProofOfFix
4717
6191
  } = resolved;
4718
6192
  const correlation = {
4719
6193
  requestId: options.requestId,
@@ -4890,7 +6364,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
4890
6364
  installPreferOffline: constraints.installPreferOffline,
4891
6365
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
4892
6366
  workspace: constraints.workspace,
4893
- vulnerableRange: vulnerable.affected.vulnerableRange
6367
+ vulnerableRange: vulnerable.affected.vulnerableRange,
6368
+ proofOfFix,
6369
+ requireProofOfFix
4894
6370
  });
4895
6371
  agentSteps += fallback.steps;
4896
6372
  collectedResults.push(finalizePatchResult(fallback.result));
@@ -4905,7 +6381,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
4905
6381
  };
4906
6382
  if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
4907
6383
  try {
4908
- if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
6384
+ if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
4909
6385
  includePrerelease: false
4910
6386
  })) {
4911
6387
  primaryResult.regressionDetected = true;
@@ -4996,8 +6472,8 @@ function accumulateStepResults(params) {
4996
6472
  }
4997
6473
 
4998
6474
  // src/remediation/orchestration-prompt.ts
4999
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
5000
- import { join as join17 } from "path";
6475
+ import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
6476
+ import { join as join20 } from "path";
5001
6477
  function buildProviderAddendum(provider, personality = "balanced") {
5002
6478
  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.";
5003
6479
  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.";
@@ -5008,13 +6484,13 @@ Provider profile:
5008
6484
  - ${personalityDirective}`;
5009
6485
  }
5010
6486
  function loadOrchestrationPrompt(ctx) {
5011
- const promptPath = join17(
6487
+ const promptPath = join20(
5012
6488
  process.cwd(),
5013
6489
  ".github",
5014
6490
  "instructions",
5015
6491
  "orchestration.instructions.md"
5016
6492
  );
5017
- if (!existsSync11(promptPath)) {
6493
+ if (!existsSync15(promptPath)) {
5018
6494
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
5019
6495
  Working directory: ${ctx.cwd}
5020
6496
  Package manager: ${ctx.packageManager}
@@ -5040,7 +6516,7 @@ Fallback sequence (when neither version bump nor override can be applied):
5040
6516
 
5041
6517
  Always respect dryRun and policy constraints.`;
5042
6518
  }
5043
- const template = readFileSync11(promptPath, "utf8");
6519
+ const template = readFileSync15(promptPath, "utf8");
5044
6520
  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(
5045
6521
  "{{directDependenciesOnly}}",
5046
6522
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -5053,81 +6529,17 @@ function createProgressEmitter(options) {
5053
6529
  if (!options.onProgress) return;
5054
6530
  options.onProgress({
5055
6531
  stage,
5056
- detail,
5057
- at: (/* @__PURE__ */ new Date()).toISOString(),
5058
- provider: extra?.provider,
5059
- model: extra?.model
5060
- });
5061
- };
5062
- }
5063
-
5064
- // src/remediation/tools/lookup-cve.ts
5065
- import { z as z9 } from "zod";
5066
-
5067
- // src/intelligence/sources/cisa-kev.ts
5068
- var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
5069
- async function fetchCisaKevFeed() {
5070
- try {
5071
- const res = await httpClient({ url: CISA_KEV_URL });
5072
- if (!res.ok) return void 0;
5073
- return res.data;
5074
- } catch {
5075
- return void 0;
5076
- }
5077
- }
5078
- function findKevEntry(feed, cveId) {
5079
- if (!feed?.vulnerabilities?.length) return void 0;
5080
- const normalized = cveId.toUpperCase();
5081
- return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
5082
- }
5083
- async function enrichWithCisaKev(details) {
5084
- const feed = await fetchCisaKevFeed();
5085
- const entry = findKevEntry(feed, details.id);
5086
- if (!entry) return details;
5087
- details.kev = {
5088
- knownExploited: true,
5089
- dateAdded: entry.dateAdded,
5090
- dueDate: entry.dueDate,
5091
- requiredAction: entry.requiredAction,
5092
- knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
5093
- };
5094
- if (!details.references.includes(CISA_KEV_URL)) {
5095
- details.references.push(CISA_KEV_URL);
5096
- }
5097
- return details;
5098
- }
5099
-
5100
- // src/intelligence/sources/epss.ts
5101
- async function fetchEpss(cveId) {
5102
- const { epssApi } = getIntelligenceSourceConfig();
5103
- if (!epssApi) return void 0;
5104
- try {
5105
- const url = new URL(epssApi);
5106
- url.searchParams.set("cve", cveId);
5107
- const res = await httpClient({ url: url.toString() });
5108
- if (!res.ok) return void 0;
5109
- const body = res.data;
5110
- return body.data?.[0];
5111
- } catch {
5112
- return void 0;
5113
- }
5114
- }
5115
- async function enrichWithEpss(details) {
5116
- const row = await fetchEpss(details.id);
5117
- if (!row) return details;
5118
- const score = Number.parseFloat(row.epss);
5119
- const percentile = Number.parseFloat(row.percentile);
5120
- if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
5121
- return details;
5122
- }
5123
- details.epss = {
5124
- score,
5125
- percentile,
5126
- date: row.date
6532
+ detail,
6533
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6534
+ provider: extra?.provider,
6535
+ model: extra?.model
6536
+ });
5127
6537
  };
5128
- return details;
5129
6538
  }
5130
6539
 
6540
+ // src/remediation/tools/lookup-cve.ts
6541
+ import { z as z9 } from "zod";
6542
+
5131
6543
  // src/intelligence/sources/cve-services.ts
5132
6544
  function pickEnglishDescription(container) {
5133
6545
  if (!container?.descriptions?.length) return void 0;
@@ -5411,7 +6823,7 @@ var lookupCveTool = defineTool({
5411
6823
 
5412
6824
  // src/remediation/tools/check-version-match.ts
5413
6825
  import { z as z10 } from "zod";
5414
- import semver6 from "semver";
6826
+ import semver7 from "semver";
5415
6827
  var affectedPackageSchema = z10.object({
5416
6828
  name: z10.string(),
5417
6829
  ecosystem: z10.literal("npm"),
@@ -5440,10 +6852,10 @@ var checkVersionMatchTool = defineTool({
5440
6852
  (p) => p.name === affected.name
5441
6853
  );
5442
6854
  for (const installed of matches) {
5443
- if (!semver6.valid(installed.version)) continue;
6855
+ if (!semver7.valid(installed.version)) continue;
5444
6856
  let isVulnerable = false;
5445
6857
  try {
5446
- isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
6858
+ isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
5447
6859
  includePrerelease: false
5448
6860
  });
5449
6861
  } catch {
@@ -5583,6 +6995,8 @@ function createRuntimeToolsForRun(options) {
5583
6995
  installPreferOffline: constraints.installPreferOffline,
5584
6996
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
5585
6997
  workspace: constraints.workspace,
6998
+ typecheck: input.typecheck ?? options.typecheck ?? constraints.typecheck,
6999
+ breakingChangeRefactor: input.breakingChangeRefactor ?? options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor,
5586
7000
  dryRun: preview ? true : input.dryRun
5587
7001
  })
5588
7002
  };
@@ -5607,6 +7021,8 @@ function createRuntimeToolsForRun(options) {
5607
7021
  installPreferOffline: constraints.installPreferOffline,
5608
7022
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
5609
7023
  workspace: constraints.workspace,
7024
+ proofOfFix: input.proofOfFix ?? options.proofOfFix,
7025
+ requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
5610
7026
  dryRun: preview ? true : input.dryRun
5611
7027
  })
5612
7028
  };
@@ -5761,7 +7177,7 @@ async function runRemediationPipeline(cveId, options = {}) {
5761
7177
  }
5762
7178
 
5763
7179
  // src/api/change-request/index.ts
5764
- import { execa as execa9 } from "execa";
7180
+ import { execa as execa10 } from "execa";
5765
7181
  import { randomBytes } from "crypto";
5766
7182
  function sanitizeBranchToken(value) {
5767
7183
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -5790,22 +7206,68 @@ function resolveProvider2(options) {
5790
7206
  return options.provider ?? "github";
5791
7207
  }
5792
7208
  function resolveGrouping(options) {
5793
- if (options.grouping && options.grouping !== "all") {
7209
+ const allowed = ["all", "per-cve", "per-package", "by-severity"];
7210
+ if (options.grouping && !allowed.includes(options.grouping)) {
5794
7211
  throw new Error(
5795
- "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
7212
+ `Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
5796
7213
  );
5797
7214
  }
5798
- return "all";
7215
+ return options.grouping ?? "all";
7216
+ }
7217
+ function groupReports(reports, grouping) {
7218
+ if (reports.length === 0) return [];
7219
+ if (grouping === "all") {
7220
+ return [reports];
7221
+ }
7222
+ if (grouping === "per-cve") {
7223
+ const map = /* @__PURE__ */ new Map();
7224
+ for (const report of reports) {
7225
+ const key = report.cveId.toUpperCase();
7226
+ const list = map.get(key) ?? [];
7227
+ list.push(report);
7228
+ map.set(key, list);
7229
+ }
7230
+ return Array.from(map.values());
7231
+ }
7232
+ if (grouping === "per-package") {
7233
+ const map = /* @__PURE__ */ new Map();
7234
+ for (const report of reports) {
7235
+ const pkgs = new Set(report.results.map((r) => r.packageName));
7236
+ if (pkgs.size === 0) {
7237
+ const list = map.get("misc") ?? [];
7238
+ list.push(report);
7239
+ map.set("misc", list);
7240
+ } else {
7241
+ for (const pkg of pkgs) {
7242
+ const list = map.get(pkg) ?? [];
7243
+ list.push(report);
7244
+ map.set(pkg, list);
7245
+ }
7246
+ }
7247
+ }
7248
+ return Array.from(map.values());
7249
+ }
7250
+ if (grouping === "by-severity") {
7251
+ const map = /* @__PURE__ */ new Map();
7252
+ for (const report of reports) {
7253
+ const sev = report.cveDetails?.severity ?? "UNKNOWN";
7254
+ const list = map.get(sev) ?? [];
7255
+ list.push(report);
7256
+ map.set(sev, list);
7257
+ }
7258
+ return Array.from(map.values());
7259
+ }
7260
+ return [reports];
5799
7261
  }
5800
7262
  async function detectCurrentBranch(cwd) {
5801
- const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7263
+ const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
5802
7264
  cwd,
5803
7265
  stdio: "pipe"
5804
7266
  });
5805
7267
  return result.stdout.trim();
5806
7268
  }
5807
7269
  async function ensureRepoHasChanges(cwd) {
5808
- const diff = await execa9("git", ["status", "--porcelain"], {
7270
+ const diff = await execa10("git", ["status", "--porcelain"], {
5809
7271
  cwd,
5810
7272
  stdio: "pipe"
5811
7273
  });
@@ -5834,7 +7296,7 @@ async function createGitHubRequest(params) {
5834
7296
  if (params.draft) {
5835
7297
  args.push("--draft");
5836
7298
  }
5837
- const result = await execa9("gh", args, {
7299
+ const result = await execa10("gh", args, {
5838
7300
  cwd: params.cwd,
5839
7301
  stdio: "pipe"
5840
7302
  });
@@ -5859,7 +7321,7 @@ async function createGitLabRequest(params) {
5859
7321
  if (params.draft) {
5860
7322
  args.push("--draft");
5861
7323
  }
5862
- const result = await execa9("glab", args, {
7324
+ const result = await execa10("glab", args, {
5863
7325
  cwd: params.cwd,
5864
7326
  stdio: "pipe"
5865
7327
  });
@@ -5869,64 +7331,68 @@ async function createChangeRequestsForReports(params) {
5869
7331
  const { cwd, options, reports } = params;
5870
7332
  const provider = resolveProvider2(options);
5871
7333
  const grouping = resolveGrouping(options);
5872
- const plan = buildPlan(reports);
5873
- const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
5874
- const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
5875
- const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
5876
- const body = bodyFooter ? `${plan.body}
7334
+ const groups = groupReports(reports, grouping);
7335
+ if (groups.length === 0) {
7336
+ return [];
7337
+ }
7338
+ const results = [];
7339
+ const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
7340
+ const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
7341
+ const branchPrefix = options.branchPrefix ?? "autoremediator";
7342
+ const pushRemote = options.pushRemote ?? "origin";
7343
+ if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
7344
+ throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
7345
+ }
7346
+ if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
7347
+ throw new Error(`Invalid repository format: ${options.repository}`);
7348
+ }
7349
+ for (const group of groups) {
7350
+ const plan = buildPlan(group);
7351
+ const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
7352
+ const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
7353
+ const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
7354
+ const body = bodyFooter ? `${plan.body}
5877
7355
 
5878
7356
  ${bodyFooter}` : plan.body;
5879
- try {
5880
- const hasChanges = await ensureRepoHasChanges(cwd);
5881
7357
  if (!hasChanges) {
5882
- return [
5883
- {
5884
- provider,
5885
- grouping,
5886
- repository: options.repository,
5887
- branchName: "",
5888
- title,
5889
- body,
5890
- created: false,
5891
- cveIds: plan.cveIds,
5892
- packageNames: plan.packageNames,
5893
- error: "No repository changes to include in change request."
5894
- }
5895
- ];
7358
+ results.push({
7359
+ provider,
7360
+ grouping,
7361
+ repository: options.repository,
7362
+ branchName: "",
7363
+ title,
7364
+ body,
7365
+ created: false,
7366
+ cveIds: plan.cveIds,
7367
+ packageNames: plan.packageNames,
7368
+ error: "No repository changes to include in change request."
7369
+ });
7370
+ continue;
5896
7371
  }
5897
- const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
5898
- const branchPrefix = options.branchPrefix ?? "autoremediator";
5899
7372
  const branchName = toBranchName(branchPrefix, plan.cveIds);
5900
- const pushRemote = options.pushRemote ?? "origin";
5901
- if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
5902
- throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
5903
- }
5904
- if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
5905
- throw new Error(`Invalid repository format: ${options.repository}`);
5906
- }
5907
- await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
5908
- await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
5909
- await execa9("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
5910
- await execa9("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
5911
- const url = provider === "github" ? await createGitHubRequest({
5912
- cwd,
5913
- repository: options.repository,
5914
- head: branchName,
5915
- base: baseBranch,
5916
- title,
5917
- body,
5918
- draft: options.draft
5919
- }) : await createGitLabRequest({
5920
- cwd,
5921
- repository: options.repository,
5922
- source: branchName,
5923
- target: baseBranch,
5924
- title,
5925
- body,
5926
- draft: options.draft
5927
- });
5928
- return [
5929
- {
7373
+ try {
7374
+ await execa10("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
7375
+ await execa10("git", ["add", "-A"], { cwd, stdio: "pipe" });
7376
+ await execa10("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
7377
+ await execa10("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
7378
+ const url = provider === "github" ? await createGitHubRequest({
7379
+ cwd,
7380
+ repository: options.repository,
7381
+ head: branchName,
7382
+ base: baseBranch,
7383
+ title,
7384
+ body,
7385
+ draft: options.draft
7386
+ }) : await createGitLabRequest({
7387
+ cwd,
7388
+ repository: options.repository,
7389
+ source: branchName,
7390
+ target: baseBranch,
7391
+ title,
7392
+ body,
7393
+ draft: options.draft
7394
+ });
7395
+ results.push({
5930
7396
  provider,
5931
7397
  grouping,
5932
7398
  repository: options.repository,
@@ -5938,11 +7404,13 @@ ${bodyFooter}` : plan.body;
5938
7404
  url,
5939
7405
  cveIds: plan.cveIds,
5940
7406
  packageNames: plan.packageNames
7407
+ });
7408
+ if (groups.length > 1) {
7409
+ await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
7410
+ });
5941
7411
  }
5942
- ];
5943
- } catch (error) {
5944
- return [
5945
- {
7412
+ } catch (error) {
7413
+ results.push({
5946
7414
  provider,
5947
7415
  grouping,
5948
7416
  repository: options.repository,
@@ -5954,9 +7422,10 @@ ${bodyFooter}` : plan.body;
5954
7422
  cveIds: plan.cveIds,
5955
7423
  packageNames: plan.packageNames,
5956
7424
  error: error instanceof Error ? error.message : String(error)
5957
- }
5958
- ];
7425
+ });
7426
+ }
5959
7427
  }
7428
+ return results;
5960
7429
  }
5961
7430
 
5962
7431
  // src/api/context.ts
@@ -5984,13 +7453,17 @@ function resolveConstraints(options, cwd) {
5984
7453
  installMode: options.constraints?.installMode ?? policy.constraints?.installMode ?? "deterministic",
5985
7454
  installPreferOffline: options.constraints?.installPreferOffline ?? policy.constraints?.installPreferOffline,
5986
7455
  enforceFrozenLockfile: options.constraints?.enforceFrozenLockfile ?? policy.constraints?.enforceFrozenLockfile,
5987
- workspace: options.constraints?.workspace ?? policy.constraints?.workspace
7456
+ workspace: options.constraints?.workspace ?? policy.constraints?.workspace,
7457
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? policy.constraints?.typecheck,
7458
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? policy.constraints?.requireProofOfFix,
7459
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? policy.constraints?.enableBreakingChangeRefactor,
7460
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? policy.constraints?.supplyChainPolicy
5988
7461
  };
5989
7462
  }
5990
7463
 
5991
7464
  // src/platform/evidence.ts
5992
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
5993
- import { join as join18 } from "path";
7465
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync6 } from "fs";
7466
+ import { join as join21 } from "path";
5994
7467
  function createEvidenceLog(cwd, cveIds, context = {}) {
5995
7468
  return {
5996
7469
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -6022,10 +7495,10 @@ function finalizeEvidence(log) {
6022
7495
  return log;
6023
7496
  }
6024
7497
  function writeEvidenceLog(cwd, log) {
6025
- const dir = join18(cwd, ".autoremediator", "evidence");
7498
+ const dir = join21(cwd, ".autoremediator", "evidence");
6026
7499
  mkdirSync3(dir, { recursive: true });
6027
- const filePath = join18(dir, `${log.runId}.json`);
6028
- writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
7500
+ const filePath = join21(dir, `${log.runId}.json`);
7501
+ writeFileSync6(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
6029
7502
  return filePath;
6030
7503
  }
6031
7504
 
@@ -6524,10 +7997,15 @@ function ciExitCode(summary) {
6524
7997
  }
6525
7998
 
6526
7999
  // src/api/remediate/validators.ts
8000
+ var CVE_ID_REGEX = /^CVE-\d{4}-\d{1,7}$/i;
8001
+ var GHSA_ID_REGEX = /^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/i;
8002
+ function isVulnerabilityId(id) {
8003
+ return CVE_ID_REGEX.test(id) || GHSA_ID_REGEX.test(id);
8004
+ }
6527
8005
  function assertValidCveId(cveId) {
6528
- if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
8006
+ if (!isVulnerabilityId(cveId)) {
6529
8007
  throw new Error(
6530
- `Invalid CVE ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337).`
8008
+ `Invalid vulnerability ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337) or GHSA-xxxx-xxxx-xxxx.`
6531
8009
  );
6532
8010
  }
6533
8011
  }
@@ -6623,13 +8101,14 @@ async function planRemediation(cveId, options = {}) {
6623
8101
  }
6624
8102
 
6625
8103
  // src/scanner/parse-input.ts
6626
- import { extname, resolve as resolve3 } from "path";
6627
- import { readFileSync as readFileSync15 } from "fs";
6628
- import { execa as execa10 } from "execa";
8104
+ import { extname, resolve as resolve5 } from "path";
8105
+ import { readFileSync as readFileSync19 } from "fs";
8106
+ import { execa as execa11 } from "execa";
6629
8107
 
6630
8108
  // src/scanner/adapters/npm-audit.ts
6631
- import { readFileSync as readFileSync12 } from "fs";
8109
+ import { readFileSync as readFileSync16 } from "fs";
6632
8110
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
8111
+ var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6633
8112
  function normalizeSeverity(raw) {
6634
8113
  if (!raw) return "UNKNOWN";
6635
8114
  const up = raw.toUpperCase();
@@ -6644,7 +8123,8 @@ function parseNpmAuditJsonFromString(content) {
6644
8123
  for (const vuln of Object.values(report.vulnerabilities ?? {})) {
6645
8124
  for (const viaEntry of vuln.via ?? []) {
6646
8125
  const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
6647
- const matches = text.match(CVE_REGEX) ?? [];
8126
+ const cveMatches = text.match(CVE_REGEX) ?? [];
8127
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
6648
8128
  for (const match of matches) {
6649
8129
  const cveId = match.toUpperCase();
6650
8130
  const key = `${cveId}:${vuln.name}`;
@@ -6662,13 +8142,14 @@ function parseNpmAuditJsonFromString(content) {
6662
8142
  return findings;
6663
8143
  }
6664
8144
  function parseNpmAuditJsonFile(filePath) {
6665
- const content = readFileSync12(filePath, "utf8");
8145
+ const content = readFileSync16(filePath, "utf8");
6666
8146
  return parseNpmAuditJsonFromString(content);
6667
8147
  }
6668
8148
 
6669
8149
  // src/scanner/adapters/yarn-audit.ts
6670
- import { readFileSync as readFileSync13 } from "fs";
8150
+ import { readFileSync as readFileSync17 } from "fs";
6671
8151
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
8152
+ var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6672
8153
  function normalizeSeverity2(raw) {
6673
8154
  if (!raw) return "UNKNOWN";
6674
8155
  const up = raw.toUpperCase();
@@ -6693,7 +8174,8 @@ function parseYarnAuditJsonFromString(content) {
6693
8174
  const packageName = advisory?.module_name;
6694
8175
  const severity = normalizeSeverity2(advisory?.severity);
6695
8176
  const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
6696
- const matches = text.match(CVE_REGEX2) ?? [];
8177
+ const cveMatches = text.match(CVE_REGEX2) ?? [];
8178
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
6697
8179
  for (const match of matches) {
6698
8180
  const cveId = match.toUpperCase();
6699
8181
  const key = `${cveId}:${packageName ?? ""}`;
@@ -6710,12 +8192,12 @@ function parseYarnAuditJsonFromString(content) {
6710
8192
  return findings;
6711
8193
  }
6712
8194
  function parseYarnAuditJsonFile(filePath) {
6713
- const content = readFileSync13(filePath, "utf8");
8195
+ const content = readFileSync17(filePath, "utf8");
6714
8196
  return parseYarnAuditJsonFromString(content);
6715
8197
  }
6716
8198
 
6717
8199
  // src/scanner/adapters/sarif.ts
6718
- import { readFileSync as readFileSync14 } from "fs";
8200
+ import { readFileSync as readFileSync18 } from "fs";
6719
8201
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
6720
8202
  function extractPackageName(result) {
6721
8203
  const pkg = result.properties?.["packageName"];
@@ -6753,7 +8235,7 @@ function parseSarifFromString(content) {
6753
8235
  return findings;
6754
8236
  }
6755
8237
  function parseSarifFile(filePath) {
6756
- const content = readFileSync14(filePath, "utf8");
8238
+ const content = readFileSync18(filePath, "utf8");
6757
8239
  return parseSarifFromString(content);
6758
8240
  }
6759
8241
 
@@ -6762,7 +8244,7 @@ function parseScanInput(filePath, format) {
6762
8244
  if (filePath.includes("\0")) {
6763
8245
  throw new Error("Invalid scan input path: path contains null bytes");
6764
8246
  }
6765
- const resolvedPath = resolve3(filePath);
8247
+ const resolvedPath = resolve5(filePath);
6766
8248
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
6767
8249
  if (resolved === "npm-audit") {
6768
8250
  return parseNpmAuditJsonFile(resolvedPath);
@@ -6784,7 +8266,7 @@ async function parseScanInputFromAudit(params) {
6784
8266
  }
6785
8267
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
6786
8268
  const [cmd, ...args] = command;
6787
- const result = await execa10(cmd, args, {
8269
+ const result = await execa11(cmd, args, {
6788
8270
  cwd: params.cwd,
6789
8271
  stdio: "pipe",
6790
8272
  reject: false
@@ -6838,7 +8320,7 @@ function inferFormat(filePath) {
6838
8320
  const ext = extname(filePath).toLowerCase();
6839
8321
  if (ext === ".sarif") return "sarif";
6840
8322
  try {
6841
- const content = readFileSync15(filePath, "utf8");
8323
+ const content = readFileSync19(filePath, "utf8");
6842
8324
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
6843
8325
  if (firstLine) {
6844
8326
  const parsed = JSON.parse(firstLine);
@@ -7249,14 +8731,14 @@ async function remediatePortfolio(targets, options = {}) {
7249
8731
  }
7250
8732
 
7251
8733
  // src/api/update-outdated/index.ts
7252
- import { join as join19 } from "path";
7253
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
7254
- import { execa as execa11 } from "execa";
8734
+ import { join as join22 } from "path";
8735
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
8736
+ import { execa as execa12 } from "execa";
7255
8737
  async function applyBump(params) {
7256
- const pkgPath = join19(params.cwd, "package.json");
8738
+ const pkgPath = join22(params.cwd, "package.json");
7257
8739
  let pkgJson;
7258
8740
  try {
7259
- pkgJson = JSON.parse(readFileSync16(pkgPath, "utf8"));
8741
+ pkgJson = JSON.parse(readFileSync20(pkgPath, "utf8"));
7260
8742
  } catch {
7261
8743
  return {
7262
8744
  applied: false,
@@ -7278,13 +8760,13 @@ async function applyBump(params) {
7278
8760
  const newRange = `${prefix}${params.toVersion}`;
7279
8761
  return withRepoLock(params.cwd, async () => {
7280
8762
  pkgJson[depField][params.packageName] = newRange;
7281
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8763
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7282
8764
  try {
7283
8765
  const [installCmd, ...installArgs] = params.installCommand;
7284
- await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8766
+ await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
7285
8767
  } catch (err) {
7286
8768
  pkgJson[depField][params.packageName] = currentRange;
7287
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8769
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7288
8770
  const message = err instanceof Error ? err.message : String(err);
7289
8771
  return {
7290
8772
  applied: false,
@@ -7294,13 +8776,13 @@ async function applyBump(params) {
7294
8776
  if (params.runTests) {
7295
8777
  try {
7296
8778
  const [testCmd, ...testArgs] = params.testCommand;
7297
- await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8779
+ await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
7298
8780
  } catch (err) {
7299
8781
  pkgJson[depField][params.packageName] = currentRange;
7300
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8782
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7301
8783
  try {
7302
8784
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
7303
- await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8785
+ await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
7304
8786
  } catch {
7305
8787
  }
7306
8788
  const message = err instanceof Error ? err.message : String(err);
@@ -7501,131 +8983,169 @@ async function checkReachability(options) {
7501
8983
  return assessPackageReachability(cwd, options.packageName, options.symbol);
7502
8984
  }
7503
8985
 
7504
- // src/remediation/evaluate-package.ts
7505
- import semver7 from "semver";
7506
- async function evaluatePackage(packageName, options = {}) {
7507
- const version = options.version?.trim();
7508
- const enrich = options.enrichIntelligence !== false;
7509
- let versions = [];
7510
- let latestVersion;
7511
- try {
7512
- versions = await fetchPackageVersions(packageName);
7513
- if (versions.length > 0) {
7514
- const valid = versions.filter((v) => semver7.valid(v)).sort((a, b) => semver7.compare(a, b));
7515
- latestVersion = valid[valid.length - 1];
7516
- }
7517
- } catch {
7518
- }
7519
- let rawVulns = [];
7520
- try {
7521
- rawVulns = await queryOsvPackage(packageName, version);
7522
- } catch {
7523
- rawVulns = [];
7524
- }
7525
- let kevFeed = null;
7526
- if (enrich) {
8986
+ // src/scanner/delta-scan.ts
8987
+ import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
8988
+ import { join as join23 } from "path";
8989
+ import { execa as execa13 } from "execa";
8990
+ import semver9 from "semver";
8991
+
8992
+ // src/scanner/lockfile-diff.ts
8993
+ import YAML2 from "yaml";
8994
+ import semver8 from "semver";
8995
+ function parseLockfileDependencies(filename, content) {
8996
+ const result = /* @__PURE__ */ new Map();
8997
+ const add = (name, ver) => {
8998
+ if (!name || !ver) return;
8999
+ const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
9000
+ let set = result.get(name);
9001
+ if (!set) {
9002
+ set = /* @__PURE__ */ new Set();
9003
+ result.set(name, set);
9004
+ }
9005
+ set.add(cleanVer);
9006
+ };
9007
+ if (filename.endsWith("package-lock.json")) {
7527
9008
  try {
7528
- kevFeed = await fetchCisaKevFeed();
7529
- } catch {
7530
- kevFeed = null;
7531
- }
7532
- }
7533
- const entries = [];
7534
- for (const raw of rawVulns) {
7535
- const cveDetails = parseOsvVuln(raw);
7536
- const affected = cveDetails.affectedPackages.find(
7537
- (p) => p.name.toLowerCase() === packageName.toLowerCase()
7538
- );
7539
- if (version && affected?.vulnerableRange) {
7540
- const cleanVer = semver7.clean(version) || version;
7541
- if (semver7.valid(cleanVer) && !semver7.satisfies(cleanVer, affected.vulnerableRange)) {
7542
- continue;
9009
+ const data = JSON.parse(content);
9010
+ if (data.packages && typeof data.packages === "object") {
9011
+ for (const [key, pkg] of Object.entries(data.packages)) {
9012
+ if (!key || key === "" || typeof pkg !== "object" || !pkg) continue;
9013
+ const idx = key.lastIndexOf("node_modules/");
9014
+ if (idx !== -1) {
9015
+ const rawName = key.slice(idx + "node_modules/".length);
9016
+ const ver = pkg.version;
9017
+ add(rawName, ver);
9018
+ }
9019
+ }
9020
+ }
9021
+ if (data.dependencies && typeof data.dependencies === "object") {
9022
+ const walkDeps = (deps) => {
9023
+ for (const [name, info] of Object.entries(deps)) {
9024
+ if (!info || typeof info !== "object") continue;
9025
+ const depObj = info;
9026
+ add(name, depObj.version);
9027
+ if (depObj.dependencies && typeof depObj.dependencies === "object") {
9028
+ walkDeps(depObj.dependencies);
9029
+ }
9030
+ }
9031
+ };
9032
+ walkDeps(data.dependencies);
7543
9033
  }
9034
+ } catch {
7544
9035
  }
7545
- let inCisaKev = false;
7546
- let epssScore;
7547
- let epssPercentile;
7548
- if (enrich && cveDetails.id.startsWith("CVE-")) {
7549
- if (kevFeed) {
7550
- inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
9036
+ } else if (filename.endsWith("pnpm-lock.yaml")) {
9037
+ try {
9038
+ const data = YAML2.parse(content);
9039
+ if (data?.packages && typeof data.packages === "object") {
9040
+ for (const [key, val] of Object.entries(data.packages)) {
9041
+ let rawKey = key.replace(/^\//, "");
9042
+ const parenIdx = rawKey.indexOf("(");
9043
+ if (parenIdx !== -1) {
9044
+ rawKey = rawKey.slice(0, parenIdx);
9045
+ }
9046
+ const lastAt = rawKey.lastIndexOf("@");
9047
+ if (lastAt > 0) {
9048
+ const name = val?.name ?? rawKey.slice(0, lastAt);
9049
+ const version = val?.version ?? rawKey.slice(lastAt + 1);
9050
+ add(name, version);
9051
+ }
9052
+ }
7551
9053
  }
7552
- try {
7553
- const epss = await fetchEpss(cveDetails.id);
7554
- if (epss) {
7555
- epssScore = Number.parseFloat(epss.epss);
7556
- epssPercentile = Number.parseFloat(epss.percentile);
9054
+ if (data?.snapshots && typeof data.snapshots === "object") {
9055
+ for (const key of Object.keys(data.snapshots)) {
9056
+ let rawKey = key.replace(/^\//, "");
9057
+ const parenIdx = rawKey.indexOf("(");
9058
+ if (parenIdx !== -1) {
9059
+ rawKey = rawKey.slice(0, parenIdx);
9060
+ }
9061
+ const lastAt = rawKey.lastIndexOf("@");
9062
+ if (lastAt > 0) {
9063
+ const name = rawKey.slice(0, lastAt);
9064
+ const version = rawKey.slice(lastAt + 1);
9065
+ add(name, version);
9066
+ }
7557
9067
  }
7558
- } catch {
7559
9068
  }
9069
+ } catch {
7560
9070
  }
7561
- let safeUpgradeVersion;
7562
- if (affected?.firstPatchedVersion) {
7563
- try {
7564
- const safe = await findSafeUpgradeVersion(
7565
- packageName,
7566
- version || "0.0.0",
7567
- affected.firstPatchedVersion
7568
- );
7569
- safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
7570
- } catch {
7571
- safeUpgradeVersion = affected.firstPatchedVersion;
9071
+ } else if (filename.endsWith("yarn.lock")) {
9072
+ const lines = content.split("\n");
9073
+ let currentPackages = [];
9074
+ for (const line of lines) {
9075
+ if (line.startsWith("#") || line.trim() === "") continue;
9076
+ if (!line.startsWith(" ")) {
9077
+ currentPackages = [];
9078
+ const parts = line.split(",").map((p) => p.trim().replace(/^"/, "").replace(/":?$/, ""));
9079
+ for (const part of parts) {
9080
+ const lastAt = part.lastIndexOf("@");
9081
+ if (lastAt > 0) {
9082
+ currentPackages.push(part.slice(0, lastAt));
9083
+ }
9084
+ }
9085
+ } else {
9086
+ const match = line.match(/^\s+version\s+"?([^"\s]+)"?/);
9087
+ if (match && currentPackages.length > 0) {
9088
+ const ver = match[1];
9089
+ for (const pkg of currentPackages) {
9090
+ add(pkg, ver);
9091
+ }
9092
+ currentPackages = [];
9093
+ }
7572
9094
  }
7573
9095
  }
7574
- entries.push({
7575
- cveId: cveDetails.id,
7576
- severity: cveDetails.severity,
7577
- summary: cveDetails.summary,
7578
- vulnerableRange: affected?.vulnerableRange,
7579
- firstPatchedVersion: affected?.firstPatchedVersion,
7580
- safeUpgradeVersion,
7581
- epssScore,
7582
- epssPercentile,
7583
- inCisaKev,
7584
- references: cveDetails.references
7585
- });
7586
9096
  }
7587
- const isVulnerable = entries.length > 0;
7588
- let verdict = "safe";
7589
- if (isVulnerable) {
7590
- const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
7591
- const hasHighOrCritical = entries.some(
7592
- (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
7593
- );
7594
- if (hasKevOrHighEpss) {
7595
- verdict = "actively-exploited";
7596
- } else if (hasHighOrCritical) {
7597
- verdict = "vulnerable";
7598
- } else {
7599
- verdict = "caution";
9097
+ return result;
9098
+ }
9099
+ function computeLockfileDiff(filename, oldContent, newContent) {
9100
+ const changes = [];
9101
+ const oldDeps = oldContent ? parseLockfileDependencies(filename, oldContent) : /* @__PURE__ */ new Map();
9102
+ const newDeps = newContent ? parseLockfileDependencies(filename, newContent) : /* @__PURE__ */ new Map();
9103
+ const allPkgs = /* @__PURE__ */ new Set([...oldDeps.keys(), ...newDeps.keys()]);
9104
+ for (const pkg of allPkgs) {
9105
+ const oldVers = oldDeps.get(pkg) ?? /* @__PURE__ */ new Set();
9106
+ const newVers = newDeps.get(pkg) ?? /* @__PURE__ */ new Set();
9107
+ for (const nVer of newVers) {
9108
+ if (!oldVers.has(nVer)) {
9109
+ if (oldVers.size === 0) {
9110
+ changes.push({
9111
+ packageName: pkg,
9112
+ changeType: "added",
9113
+ newVersion: nVer,
9114
+ dependencyType: "transitive",
9115
+ manifestPath: filename
9116
+ });
9117
+ } else {
9118
+ const [firstOld] = oldVers;
9119
+ const cleanOld = semver8.clean(firstOld) || semver8.coerce(firstOld)?.version;
9120
+ const cleanNew = semver8.clean(nVer) || semver8.coerce(nVer)?.version;
9121
+ const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
9122
+ changes.push({
9123
+ packageName: pkg,
9124
+ changeType: isUpgrade ? "upgraded" : "downgraded",
9125
+ oldVersion: firstOld,
9126
+ newVersion: nVer,
9127
+ dependencyType: "transitive",
9128
+ manifestPath: filename
9129
+ });
9130
+ }
9131
+ }
9132
+ }
9133
+ if (newVers.size === 0 && oldVers.size > 0) {
9134
+ for (const oVer of oldVers) {
9135
+ changes.push({
9136
+ packageName: pkg,
9137
+ changeType: "removed",
9138
+ oldVersion: oVer,
9139
+ dependencyType: "transitive",
9140
+ manifestPath: filename
9141
+ });
9142
+ }
7600
9143
  }
7601
9144
  }
7602
- let recommendedVersion;
7603
- if (isVulnerable) {
7604
- const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver7.valid(v))).sort((a, b) => semver7.compare(a, b));
7605
- recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
7606
- } else {
7607
- recommendedVersion = version || latestVersion;
7608
- }
7609
- const summary = isVulnerable ? `Package "${packageName}${version ? `@${version}` : ""}" has ${entries.length} known vulnerability(ies) (verdict: ${verdict}). Recommended safe version: ${recommendedVersion || "none"}.` : `Package "${packageName}${version ? `@${version}` : ""}" has no known vulnerabilities. Safe to install.`;
7610
- return {
7611
- schemaVersion: "1.0",
7612
- packageName,
7613
- evaluatedVersion: version,
7614
- isVulnerable,
7615
- verdict,
7616
- summary,
7617
- vulnerabilities: entries,
7618
- recommendedVersion,
7619
- latestVersion,
7620
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
7621
- };
9145
+ return changes;
7622
9146
  }
7623
9147
 
7624
9148
  // src/scanner/delta-scan.ts
7625
- import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
7626
- import { join as join20 } from "path";
7627
- import { execa as execa12 } from "execa";
7628
- import semver8 from "semver";
7629
9149
  var DEP_SECTIONS = [
7630
9150
  "dependencies",
7631
9151
  "devDependencies",
@@ -7635,7 +9155,7 @@ var DEP_SECTIONS = [
7635
9155
  async function getChangedManifestPaths(cwd, baseRef) {
7636
9156
  const manifests = /* @__PURE__ */ new Set();
7637
9157
  try {
7638
- const { stdout: diffFiles } = await execa12(
9158
+ const { stdout: diffFiles } = await execa13(
7639
9159
  "git",
7640
9160
  ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
7641
9161
  { cwd, reject: false }
@@ -7646,7 +9166,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7646
9166
  manifests.add(trimmed);
7647
9167
  }
7648
9168
  }
7649
- const { stdout: untrackedFiles } = await execa12(
9169
+ const { stdout: untrackedFiles } = await execa13(
7650
9170
  "git",
7651
9171
  ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
7652
9172
  { cwd, reject: false }
@@ -7658,7 +9178,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7658
9178
  }
7659
9179
  }
7660
9180
  } catch {
7661
- if (existsSync12(join20(cwd, "package.json"))) {
9181
+ if (existsSync16(join23(cwd, "package.json"))) {
7662
9182
  manifests.add("package.json");
7663
9183
  }
7664
9184
  }
@@ -7666,7 +9186,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7666
9186
  }
7667
9187
  async function getOldManifestContent(cwd, baseRef, relPath) {
7668
9188
  try {
7669
- const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
9189
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
7670
9190
  cwd,
7671
9191
  reject: false
7672
9192
  });
@@ -7677,10 +9197,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
7677
9197
  }
7678
9198
  }
7679
9199
  function getNewManifestContent(cwd, relPath) {
7680
- const fullPath = join20(cwd, relPath);
7681
- if (!existsSync12(fullPath)) return null;
9200
+ const fullPath = join23(cwd, relPath);
9201
+ if (!existsSync16(fullPath)) return null;
7682
9202
  try {
7683
- return JSON.parse(readFileSync17(fullPath, "utf8"));
9203
+ return JSON.parse(readFileSync21(fullPath, "utf8"));
7684
9204
  } catch {
7685
9205
  return null;
7686
9206
  }
@@ -7701,9 +9221,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7701
9221
  manifestPath: relPath
7702
9222
  });
7703
9223
  } else if (oldVer !== newVer) {
7704
- const cleanOld = semver8.clean(oldVer) || semver8.coerce(oldVer)?.version;
7705
- const cleanNew = semver8.clean(newVer) || semver8.coerce(newVer)?.version;
7706
- const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
9224
+ const cleanOld = semver9.clean(oldVer) || semver9.coerce(oldVer)?.version;
9225
+ const cleanNew = semver9.clean(newVer) || semver9.coerce(newVer)?.version;
9226
+ const isUpgrade = cleanOld && cleanNew && semver9.valid(cleanOld) && semver9.valid(cleanNew) ? semver9.gt(cleanNew, cleanOld) : true;
7707
9227
  changes.push({
7708
9228
  packageName: pkgName,
7709
9229
  changeType: isUpgrade ? "upgraded" : "downgraded",
@@ -7728,22 +9248,89 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7728
9248
  }
7729
9249
  return changes;
7730
9250
  }
9251
+ var LOCKFILE_PATTERNS = [
9252
+ "package-lock.json",
9253
+ "**/package-lock.json",
9254
+ "pnpm-lock.yaml",
9255
+ "**/pnpm-lock.yaml",
9256
+ "yarn.lock",
9257
+ "**/yarn.lock"
9258
+ ];
9259
+ async function getChangedLockfilePaths(cwd, baseRef) {
9260
+ const lockfiles = /* @__PURE__ */ new Set();
9261
+ try {
9262
+ const { stdout: diffFiles } = await execa13(
9263
+ "git",
9264
+ ["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
9265
+ { cwd, reject: false }
9266
+ );
9267
+ for (const f of diffFiles.split("\n")) {
9268
+ const trimmed = f.trim();
9269
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
9270
+ lockfiles.add(trimmed);
9271
+ }
9272
+ }
9273
+ const { stdout: untrackedFiles } = await execa13(
9274
+ "git",
9275
+ ["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
9276
+ { cwd, reject: false }
9277
+ );
9278
+ for (const f of untrackedFiles.split("\n")) {
9279
+ const trimmed = f.trim();
9280
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
9281
+ lockfiles.add(trimmed);
9282
+ }
9283
+ }
9284
+ } catch {
9285
+ }
9286
+ return Array.from(lockfiles);
9287
+ }
9288
+ async function getFileContentAtRef(cwd, baseRef, relPath) {
9289
+ try {
9290
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
9291
+ cwd,
9292
+ reject: false
9293
+ });
9294
+ return stdout || null;
9295
+ } catch {
9296
+ return null;
9297
+ }
9298
+ }
7731
9299
  async function scanDelta(options = {}) {
7732
9300
  const cwd = options.cwd ?? process.cwd();
7733
9301
  const baseRef = options.baseRef ?? "HEAD";
7734
9302
  const changedManifests = await getChangedManifestPaths(cwd, baseRef);
9303
+ const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
7735
9304
  const dependencyChanges = [];
9305
+ const directPkgs = /* @__PURE__ */ new Set();
7736
9306
  for (const manifestRel of changedManifests) {
7737
9307
  const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
7738
9308
  const newPkg = getNewManifestContent(cwd, manifestRel);
7739
9309
  const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
9310
+ for (const d of diffs) {
9311
+ directPkgs.add(d.packageName);
9312
+ }
7740
9313
  dependencyChanges.push(...diffs);
7741
9314
  }
9315
+ for (const lockfileRel of changedLockfiles) {
9316
+ const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
9317
+ const fullPath = join23(cwd, lockfileRel);
9318
+ const newContent = existsSync16(fullPath) ? readFileSync21(fullPath, "utf8") : null;
9319
+ const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
9320
+ for (const change of lockDiffs) {
9321
+ if (!directPkgs.has(change.packageName)) {
9322
+ dependencyChanges.push(change);
9323
+ }
9324
+ }
9325
+ if (!changedManifests.includes(lockfileRel)) {
9326
+ changedManifests.push(lockfileRel);
9327
+ }
9328
+ }
7742
9329
  const introducedFindings = [];
7743
9330
  const resolvedFindings = [];
7744
9331
  for (const change of dependencyChanges) {
7745
9332
  if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
7746
- const cleanVer = semver8.coerce(change.newVersion)?.version ?? change.newVersion;
9333
+ const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
7747
9334
  const evaluation = await evaluatePackage(change.packageName, {
7748
9335
  version: cleanVer,
7749
9336
  enrichIntelligence: true
@@ -7763,12 +9350,12 @@ async function scanDelta(options = {}) {
7763
9350
  }
7764
9351
  }
7765
9352
  if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
7766
- const cleanOldVer = semver8.coerce(change.oldVersion)?.version ?? change.oldVersion;
9353
+ const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
7767
9354
  const oldEval = await evaluatePackage(change.packageName, {
7768
9355
  version: cleanOldVer,
7769
9356
  enrichIntelligence: true
7770
9357
  });
7771
- const cleanNewVer = change.newVersion ? semver8.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
9358
+ const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
7772
9359
  const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
7773
9360
  version: cleanNewVer,
7774
9361
  enrichIntelligence: false
@@ -7931,9 +9518,9 @@ function toSarifOutput(report) {
7931
9518
  }
7932
9519
 
7933
9520
  // src/version.ts
7934
- import { readFileSync as readFileSync18 } from "fs";
9521
+ import { readFileSync as readFileSync22 } from "fs";
7935
9522
  function readPackageVersion() {
7936
- const raw = readFileSync18(new URL("../package.json", import.meta.url), "utf8");
9523
+ const raw = readFileSync22(new URL("../package.json", import.meta.url), "utf8");
7937
9524
  const metadata = JSON.parse(raw);
7938
9525
  if (!metadata.version) {
7939
9526
  throw new Error("packages/core/package.json is missing a version field.");
@@ -7958,13 +9545,17 @@ export {
7958
9545
  listPatchArtifacts,
7959
9546
  inspectPatchArtifact,
7960
9547
  validatePatchArtifact,
9548
+ evaluatePackage,
9549
+ reconcilePatchArtifacts,
7961
9550
  remediate,
7962
9551
  planRemediation,
9552
+ parseScanInput,
9553
+ parseScanInputFromAudit,
9554
+ uniqueCveIds,
7963
9555
  remediateFromScan,
7964
9556
  remediatePortfolio,
7965
9557
  updateOutdated,
7966
9558
  checkReachability,
7967
- evaluatePackage,
7968
9559
  scanDelta,
7969
9560
  submitRemediateJob,
7970
9561
  submitScanJob,