autoremediator 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +2 -1
  2. package/dist/api/change-request/index.d.ts +2 -1
  3. package/dist/api/change-request/index.d.ts.map +1 -1
  4. package/dist/api/delta-scan/delta-scan.test.d.ts +2 -0
  5. package/dist/api/delta-scan/delta-scan.test.d.ts.map +1 -0
  6. package/dist/api/delta-scan/index.d.ts +5 -0
  7. package/dist/api/delta-scan/index.d.ts.map +1 -0
  8. package/dist/api/evaluate/evaluate.test.d.ts +2 -0
  9. package/dist/api/evaluate/evaluate.test.d.ts.map +1 -0
  10. package/dist/api/evaluate/index.d.ts +5 -0
  11. package/dist/api/evaluate/index.d.ts.map +1 -0
  12. package/dist/api/index.d.ts +5 -2
  13. package/dist/api/index.d.ts.map +1 -1
  14. package/dist/api/patches/index.d.ts +1 -0
  15. package/dist/api/patches/index.d.ts.map +1 -1
  16. package/dist/api/patches/reconcile.d.ts +7 -0
  17. package/dist/api/patches/reconcile.d.ts.map +1 -0
  18. package/dist/api/patches/reconcile.test.d.ts +2 -0
  19. package/dist/api/patches/reconcile.test.d.ts.map +1 -0
  20. package/dist/api/reachability/index.d.ts +3 -0
  21. package/dist/api/reachability/index.d.ts.map +1 -0
  22. package/dist/api/reachability/reachability.test.d.ts +2 -0
  23. package/dist/api/reachability/reachability.test.d.ts.map +1 -0
  24. package/dist/api/remediate/validators.d.ts +3 -0
  25. package/dist/api/remediate/validators.d.ts.map +1 -1
  26. package/dist/api/reporting.d.ts.map +1 -1
  27. package/dist/api/vex/index.d.ts +1 -1
  28. package/dist/api/vex/index.d.ts.map +1 -1
  29. package/dist/{chunk-CQXNIRHT.js → chunk-FXOUJDQ3.js} +2003 -756
  30. package/dist/cli/program.d.ts.map +1 -1
  31. package/dist/cli/runners.d.ts +22 -0
  32. package/dist/cli/runners.d.ts.map +1 -1
  33. package/dist/cli/triage.d.ts +7 -0
  34. package/dist/cli/triage.d.ts.map +1 -0
  35. package/dist/cli/types.d.ts +2 -0
  36. package/dist/cli/types.d.ts.map +1 -1
  37. package/dist/cli.js +365 -3
  38. package/dist/index.js +1967 -721
  39. package/dist/index.js.map +1 -1
  40. package/dist/intelligence/index.d.ts +1 -1
  41. package/dist/intelligence/index.d.ts.map +1 -1
  42. package/dist/intelligence/sources/github-advisory.d.ts +8 -0
  43. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  44. package/dist/intelligence/sources/osv.d.ts +4 -0
  45. package/dist/intelligence/sources/osv.d.ts.map +1 -1
  46. package/dist/mcp/server.d.ts +106 -1
  47. package/dist/mcp/server.d.ts.map +1 -1
  48. package/dist/mcp/server.js +100 -1
  49. package/dist/openapi/rbac.d.ts +2 -2
  50. package/dist/openapi/rbac.d.ts.map +1 -1
  51. package/dist/openapi/server.d.ts.map +1 -1
  52. package/dist/openapi/server.js +13 -6
  53. package/dist/platform/package-manager/index.d.ts +1 -0
  54. package/dist/platform/package-manager/index.d.ts.map +1 -1
  55. package/dist/platform/supply-chain.d.ts +10 -0
  56. package/dist/platform/supply-chain.d.ts.map +1 -0
  57. package/dist/platform/supply-chain.test.d.ts +2 -0
  58. package/dist/platform/supply-chain.test.d.ts.map +1 -0
  59. package/dist/platform/types.d.ts +121 -4
  60. package/dist/platform/types.d.ts.map +1 -1
  61. package/dist/platform/workspace.d.ts +28 -0
  62. package/dist/platform/workspace.d.ts.map +1 -0
  63. package/dist/platform/workspace.test.d.ts +2 -0
  64. package/dist/platform/workspace.test.d.ts.map +1 -0
  65. package/dist/remediation/evaluate-package.d.ts +3 -0
  66. package/dist/remediation/evaluate-package.d.ts.map +1 -0
  67. package/dist/remediation/local/options.d.ts.map +1 -1
  68. package/dist/remediation/local/primary-strategy.d.ts +3 -1
  69. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  70. package/dist/remediation/runtime-tools.d.ts +1 -1
  71. package/dist/remediation/runtime-tools.d.ts.map +1 -1
  72. package/dist/remediation/strategies/patch-synthesis-prompt.d.ts.map +1 -1
  73. package/dist/remediation/tools/apply-patch-file/helpers.d.ts.map +1 -1
  74. package/dist/remediation/tools/apply-patch-file/index.d.ts +1 -0
  75. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  76. package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
  77. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  78. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  79. package/dist/remediation/tools/check-reachability.d.ts +6 -1
  80. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  81. package/dist/remediation/tools/generate-patch/helpers.d.ts +1 -0
  82. package/dist/remediation/tools/generate-patch/helpers.d.ts.map +1 -1
  83. package/dist/remediation/tools/generate-patch/index.d.ts +1 -0
  84. package/dist/remediation/tools/generate-patch/index.d.ts.map +1 -1
  85. package/dist/scanner/adapters/npm-audit.d.ts.map +1 -1
  86. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  87. package/dist/scanner/delta-scan.d.ts +3 -0
  88. package/dist/scanner/delta-scan.d.ts.map +1 -0
  89. package/dist/scanner/lockfile-diff.d.ts +10 -0
  90. package/dist/scanner/lockfile-diff.d.ts.map +1 -0
  91. package/dist/scanner/lockfile-diff.test.d.ts +2 -0
  92. package/dist/scanner/lockfile-diff.test.d.ts.map +1 -0
  93. package/package.json +7 -9
@@ -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;
@@ -1458,6 +1454,37 @@ async function fetchOsvVuln(cveId) {
1458
1454
  throw err;
1459
1455
  }
1460
1456
  }
1457
+ async function queryOsvPackage(packageName, version) {
1458
+ const url = `${OSV_BASE}/query`;
1459
+ const body = {
1460
+ package: {
1461
+ name: packageName,
1462
+ ecosystem: "npm"
1463
+ }
1464
+ };
1465
+ if (version) {
1466
+ body.version = version;
1467
+ }
1468
+ try {
1469
+ const res = await httpClient({
1470
+ url,
1471
+ method: "POST",
1472
+ body,
1473
+ headers: { "Content-Type": "application/json" }
1474
+ });
1475
+ if (res.status === 404) return [];
1476
+ if (!res.ok) {
1477
+ throw new Error(`OSV API error ${res.status} for query "${packageName}": ${res.text}`);
1478
+ }
1479
+ const data = res.data;
1480
+ return data.vulns ?? [];
1481
+ } catch (err) {
1482
+ if (err instanceof Error) {
1483
+ throw new Error(`OSV API error for query "${packageName}": ${err.message}`);
1484
+ }
1485
+ throw err;
1486
+ }
1487
+ }
1461
1488
  function osvEventsToSemverRange(events) {
1462
1489
  const parts = [];
1463
1490
  for (const event of events) {
@@ -1521,27 +1548,762 @@ async function lookupCveOsv(cveId) {
1521
1548
  return parseOsvVuln(vuln);
1522
1549
  }
1523
1550
 
1524
- // src/intelligence/sources/github-advisory.ts
1525
- import { Octokit } from "@octokit/rest";
1526
- function createOctokitClient() {
1527
- const token = getGitHubToken();
1528
- return new Octokit(token ? { auth: `token ${token}` } : {});
1529
- }
1530
- async function fetchGhAdvisories(cveId) {
1531
- const octokit = createOctokitClient();
1532
- try {
1533
- const response = await octokit.request("GET /advisories", {
1534
- cve_id: cveId,
1535
- ecosystem: "npm",
1536
- type: "reviewed",
1537
- per_page: 10,
1538
- headers: {
1539
- "X-GitHub-Api-Version": "2022-11-28"
1540
- }
1541
- });
1542
- return response.data;
1543
- } catch (err) {
1544
- const errorMsg = err instanceof Error ? err.message : String(err);
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
+ );
1558
+ }
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
+ });
2300
+ if (!res.ok) {
2301
+ if (res.status === 404) return [];
2302
+ throw new Error(`HTTP ${res.status}: ${res.text}`);
2303
+ }
2304
+ return Array.isArray(res.data) ? res.data : [];
2305
+ } catch (err) {
2306
+ const errorMsg = err instanceof Error ? err.message : String(err);
1545
2307
  console.warn(
1546
2308
  `[autoremediator] GitHub Advisory API error for ${cveId}: ${errorMsg} \u2014 skipping.`
1547
2309
  );
@@ -1633,9 +2395,9 @@ async function enrichWithNvd(details) {
1633
2395
  }
1634
2396
 
1635
2397
  // src/intelligence/sources/snapshot.ts
1636
- import { readFileSync as readFileSync6 } from "fs";
2398
+ import { readFileSync as readFileSync9 } from "fs";
1637
2399
  function loadIntelligenceSnapshot(filePath) {
1638
- const raw = readFileSync6(filePath, "utf8");
2400
+ const raw = readFileSync9(filePath, "utf8");
1639
2401
  const parsed = JSON.parse(raw);
1640
2402
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1641
2403
  throw new Error(`Intelligence snapshot at ${filePath} must be a JSON object keyed by CVE ID.`);
@@ -1647,8 +2409,8 @@ function lookupSnapshotCve(snapshot, cveId) {
1647
2409
  }
1648
2410
 
1649
2411
  // src/remediation/tools/check-reachability.ts
1650
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync } from "fs";
1651
- 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";
1652
2414
  import { parseSync } from "oxc-parser";
1653
2415
  import { z as z2 } from "zod";
1654
2416
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
@@ -1664,16 +2426,16 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
1664
2426
  ]);
1665
2427
  function loadTsconfigPaths(cwd) {
1666
2428
  for (const configName of ["tsconfig.json", "jsconfig.json"]) {
1667
- const fullPath = join9(cwd, configName);
1668
- if (!existsSync8(fullPath)) continue;
2429
+ const fullPath = join12(cwd, configName);
2430
+ if (!existsSync10(fullPath)) continue;
1669
2431
  try {
1670
- const raw = readFileSync7(fullPath, "utf8");
2432
+ const raw = readFileSync10(fullPath, "utf8");
1671
2433
  const cleaned = raw.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
1672
2434
  const parsed = JSON.parse(cleaned);
1673
2435
  const compilerOptions = parsed.compilerOptions;
1674
2436
  if (compilerOptions?.paths) {
1675
2437
  return {
1676
- baseUrl: compilerOptions.baseUrl ? resolve2(cwd, compilerOptions.baseUrl) : cwd,
2438
+ baseUrl: compilerOptions.baseUrl ? resolve3(cwd, compilerOptions.baseUrl) : cwd,
1677
2439
  paths: compilerOptions.paths
1678
2440
  };
1679
2441
  }
@@ -1688,14 +2450,14 @@ function resolveAliasedPath(importSource, config) {
1688
2450
  }
1689
2451
  for (const [pattern, replacements] of Object.entries(config.paths)) {
1690
2452
  if (pattern === importSource && replacements[0]) {
1691
- return resolve2(config.baseUrl, replacements[0]);
2453
+ return resolve3(config.baseUrl, replacements[0]);
1692
2454
  }
1693
2455
  if (pattern.endsWith("/*")) {
1694
2456
  const prefix = pattern.slice(0, -2);
1695
2457
  if (importSource.startsWith(prefix)) {
1696
2458
  const suffix = importSource.slice(prefix.length);
1697
2459
  const replacement = replacements[0]?.slice(0, -2) ?? "";
1698
- return resolve2(config.baseUrl, replacement + suffix);
2460
+ return resolve3(config.baseUrl, replacement + suffix);
1699
2461
  }
1700
2462
  }
1701
2463
  }
@@ -1704,16 +2466,16 @@ function resolveAliasedPath(importSource, config) {
1704
2466
  function* walkSourceFiles(dir) {
1705
2467
  let entries;
1706
2468
  try {
1707
- entries = readdirSync(dir);
2469
+ entries = readdirSync2(dir);
1708
2470
  } catch {
1709
2471
  return;
1710
2472
  }
1711
2473
  for (const entry of entries) {
1712
2474
  if (IGNORED_DIRS.has(entry)) continue;
1713
- const full = join9(dir, entry);
2475
+ const full = join12(dir, entry);
1714
2476
  let stat2;
1715
2477
  try {
1716
- stat2 = statSync(full);
2478
+ stat2 = statSync2(full);
1717
2479
  } catch {
1718
2480
  continue;
1719
2481
  }
@@ -1746,12 +2508,36 @@ function walkAst(node, visitor) {
1746
2508
  walkAst(child, visitor);
1747
2509
  }
1748
2510
  }
1749
- } else if (value && typeof value === "object" && "type" in value) {
1750
- walkAst(value, visitor);
2511
+ } else if (value && typeof value === "object" && "type" in value) {
2512
+ walkAst(value, visitor);
2513
+ }
2514
+ }
2515
+ }
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 {
1751
2536
  }
1752
2537
  }
2538
+ return null;
1753
2539
  }
1754
- function extractPackageReferences(filename, content, packageName, tsconfig) {
2540
+ function extractPackageReferences(filename, content, packageName, tsconfig, outImportedDirectPackages) {
1755
2541
  const results = [];
1756
2542
  let parseResult;
1757
2543
  try {
@@ -1763,6 +2549,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1763
2549
  walkAst(parseResult.program, (node) => {
1764
2550
  if (node.type === "ImportDeclaration") {
1765
2551
  const source = node.source;
2552
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2553
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2554
+ }
1766
2555
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1767
2556
  const symbols = [];
1768
2557
  const importedIdentifiers = /* @__PURE__ */ new Map();
@@ -1797,6 +2586,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1797
2586
  }
1798
2587
  if (node.type === "ImportExpression") {
1799
2588
  const source = node.source;
2589
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2590
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2591
+ }
1800
2592
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1801
2593
  results.push({
1802
2594
  matchType: "dynamic-import",
@@ -1808,6 +2600,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1808
2600
  }
1809
2601
  if (node.type === "CallExpression" && node.callee?.name === "require" && Array.isArray(node.arguments) && node.arguments.length > 0) {
1810
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
+ }
1811
2606
  if (firstArg?.value && typeof firstArg.value === "string" && matchesPackage(firstArg.value, packageName, tsconfig)) {
1812
2607
  results.push({
1813
2608
  matchType: "require",
@@ -1819,6 +2614,9 @@ function extractPackageReferences(filename, content, packageName, tsconfig) {
1819
2614
  }
1820
2615
  if (node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") {
1821
2616
  const source = node.source;
2617
+ if (source?.value && !source.value.startsWith(".") && !isAbsolute2(source.value)) {
2618
+ outImportedDirectPackages?.add(toRootPackageName(source.value));
2619
+ }
1822
2620
  if (source?.value && matchesPackage(source.value, packageName, tsconfig)) {
1823
2621
  const symbols = [];
1824
2622
  if (node.type === "ExportAllDeclaration") {
@@ -1895,15 +2693,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
1895
2693
  let hasReExport = false;
1896
2694
  let totalInvocations = 0;
1897
2695
  let hasPackageImport = false;
2696
+ const importedDirectPackages = /* @__PURE__ */ new Set();
1898
2697
  for (const filePath of walkSourceFiles(cwd)) {
1899
2698
  scanned++;
1900
2699
  let content;
1901
2700
  try {
1902
- content = readFileSync7(filePath, "utf8");
2701
+ content = readFileSync10(filePath, "utf8");
1903
2702
  } catch {
1904
2703
  continue;
1905
2704
  }
1906
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig);
2705
+ const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
1907
2706
  if (refs.length > 0) {
1908
2707
  hasPackageImport = true;
1909
2708
  }
@@ -1946,6 +2745,16 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
1946
2745
  };
1947
2746
  }
1948
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
+ }
1949
2758
  return {
1950
2759
  packageName,
1951
2760
  status: "not-reachable",
@@ -2074,261 +2883,64 @@ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
2074
2883
  id: cveDetails.id,
2075
2884
  kev: cveDetails.kev,
2076
2885
  epss: cveDetails.epss
2077
- },
2078
- policy: { exploitSignalOverride: opts.exploitSignalOverride }
2079
- });
2080
- if (result.exploitSignalTriggered) {
2081
- exploitSignalTriggered = true;
2082
- }
2083
- }
2084
- let slaBreaches;
2085
- if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
2086
- const breach = checkSlaBreach(
2087
- normalizedId,
2088
- cveDetails.severity,
2089
- cveDetails.publishedAt,
2090
- opts.slaPolicy
2091
- );
2092
- if (breach) {
2093
- slaBreaches = [breach];
2094
- }
2095
- }
2096
- return { suppressed: false, exploitSignalTriggered, slaBreaches };
2097
- }
2098
-
2099
- // src/remediation/local/sbom.ts
2100
- function buildSbom(packages, vulnerableNames, results) {
2101
- const statusByPackage = /* @__PURE__ */ new Map();
2102
- for (const result of results) {
2103
- if (!vulnerableNames.has(result.packageName)) continue;
2104
- if (result.suppressedBy) {
2105
- statusByPackage.set(result.packageName, "suppressed");
2106
- } else if (!result.applied && result.strategy === "none") {
2107
- statusByPackage.set(result.packageName, "skipped");
2108
- } else if (result.applied) {
2109
- statusByPackage.set(result.packageName, "patched");
2110
- } else {
2111
- statusByPackage.set(result.packageName, "unpatched");
2112
- }
2113
- }
2114
- return packages.map((pkg) => {
2115
- const isVulnerable = vulnerableNames.has(pkg.name);
2116
- const entry = {
2117
- name: pkg.name,
2118
- version: pkg.version,
2119
- scope: pkg.type === "direct" ? "direct" : "transitive"
2120
- };
2121
- if (isVulnerable) {
2122
- entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
2123
- }
2124
- return entry;
2125
- });
2126
- }
2127
-
2128
- // src/intelligence/sources/registry.ts
2129
- import { readFileSync as readFileSync8 } from "fs";
2130
- import { join as join10 } from "path";
2131
- import { execa as execa4 } from "execa";
2132
- import semver from "semver";
2133
- var NPM_REGISTRY = "https://registry.npmjs.org";
2134
- async function fetchPackageVersions(packageName) {
2135
- const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName)}`;
2136
- try {
2137
- const res = await httpClient({ url });
2138
- if (res.status === 404) return [];
2139
- if (!res.ok) {
2140
- throw new Error(`npm registry error ${res.status} for "${packageName}": ${res.text}`);
2141
- }
2142
- const data = res.data;
2143
- return Object.keys(data.versions);
2144
- } catch (err) {
2145
- if (err instanceof Error && err.message.includes("registry error")) {
2146
- throw err;
2147
- }
2148
- return [];
2149
- }
2150
- }
2151
- async function resolveSafeUpgradeVersion(packageName, installedVersion, firstPatchedVersion, vulnerableRange) {
2152
- const versions = await fetchPackageVersions(packageName);
2153
- if (!versions.length) {
2154
- return {
2155
- candidates: {},
2156
- majorOnlyFixAvailable: false
2157
- };
2158
- }
2159
- const installed = semver.parse(installedVersion);
2160
- const candidates = versions.filter((v) => semver.valid(v) && semver.gte(v, firstPatchedVersion)).filter((v) => {
2161
- if (!vulnerableRange) return true;
2162
- try {
2163
- return !semver.satisfies(v, vulnerableRange, { includePrerelease: false });
2164
- } catch {
2165
- return true;
2166
- }
2167
- }).sort(semver.compare);
2168
- if (!candidates.length) {
2169
- return {
2170
- candidates: {},
2171
- majorOnlyFixAvailable: false
2172
- };
2173
- }
2174
- const categorizedCandidates = {};
2175
- for (const candidate of candidates) {
2176
- const level = classifyUpgradeLevel(installedVersion, candidate);
2177
- if (!level) continue;
2178
- if (!categorizedCandidates[level]) {
2179
- categorizedCandidates[level] = candidate;
2180
- }
2181
- }
2182
- const safeVersion = categorizedCandidates.patch ?? categorizedCandidates.minor ?? categorizedCandidates.major;
2183
- if (!safeVersion) {
2184
- return {
2185
- candidates: categorizedCandidates,
2186
- majorOnlyFixAvailable: false
2187
- };
2188
- }
2189
- const upgradeLevel = classifyUpgradeLevel(installedVersion, safeVersion);
2190
- const majorOnlyFixAvailable = !categorizedCandidates.patch && !categorizedCandidates.minor && Boolean(categorizedCandidates.major);
2191
- if (!installed || !upgradeLevel) {
2192
- return {
2193
- safeVersion,
2194
- upgradeLevel,
2195
- candidates: categorizedCandidates,
2196
- majorOnlyFixAvailable
2197
- };
2198
- }
2199
- return {
2200
- safeVersion,
2201
- upgradeLevel,
2202
- candidates: categorizedCandidates,
2203
- majorOnlyFixAvailable
2204
- };
2205
- }
2206
- function classifyUpgradeLevel(installedVersion, candidateVersion) {
2207
- const installed = semver.parse(installedVersion);
2208
- const candidate = semver.parse(candidateVersion);
2209
- if (!installed || !candidate) return void 0;
2210
- if (candidate.major > installed.major) return "major";
2211
- if (candidate.minor > installed.minor) return "minor";
2212
- if (candidate.patch > installed.patch || candidate.version === installed.version) {
2213
- return "patch";
2214
- }
2215
- return void 0;
2216
- }
2217
- async function queryOutdatedPackages(cwd, options = {}) {
2218
- const pm = options.packageManager ?? detectPackageManager(cwd);
2219
- let stdout;
2220
- try {
2221
- const args = pm === "pnpm" ? ["outdated", "--json"] : ["outdated", "--json"];
2222
- const yarnArgs = pm === "yarn" ? ["outdated", "--json"] : void 0;
2223
- const finalArgs = yarnArgs ?? args;
2224
- const result2 = await execa4(pm, finalArgs, {
2225
- cwd,
2226
- reject: false,
2227
- stdio: "pipe"
2228
- });
2229
- if (result2.exitCode !== 0 && result2.exitCode !== 1) {
2230
- const stderr = typeof result2.stderr === "string" ? result2.stderr : "";
2231
- if (!result2.stdout) {
2232
- throw new Error(
2233
- `${pm} outdated --json failed (exit ${result2.exitCode}): ${stderr.slice(0, 500)}`
2234
- );
2235
- }
2236
- }
2237
- stdout = typeof result2.stdout === "string" ? result2.stdout : "";
2238
- } catch (err) {
2239
- if (err instanceof Error && !err.message.includes("outdated --json failed")) {
2240
- throw new Error(`Failed to query outdated packages: ${err.message}`);
2241
- }
2242
- throw err;
2243
- }
2244
- if (!stdout.trim()) {
2245
- return /* @__PURE__ */ new Map();
2246
- }
2247
- const rawEntries = {};
2248
- if (pm === "yarn") {
2249
- for (const line of stdout.split("\n")) {
2250
- const trimmed = line.trim();
2251
- if (!trimmed) continue;
2252
- try {
2253
- const parsed = JSON.parse(trimmed);
2254
- if (parsed.type === "table" && parsed.data) {
2255
- const tableData = parsed.data;
2256
- const head = tableData.head ?? [];
2257
- const currentIdx = head.indexOf("Current");
2258
- const wantedIdx = head.indexOf("Wanted");
2259
- const latestIdx = head.indexOf("Latest");
2260
- for (const row of tableData.body ?? []) {
2261
- const name = row[0];
2262
- if (!name) continue;
2263
- rawEntries[name] = {
2264
- current: row[currentIdx] ?? "",
2265
- wanted: row[wantedIdx] ?? "",
2266
- latest: row[latestIdx] ?? "",
2267
- type: row[head.indexOf("Package Type")] ?? "dependencies"
2268
- };
2269
- }
2270
- }
2271
- } catch {
2272
- }
2273
- }
2274
- } else {
2275
- try {
2276
- const parsed = JSON.parse(stdout);
2277
- if (Array.isArray(parsed)) {
2278
- for (const entry of parsed) {
2279
- const name = entry.name ?? entry.packageName;
2280
- if (!name) continue;
2281
- rawEntries[name] = {
2282
- current: entry.current,
2283
- wanted: entry.wanted,
2284
- latest: entry.latest,
2285
- type: entry.dependencyType
2286
- };
2287
- }
2288
- } else {
2289
- Object.assign(rawEntries, parsed);
2290
- }
2291
- } catch {
2292
- throw new Error(`Could not parse ${pm} outdated --json output.`);
2293
- }
2294
- }
2295
- let directDeps;
2296
- try {
2297
- const pkgRaw = JSON.parse(readFileSync8(join10(cwd, "package.json"), "utf8"));
2298
- directDeps = /* @__PURE__ */ new Set([
2299
- ...Object.keys(pkgRaw.dependencies ?? {}),
2300
- ...Object.keys(pkgRaw.devDependencies ?? {})
2301
- ]);
2302
- } catch {
2303
- directDeps = /* @__PURE__ */ new Set();
2304
- }
2305
- const result = /* @__PURE__ */ new Map();
2306
- for (const [name, entry] of Object.entries(rawEntries)) {
2307
- const currentVersion = entry.current ?? "";
2308
- const wantedVersion = entry.wanted ?? currentVersion;
2309
- const latestVersion = entry.latest ?? currentVersion;
2310
- if (!currentVersion || !latestVersion) continue;
2311
- const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
2312
- if (!options.includeTransitive && dependencyScope === "transitive") continue;
2313
- const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
2314
- result.set(name, {
2315
- currentVersion,
2316
- wantedVersion,
2317
- latestVersion,
2318
- isMajorBump,
2319
- dependencyScope
2886
+ },
2887
+ policy: { exploitSignalOverride: opts.exploitSignalOverride }
2320
2888
  });
2889
+ if (result.exploitSignalTriggered) {
2890
+ exploitSignalTriggered = true;
2891
+ }
2321
2892
  }
2322
- 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
+ });
2323
2935
  }
2324
2936
 
2325
2937
  // src/remediation/tools/apply-version-bump.ts
2326
2938
  import { z as z4 } from "zod";
2327
- import { join as join12 } from "path";
2328
- 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";
2329
2941
 
2330
2942
  // src/platform/safe-exec.ts
2331
- import { execa as execa5 } from "execa";
2943
+ import { execa as execa6 } from "execa";
2332
2944
  var DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([
2333
2945
  "NODE_OPTIONS",
2334
2946
  "NODE_PATH",
@@ -2377,7 +2989,7 @@ function safeExeca(file, args = [], options = {}) {
2377
2989
  options.env ?? process.env,
2378
2990
  options.allowNodeOptions
2379
2991
  );
2380
- return execa5(file, args, {
2992
+ return execa6(file, args, {
2381
2993
  ...options,
2382
2994
  timeout,
2383
2995
  env: sanitizedEnv
@@ -2385,19 +2997,19 @@ function safeExeca(file, args = [], options = {}) {
2385
2997
  }
2386
2998
 
2387
2999
  // src/remediation/tools/apply-version-bump.ts
2388
- import semver2 from "semver";
3000
+ import semver3 from "semver";
2389
3001
 
2390
3002
  // src/platform/repo-lock.ts
2391
- import { mkdir, rm } from "fs/promises";
2392
- import { join as join11 } from "path";
3003
+ import { mkdir, rm as rm2 } from "fs/promises";
3004
+ import { join as join13 } from "path";
2393
3005
  async function sleep(ms) {
2394
- await new Promise((resolve4) => setTimeout(resolve4, ms));
3006
+ await new Promise((resolve5) => setTimeout(resolve5, ms));
2395
3007
  }
2396
3008
  async function acquireRepoLock(cwd, options = {}) {
2397
3009
  const timeoutMs = options.timeoutMs ?? 15e3;
2398
3010
  const retryDelayMs = options.retryDelayMs ?? 125;
2399
- const lockRoot = join11(cwd, ".autoremediator", "locks");
2400
- const lockPath = join11(cwd, ".autoremediator", "locks", "remediation.lock");
3011
+ const lockRoot = join13(cwd, ".autoremediator", "locks");
3012
+ const lockPath = join13(cwd, ".autoremediator", "locks", "remediation.lock");
2401
3013
  const startedAt = Date.now();
2402
3014
  await mkdir(lockRoot, { recursive: true });
2403
3015
  while (true) {
@@ -2406,7 +3018,7 @@ async function acquireRepoLock(cwd, options = {}) {
2406
3018
  return {
2407
3019
  lockPath,
2408
3020
  release: async () => {
2409
- await rm(lockPath, { recursive: true, force: true });
3021
+ await rm2(lockPath, { recursive: true, force: true });
2410
3022
  }
2411
3023
  };
2412
3024
  } catch {
@@ -2438,6 +3050,7 @@ var applyVersionBumpTool = defineTool({
2438
3050
  dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
2439
3051
  policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
2440
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"),
2441
3054
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2442
3055
  installPreferOffline: z4.boolean().optional(),
2443
3056
  enforceFrozenLockfile: z4.boolean().optional(),
@@ -2452,13 +3065,14 @@ var applyVersionBumpTool = defineTool({
2452
3065
  dryRun,
2453
3066
  policy,
2454
3067
  runTests,
3068
+ typecheck,
2455
3069
  installMode,
2456
3070
  installPreferOffline,
2457
3071
  enforceFrozenLockfile,
2458
3072
  workspace
2459
3073
  }) => {
2460
3074
  const pm = packageManager ?? detectPackageManager(cwd);
2461
- const pkgPath = join12(cwd, "package.json");
3075
+ let pkgPath = join14(cwd, "package.json");
2462
3076
  const loadedPolicy = loadPolicy(cwd, policy);
2463
3077
  const commandConstraints = {
2464
3078
  ...loadedPolicy.constraints,
@@ -2468,9 +3082,6 @@ var applyVersionBumpTool = defineTool({
2468
3082
  workspace: workspace ?? loadedPolicy.constraints?.workspace
2469
3083
  };
2470
3084
  const yarnMajor = pm === "yarn" ? await getYarnMajorVersion(cwd) : void 0;
2471
- const installCommand = resolveInstallCommand(pm, commandConstraints, yarnMajor);
2472
- const testCommand = resolveTestCommand(pm, commandConstraints);
2473
- const dedupeCommand = resolveDedupeCommand(pm, commandConstraints);
2474
3085
  if (!isPackageAllowed(loadedPolicy, packageName)) {
2475
3086
  return {
2476
3087
  packageName,
@@ -2483,7 +3094,7 @@ var applyVersionBumpTool = defineTool({
2483
3094
  message: `Policy blocked changes for package "${packageName}".`
2484
3095
  };
2485
3096
  }
2486
- 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);
2487
3098
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2488
3099
  return {
2489
3100
  packageName,
@@ -2496,9 +3107,10 @@ var applyVersionBumpTool = defineTool({
2496
3107
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
2497
3108
  };
2498
3109
  }
3110
+ pkgPath = join14(cwd, "package.json");
2499
3111
  let pkgJson;
2500
3112
  try {
2501
- pkgJson = JSON.parse(readFileSync9(pkgPath, "utf8"));
3113
+ pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
2502
3114
  } catch {
2503
3115
  return {
2504
3116
  packageName,
@@ -2510,9 +3122,20 @@ var applyVersionBumpTool = defineTool({
2510
3122
  message: `Could not read package.json at "${pkgPath}".`
2511
3123
  };
2512
3124
  }
2513
- const depField = ["dependencies", "devDependencies", "peerDependencies"].find(
3125
+ let depField = ["dependencies", "devDependencies", "peerDependencies"].find(
2514
3126
  (f) => pkgJson[f]?.[packageName] !== void 0
2515
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
+ }
2516
3139
  if (!depField) {
2517
3140
  return {
2518
3141
  packageName,
@@ -2528,9 +3151,14 @@ var applyVersionBumpTool = defineTool({
2528
3151
  const prefixMatch = currentRange.match(/^([~^]?)/);
2529
3152
  const prefix = prefixMatch?.[1] ?? "";
2530
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);
2531
3159
  if (dryRun) {
2532
- const installCmd = installCommand.join(" ");
2533
- const testCmd = testCommand.join(" ");
3160
+ const installCmd = effectiveInstallCommand.join(" ");
3161
+ const testCmd = effectiveTestCommand.join(" ");
2534
3162
  return {
2535
3163
  packageName,
2536
3164
  strategy: "version-bump",
@@ -2538,14 +3166,14 @@ var applyVersionBumpTool = defineTool({
2538
3166
  toVersion,
2539
3167
  applied: false,
2540
3168
  dryRun: true,
2541
- 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)` : ""}.`
2542
3170
  };
2543
3171
  }
2544
3172
  return withRepoLock(cwd, async () => {
2545
3173
  pkgJson[depField][packageName] = newRange;
2546
3174
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2547
3175
  try {
2548
- const [installCmd, ...installArgs] = installCommand;
3176
+ const [installCmd, ...installArgs] = effectiveInstallCommand;
2549
3177
  await safeExeca(installCmd, installArgs, {
2550
3178
  cwd,
2551
3179
  stdio: "pipe"
@@ -2562,12 +3190,12 @@ var applyVersionBumpTool = defineTool({
2562
3190
  applied: false,
2563
3191
  dryRun: false,
2564
3192
  unresolvedReason: "install-failed",
2565
- 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}`
2566
3194
  };
2567
3195
  }
2568
3196
  if (runTests) {
2569
3197
  try {
2570
- const [testCmd, ...testArgs] = testCommand;
3198
+ const [testCmd, ...testArgs] = effectiveTestCommand;
2571
3199
  await safeExeca(testCmd, testArgs, {
2572
3200
  cwd,
2573
3201
  stdio: "pipe"
@@ -2576,7 +3204,38 @@ var applyVersionBumpTool = defineTool({
2576
3204
  pkgJson[depField][packageName] = currentRange;
2577
3205
  writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
2578
3206
  try {
2579
- 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;
2580
3239
  await safeExeca(rollbackCmd, rollbackArgs, {
2581
3240
  cwd,
2582
3241
  stdio: "pipe"
@@ -2592,20 +3251,22 @@ var applyVersionBumpTool = defineTool({
2592
3251
  applied: false,
2593
3252
  dryRun: false,
2594
3253
  unresolvedReason: "validation-failed",
2595
- 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}`
2596
3255
  };
2597
3256
  }
2598
3257
  }
2599
3258
  let dedupeNote = "";
2600
- try {
2601
- const [dedupeCmd, ...dedupeArgs] = dedupeCommand;
2602
- await safeExeca(dedupeCmd, dedupeArgs, {
2603
- cwd,
2604
- stdio: "pipe"
2605
- });
2606
- } catch (err) {
2607
- const message = err instanceof Error ? err.message : String(err);
2608
- 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
+ }
2609
3270
  }
2610
3271
  return {
2611
3272
  packageName,
@@ -2614,7 +3275,7 @@ var applyVersionBumpTool = defineTool({
2614
3275
  toVersion,
2615
3276
  applied: true,
2616
3277
  dryRun: false,
2617
- 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}`
2618
3279
  };
2619
3280
  });
2620
3281
  }
@@ -2622,9 +3283,9 @@ var applyVersionBumpTool = defineTool({
2622
3283
 
2623
3284
  // src/remediation/tools/apply-package-override/index.ts
2624
3285
  import { z as z5 } from "zod";
2625
- import { join as join13 } from "path";
2626
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
2627
- 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";
2628
3289
 
2629
3290
  // src/remediation/tools/apply-package-override/helpers.ts
2630
3291
  async function collectDependencyTrace(cwd, pm, packageName, constraints) {
@@ -2766,9 +3427,9 @@ var applyPackageOverrideTool = defineTool({
2766
3427
  workspace
2767
3428
  }) => {
2768
3429
  const pm = packageManager ?? detectPackageManager(cwd);
2769
- const pkgPath = join13(cwd, "package.json");
2770
- const denoJsonPath = join13(cwd, "deno.json");
2771
- 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);
2772
3433
  const loadedPolicy = loadPolicy(cwd, policy);
2773
3434
  const commandConstraints = {
2774
3435
  ...loadedPolicy.constraints,
@@ -2794,7 +3455,7 @@ var applyPackageOverrideTool = defineTool({
2794
3455
  message: `Policy blocked changes for package "${packageName}".`
2795
3456
  };
2796
3457
  }
2797
- 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);
2798
3459
  if (isMajorBump && !loadedPolicy.allowMajorBumps) {
2799
3460
  return {
2800
3461
  packageName,
@@ -2810,7 +3471,7 @@ var applyPackageOverrideTool = defineTool({
2810
3471
  if (isDenoNative) {
2811
3472
  let denoJson;
2812
3473
  try {
2813
- denoJson = JSON.parse(readFileSync10(denoJsonPath, "utf8"));
3474
+ denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
2814
3475
  } catch {
2815
3476
  return {
2816
3477
  packageName,
@@ -2913,7 +3574,7 @@ var applyPackageOverrideTool = defineTool({
2913
3574
  }
2914
3575
  let pkgJson;
2915
3576
  try {
2916
- pkgJson = JSON.parse(readFileSync10(pkgPath, "utf8"));
3577
+ pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
2917
3578
  } catch {
2918
3579
  return {
2919
3580
  packageName,
@@ -3010,6 +3671,76 @@ var applyPackageOverrideTool = defineTool({
3010
3671
  }
3011
3672
  });
3012
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
+
3013
3744
  // src/remediation/local/primary-strategy.ts
3014
3745
  async function resolvePrimaryResult(params) {
3015
3746
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
@@ -3080,6 +3811,22 @@ async function resolvePrimaryResult(params) {
3080
3811
  }
3081
3812
  };
3082
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
+ }
3083
3830
  const overrideResult = await applyPackageOverrideTool.execute({
3084
3831
  cwd,
3085
3832
  packageManager,
@@ -3135,6 +3882,22 @@ async function resolvePrimaryResult(params) {
3135
3882
  }
3136
3883
  };
3137
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
+ }
3138
3901
  const applyResult = await applyVersionBumpTool.execute({
3139
3902
  cwd,
3140
3903
  packageManager,
@@ -3144,6 +3907,7 @@ async function resolvePrimaryResult(params) {
3144
3907
  dryRun,
3145
3908
  policy,
3146
3909
  runTests,
3910
+ typecheck: constraints.typecheck,
3147
3911
  installMode: constraints.installMode,
3148
3912
  installPreferOffline: constraints.installPreferOffline,
3149
3913
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
@@ -3157,10 +3921,10 @@ async function resolvePrimaryResult(params) {
3157
3921
 
3158
3922
  // src/remediation/tools/fetch-package-source.ts
3159
3923
  import { z as z6 } from "zod";
3160
- import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
3161
- 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";
3162
3926
  import { tmpdir } from "os";
3163
- import { execa as execa6 } from "execa";
3927
+ import { execa as execa7 } from "execa";
3164
3928
  var fetchPackageSourceTool = defineTool({
3165
3929
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
3166
3930
  parameters: z6.object({
@@ -3178,25 +3942,25 @@ var fetchPackageSourceTool = defineTool({
3178
3942
  if (safePatterns.length === 0) {
3179
3943
  return { success: false, error: "No valid file patterns provided." };
3180
3944
  }
3181
- const tempBaseDir = await mkdtemp(join14(tmpdir(), "autoremediator-pkg-"));
3182
- const extractDir = join14(tempBaseDir, "out");
3945
+ const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
3946
+ const extractDir = join16(tempBaseDir, "out");
3183
3947
  try {
3184
3948
  const scopedName = packageName.split("/").pop();
3185
3949
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3186
3950
  await mkdir2(tempBaseDir, { recursive: true });
3187
- const tarballPath = join14(tempBaseDir, "package.tgz");
3188
- await execa6("curl", ["-L", "-o", tarballPath, npmUrl]);
3951
+ const tarballPath = join16(tempBaseDir, "package.tgz");
3952
+ await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3189
3953
  await mkdir2(extractDir, { recursive: true });
3190
- await execa6("tar", ["-xzf", tarballPath, "-C", extractDir]);
3954
+ await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3191
3955
  const extractedContents = await readdir2(extractDir);
3192
- const packageRootDir = extractedContents.includes("package") ? join14(extractDir, "package") : extractDir;
3956
+ const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
3193
3957
  const sourceCode = {};
3194
3958
  async function walkDir(dir, relativeBase) {
3195
3959
  try {
3196
3960
  const files = await readdir2(dir, { withFileTypes: true });
3197
3961
  for (const file of files) {
3198
- const fullPath = join14(dir, file.name);
3199
- const relPath = join14(relativeBase, file.name);
3962
+ const fullPath = join16(dir, file.name);
3963
+ const relPath = join16(relativeBase, file.name);
3200
3964
  if (file.isDirectory()) {
3201
3965
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3202
3966
  file.name
@@ -3210,7 +3974,7 @@ var fetchPackageSourceTool = defineTool({
3210
3974
  });
3211
3975
  if (matches) {
3212
3976
  try {
3213
- const content = await readFile3(fullPath, "utf8");
3977
+ const content = await readFile4(fullPath, "utf8");
3214
3978
  sourceCode[relPath] = content;
3215
3979
  } catch {
3216
3980
  }
@@ -3245,7 +4009,7 @@ var fetchPackageSourceTool = defineTool({
3245
4009
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
3246
4010
  };
3247
4011
  } finally {
3248
- await rm2(tempBaseDir, { recursive: true, force: true });
4012
+ await rm3(tempBaseDir, { recursive: true, force: true });
3249
4013
  }
3250
4014
  }
3251
4015
  });
@@ -3303,6 +4067,7 @@ Analyze the source code to:
3303
4067
  2. Explain the root cause of the security issue
3304
4068
  3. Propose a secure fix that addresses the vulnerability
3305
4069
  4. Provide the complete fixed version of affected files
4070
+ 5. Optionally provide a standalone Node.js reproduction/regression test script
3306
4071
 
3307
4072
  ## Response Format
3308
4073
  Respond ONLY with valid JSON (no markdown, no extra text):
@@ -3313,7 +4078,8 @@ Respond ONLY with valid JSON (no markdown, no extra text):
3313
4078
  "path/to/other.ts": "Complete fixed source code for this file"
3314
4079
  },
3315
4080
  "confidence": 0.95,
3316
- "riskLevel": "medium"
4081
+ "riskLevel": "medium",
4082
+ "reproductionTest": "// Node.js script asserting the patch resolves the exploit/regression"
3317
4083
  }
3318
4084
 
3319
4085
  Important:
@@ -3593,6 +4359,7 @@ var generatePatchTool = defineTool({
3593
4359
  success: true,
3594
4360
  patches,
3595
4361
  patchContent: patches[0]?.unifiedDiff,
4362
+ reproductionTest: analysis.reproductionTest,
3596
4363
  llmProvider: provider,
3597
4364
  llmModel: modelName,
3598
4365
  latencyMs,
@@ -3617,17 +4384,17 @@ var generatePatchTool = defineTool({
3617
4384
 
3618
4385
  // src/remediation/tools/apply-patch-file/index.ts
3619
4386
  import { z as z8 } from "zod";
3620
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
3621
- import { join as join16 } from "path";
3622
- 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";
3623
4390
 
3624
4391
  // src/remediation/tools/apply-patch-file/helpers.ts
3625
- import { existsSync as existsSync10 } from "fs";
3626
- 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";
3627
4394
  import { createHash } from "crypto";
3628
4395
  import { tmpdir as tmpdir2 } from "os";
3629
- import { join as join15 } from "path";
3630
- import { execa as execa7 } from "execa";
4396
+ import { join as join17 } from "path";
4397
+ import { execa as execa8 } from "execa";
3631
4398
  async function resolvePatchMode(packageManager, cwd) {
3632
4399
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
3633
4400
  return "patch-package";
@@ -3658,13 +4425,13 @@ function computePatchIntegrity(patchContent) {
3658
4425
  return `sha256:${hex}`;
3659
4426
  }
3660
4427
  async function writePatchManifest(manifestFilePath, artifact) {
3661
- await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4428
+ await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
3662
4429
  }
3663
4430
  async function configurePatchPackagePostinstall(cwd, packageManager) {
3664
- const pkgJsonPath = join15(cwd, "package.json");
4431
+ const pkgJsonPath = join17(cwd, "package.json");
3665
4432
  let pkgJson;
3666
4433
  try {
3667
- pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
4434
+ pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
3668
4435
  } catch {
3669
4436
  return {
3670
4437
  success: false,
@@ -3676,7 +4443,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
3676
4443
  try {
3677
4444
  const commands = getPackageManagerCommands(packageManager);
3678
4445
  const [cmd, ...args] = commands.installDev("patch-package");
3679
- await execa7(cmd, args, {
4446
+ await execa8(cmd, args, {
3680
4447
  cwd,
3681
4448
  stdio: "pipe"
3682
4449
  });
@@ -3697,13 +4464,13 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
3697
4464
  } else if (!currentPostinstall) {
3698
4465
  pkgJson.scripts.postinstall = patchApplyCmd;
3699
4466
  }
3700
- await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4467
+ await writeFile2(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3701
4468
  return { success: true };
3702
4469
  }
3703
4470
  async function capturePackageJsonSnapshot(cwd) {
3704
- const path = join15(cwd, "package.json");
4471
+ const path = join17(cwd, "package.json");
3705
4472
  try {
3706
- const content = await readFile4(path, "utf8");
4473
+ const content = await readFile5(path, "utf8");
3707
4474
  return { path, content };
3708
4475
  } catch {
3709
4476
  return void 0;
@@ -3719,19 +4486,19 @@ async function cleanupPatchArtifacts(params) {
3719
4486
  installCommand,
3720
4487
  rerunInstall
3721
4488
  } = params;
3722
- await rm3(patchFilePath, { force: true }).catch(() => void 0);
4489
+ await rm4(patchFilePath, { force: true }).catch(() => void 0);
3723
4490
  if (manifestFilePath) {
3724
- await rm3(manifestFilePath, { force: true }).catch(() => void 0);
4491
+ await rm4(manifestFilePath, { force: true }).catch(() => void 0);
3725
4492
  }
3726
4493
  if (patchMode === "patch-package" && packageJsonSnapshot) {
3727
- await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
4494
+ await writeFile2(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(
3728
4495
  () => void 0
3729
4496
  );
3730
4497
  }
3731
4498
  if (!rerunInstall) return;
3732
4499
  try {
3733
4500
  const [installCmd, ...installArgs] = installCommand;
3734
- await execa7(installCmd, installArgs, {
4501
+ await execa8(installCmd, installArgs, {
3735
4502
  cwd,
3736
4503
  stdio: "pipe"
3737
4504
  });
@@ -3745,7 +4512,7 @@ async function applyNativePatch(params) {
3745
4512
  const createArgs = ["patch", packageSpec];
3746
4513
  let patchDir;
3747
4514
  try {
3748
- const createResult = await execa7(createCommand, createArgs, {
4515
+ const createResult = await execa8(createCommand, createArgs, {
3749
4516
  cwd,
3750
4517
  stdio: "pipe"
3751
4518
  });
@@ -3773,17 +4540,17 @@ ${createResult.stderr}`);
3773
4540
  error: `Could not determine native patch directory for ${packageSpec}.`
3774
4541
  };
3775
4542
  }
3776
- const tempPatchDir = await mkdtemp2(join15(tmpdir2(), "autoremediator-native-patch-"));
3777
- const tempPatchFile = join15(tempPatchDir, "change.patch");
4543
+ const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
4544
+ const tempPatchFile = join17(tempPatchDir, "change.patch");
3778
4545
  try {
3779
- await writeFile(tempPatchFile, patchContent, "utf8");
3780
- await execa7("patch", ["-p1", "-i", tempPatchFile], {
4546
+ await writeFile2(tempPatchFile, patchContent, "utf8");
4547
+ await execa8("patch", ["-p1", "-i", tempPatchFile], {
3781
4548
  cwd: patchDir,
3782
4549
  stdio: "pipe"
3783
4550
  });
3784
4551
  const commitCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
3785
4552
  const commitArgs = patchMode === "native-pnpm" ? ["patch-commit", patchDir] : ["patch-commit", "-s", patchDir];
3786
- await execa7(commitCommand, commitArgs, {
4553
+ await execa8(commitCommand, commitArgs, {
3787
4554
  cwd,
3788
4555
  stdio: "pipe"
3789
4556
  });
@@ -3798,19 +4565,19 @@ ${createResult.stderr}`);
3798
4565
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
3799
4566
  };
3800
4567
  } finally {
3801
- await rm3(tempPatchDir, { recursive: true, force: true });
4568
+ await rm4(tempPatchDir, { recursive: true, force: true });
3802
4569
  }
3803
4570
  return { success: true };
3804
4571
  }
3805
4572
  function extractPatchDirectory(output) {
3806
4573
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
3807
4574
  for (const line of lines) {
3808
- if (existsSync10(line)) {
4575
+ if (existsSync12(line)) {
3809
4576
  return line;
3810
4577
  }
3811
4578
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
3812
4579
  for (const token of tokens) {
3813
- if (token.startsWith("/") && existsSync10(token)) {
4580
+ if (token.startsWith("/") && existsSync12(token)) {
3814
4581
  return token;
3815
4582
  }
3816
4583
  }
@@ -3820,7 +4587,7 @@ function extractPatchDirectory(output) {
3820
4587
  async function validatePatchWithTests(cwd, testCommand) {
3821
4588
  try {
3822
4589
  const [cmd, ...args] = testCommand;
3823
- const result = await execa7(cmd, args, {
4590
+ const result = await execa8(cmd, args, {
3824
4591
  cwd,
3825
4592
  timeout: 6e4,
3826
4593
  stdio: "pipe"
@@ -3830,7 +4597,8 @@ async function validatePatchWithTests(cwd, testCommand) {
3830
4597
  output: result.stdout
3831
4598
  };
3832
4599
  } catch (err) {
3833
- const errorOutput = typeof err === "object" && err !== null && "stdout" in err ? String(err.stdout ?? "") : "";
4600
+ const stdoutVal = typeof err === "object" && err !== null && "stdout" in err ? err.stdout : "";
4601
+ const errorOutput = typeof stdoutVal === "string" ? stdoutVal : Buffer.isBuffer(stdoutVal) ? stdoutVal.toString("utf8") : "";
3834
4602
  const failedTests = extractFailedTests(errorOutput);
3835
4603
  return {
3836
4604
  passed: false,
@@ -3879,6 +4647,7 @@ var applyPatchFileTool = defineTool({
3879
4647
  enforceFrozenLockfile: z8.boolean().optional(),
3880
4648
  workspace: z8.string().optional(),
3881
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"),
3882
4651
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
3883
4652
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
3884
4653
  message: "Either patchContent or patches must be provided"
@@ -3900,6 +4669,7 @@ var applyPatchFileTool = defineTool({
3900
4669
  workspace,
3901
4670
  riskLevel,
3902
4671
  validateWithTests,
4672
+ reproductionTest,
3903
4673
  dryRun
3904
4674
  }) => {
3905
4675
  try {
@@ -3950,7 +4720,7 @@ var applyPatchFileTool = defineTool({
3950
4720
  };
3951
4721
  }
3952
4722
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
3953
- const patchFilePath = join16(cwd, patchesDir, patchFileName);
4723
+ const patchFilePath = join18(cwd, patchesDir, patchFileName);
3954
4724
  const manifestFilePath = `${patchFilePath}.json`;
3955
4725
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
3956
4726
  const baseArtifact = {
@@ -3989,9 +4759,9 @@ var applyPatchFileTool = defineTool({
3989
4759
  }
3990
4760
  return withRepoLock(cwd, async () => {
3991
4761
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
3992
- const patchesDirPath = join16(cwd, patchesDir);
4762
+ const patchesDirPath = join18(cwd, patchesDir);
3993
4763
  await mkdir3(patchesDirPath, { recursive: true });
3994
- await writeFile2(patchFilePath, selectedPatch, "utf8");
4764
+ await writeFile3(patchFilePath, selectedPatch, "utf8");
3995
4765
  validationPhases.push({
3996
4766
  phase: "patch-write",
3997
4767
  passed: true,
@@ -4059,7 +4829,7 @@ var applyPatchFileTool = defineTool({
4059
4829
  if (patchMode === "patch-package") {
4060
4830
  try {
4061
4831
  const [installCmd, ...installArgs] = installCommand;
4062
- await execa8(installCmd, installArgs, {
4832
+ await execa9(installCmd, installArgs, {
4063
4833
  cwd,
4064
4834
  stdio: "pipe"
4065
4835
  });
@@ -4152,10 +4922,33 @@ var applyPatchFileTool = defineTool({
4152
4922
  message: "Patch validation tests passed."
4153
4923
  });
4154
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
+ }
4155
4946
  const finalArtifact = {
4156
4947
  ...artifact,
4157
4948
  applied: true,
4158
4949
  dryRun: false,
4950
+ reproductionTest,
4951
+ reproductionTestPath,
4159
4952
  validationPhases
4160
4953
  };
4161
4954
  await writePatchManifest(manifestFilePath, finalArtifact);
@@ -4465,7 +5258,11 @@ function resolveLocalRunOptions(options) {
4465
5258
  const runTests = options.runTests ?? false;
4466
5259
  const policy = options.policy ?? "";
4467
5260
  const patchesDir = options.patchesDir || "./patches";
4468
- 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
+ };
4469
5266
  const loadedPolicy = loadPolicy(cwd, options.policy);
4470
5267
  const llmProvider = resolveProvider(options);
4471
5268
  return {
@@ -4502,7 +5299,7 @@ function resolveLocalRunOptions(options) {
4502
5299
  }
4503
5300
 
4504
5301
  // src/remediation/local/vulnerability-match.ts
4505
- import semver4 from "semver";
5302
+ import semver5 from "semver";
4506
5303
  function findVulnerablePackages(cveDetails, installedPackages) {
4507
5304
  const vulnerablePackages = [];
4508
5305
  for (const affected of cveDetails.affectedPackages) {
@@ -4511,10 +5308,10 @@ function findVulnerablePackages(cveDetails, installedPackages) {
4511
5308
  if (affected.ecosystem !== "npm") continue;
4512
5309
  const matches = installedPackages.filter((pkg) => pkg.name === affected.name);
4513
5310
  for (const installed of matches) {
4514
- if (!semver4.valid(installed.version)) continue;
5311
+ if (!semver5.valid(installed.version)) continue;
4515
5312
  let isVulnerable = false;
4516
5313
  try {
4517
- isVulnerable = semver4.satisfies(installed.version, affected.vulnerableRange, {
5314
+ isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4518
5315
  includePrerelease: false
4519
5316
  });
4520
5317
  } catch {
@@ -4857,7 +5654,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
4857
5654
  };
4858
5655
  if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
4859
5656
  try {
4860
- if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
5657
+ if (semver6.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, {
4861
5658
  includePrerelease: false
4862
5659
  })) {
4863
5660
  primaryResult.regressionDetected = true;
@@ -4948,8 +5745,8 @@ function accumulateStepResults(params) {
4948
5745
  }
4949
5746
 
4950
5747
  // src/remediation/orchestration-prompt.ts
4951
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
4952
- import { join as join17 } from "path";
5748
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
5749
+ import { join as join19 } from "path";
4953
5750
  function buildProviderAddendum(provider, personality = "balanced") {
4954
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.";
4955
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.";
@@ -4960,13 +5757,13 @@ Provider profile:
4960
5757
  - ${personalityDirective}`;
4961
5758
  }
4962
5759
  function loadOrchestrationPrompt(ctx) {
4963
- const promptPath = join17(
5760
+ const promptPath = join19(
4964
5761
  process.cwd(),
4965
5762
  ".github",
4966
5763
  "instructions",
4967
5764
  "orchestration.instructions.md"
4968
5765
  );
4969
- if (!existsSync11(promptPath)) {
5766
+ if (!existsSync13(promptPath)) {
4970
5767
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
4971
5768
  Working directory: ${ctx.cwd}
4972
5769
  Package manager: ${ctx.packageManager}
@@ -4979,107 +5776,43 @@ Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
4979
5776
 
4980
5777
  Required sequence:
4981
5778
  1. lookup-cve
4982
- 2. check-inventory
4983
- 3. check-version-match
4984
- 4. find-fixed-version
4985
- 5. apply-version-bump
4986
- 6. apply-package-override
4987
-
4988
- Fallback sequence (when neither version bump nor override can be applied):
4989
- 1. fetch-package-source
4990
- 2. generate-patch
4991
- 3. apply-patch-file
4992
-
4993
- Always respect dryRun and policy constraints.`;
4994
- }
4995
- const template = readFileSync11(promptPath, "utf8");
4996
- 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(
4997
- "{{directDependenciesOnly}}",
4998
- String(ctx.constraints.directDependenciesOnly ?? false)
4999
- ).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
5000
- }
5001
-
5002
- // src/remediation/pipeline-runtime/progress.ts
5003
- function createProgressEmitter(options) {
5004
- return (stage, detail, extra) => {
5005
- if (!options.onProgress) return;
5006
- options.onProgress({
5007
- stage,
5008
- detail,
5009
- at: (/* @__PURE__ */ new Date()).toISOString(),
5010
- provider: extra?.provider,
5011
- model: extra?.model
5012
- });
5013
- };
5014
- }
5015
-
5016
- // src/remediation/tools/lookup-cve.ts
5017
- import { z as z9 } from "zod";
5018
-
5019
- // src/intelligence/sources/cisa-kev.ts
5020
- var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
5021
- async function fetchCisaKevFeed() {
5022
- try {
5023
- const res = await httpClient({ url: CISA_KEV_URL });
5024
- if (!res.ok) return void 0;
5025
- return res.data;
5026
- } catch {
5027
- return void 0;
5028
- }
5029
- }
5030
- function findKevEntry(feed, cveId) {
5031
- if (!feed?.vulnerabilities?.length) return void 0;
5032
- const normalized = cveId.toUpperCase();
5033
- return feed.vulnerabilities.find((v) => v.cveID.toUpperCase() === normalized);
5034
- }
5035
- async function enrichWithCisaKev(details) {
5036
- const feed = await fetchCisaKevFeed();
5037
- const entry = findKevEntry(feed, details.id);
5038
- if (!entry) return details;
5039
- details.kev = {
5040
- knownExploited: true,
5041
- dateAdded: entry.dateAdded,
5042
- dueDate: entry.dueDate,
5043
- requiredAction: entry.requiredAction,
5044
- knownRansomwareCampaignUse: entry.knownRansomwareCampaignUse
5045
- };
5046
- if (!details.references.includes(CISA_KEV_URL)) {
5047
- details.references.push(CISA_KEV_URL);
5048
- }
5049
- return details;
5050
- }
5779
+ 2. check-inventory
5780
+ 3. check-version-match
5781
+ 4. find-fixed-version
5782
+ 5. apply-version-bump
5783
+ 6. apply-package-override
5051
5784
 
5052
- // src/intelligence/sources/epss.ts
5053
- async function fetchEpss(cveId) {
5054
- const { epssApi } = getIntelligenceSourceConfig();
5055
- if (!epssApi) return void 0;
5056
- try {
5057
- const url = new URL(epssApi);
5058
- url.searchParams.set("cve", cveId);
5059
- const res = await httpClient({ url: url.toString() });
5060
- if (!res.ok) return void 0;
5061
- const body = res.data;
5062
- return body.data?.[0];
5063
- } catch {
5064
- return void 0;
5785
+ Fallback sequence (when neither version bump nor override can be applied):
5786
+ 1. fetch-package-source
5787
+ 2. generate-patch
5788
+ 3. apply-patch-file
5789
+
5790
+ Always respect dryRun and policy constraints.`;
5065
5791
  }
5792
+ const template = readFileSync13(promptPath, "utf8");
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(
5794
+ "{{directDependenciesOnly}}",
5795
+ String(ctx.constraints.directDependenciesOnly ?? false)
5796
+ ).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
5066
5797
  }
5067
- async function enrichWithEpss(details) {
5068
- const row = await fetchEpss(details.id);
5069
- if (!row) return details;
5070
- const score = Number.parseFloat(row.epss);
5071
- const percentile = Number.parseFloat(row.percentile);
5072
- if (!Number.isFinite(score) || !Number.isFinite(percentile)) {
5073
- return details;
5074
- }
5075
- details.epss = {
5076
- score,
5077
- percentile,
5078
- date: row.date
5798
+
5799
+ // src/remediation/pipeline-runtime/progress.ts
5800
+ function createProgressEmitter(options) {
5801
+ return (stage, detail, extra) => {
5802
+ if (!options.onProgress) return;
5803
+ options.onProgress({
5804
+ stage,
5805
+ detail,
5806
+ at: (/* @__PURE__ */ new Date()).toISOString(),
5807
+ provider: extra?.provider,
5808
+ model: extra?.model
5809
+ });
5079
5810
  };
5080
- return details;
5081
5811
  }
5082
5812
 
5813
+ // src/remediation/tools/lookup-cve.ts
5814
+ import { z as z9 } from "zod";
5815
+
5083
5816
  // src/intelligence/sources/cve-services.ts
5084
5817
  function pickEnglishDescription(container) {
5085
5818
  if (!container?.descriptions?.length) return void 0;
@@ -5363,7 +6096,7 @@ var lookupCveTool = defineTool({
5363
6096
 
5364
6097
  // src/remediation/tools/check-version-match.ts
5365
6098
  import { z as z10 } from "zod";
5366
- import semver6 from "semver";
6099
+ import semver7 from "semver";
5367
6100
  var affectedPackageSchema = z10.object({
5368
6101
  name: z10.string(),
5369
6102
  ecosystem: z10.literal("npm"),
@@ -5392,10 +6125,10 @@ var checkVersionMatchTool = defineTool({
5392
6125
  (p) => p.name === affected.name
5393
6126
  );
5394
6127
  for (const installed of matches) {
5395
- if (!semver6.valid(installed.version)) continue;
6128
+ if (!semver7.valid(installed.version)) continue;
5396
6129
  let isVulnerable = false;
5397
6130
  try {
5398
- isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
6131
+ isVulnerable = semver7.satisfies(installed.version, affected.vulnerableRange, {
5399
6132
  includePrerelease: false
5400
6133
  });
5401
6134
  } catch {
@@ -5713,7 +6446,7 @@ async function runRemediationPipeline(cveId, options = {}) {
5713
6446
  }
5714
6447
 
5715
6448
  // src/api/change-request/index.ts
5716
- import { execa as execa9 } from "execa";
6449
+ import { execa as execa10 } from "execa";
5717
6450
  import { randomBytes } from "crypto";
5718
6451
  function sanitizeBranchToken(value) {
5719
6452
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
@@ -5742,22 +6475,68 @@ function resolveProvider2(options) {
5742
6475
  return options.provider ?? "github";
5743
6476
  }
5744
6477
  function resolveGrouping(options) {
5745
- if (options.grouping && options.grouping !== "all") {
6478
+ const allowed = ["all", "per-cve", "per-package", "by-severity"];
6479
+ if (options.grouping && !allowed.includes(options.grouping)) {
5746
6480
  throw new Error(
5747
- "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
6481
+ `Unsupported changeRequest.grouping: "${options.grouping}". Supported values: ${allowed.join(", ")}`
5748
6482
  );
5749
6483
  }
5750
- 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];
5751
6530
  }
5752
6531
  async function detectCurrentBranch(cwd) {
5753
- const result = await execa9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
6532
+ const result = await execa10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
5754
6533
  cwd,
5755
6534
  stdio: "pipe"
5756
6535
  });
5757
6536
  return result.stdout.trim();
5758
6537
  }
5759
6538
  async function ensureRepoHasChanges(cwd) {
5760
- const diff = await execa9("git", ["status", "--porcelain"], {
6539
+ const diff = await execa10("git", ["status", "--porcelain"], {
5761
6540
  cwd,
5762
6541
  stdio: "pipe"
5763
6542
  });
@@ -5786,7 +6565,7 @@ async function createGitHubRequest(params) {
5786
6565
  if (params.draft) {
5787
6566
  args.push("--draft");
5788
6567
  }
5789
- const result = await execa9("gh", args, {
6568
+ const result = await execa10("gh", args, {
5790
6569
  cwd: params.cwd,
5791
6570
  stdio: "pipe"
5792
6571
  });
@@ -5811,7 +6590,7 @@ async function createGitLabRequest(params) {
5811
6590
  if (params.draft) {
5812
6591
  args.push("--draft");
5813
6592
  }
5814
- const result = await execa9("glab", args, {
6593
+ const result = await execa10("glab", args, {
5815
6594
  cwd: params.cwd,
5816
6595
  stdio: "pipe"
5817
6596
  });
@@ -5821,64 +6600,68 @@ async function createChangeRequestsForReports(params) {
5821
6600
  const { cwd, options, reports } = params;
5822
6601
  const provider = resolveProvider2(options);
5823
6602
  const grouping = resolveGrouping(options);
5824
- const plan = buildPlan(reports);
5825
- const titlePrefix = options.titlePrefix?.trim().slice(0, 200);
5826
- const bodyFooter = options.bodyFooter ? options.bodyFooter.slice(0, 2e3) : void 0;
5827
- const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
5828
- 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}
5829
6624
 
5830
6625
  ${bodyFooter}` : plan.body;
5831
- try {
5832
- const hasChanges = await ensureRepoHasChanges(cwd);
5833
6626
  if (!hasChanges) {
5834
- return [
5835
- {
5836
- provider,
5837
- grouping,
5838
- repository: options.repository,
5839
- branchName: "",
5840
- title,
5841
- body,
5842
- created: false,
5843
- cveIds: plan.cveIds,
5844
- packageNames: plan.packageNames,
5845
- error: "No repository changes to include in change request."
5846
- }
5847
- ];
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;
5848
6640
  }
5849
- const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
5850
- const branchPrefix = options.branchPrefix ?? "autoremediator";
5851
6641
  const branchName = toBranchName(branchPrefix, plan.cveIds);
5852
- const pushRemote = options.pushRemote ?? "origin";
5853
- if (options.pushRemote && !/^[a-zA-Z0-9._-]+$/.test(options.pushRemote)) {
5854
- throw new Error(`Invalid pushRemote value: ${options.pushRemote}`);
5855
- }
5856
- if (options.repository && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(options.repository)) {
5857
- throw new Error(`Invalid repository format: ${options.repository}`);
5858
- }
5859
- await execa9("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
5860
- await execa9("git", ["add", "-A"], { cwd, stdio: "pipe" });
5861
- await execa9("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
5862
- await execa9("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
5863
- const url = provider === "github" ? await createGitHubRequest({
5864
- cwd,
5865
- repository: options.repository,
5866
- head: branchName,
5867
- base: baseBranch,
5868
- title,
5869
- body,
5870
- draft: options.draft
5871
- }) : await createGitLabRequest({
5872
- cwd,
5873
- repository: options.repository,
5874
- source: branchName,
5875
- target: baseBranch,
5876
- title,
5877
- body,
5878
- draft: options.draft
5879
- });
5880
- return [
5881
- {
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({
5882
6665
  provider,
5883
6666
  grouping,
5884
6667
  repository: options.repository,
@@ -5890,11 +6673,13 @@ ${bodyFooter}` : plan.body;
5890
6673
  url,
5891
6674
  cveIds: plan.cveIds,
5892
6675
  packageNames: plan.packageNames
6676
+ });
6677
+ if (groups.length > 1) {
6678
+ await execa10("git", ["checkout", baseBranch], { cwd, stdio: "pipe" }).catch(() => {
6679
+ });
5893
6680
  }
5894
- ];
5895
- } catch (error) {
5896
- return [
5897
- {
6681
+ } catch (error) {
6682
+ results.push({
5898
6683
  provider,
5899
6684
  grouping,
5900
6685
  repository: options.repository,
@@ -5906,9 +6691,10 @@ ${bodyFooter}` : plan.body;
5906
6691
  cveIds: plan.cveIds,
5907
6692
  packageNames: plan.packageNames,
5908
6693
  error: error instanceof Error ? error.message : String(error)
5909
- }
5910
- ];
6694
+ });
6695
+ }
5911
6696
  }
6697
+ return results;
5912
6698
  }
5913
6699
 
5914
6700
  // src/api/context.ts
@@ -5942,7 +6728,7 @@ function resolveConstraints(options, cwd) {
5942
6728
 
5943
6729
  // src/platform/evidence.ts
5944
6730
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
5945
- import { join as join18 } from "path";
6731
+ import { join as join20 } from "path";
5946
6732
  function createEvidenceLog(cwd, cveIds, context = {}) {
5947
6733
  return {
5948
6734
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -5974,9 +6760,9 @@ function finalizeEvidence(log) {
5974
6760
  return log;
5975
6761
  }
5976
6762
  function writeEvidenceLog(cwd, log) {
5977
- const dir = join18(cwd, ".autoremediator", "evidence");
6763
+ const dir = join20(cwd, ".autoremediator", "evidence");
5978
6764
  mkdirSync3(dir, { recursive: true });
5979
- const filePath = join18(dir, `${log.runId}.json`);
6765
+ const filePath = join20(dir, `${log.runId}.json`);
5980
6766
  writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
5981
6767
  return filePath;
5982
6768
  }
@@ -6268,6 +7054,24 @@ function buildResultSimulation(report, result, options) {
6268
7054
  const dependencyScope = result.dependencyScope ?? dependencyScopeLookup.get(result.packageName);
6269
7055
  const plannedMutations = buildPlannedMutations(result, options.runTests === true);
6270
7056
  const wouldMutate = plannedMutations.length > 0;
7057
+ let unifiedDiff;
7058
+ if (result.patchArtifact?.patchFilePath) {
7059
+ unifiedDiff = `--- ${result.patchArtifact.patchFilePath}
7060
+ +++ ${result.patchArtifact.patchFilePath}`;
7061
+ } else if (result.strategy === "version-bump" && result.toVersion) {
7062
+ unifiedDiff = `--- package.json
7063
+ +++ package.json
7064
+ @@ -1,3 +1,3 @@
7065
+ - "${result.packageName}": "${result.fromVersion}"
7066
+ + "${result.packageName}": "^${result.toVersion}"`;
7067
+ } else if (result.strategy === "override" && result.toVersion) {
7068
+ unifiedDiff = `--- package.json
7069
+ +++ package.json
7070
+ @@ -1,3 +1,3 @@
7071
+ + "overrides": {
7072
+ + "${result.packageName}": "${result.toVersion}"
7073
+ + }`;
7074
+ }
6271
7075
  return {
6272
7076
  mode,
6273
7077
  wouldMutate,
@@ -6278,7 +7082,8 @@ function buildResultSimulation(report, result, options) {
6278
7082
  dependencyScope,
6279
7083
  wouldMutate,
6280
7084
  runTests: options.runTests === true
6281
- })
7085
+ }),
7086
+ unifiedDiff
6282
7087
  };
6283
7088
  }
6284
7089
  function buildSimulationSummary(reports) {
@@ -6457,10 +7262,15 @@ function ciExitCode(summary) {
6457
7262
  }
6458
7263
 
6459
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
+ }
6460
7270
  function assertValidCveId(cveId) {
6461
- if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
7271
+ if (!isVulnerabilityId(cveId)) {
6462
7272
  throw new Error(
6463
- `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.`
6464
7274
  );
6465
7275
  }
6466
7276
  }
@@ -6556,13 +7366,14 @@ async function planRemediation(cveId, options = {}) {
6556
7366
  }
6557
7367
 
6558
7368
  // src/scanner/parse-input.ts
6559
- import { extname, resolve as resolve3 } from "path";
6560
- import { readFileSync as readFileSync15 } from "fs";
6561
- 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";
6562
7372
 
6563
7373
  // src/scanner/adapters/npm-audit.ts
6564
- import { readFileSync as readFileSync12 } from "fs";
7374
+ import { readFileSync as readFileSync14 } from "fs";
6565
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;
6566
7377
  function normalizeSeverity(raw) {
6567
7378
  if (!raw) return "UNKNOWN";
6568
7379
  const up = raw.toUpperCase();
@@ -6577,7 +7388,8 @@ function parseNpmAuditJsonFromString(content) {
6577
7388
  for (const vuln of Object.values(report.vulnerabilities ?? {})) {
6578
7389
  for (const viaEntry of vuln.via ?? []) {
6579
7390
  const text = typeof viaEntry === "string" ? viaEntry : `${viaEntry.url ?? ""} ${viaEntry.name ?? ""}`;
6580
- const matches = text.match(CVE_REGEX) ?? [];
7391
+ const cveMatches = text.match(CVE_REGEX) ?? [];
7392
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX) ?? [];
6581
7393
  for (const match of matches) {
6582
7394
  const cveId = match.toUpperCase();
6583
7395
  const key = `${cveId}:${vuln.name}`;
@@ -6595,13 +7407,14 @@ function parseNpmAuditJsonFromString(content) {
6595
7407
  return findings;
6596
7408
  }
6597
7409
  function parseNpmAuditJsonFile(filePath) {
6598
- const content = readFileSync12(filePath, "utf8");
7410
+ const content = readFileSync14(filePath, "utf8");
6599
7411
  return parseNpmAuditJsonFromString(content);
6600
7412
  }
6601
7413
 
6602
7414
  // src/scanner/adapters/yarn-audit.ts
6603
- import { readFileSync as readFileSync13 } from "fs";
7415
+ import { readFileSync as readFileSync15 } from "fs";
6604
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;
6605
7418
  function normalizeSeverity2(raw) {
6606
7419
  if (!raw) return "UNKNOWN";
6607
7420
  const up = raw.toUpperCase();
@@ -6626,7 +7439,8 @@ function parseYarnAuditJsonFromString(content) {
6626
7439
  const packageName = advisory?.module_name;
6627
7440
  const severity = normalizeSeverity2(advisory?.severity);
6628
7441
  const text = `${advisory?.url ?? ""} ${(advisory?.cves ?? []).join(" ")}`;
6629
- const matches = text.match(CVE_REGEX2) ?? [];
7442
+ const cveMatches = text.match(CVE_REGEX2) ?? [];
7443
+ const matches = cveMatches.length > 0 ? cveMatches : text.match(GHSA_REGEX2) ?? [];
6630
7444
  for (const match of matches) {
6631
7445
  const cveId = match.toUpperCase();
6632
7446
  const key = `${cveId}:${packageName ?? ""}`;
@@ -6643,12 +7457,12 @@ function parseYarnAuditJsonFromString(content) {
6643
7457
  return findings;
6644
7458
  }
6645
7459
  function parseYarnAuditJsonFile(filePath) {
6646
- const content = readFileSync13(filePath, "utf8");
7460
+ const content = readFileSync15(filePath, "utf8");
6647
7461
  return parseYarnAuditJsonFromString(content);
6648
7462
  }
6649
7463
 
6650
7464
  // src/scanner/adapters/sarif.ts
6651
- import { readFileSync as readFileSync14 } from "fs";
7465
+ import { readFileSync as readFileSync16 } from "fs";
6652
7466
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
6653
7467
  function extractPackageName(result) {
6654
7468
  const pkg = result.properties?.["packageName"];
@@ -6686,7 +7500,7 @@ function parseSarifFromString(content) {
6686
7500
  return findings;
6687
7501
  }
6688
7502
  function parseSarifFile(filePath) {
6689
- const content = readFileSync14(filePath, "utf8");
7503
+ const content = readFileSync16(filePath, "utf8");
6690
7504
  return parseSarifFromString(content);
6691
7505
  }
6692
7506
 
@@ -6695,7 +7509,7 @@ function parseScanInput(filePath, format) {
6695
7509
  if (filePath.includes("\0")) {
6696
7510
  throw new Error("Invalid scan input path: path contains null bytes");
6697
7511
  }
6698
- const resolvedPath = resolve3(filePath);
7512
+ const resolvedPath = resolve4(filePath);
6699
7513
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
6700
7514
  if (resolved === "npm-audit") {
6701
7515
  return parseNpmAuditJsonFile(resolvedPath);
@@ -6706,7 +7520,7 @@ function parseScanInput(filePath, format) {
6706
7520
  if (resolved === "sarif") {
6707
7521
  return parseSarifFile(resolvedPath);
6708
7522
  }
6709
- throw new Error(`Unsupported input format: ${resolved}`);
7523
+ throw new Error(`Unsupported input format: ${String(resolved)}`);
6710
7524
  }
6711
7525
  async function parseScanInputFromAudit(params) {
6712
7526
  const pm = params.packageManager ?? detectPackageManager(params.cwd);
@@ -6717,7 +7531,7 @@ async function parseScanInputFromAudit(params) {
6717
7531
  }
6718
7532
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
6719
7533
  const [cmd, ...args] = command;
6720
- const result = await execa10(cmd, args, {
7534
+ const result = await execa11(cmd, args, {
6721
7535
  cwd: params.cwd,
6722
7536
  stdio: "pipe",
6723
7537
  reject: false
@@ -6771,7 +7585,7 @@ function inferFormat(filePath) {
6771
7585
  const ext = extname(filePath).toLowerCase();
6772
7586
  if (ext === ".sarif") return "sarif";
6773
7587
  try {
6774
- const content = readFileSync15(filePath, "utf8");
7588
+ const content = readFileSync17(filePath, "utf8");
6775
7589
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
6776
7590
  if (firstLine) {
6777
7591
  const parsed = JSON.parse(firstLine);
@@ -7182,14 +7996,14 @@ async function remediatePortfolio(targets, options = {}) {
7182
7996
  }
7183
7997
 
7184
7998
  // src/api/update-outdated/index.ts
7185
- import { join as join19 } from "path";
7186
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
7187
- 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";
7188
8002
  async function applyBump(params) {
7189
- const pkgPath = join19(params.cwd, "package.json");
8003
+ const pkgPath = join21(params.cwd, "package.json");
7190
8004
  let pkgJson;
7191
8005
  try {
7192
- pkgJson = JSON.parse(readFileSync16(pkgPath, "utf8"));
8006
+ pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
7193
8007
  } catch {
7194
8008
  return {
7195
8009
  applied: false,
@@ -7214,7 +8028,7 @@ async function applyBump(params) {
7214
8028
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7215
8029
  try {
7216
8030
  const [installCmd, ...installArgs] = params.installCommand;
7217
- await execa11(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8031
+ await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
7218
8032
  } catch (err) {
7219
8033
  pkgJson[depField][params.packageName] = currentRange;
7220
8034
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -7227,13 +8041,13 @@ async function applyBump(params) {
7227
8041
  if (params.runTests) {
7228
8042
  try {
7229
8043
  const [testCmd, ...testArgs] = params.testCommand;
7230
- await execa11(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8044
+ await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
7231
8045
  } catch (err) {
7232
8046
  pkgJson[depField][params.packageName] = currentRange;
7233
8047
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
7234
8048
  try {
7235
8049
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
7236
- await execa11(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
8050
+ await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
7237
8051
  } catch {
7238
8052
  }
7239
8053
  const message = err instanceof Error ? err.message : String(err);
@@ -7428,6 +8242,432 @@ async function updateOutdated(options = {}) {
7428
8242
  };
7429
8243
  }
7430
8244
 
8245
+ // src/api/reachability/index.ts
8246
+ async function checkReachability(options) {
8247
+ const cwd = options.cwd ?? process.cwd();
8248
+ return assessPackageReachability(cwd, options.packageName, options.symbol);
8249
+ }
8250
+
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")) {
8273
+ try {
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
+ }
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 {
8300
+ }
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
+ }
8318
+ }
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
+ }
8332
+ }
8333
+ }
8334
+ } catch {
8335
+ }
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
+ }
8359
+ }
8360
+ }
8361
+ }
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
+ }
8408
+ }
8409
+ }
8410
+ return changes;
8411
+ }
8412
+
8413
+ // src/scanner/delta-scan.ts
8414
+ var DEP_SECTIONS = [
8415
+ "dependencies",
8416
+ "devDependencies",
8417
+ "peerDependencies",
8418
+ "optionalDependencies"
8419
+ ];
8420
+ async function getChangedManifestPaths(cwd, baseRef) {
8421
+ const manifests = /* @__PURE__ */ new Set();
8422
+ try {
8423
+ const { stdout: diffFiles } = await execa13(
8424
+ "git",
8425
+ ["diff", "--name-only", baseRef, "--", "package.json", "**/package.json"],
8426
+ { cwd, reject: false }
8427
+ );
8428
+ for (const f of diffFiles.split("\n")) {
8429
+ const trimmed = f.trim();
8430
+ if (trimmed && trimmed.endsWith("package.json") && !trimmed.includes("node_modules")) {
8431
+ manifests.add(trimmed);
8432
+ }
8433
+ }
8434
+ const { stdout: untrackedFiles } = await execa13(
8435
+ "git",
8436
+ ["ls-files", "--others", "--exclude-standard", "--", "package.json", "**/package.json"],
8437
+ { cwd, reject: false }
8438
+ );
8439
+ for (const f of untrackedFiles.split("\n")) {
8440
+ const trimmed = f.trim();
8441
+ if (trimmed && trimmed.endsWith("package.json") && !trimmed.includes("node_modules")) {
8442
+ manifests.add(trimmed);
8443
+ }
8444
+ }
8445
+ } catch {
8446
+ if (existsSync14(join22(cwd, "package.json"))) {
8447
+ manifests.add("package.json");
8448
+ }
8449
+ }
8450
+ return Array.from(manifests);
8451
+ }
8452
+ async function getOldManifestContent(cwd, baseRef, relPath) {
8453
+ try {
8454
+ const { stdout } = await execa13("git", ["show", `${baseRef}:${relPath}`], {
8455
+ cwd,
8456
+ reject: false
8457
+ });
8458
+ if (!stdout || !stdout.trim()) return null;
8459
+ return JSON.parse(stdout);
8460
+ } catch {
8461
+ return null;
8462
+ }
8463
+ }
8464
+ function getNewManifestContent(cwd, relPath) {
8465
+ const fullPath = join22(cwd, relPath);
8466
+ if (!existsSync14(fullPath)) return null;
8467
+ try {
8468
+ return JSON.parse(readFileSync19(fullPath, "utf8"));
8469
+ } catch {
8470
+ return null;
8471
+ }
8472
+ }
8473
+ function computeDependencyDiff(relPath, oldPkg, newPkg) {
8474
+ const changes = [];
8475
+ for (const section of DEP_SECTIONS) {
8476
+ const oldDeps = oldPkg?.[section] ?? {};
8477
+ const newDeps = newPkg?.[section] ?? {};
8478
+ for (const [pkgName, newVer] of Object.entries(newDeps)) {
8479
+ const oldVer = oldDeps[pkgName];
8480
+ if (!oldVer) {
8481
+ changes.push({
8482
+ packageName: pkgName,
8483
+ changeType: "added",
8484
+ newVersion: newVer,
8485
+ dependencyType: section,
8486
+ manifestPath: relPath
8487
+ });
8488
+ } else if (oldVer !== newVer) {
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;
8492
+ changes.push({
8493
+ packageName: pkgName,
8494
+ changeType: isUpgrade ? "upgraded" : "downgraded",
8495
+ oldVersion: oldVer,
8496
+ newVersion: newVer,
8497
+ dependencyType: section,
8498
+ manifestPath: relPath
8499
+ });
8500
+ }
8501
+ }
8502
+ for (const [pkgName, oldVer] of Object.entries(oldDeps)) {
8503
+ if (!(pkgName in newDeps)) {
8504
+ changes.push({
8505
+ packageName: pkgName,
8506
+ changeType: "removed",
8507
+ oldVersion: oldVer,
8508
+ dependencyType: section,
8509
+ manifestPath: relPath
8510
+ });
8511
+ }
8512
+ }
8513
+ }
8514
+ return changes;
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
+ }
8564
+ async function scanDelta(options = {}) {
8565
+ const cwd = options.cwd ?? process.cwd();
8566
+ const baseRef = options.baseRef ?? "HEAD";
8567
+ const changedManifests = await getChangedManifestPaths(cwd, baseRef);
8568
+ const changedLockfiles = await getChangedLockfilePaths(cwd, baseRef);
8569
+ const dependencyChanges = [];
8570
+ const directPkgs = /* @__PURE__ */ new Set();
8571
+ for (const manifestRel of changedManifests) {
8572
+ const oldPkg = await getOldManifestContent(cwd, baseRef, manifestRel);
8573
+ const newPkg = getNewManifestContent(cwd, manifestRel);
8574
+ const diffs = computeDependencyDiff(manifestRel, oldPkg, newPkg);
8575
+ for (const d of diffs) {
8576
+ directPkgs.add(d.packageName);
8577
+ }
8578
+ dependencyChanges.push(...diffs);
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
+ }
8594
+ const introducedFindings = [];
8595
+ const resolvedFindings = [];
8596
+ for (const change of dependencyChanges) {
8597
+ if (change.changeType === "added" || change.changeType === "upgraded" || change.changeType === "downgraded") {
8598
+ const cleanVer = semver9.coerce(change.newVersion)?.version ?? change.newVersion;
8599
+ const evaluation = await evaluatePackage(change.packageName, {
8600
+ version: cleanVer,
8601
+ enrichIntelligence: true
8602
+ });
8603
+ for (const vuln of evaluation.vulnerabilities) {
8604
+ introducedFindings.push({
8605
+ packageName: change.packageName,
8606
+ cveId: vuln.cveId,
8607
+ severity: vuln.severity,
8608
+ summary: vuln.summary,
8609
+ deltaType: "introduced",
8610
+ installedVersion: change.newVersion,
8611
+ safeUpgradeVersion: vuln.safeUpgradeVersion,
8612
+ inCisaKev: vuln.inCisaKev,
8613
+ epssScore: vuln.epssScore
8614
+ });
8615
+ }
8616
+ }
8617
+ if (change.oldVersion && (change.changeType === "upgraded" || change.changeType === "removed")) {
8618
+ const cleanOldVer = semver9.coerce(change.oldVersion)?.version ?? change.oldVersion;
8619
+ const oldEval = await evaluatePackage(change.packageName, {
8620
+ version: cleanOldVer,
8621
+ enrichIntelligence: true
8622
+ });
8623
+ const cleanNewVer = change.newVersion ? semver9.coerce(change.newVersion)?.version ?? change.newVersion : void 0;
8624
+ const newEval = cleanNewVer ? await evaluatePackage(change.packageName, {
8625
+ version: cleanNewVer,
8626
+ enrichIntelligence: false
8627
+ }) : { vulnerabilities: [] };
8628
+ const newCveSet = new Set(newEval.vulnerabilities.map((v) => v.cveId));
8629
+ for (const oldVuln of oldEval.vulnerabilities) {
8630
+ if (!newCveSet.has(oldVuln.cveId)) {
8631
+ resolvedFindings.push({
8632
+ packageName: change.packageName,
8633
+ cveId: oldVuln.cveId,
8634
+ severity: oldVuln.severity,
8635
+ summary: oldVuln.summary,
8636
+ deltaType: "resolved",
8637
+ installedVersion: change.oldVersion,
8638
+ safeUpgradeVersion: oldVuln.safeUpgradeVersion,
8639
+ inCisaKev: oldVuln.inCisaKev,
8640
+ epssScore: oldVuln.epssScore
8641
+ });
8642
+ }
8643
+ }
8644
+ }
8645
+ }
8646
+ let netRiskVerdict = "neutral";
8647
+ let status = "ok";
8648
+ if (introducedFindings.length > 0) {
8649
+ netRiskVerdict = "degraded";
8650
+ status = "degraded";
8651
+ } else if (resolvedFindings.length > 0) {
8652
+ netRiskVerdict = "improved";
8653
+ status = "improved";
8654
+ }
8655
+ const summary = introducedFindings.length > 0 ? `Delta scan against ${baseRef} found ${introducedFindings.length} newly introduced vulnerability(ies) across ${dependencyChanges.length} dependency change(s) (net risk: degraded).` : resolvedFindings.length > 0 ? `Delta scan against ${baseRef} resolved ${resolvedFindings.length} vulnerability(ies) across ${dependencyChanges.length} dependency change(s) (net risk: improved).` : `Delta scan against ${baseRef} found 0 new vulnerabilities across ${dependencyChanges.length} dependency change(s) (net risk: neutral).`;
8656
+ return {
8657
+ schemaVersion: "1.0",
8658
+ status,
8659
+ baseRef,
8660
+ targetRef: "working-tree",
8661
+ changedManifests,
8662
+ dependencyChanges,
8663
+ introducedFindings,
8664
+ resolvedFindings,
8665
+ netRiskVerdict,
8666
+ summary,
8667
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
8668
+ };
8669
+ }
8670
+
7431
8671
  // src/api/jobs/index.ts
7432
8672
  import { randomUUID as randomUUID2 } from "crypto";
7433
8673
 
@@ -7543,9 +8783,9 @@ function toSarifOutput(report) {
7543
8783
  }
7544
8784
 
7545
8785
  // src/version.ts
7546
- import { readFileSync as readFileSync17 } from "fs";
8786
+ import { readFileSync as readFileSync20 } from "fs";
7547
8787
  function readPackageVersion() {
7548
- const raw = readFileSync17(new URL("../package.json", import.meta.url), "utf8");
8788
+ const raw = readFileSync20(new URL("../package.json", import.meta.url), "utf8");
7549
8789
  const metadata = JSON.parse(raw);
7550
8790
  if (!metadata.version) {
7551
8791
  throw new Error("packages/core/package.json is missing a version field.");
@@ -7570,11 +8810,18 @@ export {
7570
8810
  listPatchArtifacts,
7571
8811
  inspectPatchArtifact,
7572
8812
  validatePatchArtifact,
8813
+ evaluatePackage,
8814
+ reconcilePatchArtifacts,
7573
8815
  remediate,
7574
8816
  planRemediation,
8817
+ parseScanInput,
8818
+ parseScanInputFromAudit,
8819
+ uniqueCveIds,
7575
8820
  remediateFromScan,
7576
8821
  remediatePortfolio,
7577
8822
  updateOutdated,
8823
+ checkReachability,
8824
+ scanDelta,
7578
8825
  submitRemediateJob,
7579
8826
  submitScanJob,
7580
8827
  submitPortfolioJob,