autoremediator 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +2 -1
  2. package/dist/api/change-request/index.d.ts +2 -1
  3. package/dist/api/change-request/index.d.ts.map +1 -1
  4. package/dist/api/index.d.ts +2 -2
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api/patches/index.d.ts +1 -0
  7. package/dist/api/patches/index.d.ts.map +1 -1
  8. package/dist/api/patches/reconcile.d.ts +7 -0
  9. package/dist/api/patches/reconcile.d.ts.map +1 -0
  10. package/dist/api/patches/reconcile.test.d.ts +2 -0
  11. package/dist/api/patches/reconcile.test.d.ts.map +1 -0
  12. package/dist/api/remediate/validators.d.ts +3 -0
  13. package/dist/api/remediate/validators.d.ts.map +1 -1
  14. package/dist/{chunk-U4LIJIGD.js → chunk-FXOUJDQ3.js} +1712 -856
  15. package/dist/cli/program.d.ts.map +1 -1
  16. package/dist/cli/runners.d.ts +5 -0
  17. package/dist/cli/runners.d.ts.map +1 -1
  18. package/dist/cli/triage.d.ts +7 -0
  19. package/dist/cli/triage.d.ts.map +1 -0
  20. package/dist/cli/types.d.ts +2 -0
  21. package/dist/cli/types.d.ts.map +1 -1
  22. package/dist/cli.js +220 -3
  23. package/dist/index.js +1707 -852
  24. package/dist/index.js.map +1 -1
  25. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  26. package/dist/mcp/server.js +1 -1
  27. package/dist/openapi/server.js +1 -1
  28. package/dist/platform/package-manager/index.d.ts +1 -0
  29. package/dist/platform/package-manager/index.d.ts.map +1 -1
  30. package/dist/platform/supply-chain.d.ts +10 -0
  31. package/dist/platform/supply-chain.d.ts.map +1 -0
  32. package/dist/platform/supply-chain.test.d.ts +2 -0
  33. package/dist/platform/supply-chain.test.d.ts.map +1 -0
  34. package/dist/platform/types.d.ts +38 -4
  35. package/dist/platform/types.d.ts.map +1 -1
  36. package/dist/platform/workspace.d.ts +28 -0
  37. package/dist/platform/workspace.d.ts.map +1 -0
  38. package/dist/platform/workspace.test.d.ts +2 -0
  39. package/dist/platform/workspace.test.d.ts.map +1 -0
  40. package/dist/remediation/local/options.d.ts.map +1 -1
  41. package/dist/remediation/local/primary-strategy.d.ts +3 -1
  42. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  43. package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
  44. package/dist/remediation/tools/apply-patch-file/index.d.ts +1 -0
  45. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  46. package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
  47. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  48. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  49. package/dist/remediation/tools/check-reachability.d.ts +6 -1
  50. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  51. package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
  52. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  53. package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
  54. package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
  55. package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
  56. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  57. package/dist/scanner/delta-scan.d.ts.map +1 -1
  58. package/dist/scanner/lockfile-diff.d.ts +10 -0
  59. package/dist/scanner/lockfile-diff.d.ts.map +1 -0
  60. package/dist/scanner/lockfile-diff.test.d.ts +2 -0
  61. package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
  62. package/package.json +1 -1
@@ -463,12 +463,12 @@ import { readdir, readFile as readFile2, stat } from "fs/promises";
463
463
  import { join as join5 } from "path";
464
464
 
465
465
  // src/remediation/strategies/patch-utils.ts
466
- import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
466
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, readFileSync as readFileSync3 } from "fs";
467
467
  import { join as join4 } from "path";
468
468
  import { execa as execa2 } from "execa";
469
469
 
470
470
  // src/platform/package-manager/index.ts
471
- import { existsSync as existsSync2 } from "fs";
471
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
472
472
  import { join as join3 } from "path";
473
473
  import { execa } from "execa";
474
474
 
@@ -661,6 +661,32 @@ function resolveTestCommand(pm, constraints) {
661
661
  const base = pm === "pnpm" ? ["pnpm", "test"] : pm === "yarn" ? ["yarn", "test"] : pm === "bun" ? ["bun", "test"] : pm === "deno" ? ["deno", "test"] : ["npm", "test"];
662
662
  return withWorkspace(base, pm, constraints?.workspace);
663
663
  }
664
+ function resolveTypecheckCommand(pm, constraints, cwd = process.cwd()) {
665
+ const pkgJsonPath = join3(cwd, "package.json");
666
+ let hasTypecheckScript = false;
667
+ const hasTsConfig = existsSync2(join3(cwd, "tsconfig.json"));
668
+ if (existsSync2(pkgJsonPath)) {
669
+ try {
670
+ const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
671
+ if (pkg.scripts?.typecheck) {
672
+ hasTypecheckScript = true;
673
+ }
674
+ } catch {
675
+ }
676
+ }
677
+ if (hasTypecheckScript) {
678
+ const base = [pm, "run", "typecheck"];
679
+ return withWorkspace(base, pm, constraints?.workspace);
680
+ }
681
+ if (hasTsConfig) {
682
+ if (pm === "deno") {
683
+ return withWorkspace(["deno", "check"], pm, constraints?.workspace);
684
+ }
685
+ const base = ["npx", "tsc", "--noEmit"];
686
+ return withWorkspace(base, pm, constraints?.workspace);
687
+ }
688
+ return [];
689
+ }
664
690
  function resolveAuditCommand(pm, constraints) {
665
691
  if (pm === "deno") {
666
692
  throw new Error(
@@ -905,12 +931,12 @@ function defineTool(config) {
905
931
 
906
932
  // src/remediation/tools/check-inventory.ts
907
933
  import { z } from "zod";
908
- import { readFileSync as readFileSync4 } from "fs";
909
- import { join as join7 } from "path";
934
+ import { readFileSync as readFileSync6 } from "fs";
935
+ import { join as join8 } from "path";
910
936
  import { execa as execa3 } from "execa";
911
937
 
912
938
  // src/platform/policy.ts
913
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
939
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
914
940
  import { join as join6 } from "path";
915
941
  import { parse as yamlParse } from "yaml";
916
942
  var DEFAULT_POLICY = {
@@ -945,7 +971,7 @@ function loadPolicy(cwd, explicitPath) {
945
971
  const candidate = explicitPath ?? join6(cwd, ".github", "autoremediator.yml");
946
972
  if (!existsSync6(candidate)) return DEFAULT_POLICY;
947
973
  try {
948
- const parsed = yamlParse(readFileSync3(candidate, "utf8"));
974
+ const parsed = yamlParse(readFileSync4(candidate, "utf8"));
949
975
  return {
950
976
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
951
977
  denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
@@ -1006,7 +1032,7 @@ function isActiveSuppression(suppression) {
1006
1032
  }
1007
1033
  function loadSuppressionsFile(filePath) {
1008
1034
  try {
1009
- const content = readFileSync3(filePath, "utf8");
1035
+ const content = readFileSync4(filePath, "utf8");
1010
1036
  const parsed = yamlParse(content);
1011
1037
  return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
1012
1038
  } catch {
@@ -1032,6 +1058,140 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
1032
1058
  };
1033
1059
  }
1034
1060
 
1061
+ // src/platform/workspace.ts
1062
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1063
+ import { join as join7, resolve as resolve2, relative } from "path";
1064
+ import yaml from "yaml";
1065
+ function findWorkspaceRoot(startDir) {
1066
+ let curr = resolve2(startDir);
1067
+ while (true) {
1068
+ if (existsSync7(join7(curr, "pnpm-workspace.yaml"))) return curr;
1069
+ const pkgPath = join7(curr, "package.json");
1070
+ if (existsSync7(pkgPath)) {
1071
+ try {
1072
+ const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
1073
+ if (parsed.workspaces) return curr;
1074
+ } catch {
1075
+ }
1076
+ }
1077
+ const parent = resolve2(curr, "..");
1078
+ if (parent === curr) break;
1079
+ curr = parent;
1080
+ }
1081
+ return null;
1082
+ }
1083
+ function findWorkspacePackages(cwd) {
1084
+ const rootCwd = findWorkspaceRoot(cwd) ?? resolve2(cwd);
1085
+ const packages = [];
1086
+ const packageGlobs = [];
1087
+ const pnpmWorkspaceFile = join7(rootCwd, "pnpm-workspace.yaml");
1088
+ if (existsSync7(pnpmWorkspaceFile)) {
1089
+ try {
1090
+ const content = readFileSync5(pnpmWorkspaceFile, "utf8");
1091
+ const parsed = yaml.parse(content);
1092
+ if (Array.isArray(parsed?.packages)) {
1093
+ packageGlobs.push(...parsed.packages);
1094
+ }
1095
+ } catch {
1096
+ }
1097
+ }
1098
+ const rootPkgFile = join7(rootCwd, "package.json");
1099
+ if (existsSync7(rootPkgFile)) {
1100
+ try {
1101
+ const rootPkg = JSON.parse(readFileSync5(rootPkgFile, "utf8"));
1102
+ if (Array.isArray(rootPkg.workspaces)) {
1103
+ packageGlobs.push(...rootPkg.workspaces);
1104
+ } else if (Array.isArray(rootPkg.workspaces?.packages)) {
1105
+ packageGlobs.push(...rootPkg.workspaces.packages);
1106
+ }
1107
+ } catch {
1108
+ }
1109
+ }
1110
+ const denoJsonFile = join7(rootCwd, "deno.json");
1111
+ if (existsSync7(denoJsonFile)) {
1112
+ try {
1113
+ const denoJson = JSON.parse(readFileSync5(denoJsonFile, "utf8"));
1114
+ if (Array.isArray(denoJson.workspace)) {
1115
+ packageGlobs.push(...denoJson.workspace);
1116
+ }
1117
+ } catch {
1118
+ }
1119
+ }
1120
+ if (packageGlobs.length === 0) {
1121
+ return packages;
1122
+ }
1123
+ const seenDirs = /* @__PURE__ */ new Set();
1124
+ for (const glob of packageGlobs) {
1125
+ const cleanGlob = glob.trim().replace(/\/+$/, "");
1126
+ if (!cleanGlob || cleanGlob.startsWith("!")) continue;
1127
+ if (cleanGlob.endsWith("/*")) {
1128
+ const parentDir = join7(rootCwd, cleanGlob.slice(0, -2));
1129
+ if (!existsSync7(parentDir)) continue;
1130
+ try {
1131
+ const entries = readdirSync(parentDir);
1132
+ for (const entry of entries) {
1133
+ if (entry.startsWith(".") || entry === "node_modules") continue;
1134
+ const subDir = join7(parentDir, entry);
1135
+ try {
1136
+ if (statSync(subDir).isDirectory()) {
1137
+ addWorkspacePackage(rootCwd, subDir, packages, seenDirs);
1138
+ }
1139
+ } catch {
1140
+ }
1141
+ }
1142
+ } catch {
1143
+ }
1144
+ } else {
1145
+ const directDir = join7(rootCwd, cleanGlob);
1146
+ if (existsSync7(directDir)) {
1147
+ try {
1148
+ if (statSync(directDir).isDirectory()) {
1149
+ addWorkspacePackage(rootCwd, directDir, packages, seenDirs);
1150
+ }
1151
+ } catch {
1152
+ }
1153
+ }
1154
+ }
1155
+ }
1156
+ return packages;
1157
+ }
1158
+ function addWorkspacePackage(rootCwd, dir, out, seen) {
1159
+ const norm = resolve2(dir);
1160
+ if (seen.has(norm)) return;
1161
+ seen.add(norm);
1162
+ const manifestPath = join7(dir, "package.json");
1163
+ if (!existsSync7(manifestPath)) return;
1164
+ try {
1165
+ const content = readFileSync5(manifestPath, "utf8");
1166
+ const manifest = JSON.parse(content);
1167
+ out.push({
1168
+ name: manifest.name ?? relative(rootCwd, dir),
1169
+ relativeDir: relative(rootCwd, dir),
1170
+ absoluteDir: norm,
1171
+ manifestPath,
1172
+ manifest
1173
+ });
1174
+ } catch {
1175
+ }
1176
+ }
1177
+ function findDeclaringWorkspacePackage(cwd, dependencyName, filterWorkspace) {
1178
+ const workspaces = findWorkspacePackages(cwd);
1179
+ if (workspaces.length === 0) return null;
1180
+ const matchesFilter = (wp) => {
1181
+ if (!filterWorkspace) return true;
1182
+ const filter = filterWorkspace.trim();
1183
+ return wp.name === filter || wp.relativeDir === filter || wp.relativeDir.replace(/\\/g, "/") === filter.replace(/\\/g, "/");
1184
+ };
1185
+ for (const wp of workspaces) {
1186
+ if (!matchesFilter(wp)) continue;
1187
+ const isDeclared = Boolean(wp.manifest.dependencies?.[dependencyName]) || Boolean(wp.manifest.devDependencies?.[dependencyName]) || Boolean(wp.manifest.peerDependencies?.[dependencyName]);
1188
+ if (isDeclared) {
1189
+ return wp;
1190
+ }
1191
+ }
1192
+ return null;
1193
+ }
1194
+
1035
1195
  // src/remediation/tools/check-inventory.ts
1036
1196
  var checkInventoryTool = defineTool({
1037
1197
  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 +1209,7 @@ var checkInventoryTool = defineTool({
1049
1209
  }) => {
1050
1210
  let pkgJson;
1051
1211
  try {
1052
- pkgJson = JSON.parse(readFileSync4(join7(cwd, "package.json"), "utf8"));
1212
+ pkgJson = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1053
1213
  } catch {
1054
1214
  return {
1055
1215
  packages: [],
@@ -1081,7 +1241,8 @@ var checkInventoryTool = defineTool({
1081
1241
  }
1082
1242
  const packages = [];
1083
1243
  for (const [name, version] of installedVersions.entries()) {
1084
- const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]);
1244
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, name, workspace);
1245
+ const isDirect = Boolean(pkgJson.dependencies?.[name]) || Boolean(pkgJson.devDependencies?.[name]) || Boolean(pkgJson.peerDependencies?.[name]) || Boolean(declaringWorkspace);
1085
1246
  packages.push({
1086
1247
  name,
1087
1248
  version,
@@ -1204,173 +1365,8 @@ async function validatePatchArtifact(patchFilePath, options = {}) {
1204
1365
  };
1205
1366
  }
1206
1367
 
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";
1368
+ // src/remediation/evaluate-package.ts
1369
+ import semver2 from "semver";
1374
1370
 
1375
1371
  // src/platform/http-client.ts
1376
1372
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -1552,27 +1548,755 @@ async function lookupCveOsv(cveId) {
1552
1548
  return parseOsvVuln(vuln);
1553
1549
  }
1554
1550
 
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}`;
1551
+ // src/platform/config.ts
1552
+ function resolveProvider(options = {}) {
1553
+ const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
1554
+ if (raw !== "remote" && raw !== "local") {
1555
+ throw new Error(
1556
+ `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
1557
+ );
1565
1558
  }
1566
- const url = new URL(GITHUB_API_BASE);
1567
- url.searchParams.set("cve_id", cveId);
1568
- url.searchParams.set("ecosystem", "npm");
1569
- url.searchParams.set("type", "reviewed");
1570
- url.searchParams.set("per_page", "10");
1571
- try {
1572
- const res = await httpClient({
1573
- url: url.toString(),
1574
- headers
1575
- });
1559
+ return raw;
1560
+ }
1561
+ function resolveModelName(provider, options = {}, routing = {}) {
1562
+ if (options.model) return options.model;
1563
+ if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
1564
+ const cwd = options.cwd ?? process.cwd();
1565
+ const policy = loadPolicy(cwd, options.policy);
1566
+ const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
1567
+ if (providerEnvModel) return providerEnvModel;
1568
+ const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
1569
+ if (policyPinnedModel) return policyPinnedModel;
1570
+ const defaults = {
1571
+ remote: "remote-default",
1572
+ local: "local"
1573
+ };
1574
+ const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
1575
+ const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
1576
+ if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
1577
+ return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
1578
+ }
1579
+ return defaults[provider];
1580
+ }
1581
+ async function loadRemoteFactory() {
1582
+ const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
1583
+ const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
1584
+ if (!moduleName) {
1585
+ throw new Error(
1586
+ "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
1587
+ );
1588
+ }
1589
+ if (moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/") || moduleName.startsWith("file:")) {
1590
+ throw new Error(
1591
+ `AUTOREMEDIATOR_REMOTE_CLIENT_MODULE must be a package name, not a file path: ${moduleName}`
1592
+ );
1593
+ }
1594
+ const loaded = await import(moduleName);
1595
+ const factory = loaded[exportName];
1596
+ if (typeof factory !== "function") {
1597
+ throw new Error(
1598
+ `Remote client factory "${exportName}" was not found in module "${moduleName}".`
1599
+ );
1600
+ }
1601
+ return factory;
1602
+ }
1603
+ async function createModel(options = {}, routing = {}) {
1604
+ const provider = resolveProvider(options);
1605
+ if (provider === "local") {
1606
+ throw new Error(
1607
+ "Local provider does not create a language model. Use the deterministic pipeline path instead."
1608
+ );
1609
+ }
1610
+ const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
1611
+ if (!apiKey) {
1612
+ throw new Error(
1613
+ "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
1614
+ );
1615
+ }
1616
+ const modelName = resolveModelName(provider, options, routing);
1617
+ const createRemoteClient = await loadRemoteFactory();
1618
+ const remoteClient = createRemoteClient({ apiKey });
1619
+ return remoteClient(modelName);
1620
+ }
1621
+ function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed", riskLevel = "medium", overrides) {
1622
+ const relaxed = {
1623
+ remote: { low: 0.65, medium: 0.7, high: 0.8 },
1624
+ local: { low: 0.65, medium: 0.7, high: 0.8 }
1625
+ };
1626
+ const strict = {
1627
+ remote: { low: 0.8, medium: 0.85, high: 0.9 },
1628
+ local: { low: 0.8, medium: 0.85, high: 0.9 }
1629
+ };
1630
+ const baseThreshold = safetyProfile === "strict" ? strict[provider][riskLevel] : relaxed[provider][riskLevel];
1631
+ const override = overrides?.[riskLevel];
1632
+ if (typeof override === "number" && Number.isFinite(override)) {
1633
+ return Math.max(0, Math.min(1, override));
1634
+ }
1635
+ return baseThreshold;
1636
+ }
1637
+ function estimateModelCostUsd(params) {
1638
+ const inputTokens = params.promptChars / 4;
1639
+ const outputTokens = params.completionChars / 4;
1640
+ const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
1641
+ return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
1642
+ }
1643
+ function getNvdConfig() {
1644
+ return {
1645
+ apiKey: process.env.AUTOREMEDIATOR_NVD_API_KEY
1646
+ };
1647
+ }
1648
+ function getGitHubToken() {
1649
+ return process.env.GITHUB_TOKEN;
1650
+ }
1651
+ function getIntelligenceSourceConfig() {
1652
+ return {
1653
+ gitLabAdvisoryApi: process.env.AUTOREMEDIATOR_GITLAB_ADVISORY_API ?? "https://advisories.gitlab.com/api/v1/advisories",
1654
+ certCcSearchUrl: process.env.AUTOREMEDIATOR_CERTCC_SEARCH_URL ?? "https://www.kb.cert.org/vuls/search",
1655
+ epssApi: process.env.AUTOREMEDIATOR_EPSS_API ?? "https://api.first.org/data/v1/epss",
1656
+ cveServicesApi: process.env.AUTOREMEDIATOR_CVE_SERVICES_API ?? "https://cveawg.mitre.org/api/cve",
1657
+ depsDevApi: process.env.AUTOREMEDIATOR_DEPSDEV_API ?? "https://api.deps.dev/v3",
1658
+ scorecardApi: process.env.AUTOREMEDIATOR_SCORECARD_API ?? "https://api.securityscorecards.dev",
1659
+ vendorAdvisoryFeeds: (process.env.AUTOREMEDIATOR_VENDOR_ADVISORY_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1660
+ commercialFeeds: (process.env.AUTOREMEDIATOR_COMMERCIAL_FEEDS ?? "").split(",").map((v) => v.trim()).filter(Boolean),
1661
+ commercialFeedToken: process.env.AUTOREMEDIATOR_COMMERCIAL_FEED_TOKEN
1662
+ };
1663
+ }
1664
+
1665
+ // src/intelligence/sources/epss.ts
1666
+ async function fetchEpss(cveId) {
1667
+ const { epssApi } = getIntelligenceSourceConfig();
1668
+ if (!epssApi) return void 0;
1669
+ try {
1670
+ const url = new URL(epssApi);
1671
+ url.searchParams.set("cve", cveId);
1672
+ const res = await httpClient({ url: url.toString() });
1673
+ if (!res.ok) return void 0;
1674
+ const body = res.data;
1675
+ return body.data?.[0];
1676
+ } catch {
1677
+ return void 0;
1678
+ }
1679
+ }
1680
+ async function enrichWithEpss(details) {
1681
+ const row = await fetchEpss(details.id);
1682
+ if (!row) return details;
1683
+ const score = Number.parseFloat(row.epss);
1684
+ const percentile = Number.parseFloat(row.percentile);
1685
+ if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
1686
+ return details;
1687
+ }
1688
+ details.epss = {
1689
+ score,
1690
+ percentile,
1691
+ date: row.date
1692
+ };
1693
+ return details;
1694
+ }
1695
+
1696
+ // src/intelligence/sources/cisa-kev.ts
1697
+ var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
1698
+ async function fetchCisaKevFeed() {
1699
+ try {
1700
+ const res = await httpClient({ url: CISA_KEV_URL });
1701
+ if (!res.ok) return void 0;
1702
+ return res.data;
1703
+ } catch {
1704
+ return void 0;
1705
+ }
1706
+ }
1707
+ function findKevEntry(feed, cveId) {
1708
+ if (!feed?.vulnerabilities?.length) return void 0;
1709
+ const normalized = cveId.toUpperCase();
1710
+ return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
1711
+ }
1712
+ async function enrichWithCisaKev(details) {
1713
+ const feed = await fetchCisaKevFeed();
1714
+ const entry = findKevEntry(feed, details.id);
1715
+ if (!entry) return details;
1716
+ details.kev = {
1717
+ knownExploited: true,
1718
+ dateAdded: entry.dateAdded,
1719
+ dueDate: entry.dueDate,
1720
+ requiredAction: entry.requiredAction,
1721
+ knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
1722
+ };
1723
+ if (!details.references.includes(CISA_KEV_URL)) {
1724
+ details.references.push(CISA_KEV_URL);
1725
+ }
1726
+ return details;
1727
+ }
1728
+
1729
+ // src/intelligence/sources/registry.ts
1730
+ import { readFileSync as readFileSync7 } from "fs";
1731
+ import { join as join9 } from "path";
1732
+ import { execa as execa4 } from "execa";
1733
+ import semver from "semver";
1734
+ var NPM_REGISTRY = "https://registry.npmjs.org";
1735
+ async function fetchPackageVersions(packageName) {
1736
+ const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
1737
+ try {
1738
+ const res = await httpClient({ url });
1739
+ if (res.status === 404) return [];
1740
+ if (!res.ok) {
1741
+ throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
1742
+ }
1743
+ const data = res.data;
1744
+ return Object.keys(data.versions);
1745
+ } catch (err) {
1746
+ if (err instanceof Error && err.message.includes("registry error")) {
1747
+ throw err;
1748
+ }
1749
+ return [];
1750
+ }
1751
+ }
1752
+ async function findSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
1753
+ const resolution = await resolveSafeUpgradeVersion(
1754
+ packageName,
1755
+ installedVersion,
1756
+ firstPatchedVersion,
1757
+ vulnerableRange
1758
+ );
1759
+ return resolution.safeVersion;
1760
+ }
1761
+ async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
1762
+ const versions = await fetchPackageVersions(packageName);
1763
+ if (!versions.length) {
1764
+ return {
1765
+ candidates: {},
1766
+ majorOnlyFixAvailable: false
1767
+ };
1768
+ }
1769
+ const installed = semver.parse(installedVersion);
1770
+ const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
1771
+ if (!vulnerableRange) return true;
1772
+ try {
1773
+ return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
1774
+ } catch {
1775
+ return true;
1776
+ }
1777
+ }).sort(semver.compare);
1778
+ if (!candidates.length) {
1779
+ return {
1780
+ candidates: {},
1781
+ majorOnlyFixAvailable: false
1782
+ };
1783
+ }
1784
+ const categorizedCandidates = {};
1785
+ for (const candidate of candidates) {
1786
+ const level = classifyUpgradeLevel(installedVersion, candidate);
1787
+ if (!level) continue;
1788
+ if (!categorizedCandidates[level]) {
1789
+ categorizedCandidates[level] = candidate;
1790
+ }
1791
+ }
1792
+ const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
1793
+ if (!safeVersion) {
1794
+ return {
1795
+ candidates: categorizedCandidates,
1796
+ majorOnlyFixAvailable: false
1797
+ };
1798
+ }
1799
+ const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
1800
+ const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
1801
+ if (!installed || !upgradeLevel) {
1802
+ return {
1803
+ safeVersion,
1804
+ upgradeLevel,
1805
+ candidates: categorizedCandidates,
1806
+ majorOnlyFixAvailable
1807
+ };
1808
+ }
1809
+ return {
1810
+ safeVersion,
1811
+ upgradeLevel,
1812
+ candidates: categorizedCandidates,
1813
+ majorOnlyFixAvailable
1814
+ };
1815
+ }
1816
+ function classifyUpgradeLevel(installedVersion, candidateVersion) {
1817
+ const installed = semver.parse(installedVersion);
1818
+ const candidate = semver.parse(candidateVersion);
1819
+ if (!installed || !candidate) return void 0;
1820
+ if (candidate.major > installed.major) return "major";
1821
+ if (candidate.minor > installed.minor) return "minor";
1822
+ if (candidate.patch > installed.patch || candidate.version === installed.version) {
1823
+ return "patch";
1824
+ }
1825
+ return void 0;
1826
+ }
1827
+ async function queryOutdatedPackages(cwd, options = {}) {
1828
+ const pm = options.packageManager ?? detectPackageManager(cwd);
1829
+ let stdout;
1830
+ try {
1831
+ const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
1832
+ const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
1833
+ const finalArgs = yarnArgs ?? args;
1834
+ const result2 = await execa4(pm, finalArgs, {
1835
+ cwd,
1836
+ reject: false,
1837
+ stdio: "pipe"
1838
+ });
1839
+ if (result2.exitCode !== 0 && result2.exitCode !== 1) {
1840
+ const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
1841
+ if (!result2.stdout) {
1842
+ throw new Error(
1843
+ `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
1844
+ );
1845
+ }
1846
+ }
1847
+ stdout = typeof result2.stdout === "string" ? result2.stdout : "";
1848
+ } catch (err) {
1849
+ if (err instanceof Error && !err.message.includes("outdated --json failed")) {
1850
+ throw new Error(`Failed to query outdated packages: ${err.message}`);
1851
+ }
1852
+ throw err;
1853
+ }
1854
+ if (!stdout.trim()) {
1855
+ return /* @__PURE__ */ new Map();
1856
+ }
1857
+ const rawEntries = {};
1858
+ if (pm === "yarn") {
1859
+ for (const line of stdout.split("\n")) {
1860
+ const trimmed = line.trim();
1861
+ if (!trimmed) continue;
1862
+ try {
1863
+ const parsed = JSON.parse(trimmed);
1864
+ if (parsed.type === "table" && parsed.data) {
1865
+ const tableData = parsed.data;
1866
+ const head = tableData.head ?? [];
1867
+ const currentIdx = head.indexOf("Current");
1868
+ const wantedIdx = head.indexOf("Wanted");
1869
+ const latestIdx = head.indexOf("Latest");
1870
+ for (const row of tableData.body ?? []) {
1871
+ const name = row[0];
1872
+ if (!name) continue;
1873
+ rawEntries[name] = {
1874
+ current: row[currentIdx] ?? "",
1875
+ wanted: row[wantedIdx] ?? "",
1876
+ latest: row[latestIdx] ?? "",
1877
+ type: row[head.indexOf("Package Type")] ?? "dependencies"
1878
+ };
1879
+ }
1880
+ }
1881
+ } catch {
1882
+ }
1883
+ }
1884
+ } else {
1885
+ try {
1886
+ const parsed = JSON.parse(stdout);
1887
+ if (Array.isArray(parsed)) {
1888
+ for (const entry of parsed) {
1889
+ const name = entry.name ?? entry.packageName;
1890
+ if (!name) continue;
1891
+ rawEntries[name] = {
1892
+ current: entry.current,
1893
+ wanted: entry.wanted,
1894
+ latest: entry.latest,
1895
+ type: entry.dependencyType
1896
+ };
1897
+ }
1898
+ } else {
1899
+ Object.assign(rawEntries, parsed);
1900
+ }
1901
+ } catch {
1902
+ throw new Error(`Could not parse ${pm} outdated --json output.`);
1903
+ }
1904
+ }
1905
+ let directDeps;
1906
+ try {
1907
+ const pkgRaw = JSON.parse(readFileSync7(join9(cwd, "package.json"), "utf8"));
1908
+ directDeps = /* @__PURE__ */ new Set([
1909
+ ...Object.keys(pkgRaw.dependencies ?? {}),
1910
+ ...Object.keys(pkgRaw.devDependencies ?? {})
1911
+ ]);
1912
+ } catch {
1913
+ directDeps = /* @__PURE__ */ new Set();
1914
+ }
1915
+ const result = /* @__PURE__ */ new Map();
1916
+ for (const [name, entry] of Object.entries(rawEntries)) {
1917
+ const currentVersion = entry.current ?? "";
1918
+ const wantedVersion = entry.wanted ?? currentVersion;
1919
+ const latestVersion = entry.latest ?? currentVersion;
1920
+ if (!currentVersion || !latestVersion) continue;
1921
+ const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
1922
+ if (!options.includeTransitive && dependencyScope === "transitive") continue;
1923
+ const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
1924
+ result.set(name, {
1925
+ currentVersion,
1926
+ wantedVersion,
1927
+ latestVersion,
1928
+ isMajorBump,
1929
+ dependencyScope
1930
+ });
1931
+ }
1932
+ return result;
1933
+ }
1934
+
1935
+ // src/remediation/evaluate-package.ts
1936
+ async function evaluatePackage(packageName, options = {}) {
1937
+ const version = options.version?.trim();
1938
+ const enrich = options.enrichIntelligence !== false;
1939
+ let versions = [];
1940
+ let latestVersion;
1941
+ try {
1942
+ versions = await fetchPackageVersions(packageName);
1943
+ if (versions.length > 0) {
1944
+ const valid = versions.filter((v) => semver2.valid(v)).sort((a, b) => semver2.compare(a, b));
1945
+ latestVersion = valid[valid.length - 1];
1946
+ }
1947
+ } catch {
1948
+ }
1949
+ let rawVulns = [];
1950
+ try {
1951
+ rawVulns = await queryOsvPackage(packageName, version);
1952
+ } catch {
1953
+ rawVulns = [];
1954
+ }
1955
+ let kevFeed = null;
1956
+ if (enrich) {
1957
+ try {
1958
+ kevFeed = await fetchCisaKevFeed();
1959
+ } catch {
1960
+ kevFeed = null;
1961
+ }
1962
+ }
1963
+ const entries = [];
1964
+ for (const raw of rawVulns) {
1965
+ const cveDetails = parseOsvVuln(raw);
1966
+ const affected = cveDetails.affectedPackages.find(
1967
+ (p) => p.name.toLowerCase() === packageName.toLowerCase()
1968
+ );
1969
+ if (version && affected?.vulnerableRange) {
1970
+ const cleanVer = semver2.clean(version) || version;
1971
+ if (semver2.valid(cleanVer) && !semver2.satisfies(cleanVer, affected.vulnerableRange)) {
1972
+ continue;
1973
+ }
1974
+ }
1975
+ let inCisaKev = false;
1976
+ let epssScore;
1977
+ let epssPercentile;
1978
+ if (enrich && cveDetails.id.startsWith("CVE-")) {
1979
+ if (kevFeed) {
1980
+ inCisaKev = Boolean(findKevEntry(kevFeed, cveDetails.id));
1981
+ }
1982
+ try {
1983
+ const epss = await fetchEpss(cveDetails.id);
1984
+ if (epss) {
1985
+ epssScore = Number.parseFloat(epss.epss);
1986
+ epssPercentile = Number.parseFloat(epss.percentile);
1987
+ }
1988
+ } catch {
1989
+ }
1990
+ }
1991
+ let safeUpgradeVersion;
1992
+ if (affected?.firstPatchedVersion) {
1993
+ try {
1994
+ const safe = await findSafeUpgradeVersion(
1995
+ packageName,
1996
+ version || "0.0.0",
1997
+ affected.firstPatchedVersion
1998
+ );
1999
+ safeUpgradeVersion = safe ?? affected.firstPatchedVersion;
2000
+ } catch {
2001
+ safeUpgradeVersion = affected.firstPatchedVersion;
2002
+ }
2003
+ }
2004
+ entries.push({
2005
+ cveId: cveDetails.id,
2006
+ severity: cveDetails.severity,
2007
+ summary: cveDetails.summary,
2008
+ vulnerableRange: affected?.vulnerableRange,
2009
+ firstPatchedVersion: affected?.firstPatchedVersion,
2010
+ safeUpgradeVersion,
2011
+ epssScore,
2012
+ epssPercentile,
2013
+ inCisaKev,
2014
+ references: cveDetails.references
2015
+ });
2016
+ }
2017
+ const isVulnerable = entries.length > 0;
2018
+ let verdict = "safe";
2019
+ if (isVulnerable) {
2020
+ const hasKevOrHighEpss = entries.some((e) => e.inCisaKev || e.epssScore && e.epssScore > 0.5);
2021
+ const hasHighOrCritical = entries.some(
2022
+ (e) => e.severity === "CRITICAL" || e.severity === "HIGH"
2023
+ );
2024
+ if (hasKevOrHighEpss) {
2025
+ verdict = "actively-exploited";
2026
+ } else if (hasHighOrCritical) {
2027
+ verdict = "vulnerable";
2028
+ } else {
2029
+ verdict = "caution";
2030
+ }
2031
+ }
2032
+ let recommendedVersion;
2033
+ if (isVulnerable) {
2034
+ const upgradeCandidates = entries.map((e) => e.safeUpgradeVersion).filter((v) => Boolean(v && semver2.valid(v))).sort((a, b) => semver2.compare(a, b));
2035
+ recommendedVersion = upgradeCandidates[upgradeCandidates.length - 1] || latestVersion;
2036
+ } else {
2037
+ recommendedVersion = version || latestVersion;
2038
+ }
2039
+ 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.`;
2040
+ return {
2041
+ schemaVersion: "1.0",
2042
+ packageName,
2043
+ evaluatedVersion: version,
2044
+ isVulnerable,
2045
+ verdict,
2046
+ summary,
2047
+ vulnerabilities: entries,
2048
+ recommendedVersion,
2049
+ latestVersion,
2050
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2051
+ };
2052
+ }
2053
+
2054
+ // src/api/patches/reconcile.ts
2055
+ import { existsSync as existsSync8 } from "fs";
2056
+ import { readFile as readFile3, rm, writeFile } from "fs/promises";
2057
+ import { join as join10 } from "path";
2058
+ import { execa as execa5 } from "execa";
2059
+ async function reconcilePatchArtifacts(options = {}) {
2060
+ const cwd = options.cwd ?? process.cwd();
2061
+ const pm = options.packageManager ?? detectPackageManager(cwd);
2062
+ const summaries = await listPatchArtifacts(options);
2063
+ const results = [];
2064
+ for (const summary of summaries) {
2065
+ const inspection = await inspectPatchArtifact(summary.patchFilePath, options);
2066
+ let packageName = inspection.packageName;
2067
+ let vulnerableVersion = inspection.vulnerableVersion;
2068
+ const cveId = inspection.cveId;
2069
+ if (!packageName && summary.patchFileName) {
2070
+ const basename = summary.patchFileName.replace(/\.patch$/, "");
2071
+ const plusIdx = basename.lastIndexOf("+");
2072
+ if (plusIdx > 0) {
2073
+ let rawPkg = basename.slice(0, plusIdx);
2074
+ vulnerableVersion = basename.slice(plusIdx + 1);
2075
+ if (rawPkg.includes("+")) {
2076
+ rawPkg = "@" + rawPkg.replace("+", "/");
2077
+ }
2078
+ packageName = rawPkg;
2079
+ } else {
2080
+ packageName = basename;
2081
+ }
2082
+ }
2083
+ if (!packageName) {
2084
+ results.push({
2085
+ patchFilePath: summary.patchFilePath,
2086
+ packageName: "unknown",
2087
+ cveId,
2088
+ status: "patch-retained",
2089
+ reason: "Cannot determine package name from patch artifact."
2090
+ });
2091
+ continue;
2092
+ }
2093
+ let safeUpgradeVersion;
2094
+ try {
2095
+ const evaluation = await evaluatePackage(packageName, {
2096
+ version: vulnerableVersion,
2097
+ enrichIntelligence: false
2098
+ });
2099
+ if (cveId) {
2100
+ const vuln = evaluation.vulnerabilities.find(
2101
+ (v) => v.cveId.toLowerCase() === cveId.toLowerCase()
2102
+ );
2103
+ if (vuln?.safeUpgradeVersion) {
2104
+ safeUpgradeVersion = vuln.safeUpgradeVersion;
2105
+ }
2106
+ } else {
2107
+ safeUpgradeVersion = evaluation.vulnerabilities.find(
2108
+ (v) => v.safeUpgradeVersion
2109
+ )?.safeUpgradeVersion;
2110
+ }
2111
+ } catch {
2112
+ }
2113
+ if (!safeUpgradeVersion) {
2114
+ results.push({
2115
+ patchFilePath: summary.patchFilePath,
2116
+ packageName,
2117
+ cveId,
2118
+ installedVersion: vulnerableVersion,
2119
+ status: "patch-retained",
2120
+ reason: "No safe upstream release exists yet; patch remains necessary."
2121
+ });
2122
+ continue;
2123
+ }
2124
+ if (!options.autoApply) {
2125
+ results.push({
2126
+ patchFilePath: summary.patchFilePath,
2127
+ packageName,
2128
+ cveId,
2129
+ installedVersion: vulnerableVersion,
2130
+ safeUpstreamVersion: safeUpgradeVersion,
2131
+ status: "safe-version-available",
2132
+ reason: `Safe upstream version ${safeUpgradeVersion} is available. Re-run with autoApply: true or --auto-apply to upgrade.`
2133
+ });
2134
+ continue;
2135
+ }
2136
+ try {
2137
+ const pkgJsonPath = join10(cwd, "package.json");
2138
+ if (existsSync8(pkgJsonPath)) {
2139
+ const pkgJson = JSON.parse(await readFile3(pkgJsonPath, "utf8"));
2140
+ let updated = false;
2141
+ const depSections = [
2142
+ "dependencies",
2143
+ "devDependencies",
2144
+ "peerDependencies",
2145
+ "optionalDependencies"
2146
+ ];
2147
+ for (const depSection of depSections) {
2148
+ if (pkgJson[depSection] && pkgJson[depSection][packageName]) {
2149
+ pkgJson[depSection][packageName] = `^${safeUpgradeVersion}`;
2150
+ updated = true;
2151
+ }
2152
+ }
2153
+ if (pkgJson.pnpm?.patchedDependencies) {
2154
+ for (const key of Object.keys(pkgJson.pnpm.patchedDependencies)) {
2155
+ if (key.startsWith(`${packageName}@`)) {
2156
+ delete pkgJson.pnpm.patchedDependencies[key];
2157
+ updated = true;
2158
+ }
2159
+ }
2160
+ }
2161
+ if (updated) {
2162
+ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2163
+ }
2164
+ }
2165
+ await rm(summary.patchFilePath, { force: true });
2166
+ if (inspection.manifestFilePath) {
2167
+ await rm(inspection.manifestFilePath, { force: true });
2168
+ }
2169
+ const installCmd = resolveInstallCommand(pm);
2170
+ await execa5(installCmd[0], installCmd.slice(1), { cwd, reject: false });
2171
+ if (options.runTests) {
2172
+ const testCmd = resolveTestCommand(pm);
2173
+ await execa5(testCmd[0], testCmd.slice(1), { cwd, reject: false });
2174
+ }
2175
+ results.push({
2176
+ patchFilePath: summary.patchFilePath,
2177
+ packageName,
2178
+ cveId,
2179
+ installedVersion: vulnerableVersion,
2180
+ safeUpstreamVersion: safeUpgradeVersion,
2181
+ status: "upgraded",
2182
+ reason: `Successfully upgraded to ${safeUpgradeVersion} and retired patch artifact.`
2183
+ });
2184
+ } catch (err) {
2185
+ results.push({
2186
+ patchFilePath: summary.patchFilePath,
2187
+ packageName,
2188
+ cveId,
2189
+ installedVersion: vulnerableVersion,
2190
+ safeUpstreamVersion: safeUpgradeVersion,
2191
+ status: "error",
2192
+ reason: err instanceof Error ? err.message : String(err)
2193
+ });
2194
+ }
2195
+ }
2196
+ const reconciledCount = results.filter((r) => r.status === "upgraded").length;
2197
+ const retainedCount = results.filter(
2198
+ (r) => r.status === "patch-retained" || r.status === "safe-version-available"
2199
+ ).length;
2200
+ return {
2201
+ reconciled: results,
2202
+ reconciledCount,
2203
+ retainedCount
2204
+ };
2205
+ }
2206
+
2207
+ // src/platform/idempotency.ts
2208
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
2209
+ import { join as join11 } from "path";
2210
+ var DEFAULT_INDEX = {
2211
+ schemaVersion: "1.0",
2212
+ entries: {}
2213
+ };
2214
+ function indexFilePath(cwd) {
2215
+ return join11(cwd, ".autoremediator", "state", "idempotency.json");
2216
+ }
2217
+ function entryKey(idempotencyKey, cveId) {
2218
+ return `${idempotencyKey}::${cveId.toUpperCase()}`;
2219
+ }
2220
+ function loadIndex(cwd) {
2221
+ const filePath = indexFilePath(cwd);
2222
+ if (!existsSync9(filePath)) return DEFAULT_INDEX;
2223
+ try {
2224
+ const parsed = JSON.parse(readFileSync8(filePath, "utf8"));
2225
+ if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
2226
+ return parsed;
2227
+ }
2228
+ return DEFAULT_INDEX;
2229
+ } catch {
2230
+ return DEFAULT_INDEX;
2231
+ }
2232
+ }
2233
+ function saveIndex(cwd, index) {
2234
+ const filePath = indexFilePath(cwd);
2235
+ mkdirSync2(join11(cwd, ".autoremediator", "state"), { recursive: true });
2236
+ writeFileSync2(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2237
+ }
2238
+ function readIdempotentReport(cwd, idempotencyKey, cveId) {
2239
+ const index = loadIndex(cwd);
2240
+ const key = entryKey(idempotencyKey, cveId);
2241
+ return index.entries[key]?.report;
2242
+ }
2243
+ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
2244
+ const index = loadIndex(cwd);
2245
+ const key = entryKey(idempotencyKey, cveId);
2246
+ index.entries[key] = {
2247
+ key: idempotencyKey,
2248
+ cveId: cveId.toUpperCase(),
2249
+ report,
2250
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
2251
+ };
2252
+ saveIndex(cwd, index);
2253
+ }
2254
+
2255
+ // src/remediation/pipeline.ts
2256
+ import { generateText as generateText2 } from "ai";
2257
+
2258
+ // src/remediation/local/run.ts
2259
+ import semver6 from "semver";
2260
+
2261
+ // src/intelligence/sources/github-advisory.ts
2262
+ var GITHUB_API_BASE = "https://api.github.com/advisories";
2263
+ async function fetchGhAdvisories(cveId) {
2264
+ const token = getGitHubToken();
2265
+ const headers = {
2266
+ "X-GitHub-Api-Version": "2022-11-28",
2267
+ "User-Agent": "autoremediator"
2268
+ };
2269
+ if (token) {
2270
+ headers["Authorization"] = `token ${token}`;
2271
+ }
2272
+ const normalized = cveId.trim();
2273
+ if (normalized.toUpperCase().startsWith("GHSA-")) {
2274
+ const url2 = `${GITHUB_API_BASE}/${encodeURIComponent(normalized)}`;
2275
+ try {
2276
+ const res = await httpClient({ url: url2, headers });
2277
+ if (!res.ok) {
2278
+ if (res.status === 404) return [];
2279
+ throw new Error(`HTTP ${res.status}: ${res.text}`);
2280
+ }
2281
+ return res.data ? [res.data] : [];
2282
+ } catch (err) {
2283
+ const errorMsg = err instanceof Error ? err.message : String(err);
2284
+ console.warn(
2285
+ `[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
2286
+ );
2287
+ return [];
2288
+ }
2289
+ }
2290
+ const url = new URL(GITHUB_API_BASE);
2291
+ url.searchParams.set("cve_id", cveId);
2292
+ url.searchParams.set("ecosystem", "npm");
2293
+ url.searchParams.set("type", "reviewed");
2294
+ url.searchParams.set("per_page", "10");
2295
+ try {
2296
+ const res = await httpClient({
2297
+ url: url.toString(),
2298
+ headers
2299
+ });
1576
2300
  if (!res.ok) {
1577
2301
  if (res.status === 404) return [];
1578
2302
  throw new Error(`HTTP ${res.status}: ${res.text}`);
@@ -1671,9 +2395,9 @@ async function enrichWithNvd(details) {
1671
2395
  }
1672
2396
 
1673
2397
  // src/intelligence/sources/snapshot.ts
1674
- import { readFileSync as readFileSync6 } from "fs";
2398
+ import { readFileSync as readFileSync9 } from "fs";
1675
2399
  function loadIntelligenceSnapshot(filePath) {
1676
- const raw = readFileSync6(filePath, "utf8");
2400
+ const raw = readFileSync9(filePath, "utf8");
1677
2401
  const parsed = JSON.parse(raw);
1678
2402
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1679
2403
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -1685,8 +2409,8 @@ function lookupSnapshotCve(snapshot, cveId) {
1685
2409
  }
1686
2410
 
1687
2411
  // 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";
2412
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
2413
+ import { isAbsolute as isAbsolute2, join as join12, resolve as resolve3 } from "path";
1690
2414
  import { parseSync } from "oxc-parser";
1691
2415
  import { z as z2 } from "zod";
1692
2416
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
@@ -1702,16 +2426,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
1702
2426
  ]);
1703
2427
  function loadTsconfigPaths(cwd) {
1704
2428
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
1705
- const fullPath = join9(cwd, configName);
1706
- if (!existsSync8(fullPath)) continue;
2429
+ const fullPath = join12(cwd, configName);
2430
+ if (!existsSync10(fullPath)) continue;
1707
2431
  try {
1708
- const raw = readFileSync7(fullPath, "utf8");
2432
+ const raw = readFileSync10(fullPath, "utf8");
1709
2433
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
1710
2434
  const parsed = JSON.parse(cleaned);
1711
2435
  const compilerOptions = parsed.compilerOptions;
1712
2436
  if (compilerOptions?.paths) {
1713
2437
  return {
1714
- baseUrl: compilerOptions.baseUrl ? resolve2(cwd, compilerOptions.baseUrl) : cwd,
2438
+ baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
1715
2439
  paths: compilerOptions.paths
1716
2440
  };
1717
2441
  }
@@ -1726,14 +2450,14 @@ function resolveAliasedPath(importSource, config) {
1726
2450
  }
1727
2451
  for (const [pattern, replacements] of Object.entries(config.paths)) {
1728
2452
  if (pattern === importSource && replacements[0]) {
1729
- return resolve2(config.baseUrl, replacements[0]);
2453
+ return resolve3(config.baseUrl, replacements[0]);
1730
2454
  }
1731
2455
  if (pattern.endsWith("/*")) {
1732
2456
  const prefix = pattern.slice(0, -2);
1733
2457
  if (importSource.startsWith(prefix)) {
1734
2458
  const suffix = importSource.slice(prefix.length);
1735
2459
  const replacement = replacements[0]?.slice(0, -2) ?? "";
1736
- return resolve2(config.baseUrl, replacement + suffix);
2460
+ return resolve3(config.baseUrl, replacement + suffix);
1737
2461
  }
1738
2462
  }
1739
2463
  }
@@ -1742,16 +2466,16 @@ function resolveAliasedPath(importSource, config) {
1742
2466
  function* walkSourceFiles(dir) {
1743
2467
  let entries;
1744
2468
  try {
1745
- entries = readdirSync(dir);
2469
+ entries = readdirSync2(dir);
1746
2470
  } catch {
1747
2471
  return;
1748
2472
  }
1749
2473
  for (const entry of entries) {
1750
2474
  if (IGNORED_DIRS.has(entry)) continue;
1751
- const full = join9(dir, entry);
2475
+ const full = join12(dir, entry);
1752
2476
  let stat2;
1753
2477
  try {
1754
- stat2 = statSync(full);
2478
+ stat2 = statSync2(full);
1755
2479
  } catch {
1756
2480
  continue;
1757
2481
  }
@@ -1789,7 +2513,31 @@ function walkAst(node, visitor) {
1789
2513
  }
1790
2514
  }
1791
2515
  }
1792
- function extractPackageReferences(filename, content, packageName, tsconfig) {
2516
+ function toRootPackageName(importSource) {
2517
+ if (importSource.startsWith("@")) {
2518
+ const parts = importSource.split("/");
2519
+ return parts.slice(0, 2).join("/");
2520
+ }
2521
+ return importSource.split("/")[0] ?? importSource;
2522
+ }
2523
+ function findTransitiveDependencyPath(cwd, targetPackage, importedDirectPackages) {
2524
+ for (const directPkg of importedDirectPackages) {
2525
+ const manifestPath = join12(cwd, "node_modules", directPkg, "package.json");
2526
+ if (!existsSync10(manifestPath)) continue;
2527
+ try {
2528
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
2529
+ if (manifest.dependencies && manifest.dependencies[targetPackage]) {
2530
+ return {
2531
+ directDependency: directPkg,
2532
+ version: manifest.dependencies[targetPackage]
2533
+ };
2534
+ }
2535
+ } catch {
2536
+ }
2537
+ }
2538
+ return null;
2539
+ }
2540
+ function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
1793
2541
  const results = [];
1794
2542
  let parseResult;
1795
2543
  try {
@@ -1801,6 +2549,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1801
2549
  walkAst(parseResult.program, (node) => {
1802
2550
  if (node.type === "ImportDeclaration") {
1803
2551
  const source = node.source;
2552
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2553
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2554
+ }
1804
2555
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1805
2556
  const symbols = [];
1806
2557
  const importedIdentifiers = /* @__PURE__ */ new Map();
@@ -1835,6 +2586,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1835
2586
  }
1836
2587
  if (node.type === "ImportExpression") {
1837
2588
  const source = node.source;
2589
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2590
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2591
+ }
1838
2592
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1839
2593
  results.push({
1840
2594
  matchType: "dynamic-import",
@@ -1846,6 +2600,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1846
2600
  }
1847
2601
  if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
1848
2602
  const firstArg = node.arguments[0];
2603
+ if (firstArg?.value && typeof firstArg.value === "string" && !firstArg.value.startsWith(".") && !isAbsolute2(firstArg.value)) {
2604
+ outImportedDirectPackages?.add(toRootPackageName(firstArg.value));
2605
+ }
1849
2606
  if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
1850
2607
  results.push({
1851
2608
  matchType: "require",
@@ -1857,6 +2614,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1857
2614
  }
1858
2615
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
1859
2616
  const source = node.source;
2617
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2618
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2619
+ }
1860
2620
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1861
2621
  const symbols = [];
1862
2622
  if (node.type === "ExportAllDeclaration") {
@@ -1933,15 +2693,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
1933
2693
  let hasReExport = false;
1934
2694
  let totalInvocations = 0;
1935
2695
  let hasPackageImport = false;
2696
+ const importedDirectPackages = /* @__PURE__ */ new Set();
1936
2697
  for (const filePath of walkSourceFiles(cwd)) {
1937
2698
  scanned++;
1938
2699
  let content;
1939
2700
  try {
1940
- content = readFileSync7(filePath, "utf8");
2701
+ content = readFileSync10(filePath, "utf8");
1941
2702
  } catch {
1942
2703
  continue;
1943
2704
  }
1944
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
2705
+ const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
1945
2706
  if (refs.length > 0) {
1946
2707
  hasPackageImport = true;
1947
2708
  }
@@ -1984,6 +2745,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
1984
2745
  };
1985
2746
  }
1986
2747
  if (!hasPackageImport) {
2748
+ const transitivePath = findTransitiveDependencyPath(cwd, packageName, importedDirectPackages);
2749
+ if (transitivePath) {
2750
+ return {
2751
+ packageName,
2752
+ status: "reachable",
2753
+ reason: `Package '${packageName}' is a transitive dependency of imported package '${transitivePath.directDependency}'`,
2754
+ reachabilityBasis: "transitive-path-found",
2755
+ evidence: []
2756
+ };
2757
+ }
1987
2758
  return {
1988
2759
  packageName,
1989
2760
  status: "not-reachable",
@@ -2112,270 +2883,64 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
2112
2883
  id: cveDetails.id,
2113
2884
  kev: cveDetails.kev,
2114
2885
  epss: cveDetails.epss
2115
- },
2116
- policy: { exploitSignalOverride: opts.exploitSignalOverride }
2117
- });
2118
- if (result.exploitSignalTriggered) {
2119
- exploitSignalTriggered = true;
2120
- }
2121
- }
2122
- let slaBreaches;
2123
- if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
2124
- const breach = checkSlaBreach(
2125
- normalizedId,
2126
- cveDetails.severity,
2127
- cveDetails.publishedAt,
2128
- opts.slaPolicy
2129
- );
2130
- 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.`);
2340
- }
2341
- }
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
2886
+ },
2887
+ policy: { exploitSignalOverride: opts.exploitSignalOverride }
2367
2888
  });
2889
+ if (result.exploitSignalTriggered) {
2890
+ exploitSignalTriggered = true;
2891
+ }
2368
2892
  }
2369
- return result;
2893
+ let slaBreaches;
2894
+ if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
2895
+ const breach = checkSlaBreach(
2896
+ normalizedId,
2897
+ cveDetails.severity,
2898
+ cveDetails.publishedAt,
2899
+ opts.slaPolicy
2900
+ );
2901
+ if (breach) {
2902
+ slaBreaches = [breach];
2903
+ }
2904
+ }
2905
+ return { suppressed: false, exploitSignalTriggered, slaBreaches };
2906
+ }
2907
+
2908
+ // src/remediation/local/sbom.ts
2909
+ function buildSbom(packages, vulnerableNames, results) {
2910
+ const statusByPackage = /* @__PURE__ */ new Map();
2911
+ for (const result of results) {
2912
+ if (!vulnerableNames.has(result.packageName)) continue;
2913
+ if (result.suppressedBy) {
2914
+ statusByPackage.set(result.packageName, "suppressed");
2915
+ } else if (!result.applied && result.strategy === "none") {
2916
+ statusByPackage.set(result.packageName, "skipped");
2917
+ } else if (result.applied) {
2918
+ statusByPackage.set(result.packageName, "patched");
2919
+ } else {
2920
+ statusByPackage.set(result.packageName, "unpatched");
2921
+ }
2922
+ }
2923
+ return packages.map((pkg) => {
2924
+ const isVulnerable = vulnerableNames.has(pkg.name);
2925
+ const entry = {
2926
+ name: pkg.name,
2927
+ version: pkg.version,
2928
+ scope: pkg.type === "direct" ? "direct" : "transitive"
2929
+ };
2930
+ if (isVulnerable) {
2931
+ entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2932
+ }
2933
+ return entry;
2934
+ });
2370
2935
  }
2371
2936
 
2372
2937
  // src/remediation/tools/apply-version-bump.ts
2373
2938
  import { z as z4 } from "zod";
2374
- import { join as join12 } from "path";
2375
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
2939
+ import { join as join14 } from "path";
2940
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
2376
2941
 
2377
2942
  // src/platform/safe-exec.ts
2378
- import { execa as execa5 } from "execa";
2943
+ import { execa as execa6 } from "execa";
2379
2944
  var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2380
2945
  "NODE_OPTIONS",
2381
2946
  "NODE_PATH",
@@ -2424,7 +2989,7 @@ function safeExeca(file, args = [], options = {}) {
2424
2989
  options.env ?? process.env,
2425
2990
  options.allowNodeOptions
2426
2991
  );
2427
- return execa5(file, args, {
2992
+ return execa6(file, args, {
2428
2993
  ...options,
2429
2994
  timeout,
2430
2995
  env: sanitizedEnv
@@ -2432,19 +2997,19 @@ function safeExeca(file, args = [], options = {}) {
2432
2997
  }
2433
2998
 
2434
2999
  // src/remediation/tools/apply-version-bump.ts
2435
- import semver2 from "semver";
3000
+ import semver3 from "semver";
2436
3001
 
2437
3002
  // src/platform/repo-lock.ts
2438
- import { mkdir, rm } from "fs/promises";
2439
- import { join as join11 } from "path";
3003
+ import { mkdir, rm as rm2 } from "fs/promises";
3004
+ import { join as join13 } from "path";
2440
3005
  async function sleep(ms) {
2441
- await new Promise((resolve4) => setTimeout(resolve4, ms));
3006
+ await new Promise((resolve5) => setTimeout(resolve5, ms));
2442
3007
  }
2443
3008
  async function acquireRepoLock(cwd, options = {}) {
2444
3009
  const timeoutMs = options.timeoutMs ?? 15e3;
2445
3010
  const retryDelayMs = options.retryDelayMs ?? 125;
2446
- const lockRoot = join11(cwd, ".autoremediator", "locks");
2447
- const lockPath = join11(cwd, ".autoremediator", "locks", "remediation.lock");
3011
+ const lockRoot = join13(cwd, ".autoremediator", "locks");
3012
+ const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
2448
3013
  const startedAt = Date.now();
2449
3014
  await mkdir(lockRoot, { recursive: true });
2450
3015
  while (true) {
@@ -2453,7 +3018,7 @@ async function acquireRepoLock(cwd, options = {}) {
2453
3018
  return {
2454
3019
  lockPath,
2455
3020
  release: async () => {
2456
- await rm(lockPath, { recursive: true, force: true });
3021
+ await rm2(lockPath, { recursive: true, force: true });
2457
3022
  }
2458
3023
  };
2459
3024
  } catch {
@@ -2485,6 +3050,7 @@ var applyVersionBumpTool = defineTool({
2485
3050
  dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
2486
3051
  policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
2487
3052
  runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
3053
+ typecheck: z4.boolean().default(false).describe("If true, run typecheck validation after applying the fix"),
2488
3054
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2489
3055
  installPreferOffline: z4.boolean().optional(),
2490
3056
  enforceFrozenLockfile: z4.boolean().optional(),
@@ -2499,13 +3065,14 @@ var applyVersionBumpTool = defineTool({
2499
3065
  dryRun,
2500
3066
  policy,
2501
3067
  runTests,
3068
+ typecheck,
2502
3069
  installMode,
2503
3070
  installPreferOffline,
2504
3071
  enforceFrozenLockfile,
2505
3072
  workspace
2506
3073
  }) => {
2507
3074
  const pm = packageManager ?? detectPackageManager(cwd);
2508
- const pkgPath = join12(cwd, "package.json");
3075
+ let pkgPath = join14(cwd, "package.json");
2509
3076
  const loadedPolicy = loadPolicy(cwd, policy);
2510
3077
  const commandConstraints = {
2511
3078
  ...loadedPolicy.constraints,
@@ -2515,9 +3082,6 @@ var applyVersionBumpTool = defineTool({
2515
3082
  workspace: workspace ?? loadedPolicy.constraints?.workspace
2516
3083
  };
2517
3084
  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
3085
  if (!isPackageAllowed(loadedPolicy, packageName)) {
2522
3086
  return {
2523
3087
  packageName,
@@ -2530,7 +3094,7 @@ var applyVersionBumpTool = defineTool({
2530
3094
  message: `Policy blocked changes for package "${packageName}".`
2531
3095
  };
2532
3096
  }
2533
- const isMajorBump = semver2.valid(fromVersion) && semver2.valid(toVersion) && semver2.major(toVersion) > semver2.major(fromVersion);
3097
+ const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
2534
3098
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2535
3099
  return {
2536
3100
  packageName,
@@ -2543,9 +3107,10 @@ var applyVersionBumpTool = defineTool({
2543
3107
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
2544
3108
  };
2545
3109
  }
3110
+ pkgPath = join14(cwd, "package.json");
2546
3111
  let pkgJson;
2547
3112
  try {
2548
- pkgJson = JSON.parse(readFileSync9(pkgPath, "utf8"));
3113
+ pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
2549
3114
  } catch {
2550
3115
  return {
2551
3116
  packageName,
@@ -2557,9 +3122,20 @@ var applyVersionBumpTool = defineTool({
2557
3122
  message: `Could not read package.json at "${pkgPath}".`
2558
3123
  };
2559
3124
  }
2560
- const depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3125
+ let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
2561
3126
  (f) => pkgJson[f]?.[packageName] !== void 0
2562
3127
  );
3128
+ if (!depField) {
3129
+ const declaringWorkspace = findDeclaringWorkspacePackage(cwd, packageName, workspace);
3130
+ if (declaringWorkspace) {
3131
+ pkgPath = declaringWorkspace.manifestPath;
3132
+ pkgJson = declaringWorkspace.manifest;
3133
+ depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3134
+ (f) => pkgJson[f]?.[packageName] !== void 0
3135
+ );
3136
+ commandConstraints.workspace = workspace ?? declaringWorkspace.name;
3137
+ }
3138
+ }
2563
3139
  if (!depField) {
2564
3140
  return {
2565
3141
  packageName,
@@ -2575,9 +3151,14 @@ var applyVersionBumpTool = defineTool({
2575
3151
  const prefixMatch = currentRange.match(/^([~^]?)/);
2576
3152
  const prefix = prefixMatch?.[1] ?? "";
2577
3153
  const newRange = `${prefix}${toVersion}`;
3154
+ const effectiveInstallCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
3155
+ const effectiveTestCommand = resolveTestCommand(pm, commandConstraints);
3156
+ const effectiveTypecheckCommand = resolveTypecheckCommand(pm, commandConstraints, cwd);
3157
+ const effectiveDedupeCommand = resolveDedupeCommand(pm, commandConstraints);
3158
+ const shouldTypecheck = typecheck || Boolean(commandConstraints.typecheck);
2578
3159
  if (dryRun) {
2579
- const installCmd = installCommand.join(" ");
2580
- const testCmd = testCommand.join(" ");
3160
+ const installCmd = effectiveInstallCommand.join(" ");
3161
+ const testCmd = effectiveTestCommand.join(" ");
2581
3162
  return {
2582
3163
  packageName,
2583
3164
  strategy: "version-bump",
@@ -2585,14 +3166,14 @@ var applyVersionBumpTool = defineTool({
2585
3166
  toVersion,
2586
3167
  applied: false,
2587
3168
  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)` : ""}.`
3169
+ message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" -> "${newRange}", then run ${installCmd}${runTests ? ` and ${testCmd}` : ""}${effectiveDedupeCommand.length > 0 ? ` and ${effectiveDedupeCommand.join(" ")} (best-effort)` : ""}.`
2589
3170
  };
2590
3171
  }
2591
3172
  return withRepoLock(cwd, async () => {
2592
3173
  pkgJson[depField][packageName] = newRange;
2593
3174
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2594
3175
  try {
2595
- const [installCmd, ...installArgs] = installCommand;
3176
+ const [installCmd, ...installArgs] = effectiveInstallCommand;
2596
3177
  await safeExeca(installCmd, installArgs, {
2597
3178
  cwd,
2598
3179
  stdio: "pipe"
@@ -2609,12 +3190,12 @@ var applyVersionBumpTool = defineTool({
2609
3190
  applied: false,
2610
3191
  dryRun: false,
2611
3192
  unresolvedReason: "install-failed",
2612
- message: `${installCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3193
+ message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
2613
3194
  };
2614
3195
  }
2615
3196
  if (runTests) {
2616
3197
  try {
2617
- const [testCmd, ...testArgs] = testCommand;
3198
+ const [testCmd, ...testArgs] = effectiveTestCommand;
2618
3199
  await safeExeca(testCmd, testArgs, {
2619
3200
  cwd,
2620
3201
  stdio: "pipe"
@@ -2623,7 +3204,38 @@ var applyVersionBumpTool = defineTool({
2623
3204
  pkgJson[depField][packageName] = currentRange;
2624
3205
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2625
3206
  try {
2626
- const [rollbackCmd, ...rollbackArgs] = installCommand;
3207
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3208
+ await safeExeca(rollbackCmd, rollbackArgs, {
3209
+ cwd,
3210
+ stdio: "pipe"
3211
+ });
3212
+ } catch {
3213
+ }
3214
+ const message = err instanceof Error ? err.message : String(err);
3215
+ return {
3216
+ packageName,
3217
+ strategy: "version-bump",
3218
+ fromVersion,
3219
+ toVersion,
3220
+ applied: false,
3221
+ dryRun: false,
3222
+ unresolvedReason: "validation-failed",
3223
+ message: `${effectiveTestCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3224
+ };
3225
+ }
3226
+ }
3227
+ if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
3228
+ try {
3229
+ const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
3230
+ await safeExeca(tcCmd, tcArgs, {
3231
+ cwd,
3232
+ stdio: "pipe"
3233
+ });
3234
+ } catch (err) {
3235
+ pkgJson[depField][packageName] = currentRange;
3236
+ writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3237
+ try {
3238
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
2627
3239
  await safeExeca(rollbackCmd, rollbackArgs, {
2628
3240
  cwd,
2629
3241
  stdio: "pipe"
@@ -2639,20 +3251,22 @@ var applyVersionBumpTool = defineTool({
2639
3251
  applied: false,
2640
3252
  dryRun: false,
2641
3253
  unresolvedReason: "validation-failed",
2642
- message: `${testCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3254
+ message: `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")}) after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
2643
3255
  };
2644
3256
  }
2645
3257
  }
2646
3258
  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}).`;
3259
+ if (effectiveDedupeCommand.length > 0) {
3260
+ try {
3261
+ const [dedupeCmd, ...dedupeArgs] = effectiveDedupeCommand;
3262
+ await safeExeca(dedupeCmd, dedupeArgs, {
3263
+ cwd,
3264
+ stdio: "pipe"
3265
+ });
3266
+ } catch (err) {
3267
+ const message = err instanceof Error ? err.message : String(err);
3268
+ dedupeNote = ` Dedupe warning: ${effectiveDedupeCommand.join(" ")} failed (${message}).`;
3269
+ }
2656
3270
  }
2657
3271
  return {
2658
3272
  packageName,
@@ -2661,7 +3275,7 @@ var applyVersionBumpTool = defineTool({
2661
3275
  toVersion,
2662
3276
  applied: true,
2663
3277
  dryRun: false,
2664
- message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${installCommand.join(" ")}${runTests ? `, and passed ${testCommand.join(" ")}` : ""}.${dedupeNote}`
3278
+ message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion} in ${depField}, then ran ${effectiveInstallCommand.join(" ")}${runTests ? ` and passed ${effectiveTestCommand.join(" ")}` : ""}.${dedupeNote}`
2665
3279
  };
2666
3280
  });
2667
3281
  }
@@ -2669,9 +3283,9 @@ var applyVersionBumpTool = defineTool({
2669
3283
 
2670
3284
  // src/remediation/tools/apply-package-override/index.ts
2671
3285
  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";
3286
+ import { join as join15 } from "path";
3287
+ import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
3288
+ import semver4 from "semver";
2675
3289
 
2676
3290
  // src/remediation/tools/apply-package-override/helpers.ts
2677
3291
  async function collectDependencyTrace(cwd, pm, packageName, constraints) {
@@ -2813,9 +3427,9 @@ var applyPackageOverrideTool = defineTool({
2813
3427
  workspace
2814
3428
  }) => {
2815
3429
  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);
3430
+ const pkgPath = join15(cwd, "package.json");
3431
+ const denoJsonPath = join15(cwd, "deno.json");
3432
+ const isDenoNative = pm === "deno" && !existsSync11(pkgPath);
2819
3433
  const loadedPolicy = loadPolicy(cwd, policy);
2820
3434
  const commandConstraints = {
2821
3435
  ...loadedPolicy.constraints,
@@ -2841,7 +3455,7 @@ var applyPackageOverrideTool = defineTool({
2841
3455
  message: `Policy blocked changes for package "${packageName}".`
2842
3456
  };
2843
3457
  }
2844
- const isMajorBump = semver3.valid(fromVersion) && semver3.valid(toVersion) && semver3.major(toVersion) > semver3.major(fromVersion);
3458
+ const isMajorBump = semver4.valid(fromVersion) && semver4.valid(toVersion) && semver4.major(toVersion) > semver4.major(fromVersion);
2845
3459
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2846
3460
  return {
2847
3461
  packageName,
@@ -2857,7 +3471,7 @@ var applyPackageOverrideTool = defineTool({
2857
3471
  if (isDenoNative) {
2858
3472
  let denoJson;
2859
3473
  try {
2860
- denoJson = JSON.parse(readFileSync10(denoJsonPath, "utf8"));
3474
+ denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
2861
3475
  } catch {
2862
3476
  return {
2863
3477
  packageName,
@@ -2960,7 +3574,7 @@ var applyPackageOverrideTool = defineTool({
2960
3574
  }
2961
3575
  let pkgJson;
2962
3576
  try {
2963
- pkgJson = JSON.parse(readFileSync10(pkgPath, "utf8"));
3577
+ pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
2964
3578
  } catch {
2965
3579
  return {
2966
3580
  packageName,
@@ -3057,6 +3671,76 @@ var applyPackageOverrideTool = defineTool({
3057
3671
  }
3058
3672
  });
3059
3673
 
3674
+ // src/platform/supply-chain.ts
3675
+ async function verifyPackageTrust(packageName, targetVersion, policy) {
3676
+ if (!policy || !policy.minPublishAgeHours && !policy.blockInstallScripts && !policy.requireProvenance) {
3677
+ return { trusted: true };
3678
+ }
3679
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
3680
+ let packument = null;
3681
+ try {
3682
+ const res = await httpClient({
3683
+ url,
3684
+ headers: { Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" }
3685
+ });
3686
+ if (res.ok && res.data) {
3687
+ packument = res.data;
3688
+ }
3689
+ } catch (err) {
3690
+ const msg = err instanceof Error ? err.message : String(err);
3691
+ return {
3692
+ trusted: false,
3693
+ reason: `Could not verify supply-chain trust for "${packageName}@${targetVersion}": ${msg}`
3694
+ };
3695
+ }
3696
+ if (!packument) {
3697
+ return {
3698
+ trusted: false,
3699
+ reason: `Package packument for "${packageName}" not found on registry.`
3700
+ };
3701
+ }
3702
+ let publishAgeHours;
3703
+ if (packument.time?.[targetVersion]) {
3704
+ const publishedAt = new Date(packument.time[targetVersion]).getTime();
3705
+ publishAgeHours = Math.max(0, (Date.now() - publishedAt) / (1e3 * 60 * 60));
3706
+ if (policy.minPublishAgeHours && publishAgeHours < policy.minPublishAgeHours) {
3707
+ return {
3708
+ trusted: false,
3709
+ publishAgeHours,
3710
+ reason: `Target release "${packageName}@${targetVersion}" was published only ${publishAgeHours.toFixed(1)}h ago (minimum required age is ${policy.minPublishAgeHours}h).`
3711
+ };
3712
+ }
3713
+ }
3714
+ const versionData = packument.versions?.[targetVersion];
3715
+ const scripts = versionData?.scripts ?? {};
3716
+ const installScriptKeys = ["preinstall", "install", "postinstall"].filter((k) => Boolean(scripts[k]));
3717
+ const hasInstallScripts = installScriptKeys.length > 0;
3718
+ if (policy.blockInstallScripts && hasInstallScripts) {
3719
+ return {
3720
+ trusted: false,
3721
+ publishAgeHours,
3722
+ hasInstallScripts: true,
3723
+ reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
3724
+ };
3725
+ }
3726
+ const hasProvenance = Boolean(versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url);
3727
+ if (policy.requireProvenance && !hasProvenance) {
3728
+ return {
3729
+ trusted: false,
3730
+ publishAgeHours,
3731
+ hasInstallScripts,
3732
+ hasProvenance: false,
3733
+ reason: `Target release "${packageName}@${targetVersion}" does not have cryptographic build provenance attestations.`
3734
+ };
3735
+ }
3736
+ return {
3737
+ trusted: true,
3738
+ publishAgeHours,
3739
+ hasInstallScripts,
3740
+ hasProvenance
3741
+ };
3742
+ }
3743
+
3060
3744
  // src/remediation/local/primary-strategy.ts
3061
3745
  async function resolvePrimaryResult(params) {
3062
3746
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
@@ -3127,6 +3811,22 @@ async function resolvePrimaryResult(params) {
3127
3811
  }
3128
3812
  };
3129
3813
  }
3814
+ const trust2 = await verifyPackageTrust(pkg.name, safeUpgrade2.safeVersion, constraints.supplyChainPolicy);
3815
+ if (!trust2.trusted) {
3816
+ return {
3817
+ steps: 1,
3818
+ result: {
3819
+ packageName: pkg.name,
3820
+ strategy: "none",
3821
+ fromVersion: pkg.version,
3822
+ applied: false,
3823
+ dryRun,
3824
+ unresolvedReason: "policy-blocked",
3825
+ vulnerableRange: vulnerable.affected.vulnerableRange,
3826
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade2.safeVersion}": ${trust2.reason}`
3827
+ }
3828
+ };
3829
+ }
3130
3830
  const overrideResult = await applyPackageOverrideTool.execute({
3131
3831
  cwd,
3132
3832
  packageManager,
@@ -3182,6 +3882,22 @@ async function resolvePrimaryResult(params) {
3182
3882
  }
3183
3883
  };
3184
3884
  }
3885
+ const trust = await verifyPackageTrust(pkg.name, safeUpgrade.safeVersion, constraints.supplyChainPolicy);
3886
+ if (!trust.trusted) {
3887
+ return {
3888
+ steps: 1,
3889
+ result: {
3890
+ packageName: pkg.name,
3891
+ strategy: "none",
3892
+ fromVersion: pkg.version,
3893
+ applied: false,
3894
+ dryRun,
3895
+ unresolvedReason: "policy-blocked",
3896
+ vulnerableRange: vulnerable.affected.vulnerableRange,
3897
+ message: `Supply chain trust gate blocked "${pkg.name}@${safeUpgrade.safeVersion}": ${trust.reason}`
3898
+ }
3899
+ };
3900
+ }
3185
3901
  const applyResult = await applyVersionBumpTool.execute({
3186
3902
  cwd,
3187
3903
  packageManager,
@@ -3191,6 +3907,7 @@ async function resolvePrimaryResult(params) {
3191
3907
  dryRun,
3192
3908
  policy,
3193
3909
  runTests,
3910
+ typecheck: constraints.typecheck,
3194
3911
  installMode: constraints.installMode,
3195
3912
  installPreferOffline: constraints.installPreferOffline,
3196
3913
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
@@ -3204,10 +3921,10 @@ async function resolvePrimaryResult(params) {
3204
3921
 
3205
3922
  // src/remediation/tools/fetch-package-source.ts
3206
3923
  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";
3924
+ import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
3925
+ import { join as join16 } from "path";
3209
3926
  import { tmpdir } from "os";
3210
- import { execa as execa6 } from "execa";
3927
+ import { execa as execa7 } from "execa";
3211
3928
  var fetchPackageSourceTool = defineTool({
3212
3929
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
3213
3930
  parameters: z6.object({
@@ -3225,25 +3942,25 @@ var fetchPackageSourceTool = defineTool({
3225
3942
  if (safePatterns.length === 0) {
3226
3943
  return { success: false, error: "No valid file patterns provided." };
3227
3944
  }
3228
- const tempBaseDir = await mkdtemp(join14(tmpdir(), "autoremediator-pkg-"));
3229
- const extractDir = join14(tempBaseDir, "out");
3945
+ const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
3946
+ const extractDir = join16(tempBaseDir, "out");
3230
3947
  try {
3231
3948
  const scopedName = packageName.split("/").pop();
3232
3949
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3233
3950
  await mkdir2(tempBaseDir, { recursive: true });
3234
- const tarballPath = join14(tempBaseDir, "package.tgz");
3235
- await execa6("curl", ["-L", "-o", tarballPath, npmUrl]);
3951
+ const tarballPath = join16(tempBaseDir, "package.tgz");
3952
+ await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3236
3953
  await mkdir2(extractDir, { recursive: true });
3237
- await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
3954
+ await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3238
3955
  const extractedContents = await readdir2(extractDir);
3239
- const packageRootDir = extractedContents.includes("package") ? join14(extractDir, "package") : extractDir;
3956
+ const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
3240
3957
  const sourceCode = {};
3241
3958
  async function walkDir(dir, relativeBase) {
3242
3959
  try {
3243
3960
  const files = await readdir2(dir, { withFileTypes: true });
3244
3961
  for (const file of files) {
3245
- const fullPath = join14(dir, file.name);
3246
- const relPath = join14(relativeBase, file.name);
3962
+ const fullPath = join16(dir, file.name);
3963
+ const relPath = join16(relativeBase, file.name);
3247
3964
  if (file.isDirectory()) {
3248
3965
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3249
3966
  file.name
@@ -3257,7 +3974,7 @@ var fetchPackageSourceTool = defineTool({
3257
3974
  });
3258
3975
  if (matches) {
3259
3976
  try {
3260
- const content = await readFile3(fullPath, "utf8");
3977
+ const content = await readFile4(fullPath, "utf8");
3261
3978
  sourceCode[relPath] = content;
3262
3979
  } catch {
3263
3980
  }
@@ -3292,7 +4009,7 @@ var fetchPackageSourceTool = defineTool({
3292
4009
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
3293
4010
  };
3294
4011
  } finally {
3295
- await rm2(tempBaseDir, { recursive: true, force: true });
4012
+ await rm3(tempBaseDir, { recursive: true, force: true });
3296
4013
  }
3297
4014
  }
3298
4015
  });
@@ -3350,6 +4067,7 @@ Analyze the source code to:
3350
4067
  2. Explain the root cause of the security issue
3351
4068
  3. Propose a secure fix that addresses the vulnerability
3352
4069
  4. Provide the complete fixed version of affected files
4070
+ 5. Optionally provide a standalone Node.js reproduction/regression test script
3353
4071
 
3354
4072
  ## Response Format
3355
4073
  Respond ONLY with valid JSON (no markdown, no extra text):
@@ -3360,7 +4078,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
3360
4078
  "path/to/other.ts": "Complete fixed source code for this file"
3361
4079
  },
3362
4080
  "confidence": 0.95,
3363
- "riskLevel": "medium"
4081
+ "riskLevel": "medium",
4082
+ "reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
3364
4083
  }
3365
4084
 
3366
4085
  Important:
@@ -3640,6 +4359,7 @@ var generatePatchTool = defineTool({
3640
4359
  success: true,
3641
4360
  patches,
3642
4361
  patchContent: patches[0]?.unifiedDiff,
4362
+ reproductionTest: analysis.reproductionTest,
3643
4363
  llmProvider: provider,
3644
4364
  llmModel: modelName,
3645
4365
  latencyMs,
@@ -3664,17 +4384,17 @@ var generatePatchTool = defineTool({
3664
4384
 
3665
4385
  // src/remediation/tools/apply-patch-file/index.ts
3666
4386
  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";
4387
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
4388
+ import { join as join18 } from "path";
4389
+ import { execa as execa9 } from "execa";
3670
4390
 
3671
4391
  // 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";
4392
+ import { existsSync as existsSync12 } from "fs";
4393
+ import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
3674
4394
  import { createHash } from "crypto";
3675
4395
  import { tmpdir as tmpdir2 } from "os";
3676
- import { join as join15 } from "path";
3677
- import { execa as execa7 } from "execa";
4396
+ import { join as join17 } from "path";
4397
+ import { execa as execa8 } from "execa";
3678
4398
  async function resolvePatchMode(packageManager, cwd) {
3679
4399
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
3680
4400
  return "patch-package";
@@ -3705,13 +4425,13 @@ function computePatchIntegrity(patchContent) {
3705
4425
  return `sha256:${hex}`;
3706
4426
  }
3707
4427
  async function writePatchManifest(manifestFilePath, artifact) {
3708
- await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4428
+ await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
3709
4429
  }
3710
4430
  async function configurePatchPackagePostinstall(cwd, packageManager) {
3711
- const pkgJsonPath = join15(cwd, "package.json");
4431
+ const pkgJsonPath = join17(cwd, "package.json");
3712
4432
  let pkgJson;
3713
4433
  try {
3714
- pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
4434
+ pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
3715
4435
  } catch {
3716
4436
  return {
3717
4437
  success: false,
@@ -3723,7 +4443,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
3723
4443
  try {
3724
4444
  const commands = getPackageManagerCommands(packageManager);
3725
4445
  const [cmd, ...args] = commands.installDev("patch-package");
3726
- await execa7(cmd, args, {
4446
+ await execa8(cmd, args, {
3727
4447
  cwd,
3728
4448
  stdio: "pipe"
3729
4449
  });
@@ -3744,13 +4464,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
3744
4464
  } else if (!currentPostinstall) {
3745
4465
  pkgJson.scripts.postinstall = patchApplyCmd;
3746
4466
  }
3747
- await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4467
+ await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3748
4468
  return { success: true };
3749
4469
  }
3750
4470
  async function capturePackageJsonSnapshot(cwd) {
3751
- const path = join15(cwd, "package.json");
4471
+ const path = join17(cwd, "package.json");
3752
4472
  try {
3753
- const content = await readFile4(path, "utf8");
4473
+ const content = await readFile5(path, "utf8");
3754
4474
  return { path, content };
3755
4475
  } catch {
3756
4476
  return void 0;
@@ -3766,19 +4486,19 @@ async function cleanupPatchArtifacts(params) {
3766
4486
  installCommand,
3767
4487
  rerunInstall
3768
4488
  } = params;
3769
- await rm3(patchFilePath, { force: true }).catch(() => void 0);
4489
+ await rm4(patchFilePath, { force: true }).catch(() => void 0);
3770
4490
  if (manifestFilePath) {
3771
- await rm3(manifestFilePath, { force: true }).catch(() => void 0);
4491
+ await rm4(manifestFilePath, { force: true }).catch(() => void 0);
3772
4492
  }
3773
4493
  if (patchMode === "patch-package" && packageJsonSnapshot) {
3774
- await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4494
+ await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
3775
4495
  () => void 0
3776
4496
  );
3777
4497
  }
3778
4498
  if (!rerunInstall) return;
3779
4499
  try {
3780
4500
  const [installCmd, ...installArgs] = installCommand;
3781
- await execa7(installCmd, installArgs, {
4501
+ await execa8(installCmd, installArgs, {
3782
4502
  cwd,
3783
4503
  stdio: "pipe"
3784
4504
  });
@@ -3792,7 +4512,7 @@ async function applyNativePatch(params) {
3792
4512
  const createArgs = ["patch", packageSpec];
3793
4513
  let patchDir;
3794
4514
  try {
3795
- const createResult = await execa7(createCommand, createArgs, {
4515
+ const createResult = await execa8(createCommand, createArgs, {
3796
4516
  cwd,
3797
4517
  stdio: "pipe"
3798
4518
  });
@@ -3820,17 +4540,17 @@ ${createResult.stderr}`);
3820
4540
  error: `Could not determine native patch directory for ${packageSpec}.`
3821
4541
  };
3822
4542
  }
3823
- const tempPatchDir = await mkdtemp2(join15(tmpdir2(), "autoremediator-native-patch-"));
3824
- const tempPatchFile = join15(tempPatchDir, "change.patch");
4543
+ const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
4544
+ const tempPatchFile = join17(tempPatchDir, "change.patch");
3825
4545
  try {
3826
- await writeFile(tempPatchFile, patchContent, "utf8");
3827
- await execa7("patch", ["-p1", "-i", tempPatchFile], {
4546
+ await writeFile2(tempPatchFile, patchContent, "utf8");
4547
+ await execa8("patch", ["-p1", "-i", tempPatchFile], {
3828
4548
  cwd: patchDir,
3829
4549
  stdio: "pipe"
3830
4550
  });
3831
4551
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
3832
4552
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
3833
- await execa7(commitCommand, commitArgs, {
4553
+ await execa8(commitCommand, commitArgs, {
3834
4554
  cwd,
3835
4555
  stdio: "pipe"
3836
4556
  });
@@ -3845,19 +4565,19 @@ ${createResult.stderr}`);
3845
4565
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
3846
4566
  };
3847
4567
  } finally {
3848
- await rm3(tempPatchDir, { recursive: true, force: true });
4568
+ await rm4(tempPatchDir, { recursive: true, force: true });
3849
4569
  }
3850
4570
  return { success: true };
3851
4571
  }
3852
4572
  function extractPatchDirectory(output) {
3853
4573
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
3854
4574
  for (const line of lines) {
3855
- if (existsSync10(line)) {
4575
+ if (existsSync12(line)) {
3856
4576
  return line;
3857
4577
  }
3858
4578
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
3859
4579
  for (const token of tokens) {
3860
- if (token.startsWith("/") && existsSync10(token)) {
4580
+ if (token.startsWith("/") && existsSync12(token)) {
3861
4581
  return token;
3862
4582
  }
3863
4583
  }
@@ -3867,7 +4587,7 @@ function extractPatchDirectory(output) {
3867
4587
  async function validatePatchWithTests(cwd, testCommand) {
3868
4588
  try {
3869
4589
  const [cmd, ...args] = testCommand;
3870
- const result = await execa7(cmd, args, {
4590
+ const result = await execa8(cmd, args, {
3871
4591
  cwd,
3872
4592
  timeout: 6e4,
3873
4593
  stdio: "pipe"
@@ -3927,6 +4647,7 @@ var applyPatchFileTool = defineTool({
3927
4647
  enforceFrozenLockfile: z8.boolean().optional(),
3928
4648
  workspace: z8.string().optional(),
3929
4649
  validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
4650
+ reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
3930
4651
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
3931
4652
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
3932
4653
  message: "Either patchContent or patches must be provided"
@@ -3948,6 +4669,7 @@ var applyPatchFileTool = defineTool({
3948
4669
  workspace,
3949
4670
  riskLevel,
3950
4671
  validateWithTests,
4672
+ reproductionTest,
3951
4673
  dryRun
3952
4674
  }) => {
3953
4675
  try {
@@ -3998,7 +4720,7 @@ var applyPatchFileTool = defineTool({
3998
4720
  };
3999
4721
  }
4000
4722
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
4001
- const patchFilePath = join16(cwd, patchesDir, patchFileName);
4723
+ const patchFilePath = join18(cwd, patchesDir, patchFileName);
4002
4724
  const manifestFilePath = `${patchFilePath}.json`;
4003
4725
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4004
4726
  const baseArtifact = {
@@ -4037,9 +4759,9 @@ var applyPatchFileTool = defineTool({
4037
4759
  }
4038
4760
  return withRepoLock(cwd, async () => {
4039
4761
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
4040
- const patchesDirPath = join16(cwd, patchesDir);
4762
+ const patchesDirPath = join18(cwd, patchesDir);
4041
4763
  await mkdir3(patchesDirPath, { recursive: true });
4042
- await writeFile2(patchFilePath, selectedPatch, "utf8");
4764
+ await writeFile3(patchFilePath, selectedPatch, "utf8");
4043
4765
  validationPhases.push({
4044
4766
  phase: "patch-write",
4045
4767
  passed: true,
@@ -4107,7 +4829,7 @@ var applyPatchFileTool = defineTool({
4107
4829
  if (patchMode === "patch-package") {
4108
4830
  try {
4109
4831
  const [installCmd, ...installArgs] = installCommand;
4110
- await execa8(installCmd, installArgs, {
4832
+ await execa9(installCmd, installArgs, {
4111
4833
  cwd,
4112
4834
  stdio: "pipe"
4113
4835
  });
@@ -4200,10 +4922,33 @@ var applyPatchFileTool = defineTool({
4200
4922
  message: "Patch validation tests passed."
4201
4923
  });
4202
4924
  }
4925
+ let reproductionTestPath;
4926
+ if (reproductionTest) {
4927
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
4928
+ reproductionTestPath = join18(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
4929
+ await writeFile3(reproductionTestPath, reproductionTest, "utf8");
4930
+ try {
4931
+ const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
4932
+ validationPhases.push({
4933
+ phase: "reproduction-test",
4934
+ passed: reproRun.exitCode === 0,
4935
+ message: "Reproduction test verified fix successfully."
4936
+ });
4937
+ } catch (err) {
4938
+ const reproErr = err instanceof Error ? err.message : String(err);
4939
+ validationPhases.push({
4940
+ phase: "reproduction-test",
4941
+ passed: false,
4942
+ error: `Reproduction test failed: ${reproErr}`
4943
+ });
4944
+ }
4945
+ }
4203
4946
  const finalArtifact = {
4204
4947
  ...artifact,
4205
4948
  applied: true,
4206
4949
  dryRun: false,
4950
+ reproductionTest,
4951
+ reproductionTestPath,
4207
4952
  validationPhases
4208
4953
  };
4209
4954
  await writePatchManifest(manifestFilePath, finalArtifact);
@@ -4513,7 +5258,11 @@ function resolveLocalRunOptions(options) {
4513
5258
  const runTests = options.runTests ?? false;
4514
5259
  const policy = options.policy ?? "";
4515
5260
  const patchesDir = options.patchesDir || "./patches";
4516
- const constraints = options.constraints ?? {};
5261
+ const constraints = {
5262
+ ...options.constraints,
5263
+ typecheck: options.typecheck ?? options.constraints?.typecheck,
5264
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy
5265
+ };
4517
5266
  const loadedPolicy = loadPolicy(cwd, options.policy);
4518
5267
  const llmProvider = resolveProvider(options);
4519
5268
  return {
@@ -4550,7 +5299,7 @@ function resolveLocalRunOptions(options) {
4550
5299
  }
4551
5300
 
4552
5301
  // src/remediation/local/vulnerability-match.ts
4553
- import semver4 from "semver";
5302
+ import semver5 from "semver";
4554
5303
  function findVulnerablePackages(cveDetails, installedPackages) {
4555
5304
  const vulnerablePackages = [];
4556
5305
  for (const affected of cveDetails.affectedPackages) {
@@ -4559,10 +5308,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
4559
5308
  if (affected.ecosystem !== "npm") continue;
4560
5309
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
4561
5310
  for (const installed of matches) {
4562
- if (!semver4.valid(installed.version)) continue;
5311
+ if (!semver5.valid(installed.version)) continue;
4563
5312
  let isVulnerable = false;
4564
5313
  try {
4565
- isVulnerable = semver4.satisfies(installed.version, affected.vulnerableRange, {
5314
+ isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4566
5315
  includePrerelease: false
4567
5316
  });
4568
5317
  } catch {
@@ -4905,7 +5654,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
4905
5654
  };
4906
5655
  if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
4907
5656
  try {
4908
- if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
5657
+ if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
4909
5658
  includePrerelease: false
4910
5659
  })) {
4911
5660
  primaryResult.regressionDetected = true;
@@ -4996,8 +5745,8 @@ function accumulateStepResults(params) {
4996
5745
  }
4997
5746
 
4998
5747
  // src/remediation/orchestration-prompt.ts
4999
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
5000
- import { join as join17 } from "path";
5748
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
5749
+ import { join as join19 } from "path";
5001
5750
  function buildProviderAddendum(provider, personality = "balanced") {
5002
5751
  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
5752
  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 +5757,13 @@ Provider profile:
5008
5757
  - ${personalityDirective}`;
5009
5758
  }
5010
5759
  function loadOrchestrationPrompt(ctx) {
5011
- const promptPath = join17(
5760
+ const promptPath = join19(
5012
5761
  process.cwd(),
5013
5762
  ".github",
5014
5763
  "instructions",
5015
5764
  "orchestration.instructions.md"
5016
5765
  );
5017
- if (!existsSync11(promptPath)) {
5766
+ if (!existsSync13(promptPath)) {
5018
5767
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
5019
5768
  Working directory: ${ctx.cwd}
5020
5769
  Package manager: ${ctx.packageManager}
@@ -5040,7 +5789,7 @@ Fallback sequence (when neither version bump nor override can be applied):
5040
5789
 
5041
5790
  Always respect dryRun and policy constraints.`;
5042
5791
  }
5043
- const template = readFileSync11(promptPath, "utf8");
5792
+ const template = readFileSync13(promptPath, "utf8");
5044
5793
  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
5794
  "{{directDependenciesOnly}}",
5046
5795
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -5061,73 +5810,9 @@ function createProgressEmitter(options) {
5061
5810
  };
5062
5811
  }
5063
5812
 
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
5127
- };
5128
- return details;
5129
- }
5130
-
5813
+ // src/remediation/tools/lookup-cve.ts
5814
+ import { z as z9 } from "zod";
5815
+
5131
5816
  // src/intelligence/sources/cve-services.ts
5132
5817
  function pickEnglishDescription(container) {
5133
5818
  if (!container?.descriptions?.length) return void 0;
@@ -5411,7 +6096,7 @@ var lookupCveTool = defineTool({
5411
6096
 
5412
6097
  // src/remediation/tools/check-version-match.ts
5413
6098
  import { z as z10 } from "zod";
5414
- import semver6 from "semver";
6099
+ import semver7 from "semver";
5415
6100
  var affectedPackageSchema = z10.object({
5416
6101
  name: z10.string(),
5417
6102
  ecosystem: z10.literal("npm"),
@@ -5440,10 +6125,10 @@ var checkVersionMatchTool = defineTool({
5440
6125
  (p) => p.name === affected.name
5441
6126
  );
5442
6127
  for (const installed of matches) {
5443
- if (!semver6.valid(installed.version)) continue;
6128
+ if (!semver7.valid(installed.version)) continue;
5444
6129
  let isVulnerable = false;
5445
6130
  try {
5446
- isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
6131
+ isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
5447
6132
  includePrerelease: false
5448
6133
  });
5449
6134
  } catch {
@@ -5761,7 +6446,7 @@ async function runRemediationPipeline(cveId, options = {}) {
5761
6446
  }
5762
6447
 
5763
6448
  // src/api/change-request/index.ts
5764
- import { execa as execa9 } from "execa";
6449
+ import { execa as execa10 } from "execa";
5765
6450
  import { randomBytes } from "crypto";
5766
6451
  function sanitizeBranchToken(value) {
5767
6452
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -5790,22 +6475,68 @@ function resolveProvider2(options) {
5790
6475
  return options.provider ?? "github";
5791
6476
  }
5792
6477
  function resolveGrouping(options) {
5793
- if (options.grouping && options.grouping !== "all") {
6478
+ const allowed = ["all", "per-cve", "per-package", "by-severity"];
6479
+ if (options.grouping && !allowed.includes(options.grouping)) {
5794
6480
  throw new Error(
5795
- "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
6481
+ `Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
5796
6482
  );
5797
6483
  }
5798
- return "all";
6484
+ return options.grouping ?? "all";
6485
+ }
6486
+ function groupReports(reports, grouping) {
6487
+ if (reports.length === 0) return [];
6488
+ if (grouping === "all") {
6489
+ return [reports];
6490
+ }
6491
+ if (grouping === "per-cve") {
6492
+ const map = /* @__PURE__ */ new Map();
6493
+ for (const report of reports) {
6494
+ const key = report.cveId.toUpperCase();
6495
+ const list = map.get(key) ?? [];
6496
+ list.push(report);
6497
+ map.set(key, list);
6498
+ }
6499
+ return Array.from(map.values());
6500
+ }
6501
+ if (grouping === "per-package") {
6502
+ const map = /* @__PURE__ */ new Map();
6503
+ for (const report of reports) {
6504
+ const pkgs = new Set(report.results.map((r) => r.packageName));
6505
+ if (pkgs.size === 0) {
6506
+ const list = map.get("misc") ?? [];
6507
+ list.push(report);
6508
+ map.set("misc", list);
6509
+ } else {
6510
+ for (const pkg of pkgs) {
6511
+ const list = map.get(pkg) ?? [];
6512
+ list.push(report);
6513
+ map.set(pkg, list);
6514
+ }
6515
+ }
6516
+ }
6517
+ return Array.from(map.values());
6518
+ }
6519
+ if (grouping === "by-severity") {
6520
+ const map = /* @__PURE__ */ new Map();
6521
+ for (const report of reports) {
6522
+ const sev = report.cveDetails?.severity ?? "UNKNOWN";
6523
+ const list = map.get(sev) ?? [];
6524
+ list.push(report);
6525
+ map.set(sev, list);
6526
+ }
6527
+ return Array.from(map.values());
6528
+ }
6529
+ return [reports];
5799
6530
  }
5800
6531
  async function detectCurrentBranch(cwd) {
5801
- const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6532
+ const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
5802
6533
  cwd,
5803
6534
  stdio: "pipe"
5804
6535
  });
5805
6536
  return result.stdout.trim();
5806
6537
  }
5807
6538
  async function ensureRepoHasChanges(cwd) {
5808
- const diff = await execa9("git", ["status", "--porcelain"], {
6539
+ const diff = await execa10("git", ["status", "--porcelain"], {
5809
6540
  cwd,
5810
6541
  stdio: "pipe"
5811
6542
  });
@@ -5834,7 +6565,7 @@ async function createGitHubRequest(params) {
5834
6565
  if (params.draft) {
5835
6566
  args.push("--draft");
5836
6567
  }
5837
- const result = await execa9("gh", args, {
6568
+ const result = await execa10("gh", args, {
5838
6569
  cwd: params.cwd,
5839
6570
  stdio: "pipe"
5840
6571
  });
@@ -5859,7 +6590,7 @@ async function createGitLabRequest(params) {
5859
6590
  if (params.draft) {
5860
6591
  args.push("--draft");
5861
6592
  }
5862
- const result = await execa9("glab", args, {
6593
+ const result = await execa10("glab", args, {
5863
6594
  cwd: params.cwd,
5864
6595
  stdio: "pipe"
5865
6596
  });
@@ -5869,64 +6600,68 @@ async function createChangeRequestsForReports(params) {
5869
6600
  const { cwd, options, reports } = params;
5870
6601
  const provider = resolveProvider2(options);
5871
6602
  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}
6603
+ const groups = groupReports(reports, grouping);
6604
+ if (groups.length === 0) {
6605
+ return [];
6606
+ }
6607
+ const results = [];
6608
+ const hasChanges = await ensureRepoHasChanges(cwd).catch(() => false);
6609
+ const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd).catch(() => "main");
6610
+ const branchPrefix = options.branchPrefix ?? "autoremediator";
6611
+ const pushRemote = options.pushRemote ?? "origin";
6612
+ if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
6613
+ throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
6614
+ }
6615
+ if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
6616
+ throw new Error(`Invalid repository format: ${options.repository}`);
6617
+ }
6618
+ for (const group of groups) {
6619
+ const plan = buildPlan(group);
6620
+ const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
6621
+ const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
6622
+ const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
6623
+ const body = bodyFooter ? `${plan.body}
5877
6624
 
5878
6625
  ${bodyFooter}` : plan.body;
5879
- try {
5880
- const hasChanges = await ensureRepoHasChanges(cwd);
5881
6626
  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
- ];
6627
+ results.push({
6628
+ provider,
6629
+ grouping,
6630
+ repository: options.repository,
6631
+ branchName: "",
6632
+ title,
6633
+ body,
6634
+ created: false,
6635
+ cveIds: plan.cveIds,
6636
+ packageNames: plan.packageNames,
6637
+ error: "No repository changes to include in change request."
6638
+ });
6639
+ continue;
5896
6640
  }
5897
- const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
5898
- const branchPrefix = options.branchPrefix ?? "autoremediator";
5899
6641
  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
- {
6642
+ try {
6643
+ await execa10("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
6644
+ await execa10("git", ["add", "-A"], { cwd, stdio: "pipe" });
6645
+ await execa10("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
6646
+ await execa10("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
6647
+ const url = provider === "github" ? await createGitHubRequest({
6648
+ cwd,
6649
+ repository: options.repository,
6650
+ head: branchName,
6651
+ base: baseBranch,
6652
+ title,
6653
+ body,
6654
+ draft: options.draft
6655
+ }) : await createGitLabRequest({
6656
+ cwd,
6657
+ repository: options.repository,
6658
+ source: branchName,
6659
+ target: baseBranch,
6660
+ title,
6661
+ body,
6662
+ draft: options.draft
6663
+ });
6664
+ results.push({
5930
6665
  provider,
5931
6666
  grouping,
5932
6667
  repository: options.repository,
@@ -5938,11 +6673,13 @@ ${bodyFooter}` : plan.body;
5938
6673
  url,
5939
6674
  cveIds: plan.cveIds,
5940
6675
  packageNames: plan.packageNames
6676
+ });
6677
+ if (groups.length > 1) {
6678
+ await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
6679
+ });
5941
6680
  }
5942
- ];
5943
- } catch (error) {
5944
- return [
5945
- {
6681
+ } catch (error) {
6682
+ results.push({
5946
6683
  provider,
5947
6684
  grouping,
5948
6685
  repository: options.repository,
@@ -5954,9 +6691,10 @@ ${bodyFooter}` : plan.body;
5954
6691
  cveIds: plan.cveIds,
5955
6692
  packageNames: plan.packageNames,
5956
6693
  error: error instanceof Error ? error.message : String(error)
5957
- }
5958
- ];
6694
+ });
6695
+ }
5959
6696
  }
6697
+ return results;
5960
6698
  }
5961
6699
 
5962
6700
  // src/api/context.ts
@@ -5990,7 +6728,7 @@ function resolveConstraints(options, cwd) {
5990
6728
 
5991
6729
  // src/platform/evidence.ts
5992
6730
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
5993
- import { join as join18 } from "path";
6731
+ import { join as join20 } from "path";
5994
6732
  function createEvidenceLog(cwd, cveIds, context = {}) {
5995
6733
  return {
5996
6734
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -6022,9 +6760,9 @@ function finalizeEvidence(log) {
6022
6760
  return log;
6023
6761
  }
6024
6762
  function writeEvidenceLog(cwd, log) {
6025
- const dir = join18(cwd, ".autoremediator", "evidence");
6763
+ const dir = join20(cwd, ".autoremediator", "evidence");
6026
6764
  mkdirSync3(dir, { recursive: true });
6027
- const filePath = join18(dir, `${log.runId}.json`);
6765
+ const filePath = join20(dir, `${log.runId}.json`);
6028
6766
  writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
6029
6767
  return filePath;
6030
6768
  }
@@ -6524,10 +7262,15 @@ function ciExitCode(summary) {
6524
7262
  }
6525
7263
 
6526
7264
  // src/api/remediate/validators.ts
7265
+ var CVE_ID_REGEX = /^CVE-\d{4}-\d{1,7}$/i;
7266
+ var GHSA_ID_REGEX = /^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/i;
7267
+ function isVulnerabilityId(id) {
7268
+ return CVE_ID_REGEX.test(id) || GHSA_ID_REGEX.test(id);
7269
+ }
6527
7270
  function assertValidCveId(cveId) {
6528
- if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
7271
+ if (!isVulnerabilityId(cveId)) {
6529
7272
  throw new Error(
6530
- `Invalid CVE ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337).`
7273
+ `Invalid vulnerability ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337) or GHSA-xxxx-xxxx-xxxx.`
6531
7274
  );
6532
7275
  }
6533
7276
  }
@@ -6623,13 +7366,14 @@ async function planRemediation(cveId, options = {}) {
6623
7366
  }
6624
7367
 
6625
7368
  // 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";
7369
+ import { extname, resolve as resolve4 } from "path";
7370
+ import { readFileSync as readFileSync17 } from "fs";
7371
+ import { execa as execa11 } from "execa";
6629
7372
 
6630
7373
  // src/scanner/adapters/npm-audit.ts
6631
- import { readFileSync as readFileSync12 } from "fs";
7374
+ import { readFileSync as readFileSync14 } from "fs";
6632
7375
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
7376
+ var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6633
7377
  function normalizeSeverity(raw) {
6634
7378
  if (!raw) return "UNKNOWN";
6635
7379
  const up = raw.toUpperCase();
@@ -6644,7 +7388,8 @@ function parseNpmAuditJsonFromString(content) {
6644
7388
  for (const vuln of Object.values(report.vulnerabilities ?? {})) {
6645
7389
  for (const viaEntry of vuln.via ?? []) {
6646
7390
  const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
6647
- const matches = text.match(CVE_REGEX) ?? [];
7391
+ const cveMatches = text.match(CVE_REGEX) ?? [];
7392
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
6648
7393
  for (const match of matches) {
6649
7394
  const cveId = match.toUpperCase();
6650
7395
  const key = `${cveId}:${vuln.name}`;
@@ -6662,13 +7407,14 @@ function parseNpmAuditJsonFromString(content) {
6662
7407
  return findings;
6663
7408
  }
6664
7409
  function parseNpmAuditJsonFile(filePath) {
6665
- const content = readFileSync12(filePath, "utf8");
7410
+ const content = readFileSync14(filePath, "utf8");
6666
7411
  return parseNpmAuditJsonFromString(content);
6667
7412
  }
6668
7413
 
6669
7414
  // src/scanner/adapters/yarn-audit.ts
6670
- import { readFileSync as readFileSync13 } from "fs";
7415
+ import { readFileSync as readFileSync15 } from "fs";
6671
7416
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
7417
+ var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
6672
7418
  function normalizeSeverity2(raw) {
6673
7419
  if (!raw) return "UNKNOWN";
6674
7420
  const up = raw.toUpperCase();
@@ -6693,7 +7439,8 @@ function parseYarnAuditJsonFromString(content) {
6693
7439
  const packageName = advisory?.module_name;
6694
7440
  const severity = normalizeSeverity2(advisory?.severity);
6695
7441
  const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
6696
- const matches = text.match(CVE_REGEX2) ?? [];
7442
+ const cveMatches = text.match(CVE_REGEX2) ?? [];
7443
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
6697
7444
  for (const match of matches) {
6698
7445
  const cveId = match.toUpperCase();
6699
7446
  const key = `${cveId}:${packageName ?? ""}`;
@@ -6710,12 +7457,12 @@ function parseYarnAuditJsonFromString(content) {
6710
7457
  return findings;
6711
7458
  }
6712
7459
  function parseYarnAuditJsonFile(filePath) {
6713
- const content = readFileSync13(filePath, "utf8");
7460
+ const content = readFileSync15(filePath, "utf8");
6714
7461
  return parseYarnAuditJsonFromString(content);
6715
7462
  }
6716
7463
 
6717
7464
  // src/scanner/adapters/sarif.ts
6718
- import { readFileSync as readFileSync14 } from "fs";
7465
+ import { readFileSync as readFileSync16 } from "fs";
6719
7466
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
6720
7467
  function extractPackageName(result) {
6721
7468
  const pkg = result.properties?.["packageName"];
@@ -6753,7 +7500,7 @@ function parseSarifFromString(content) {
6753
7500
  return findings;
6754
7501
  }
6755
7502
  function parseSarifFile(filePath) {
6756
- const content = readFileSync14(filePath, "utf8");
7503
+ const content = readFileSync16(filePath, "utf8");
6757
7504
  return parseSarifFromString(content);
6758
7505
  }
6759
7506
 
@@ -6762,7 +7509,7 @@ function parseScanInput(filePath, format) {
6762
7509
  if (filePath.includes("\0")) {
6763
7510
  throw new Error("Invalid scan input path: path contains null bytes");
6764
7511
  }
6765
- const resolvedPath = resolve3(filePath);
7512
+ const resolvedPath = resolve4(filePath);
6766
7513
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
6767
7514
  if (resolved === "npm-audit") {
6768
7515
  return parseNpmAuditJsonFile(resolvedPath);
@@ -6784,7 +7531,7 @@ async function parseScanInputFromAudit(params) {
6784
7531
  }
6785
7532
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
6786
7533
  const [cmd, ...args] = command;
6787
- const result = await execa10(cmd, args, {
7534
+ const result = await execa11(cmd, args, {
6788
7535
  cwd: params.cwd,
6789
7536
  stdio: "pipe",
6790
7537
  reject: false
@@ -6838,7 +7585,7 @@ function inferFormat(filePath) {
6838
7585
  const ext = extname(filePath).toLowerCase();
6839
7586
  if (ext === ".sarif") return "sarif";
6840
7587
  try {
6841
- const content = readFileSync15(filePath, "utf8");
7588
+ const content = readFileSync17(filePath, "utf8");
6842
7589
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
6843
7590
  if (firstLine) {
6844
7591
  const parsed = JSON.parse(firstLine);
@@ -7249,14 +7996,14 @@ async function remediatePortfolio(targets, options = {}) {
7249
7996
  }
7250
7997
 
7251
7998
  // 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";
7999
+ import { join as join21 } from "path";
8000
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "fs";
8001
+ import { execa as execa12 } from "execa";
7255
8002
  async function applyBump(params) {
7256
- const pkgPath = join19(params.cwd, "package.json");
8003
+ const pkgPath = join21(params.cwd, "package.json");
7257
8004
  let pkgJson;
7258
8005
  try {
7259
- pkgJson = JSON.parse(readFileSync16(pkgPath, "utf8"));
8006
+ pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
7260
8007
  } catch {
7261
8008
  return {
7262
8009
  applied: false,
@@ -7281,7 +8028,7 @@ async function applyBump(params) {
7281
8028
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7282
8029
  try {
7283
8030
  const [installCmd, ...installArgs] = params.installCommand;
7284
- await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8031
+ await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
7285
8032
  } catch (err) {
7286
8033
  pkgJson[depField][params.packageName] = currentRange;
7287
8034
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -7294,13 +8041,13 @@ async function applyBump(params) {
7294
8041
  if (params.runTests) {
7295
8042
  try {
7296
8043
  const [testCmd, ...testArgs] = params.testCommand;
7297
- await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8044
+ await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
7298
8045
  } catch (err) {
7299
8046
  pkgJson[depField][params.packageName] = currentRange;
7300
8047
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7301
8048
  try {
7302
8049
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
7303
- await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8050
+ await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
7304
8051
  } catch {
7305
8052
  }
7306
8053
  const message = err instanceof Error ? err.message : String(err);
@@ -7501,131 +8248,169 @@ async function checkReachability(options) {
7501
8248
  return assessPackageReachability(cwd, options.packageName, options.symbol);
7502
8249
  }
7503
8250
 
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) {
8251
+ // src/scanner/delta-scan.ts
8252
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
8253
+ import { join as join22 } from "path";
8254
+ import { execa as execa13 } from "execa";
8255
+ import semver9 from "semver";
8256
+
8257
+ // src/scanner/lockfile-diff.ts
8258
+ import YAML2 from "yaml";
8259
+ import semver8 from "semver";
8260
+ function parseLockfileDependencies(filename, content) {
8261
+ const result = /* @__PURE__ */ new Map();
8262
+ const add = (name, ver) => {
8263
+ if (!name || !ver) return;
8264
+ const cleanVer = semver8.clean(ver) || semver8.coerce(ver)?.version || ver;
8265
+ let set = result.get(name);
8266
+ if (!set) {
8267
+ set = /* @__PURE__ */ new Set();
8268
+ result.set(name, set);
8269
+ }
8270
+ set.add(cleanVer);
8271
+ };
8272
+ if (filename.endsWith("package-lock.json")) {
7527
8273
  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;
8274
+ const data = JSON.parse(content);
8275
+ if (data.packages && typeof data.packages === "object") {
8276
+ for (const [key, pkg] of Object.entries(data.packages)) {
8277
+ if (!key || key === "" || typeof pkg !== "object" || !pkg) continue;
8278
+ const idx = key.lastIndexOf("node_modules/");
8279
+ if (idx !== -1) {
8280
+ const rawName = key.slice(idx + "node_modules/".length);
8281
+ const ver = pkg.version;
8282
+ add(rawName, ver);
8283
+ }
8284
+ }
7543
8285
  }
8286
+ if (data.dependencies && typeof data.dependencies === "object") {
8287
+ const walkDeps = (deps) => {
8288
+ for (const [name, info] of Object.entries(deps)) {
8289
+ if (!info || typeof info !== "object") continue;
8290
+ const depObj = info;
8291
+ add(name, depObj.version);
8292
+ if (depObj.dependencies && typeof depObj.dependencies === "object") {
8293
+ walkDeps(depObj.dependencies);
8294
+ }
8295
+ }
8296
+ };
8297
+ walkDeps(data.dependencies);
8298
+ }
8299
+ } catch {
7544
8300
  }
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));
8301
+ } else if (filename.endsWith("pnpm-lock.yaml")) {
8302
+ try {
8303
+ const data = YAML2.parse(content);
8304
+ if (data?.packages && typeof data.packages === "object") {
8305
+ for (const [key, val] of Object.entries(data.packages)) {
8306
+ let rawKey = key.replace(/^\//, "");
8307
+ const parenIdx = rawKey.indexOf("(");
8308
+ if (parenIdx !== -1) {
8309
+ rawKey = rawKey.slice(0, parenIdx);
8310
+ }
8311
+ const lastAt = rawKey.lastIndexOf("@");
8312
+ if (lastAt > 0) {
8313
+ const name = val?.name ?? rawKey.slice(0, lastAt);
8314
+ const version = val?.version ?? rawKey.slice(lastAt + 1);
8315
+ add(name, version);
8316
+ }
8317
+ }
7551
8318
  }
7552
- try {
7553
- const epss = await fetchEpss(cveDetails.id);
7554
- if (epss) {
7555
- epssScore = Number.parseFloat(epss.epss);
7556
- epssPercentile = Number.parseFloat(epss.percentile);
8319
+ if (data?.snapshots && typeof data.snapshots === "object") {
8320
+ for (const key of Object.keys(data.snapshots)) {
8321
+ let rawKey = key.replace(/^\//, "");
8322
+ const parenIdx = rawKey.indexOf("(");
8323
+ if (parenIdx !== -1) {
8324
+ rawKey = rawKey.slice(0, parenIdx);
8325
+ }
8326
+ const lastAt = rawKey.lastIndexOf("@");
8327
+ if (lastAt > 0) {
8328
+ const name = rawKey.slice(0, lastAt);
8329
+ const version = rawKey.slice(lastAt + 1);
8330
+ add(name, version);
8331
+ }
7557
8332
  }
7558
- } catch {
7559
8333
  }
8334
+ } catch {
7560
8335
  }
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;
8336
+ } else if (filename.endsWith("yarn.lock")) {
8337
+ const lines = content.split("\n");
8338
+ let currentPackages = [];
8339
+ for (const line of lines) {
8340
+ if (line.startsWith("#") || line.trim() === "") continue;
8341
+ if (!line.startsWith(" ")) {
8342
+ currentPackages = [];
8343
+ const parts = line.split(",").map((p) => p.trim().replace(/^"/, "").replace(/":?$/, ""));
8344
+ for (const part of parts) {
8345
+ const lastAt = part.lastIndexOf("@");
8346
+ if (lastAt > 0) {
8347
+ currentPackages.push(part.slice(0, lastAt));
8348
+ }
8349
+ }
8350
+ } else {
8351
+ const match = line.match(/^\s+version\s+"?([^"\s]+)"?/);
8352
+ if (match && currentPackages.length > 0) {
8353
+ const ver = match[1];
8354
+ for (const pkg of currentPackages) {
8355
+ add(pkg, ver);
8356
+ }
8357
+ currentPackages = [];
8358
+ }
7572
8359
  }
7573
8360
  }
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
8361
  }
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";
8362
+ return result;
8363
+ }
8364
+ function computeLockfileDiff(filename, oldContent, newContent) {
8365
+ const changes = [];
8366
+ const oldDeps = oldContent ? parseLockfileDependencies(filename, oldContent) : /* @__PURE__ */ new Map();
8367
+ const newDeps = newContent ? parseLockfileDependencies(filename, newContent) : /* @__PURE__ */ new Map();
8368
+ const allPkgs = /* @__PURE__ */ new Set([...oldDeps.keys(), ...newDeps.keys()]);
8369
+ for (const pkg of allPkgs) {
8370
+ const oldVers = oldDeps.get(pkg) ?? /* @__PURE__ */ new Set();
8371
+ const newVers = newDeps.get(pkg) ?? /* @__PURE__ */ new Set();
8372
+ for (const nVer of newVers) {
8373
+ if (!oldVers.has(nVer)) {
8374
+ if (oldVers.size === 0) {
8375
+ changes.push({
8376
+ packageName: pkg,
8377
+ changeType: "added",
8378
+ newVersion: nVer,
8379
+ dependencyType: "transitive",
8380
+ manifestPath: filename
8381
+ });
8382
+ } else {
8383
+ const [firstOld] = oldVers;
8384
+ const cleanOld = semver8.clean(firstOld) || semver8.coerce(firstOld)?.version;
8385
+ const cleanNew = semver8.clean(nVer) || semver8.coerce(nVer)?.version;
8386
+ const isUpgrade = cleanOld && cleanNew && semver8.valid(cleanOld) && semver8.valid(cleanNew) ? semver8.gt(cleanNew, cleanOld) : true;
8387
+ changes.push({
8388
+ packageName: pkg,
8389
+ changeType: isUpgrade ? "upgraded" : "downgraded",
8390
+ oldVersion: firstOld,
8391
+ newVersion: nVer,
8392
+ dependencyType: "transitive",
8393
+ manifestPath: filename
8394
+ });
8395
+ }
8396
+ }
8397
+ }
8398
+ if (newVers.size === 0 && oldVers.size > 0) {
8399
+ for (const oVer of oldVers) {
8400
+ changes.push({
8401
+ packageName: pkg,
8402
+ changeType: "removed",
8403
+ oldVersion: oVer,
8404
+ dependencyType: "transitive",
8405
+ manifestPath: filename
8406
+ });
8407
+ }
7600
8408
  }
7601
8409
  }
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
- };
8410
+ return changes;
7622
8411
  }
7623
8412
 
7624
8413
  // 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
8414
  var DEP_SECTIONS = [
7630
8415
  "dependencies",
7631
8416
  "devDependencies",
@@ -7635,7 +8420,7 @@ var DEP_SECTIONS = [
7635
8420
  async function getChangedManifestPaths(cwd, baseRef) {
7636
8421
  const manifests = /* @__PURE__ */ new Set();
7637
8422
  try {
7638
- const { stdout: diffFiles } = await execa12(
8423
+ const { stdout: diffFiles } = await execa13(
7639
8424
  "git",
7640
8425
  ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
7641
8426
  { cwd, reject: false }
@@ -7646,7 +8431,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7646
8431
  manifests.add(trimmed);
7647
8432
  }
7648
8433
  }
7649
- const { stdout: untrackedFiles } = await execa12(
8434
+ const { stdout: untrackedFiles } = await execa13(
7650
8435
  "git",
7651
8436
  ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
7652
8437
  { cwd, reject: false }
@@ -7658,7 +8443,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7658
8443
  }
7659
8444
  }
7660
8445
  } catch {
7661
- if (existsSync12(join20(cwd, "package.json"))) {
8446
+ if (existsSync14(join22(cwd, "package.json"))) {
7662
8447
  manifests.add("package.json");
7663
8448
  }
7664
8449
  }
@@ -7666,7 +8451,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
7666
8451
  }
7667
8452
  async function getOldManifestContent(cwd, baseRef, relPath) {
7668
8453
  try {
7669
- const { stdout } = await execa12("git", ["show", `${baseRef}:${relPath}`], {
8454
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
7670
8455
  cwd,
7671
8456
  reject: false
7672
8457
  });
@@ -7677,10 +8462,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
7677
8462
  }
7678
8463
  }
7679
8464
  function getNewManifestContent(cwd, relPath) {
7680
- const fullPath = join20(cwd, relPath);
7681
- if (!existsSync12(fullPath)) return null;
8465
+ const fullPath = join22(cwd, relPath);
8466
+ if (!existsSync14(fullPath)) return null;
7682
8467
  try {
7683
- return JSON.parse(readFileSync17(fullPath, "utf8"));
8468
+ return JSON.parse(readFileSync19(fullPath, "utf8"));
7684
8469
  } catch {
7685
8470
  return null;
7686
8471
  }
@@ -7701,9 +8486,9 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7701
8486
  manifestPath: relPath
7702
8487
  });
7703
8488
  } 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;
8489
+ const cleanOld = semver9.clean(oldVer) || semver9.coerce(oldVer)?.version;
8490
+ const cleanNew = semver9.clean(newVer) || semver9.coerce(newVer)?.version;
8491
+ const isUpgrade = cleanOld && cleanNew && semver9.valid(cleanOld) && semver9.valid(cleanNew) ? semver9.gt(cleanNew, cleanOld) : true;
7707
8492
  changes.push({
7708
8493
  packageName: pkgName,
7709
8494
  changeType: isUpgrade ? "upgraded" : "downgraded",
@@ -7728,22 +8513,89 @@ function computeDependencyDiff(relPath, oldPkg, newPkg) {
7728
8513
  }
7729
8514
  return changes;
7730
8515
  }
8516
+ var LOCKFILE_PATTERNS = [
8517
+ "package-lock.json",
8518
+ "**/package-lock.json",
8519
+ "pnpm-lock.yaml",
8520
+ "**/pnpm-lock.yaml",
8521
+ "yarn.lock",
8522
+ "**/yarn.lock"
8523
+ ];
8524
+ async function getChangedLockfilePaths(cwd, baseRef) {
8525
+ const lockfiles = /* @__PURE__ */ new Set();
8526
+ try {
8527
+ const { stdout: diffFiles } = await execa13(
8528
+ "git",
8529
+ ["diff", "--name-only", baseRef, "--", ...LOCKFILE_PATTERNS],
8530
+ { cwd, reject: false }
8531
+ );
8532
+ for (const f of diffFiles.split("\n")) {
8533
+ const trimmed = f.trim();
8534
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
8535
+ lockfiles.add(trimmed);
8536
+ }
8537
+ }
8538
+ const { stdout: untrackedFiles } = await execa13(
8539
+ "git",
8540
+ ["ls-files", "--others", "--exclude-standard", "--", ...LOCKFILE_PATTERNS],
8541
+ { cwd, reject: false }
8542
+ );
8543
+ for (const f of untrackedFiles.split("\n")) {
8544
+ const trimmed = f.trim();
8545
+ if (trimmed && !trimmed.includes("node_modules") && (trimmed.endsWith("package-lock.json") || trimmed.endsWith("pnpm-lock.yaml") || trimmed.endsWith("yarn.lock"))) {
8546
+ lockfiles.add(trimmed);
8547
+ }
8548
+ }
8549
+ } catch {
8550
+ }
8551
+ return Array.from(lockfiles);
8552
+ }
8553
+ async function getFileContentAtRef(cwd, baseRef, relPath) {
8554
+ try {
8555
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
8556
+ cwd,
8557
+ reject: false
8558
+ });
8559
+ return stdout || null;
8560
+ } catch {
8561
+ return null;
8562
+ }
8563
+ }
7731
8564
  async function scanDelta(options = {}) {
7732
8565
  const cwd = options.cwd ?? process.cwd();
7733
8566
  const baseRef = options.baseRef ?? "HEAD";
7734
8567
  const changedManifests = await getChangedManifestPaths(cwd, baseRef);
8568
+ const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
7735
8569
  const dependencyChanges = [];
8570
+ const directPkgs = /* @__PURE__ */ new Set();
7736
8571
  for (const manifestRel of changedManifests) {
7737
8572
  const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
7738
8573
  const newPkg = getNewManifestContent(cwd, manifestRel);
7739
8574
  const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
8575
+ for (const d of diffs) {
8576
+ directPkgs.add(d.packageName);
8577
+ }
7740
8578
  dependencyChanges.push(...diffs);
7741
8579
  }
8580
+ for (const lockfileRel of changedLockfiles) {
8581
+ const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
8582
+ const fullPath = join22(cwd, lockfileRel);
8583
+ const newContent = existsSync14(fullPath) ? readFileSync19(fullPath, "utf8") : null;
8584
+ const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
8585
+ for (const change of lockDiffs) {
8586
+ if (!directPkgs.has(change.packageName)) {
8587
+ dependencyChanges.push(change);
8588
+ }
8589
+ }
8590
+ if (!changedManifests.includes(lockfileRel)) {
8591
+ changedManifests.push(lockfileRel);
8592
+ }
8593
+ }
7742
8594
  const introducedFindings = [];
7743
8595
  const resolvedFindings = [];
7744
8596
  for (const change of dependencyChanges) {
7745
8597
  if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
7746
- const cleanVer = semver8.coerce(change.newVersion)?.version ?? change.newVersion;
8598
+ const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
7747
8599
  const evaluation = await evaluatePackage(change.packageName, {
7748
8600
  version: cleanVer,
7749
8601
  enrichIntelligence: true
@@ -7763,12 +8615,12 @@ async function scanDelta(options = {}) {
7763
8615
  }
7764
8616
  }
7765
8617
  if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
7766
- const cleanOldVer = semver8.coerce(change.oldVersion)?.version ?? change.oldVersion;
8618
+ const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
7767
8619
  const oldEval = await evaluatePackage(change.packageName, {
7768
8620
  version: cleanOldVer,
7769
8621
  enrichIntelligence: true
7770
8622
  });
7771
- const cleanNewVer = change.newVersion ? semver8.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
8623
+ const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
7772
8624
  const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
7773
8625
  version: cleanNewVer,
7774
8626
  enrichIntelligence: false
@@ -7931,9 +8783,9 @@ function toSarifOutput(report) {
7931
8783
  }
7932
8784
 
7933
8785
  // src/version.ts
7934
- import { readFileSync as readFileSync18 } from "fs";
8786
+ import { readFileSync as readFileSync20 } from "fs";
7935
8787
  function readPackageVersion() {
7936
- const raw = readFileSync18(new URL("../package.json", import.meta.url), "utf8");
8788
+ const raw = readFileSync20(new URL("../package.json", import.meta.url), "utf8");
7937
8789
  const metadata = JSON.parse(raw);
7938
8790
  if (!metadata.version) {
7939
8791
  throw new Error("packages/core/package.json is missing a version field.");
@@ -7958,13 +8810,17 @@ export {
7958
8810
  listPatchArtifacts,
7959
8811
  inspectPatchArtifact,
7960
8812
  validatePatchArtifact,
8813
+ evaluatePackage,
8814
+ reconcilePatchArtifacts,
7961
8815
  remediate,
7962
8816
  planRemediation,
8817
+ parseScanInput,
8818
+ parseScanInputFromAudit,
8819
+ uniqueCveIds,
7963
8820
  remediateFromScan,
7964
8821
  remediatePortfolio,
7965
8822
  updateOutdated,
7966
8823
  checkReachability,
7967
- evaluatePackage,
7968
8824
  scanDelta,
7969
8825
  submitRemediateJob,
7970
8826
  submitScanJob,