autoremediator 0.10.0 → 0.12.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.
@@ -40,8 +40,21 @@ var OPTION_DESCRIPTIONS = {
40
40
  installPreferOffline: "Override prefer-offline flag behavior for install commands",
41
41
  enforceFrozenLockfile: "Override frozen lockfile behavior for install commands",
42
42
  workspace: "Workspace/package selector for scoped remediation in monorepos",
43
- includeTransitive: "Include indirect/transitive dependencies in the outdated check. Default: false.",
44
- updateOutdated: "Run in update-outdated mode: bump all outdated npm packages without requiring a CVE."
43
+ createChangeRequest: "Enable creation of native pull request / merge request after remediation",
44
+ changeRequestProvider: "Change request provider (github|gitlab)",
45
+ changeRequestGrouping: "Grouping strategy for change requests (all|per-cve|per-package)",
46
+ changeRequestRepository: "Repository slug override for change request creation",
47
+ changeRequestBaseBranch: "Base branch used for change request targeting",
48
+ changeRequestBranchPrefix: "Branch prefix for generated change request branches",
49
+ changeRequestTitlePrefix: "Title prefix for generated change requests",
50
+ includeTransitive: "Include transitive dependencies in the outdated check. Default: false.",
51
+ updateOutdated: "Run in update-outdated mode: bump all outdated npm packages without requiring a CVE.",
52
+ kevMandatory: "If true, CVEs with active CISA KEV status bypass severity filtering and are treated as mandatory",
53
+ epssThreshold: "EPSS probability threshold (0..1) above which a CVE is treated as mandatory regardless of severity",
54
+ suppressionsFile: "Path to a YAML file containing additional VEX suppression entries to merge with policy-inline suppressions",
55
+ slaCheck: "Compare CVE publication dates against configured SLA windows and include breach records in the report output",
56
+ skipUnreachable: "Skip remediation for CVEs where the vulnerable package cannot be reached from any project entry point (requires static import analysis)",
57
+ regressionCheck: "After applying a fix, verify the patched version falls outside the CVE's vulnerable range and flag any regression in the report"
45
58
  };
46
59
  function createConstraintSchemaProperties() {
47
60
  return {
@@ -97,14 +110,41 @@ function createRemediateOptionSchemaProperties(options) {
97
110
  constraints: {
98
111
  type: "object",
99
112
  properties: createConstraintSchemaProperties()
100
- }
113
+ },
114
+ changeRequest: {
115
+ type: "object",
116
+ properties: {
117
+ enabled: { type: "boolean", description: OPTION_DESCRIPTIONS.createChangeRequest },
118
+ provider: {
119
+ type: "string",
120
+ enum: ["github", "gitlab"],
121
+ description: OPTION_DESCRIPTIONS.changeRequestProvider
122
+ },
123
+ grouping: {
124
+ type: "string",
125
+ enum: ["all", "per-cve", "per-package"],
126
+ description: OPTION_DESCRIPTIONS.changeRequestGrouping
127
+ },
128
+ repository: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestRepository },
129
+ baseBranch: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestBaseBranch },
130
+ branchPrefix: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestBranchPrefix },
131
+ titlePrefix: { type: "string", description: OPTION_DESCRIPTIONS.changeRequestTitlePrefix }
132
+ }
133
+ },
134
+ kevMandatory: { type: "boolean", description: OPTION_DESCRIPTIONS.kevMandatory },
135
+ epssThreshold: { type: "number", minimum: 0, maximum: 1, description: OPTION_DESCRIPTIONS.epssThreshold },
136
+ suppressionsFile: { type: "string", description: OPTION_DESCRIPTIONS.suppressionsFile },
137
+ slaCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.slaCheck },
138
+ skipUnreachable: { type: "boolean", description: OPTION_DESCRIPTIONS.skipUnreachable },
139
+ regressionCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.regressionCheck }
101
140
  };
102
141
  }
103
142
  function createScanOptionSchemaProperties() {
104
143
  return {
105
144
  ...createRemediateOptionSchemaProperties({ includeEvidence: true }),
106
145
  format: { type: "string", enum: ["npm-audit", "yarn-audit", "sarif", "auto"], description: OPTION_DESCRIPTIONS.format },
107
- audit: { type: "boolean", description: OPTION_DESCRIPTIONS.audit }
146
+ audit: { type: "boolean", description: OPTION_DESCRIPTIONS.audit },
147
+ slaCheck: { type: "boolean", description: OPTION_DESCRIPTIONS.slaCheck }
108
148
  };
109
149
  }
110
150
  function createScanReportSchemaProperties() {
@@ -139,7 +179,8 @@ function createScanReportSchemaProperties() {
139
179
  idempotencyKey: { type: "string" },
140
180
  llmUsageCount: { type: "number" },
141
181
  estimatedCostUsd: { type: "number" },
142
- totalLlmLatencyMs: { type: "number" }
182
+ totalLlmLatencyMs: { type: "number" },
183
+ changeRequests: { type: "array", items: { type: "object" } }
143
184
  };
144
185
  }
145
186
  function createUpdateOutdatedOptionSchemaProperties() {
@@ -464,8 +505,13 @@ async function inspectPatchArtifact(patchFilePath, options = {}) {
464
505
  };
465
506
  }
466
507
 
467
- // src/remediation/tools/check-inventory.ts
508
+ // src/remediation/tools/tool-compat.ts
468
509
  import { tool } from "ai";
510
+ function defineTool(config) {
511
+ return tool(config);
512
+ }
513
+
514
+ // src/remediation/tools/check-inventory.ts
469
515
  import { z } from "zod";
470
516
  import { readFileSync as readFileSync3 } from "fs";
471
517
  import { join as join5 } from "path";
@@ -494,7 +540,11 @@ var DEFAULT_POLICY = {
494
540
  consensusModel: void 0,
495
541
  patchConfidenceThresholds: {},
496
542
  dynamicModelRouting: false,
497
- dynamicRoutingThresholdChars: 18e3
543
+ dynamicRoutingThresholdChars: 18e3,
544
+ exploitSignalOverride: void 0,
545
+ suppressions: [],
546
+ sla: void 0,
547
+ skipUnreachable: false
498
548
  };
499
549
  function loadPolicy(cwd, explicitPath) {
500
550
  const candidate = explicitPath ?? join4(cwd, ".github", "autoremediator.yml");
@@ -527,7 +577,19 @@ function loadPolicy(cwd, explicitPath) {
527
577
  high: parsed.patchConfidenceThresholds?.high ?? DEFAULT_POLICY.patchConfidenceThresholds?.high
528
578
  },
529
579
  dynamicModelRouting: parsed.dynamicModelRouting ?? DEFAULT_POLICY.dynamicModelRouting,
530
- dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars
580
+ dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars,
581
+ exploitSignalOverride: parsed.exploitSignalOverride ? {
582
+ kev: parsed.exploitSignalOverride.kev ?? void 0,
583
+ epss: parsed.exploitSignalOverride.epss ?? void 0
584
+ } : void 0,
585
+ suppressions: Array.isArray(parsed.suppressions) ? parsed.suppressions : DEFAULT_POLICY.suppressions,
586
+ sla: parsed.sla ? {
587
+ critical: parsed.sla.critical,
588
+ high: parsed.sla.high,
589
+ medium: parsed.sla.medium,
590
+ low: parsed.sla.low
591
+ } : void 0,
592
+ skipUnreachable: parsed.skipUnreachable ?? DEFAULT_POLICY.skipUnreachable
531
593
  };
532
594
  } catch {
533
595
  return DEFAULT_POLICY;
@@ -540,9 +602,40 @@ function isPackageAllowed(policy, packageName) {
540
602
  }
541
603
  return true;
542
604
  }
605
+ function isActiveSuppression(suppression) {
606
+ if (!suppression.expiresAt) return true;
607
+ return new Date(suppression.expiresAt) > /* @__PURE__ */ new Date();
608
+ }
609
+ function loadSuppressionsFile(filePath) {
610
+ try {
611
+ const content = readFileSync2(filePath, "utf8");
612
+ const parsed = yamlParse(content);
613
+ return Array.isArray(parsed?.suppressions) ? parsed.suppressions : [];
614
+ } catch {
615
+ return [];
616
+ }
617
+ }
618
+ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
619
+ const severityKey = severity.toLowerCase();
620
+ const deadlineHours = slaPolicy[severityKey];
621
+ if (typeof deadlineHours !== "number") return null;
622
+ const publishedMs = new Date(publishedAt).getTime();
623
+ if (isNaN(publishedMs)) return null;
624
+ const deadlineMs = publishedMs + deadlineHours * 60 * 60 * 1e3;
625
+ const nowMs = Date.now();
626
+ if (nowMs <= deadlineMs) return null;
627
+ const hoursOverdue = Math.round((nowMs - deadlineMs) / (60 * 60 * 1e3));
628
+ return {
629
+ cveId,
630
+ severity,
631
+ publishedAt,
632
+ deadlineAt: new Date(deadlineMs).toISOString(),
633
+ hoursOverdue
634
+ };
635
+ }
543
636
 
544
637
  // src/remediation/tools/check-inventory.ts
545
- var checkInventoryTool = tool({
638
+ var checkInventoryTool = defineTool({
546
639
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
547
640
  parameters: z.object({
548
641
  cwd: z.string().describe("Absolute path to the consumer project's root directory"),
@@ -583,7 +676,7 @@ var checkInventoryTool = tool({
583
676
  packages.push({
584
677
  name,
585
678
  version,
586
- type: isDirect ? "direct" : "indirect"
679
+ type: isDirect ? "direct" : "transitive"
587
680
  });
588
681
  }
589
682
  if (packages.length === 0) {
@@ -864,6 +957,9 @@ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
864
957
  // src/remediation/pipeline.ts
865
958
  import { generateText as generateText2 } from "ai";
866
959
 
960
+ // src/remediation/local/run.ts
961
+ import semver5 from "semver";
962
+
867
963
  // src/platform/http-client.ts
868
964
  var DEFAULT_TIMEOUT_MS = 1e4;
869
965
  async function requestWithTimeout(url, init, timeoutMs) {
@@ -1124,9 +1220,214 @@ async function enrichWithNvd(details) {
1124
1220
  return details;
1125
1221
  }
1126
1222
 
1223
+ // src/remediation/tools/check-reachability.ts
1224
+ import { z as z2 } from "zod";
1225
+ import { readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1226
+ import { join as join7, extname } from "path";
1227
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
1228
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".git", "coverage", ".cache"]);
1229
+ var MAX_FILES = 500;
1230
+ function collectSourceFiles(dir, files = []) {
1231
+ if (files.length >= MAX_FILES) return files;
1232
+ let entries;
1233
+ try {
1234
+ entries = readdirSync(dir);
1235
+ } catch {
1236
+ return files;
1237
+ }
1238
+ for (const entry of entries) {
1239
+ if (files.length >= MAX_FILES) break;
1240
+ const full = join7(dir, entry);
1241
+ let stat2;
1242
+ try {
1243
+ stat2 = statSync(full);
1244
+ } catch {
1245
+ continue;
1246
+ }
1247
+ if (stat2.isDirectory()) {
1248
+ if (!SKIP_DIRS.has(entry)) collectSourceFiles(full, files);
1249
+ } else if (SOURCE_EXTENSIONS.has(extname(entry))) {
1250
+ files.push(full);
1251
+ }
1252
+ }
1253
+ return files;
1254
+ }
1255
+ function assessPackageReachability(cwd, packageName) {
1256
+ const files = collectSourceFiles(cwd);
1257
+ if (files.length === 0) {
1258
+ return {
1259
+ packageName,
1260
+ status: "unknown",
1261
+ reason: "No source files found to scan."
1262
+ };
1263
+ }
1264
+ const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1265
+ const pattern = new RegExp(
1266
+ `(?:import\\s[^'"]*from\\s['"]${escaped}(?:/[^'"]*)?['"]|require\\(['"]${escaped}(?:/[^'"]*)?['"]\\)|from\\s['"]${escaped}(?:/[^'"]*)?['"])`,
1267
+ "m"
1268
+ );
1269
+ const evidence = [];
1270
+ for (const filePath of files) {
1271
+ let content;
1272
+ try {
1273
+ content = readFileSync5(filePath, "utf8");
1274
+ } catch {
1275
+ continue;
1276
+ }
1277
+ if (pattern.test(content)) {
1278
+ const relative = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
1279
+ const matchType = content.includes(`require('${packageName}`) || content.includes(`require("${packageName}`) ? "require" : content.includes(`import(`) ? "dynamic-import" : "import";
1280
+ evidence.push({ filePath: relative, matchType });
1281
+ if (evidence.length >= 3) break;
1282
+ }
1283
+ }
1284
+ if (evidence.length > 0) {
1285
+ return {
1286
+ packageName,
1287
+ status: "reachable",
1288
+ reason: `Found ${evidence.length} import reference(s) in source files.`,
1289
+ reachabilityBasis: "import-present",
1290
+ evidence
1291
+ };
1292
+ }
1293
+ return {
1294
+ packageName,
1295
+ status: "not-reachable",
1296
+ reason: `No import or require of '${packageName}' found in ${files.length} source file(s).`
1297
+ };
1298
+ }
1299
+ var checkReachabilityTool = defineTool({
1300
+ description: "Assess whether an npm package is statically reachable (imported) from the project's source files. Returns reachable, not-reachable, or unknown.",
1301
+ parameters: z2.object({
1302
+ cwd: z2.string().describe("Project root directory"),
1303
+ packageName: z2.string().describe("The npm package name to search for")
1304
+ }),
1305
+ execute: async ({ cwd, packageName }) => {
1306
+ return assessPackageReachability(cwd, packageName);
1307
+ }
1308
+ });
1309
+
1310
+ // src/remediation/tools/check-exploit-signal.ts
1311
+ import { z as z3 } from "zod";
1312
+ var kevSchema = z3.object({
1313
+ knownExploited: z3.boolean(),
1314
+ dateAdded: z3.string().optional(),
1315
+ dueDate: z3.string().optional(),
1316
+ requiredAction: z3.string().optional(),
1317
+ knownRansomwareCampaignUse: z3.string().optional()
1318
+ }).passthrough();
1319
+ var epssSchema = z3.object({
1320
+ score: z3.number(),
1321
+ percentile: z3.number(),
1322
+ date: z3.string().optional()
1323
+ }).passthrough();
1324
+ var exploitSignalPolicySchema = z3.object({
1325
+ kev: z3.object({ mandatory: z3.boolean() }).optional(),
1326
+ epss: z3.object({ mandatory: z3.boolean(), threshold: z3.number() }).optional()
1327
+ }).optional();
1328
+ var checkExploitSignalTool = defineTool({
1329
+ description: "Evaluate KEV and EPSS exploit signals for a CVE against policy thresholds. Returns whether the exploit signal overrides severity filtering (mandatory gate).",
1330
+ parameters: z3.object({
1331
+ cveDetails: z3.object({
1332
+ id: z3.string(),
1333
+ kev: kevSchema.optional(),
1334
+ epss: epssSchema.optional()
1335
+ }).passthrough().describe("CveDetails object from lookup-cve"),
1336
+ policy: z3.object({
1337
+ exploitSignalOverride: exploitSignalPolicySchema
1338
+ }).passthrough().describe("Policy object containing exploitSignalOverride configuration")
1339
+ }),
1340
+ execute: async ({ cveDetails, policy }) => {
1341
+ const override = policy.exploitSignalOverride;
1342
+ if (!override) {
1343
+ return {
1344
+ exploitSignalTriggered: false,
1345
+ reason: "No exploitSignalOverride policy configured."
1346
+ };
1347
+ }
1348
+ if (override.kev?.mandatory && cveDetails.kev?.knownExploited === true) {
1349
+ return {
1350
+ exploitSignalTriggered: true,
1351
+ reason: `CVE ${cveDetails.id} is in the CISA Known Exploited Vulnerabilities (KEV) catalog and kev.mandatory is enabled.`
1352
+ };
1353
+ }
1354
+ if (override.epss?.mandatory && typeof override.epss.threshold === "number" && typeof cveDetails.epss?.score === "number" && cveDetails.epss.score >= override.epss.threshold) {
1355
+ return {
1356
+ exploitSignalTriggered: true,
1357
+ reason: `CVE ${cveDetails.id} EPSS score ${cveDetails.epss.score} meets or exceeds threshold ${override.epss.threshold} and epss.mandatory is enabled.`
1358
+ };
1359
+ }
1360
+ return {
1361
+ exploitSignalTriggered: false,
1362
+ reason: "Exploit signal thresholds not met."
1363
+ };
1364
+ }
1365
+ });
1366
+
1367
+ // src/remediation/local/secops-preflight.ts
1368
+ async function runSecOpsPreflight(normalizedId, cveDetails, opts) {
1369
+ const allSuppressions = opts.suppressionsFile ? [...opts.suppressions, ...loadSuppressionsFile(opts.suppressionsFile)] : opts.suppressions;
1370
+ const activeSuppression = allSuppressions.find(
1371
+ (s) => s.cveId === normalizedId && isActiveSuppression(s)
1372
+ );
1373
+ if (activeSuppression) {
1374
+ return {
1375
+ suppressed: true,
1376
+ summary: `CVE ${normalizedId} suppressed by VEX policy: ${activeSuppression.justification}${activeSuppression.notes ? ` \u2014 ${activeSuppression.notes}` : ""}`
1377
+ };
1378
+ }
1379
+ let exploitSignalTriggered;
1380
+ if (opts.exploitSignalOverride) {
1381
+ const result = await checkExploitSignalTool.execute({
1382
+ cveDetails,
1383
+ policy: { exploitSignalOverride: opts.exploitSignalOverride }
1384
+ });
1385
+ if (result.exploitSignalTriggered) {
1386
+ exploitSignalTriggered = true;
1387
+ }
1388
+ }
1389
+ let slaBreaches;
1390
+ if (opts.slaCheck && opts.slaPolicy && cveDetails.publishedAt) {
1391
+ const breach = checkSlaBreach(normalizedId, cveDetails.severity, cveDetails.publishedAt, opts.slaPolicy);
1392
+ if (breach) {
1393
+ slaBreaches = [breach];
1394
+ }
1395
+ }
1396
+ return { suppressed: false, exploitSignalTriggered, slaBreaches };
1397
+ }
1398
+
1399
+ // src/remediation/local/sbom.ts
1400
+ function buildSbom(packages, vulnerableNames, results) {
1401
+ const statusByPackage = /* @__PURE__ */ new Map();
1402
+ for (const result of results) {
1403
+ if (!vulnerableNames.has(result.packageName)) continue;
1404
+ if (result.suppressedBy) {
1405
+ statusByPackage.set(result.packageName, "suppressed");
1406
+ } else if (!result.applied && result.strategy === "none") {
1407
+ statusByPackage.set(result.packageName, "skipped");
1408
+ } else if (result.applied) {
1409
+ statusByPackage.set(result.packageName, "patched");
1410
+ } else {
1411
+ statusByPackage.set(result.packageName, "unpatched");
1412
+ }
1413
+ }
1414
+ return packages.map((pkg) => {
1415
+ const isVulnerable = vulnerableNames.has(pkg.name);
1416
+ const entry = {
1417
+ name: pkg.name,
1418
+ version: pkg.version,
1419
+ scope: pkg.type === "direct" ? "direct" : "transitive"
1420
+ };
1421
+ if (isVulnerable) {
1422
+ entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
1423
+ }
1424
+ return entry;
1425
+ });
1426
+ }
1427
+
1127
1428
  // src/intelligence/sources/registry.ts
1128
- import { readFileSync as readFileSync5 } from "fs";
1129
- import { join as join7 } from "path";
1429
+ import { readFileSync as readFileSync6 } from "fs";
1430
+ import { join as join8 } from "path";
1130
1431
  import { execa as execa4 } from "execa";
1131
1432
  import semver from "semver";
1132
1433
  var NPM_REGISTRY = "https://registry.npmjs.org";
@@ -1295,7 +1596,7 @@ async function queryOutdatedPackages(cwd, options = {}) {
1295
1596
  }
1296
1597
  let directDeps;
1297
1598
  try {
1298
- const pkgRaw = JSON.parse(readFileSync5(join7(cwd, "package.json"), "utf8"));
1599
+ const pkgRaw = JSON.parse(readFileSync6(join8(cwd, "package.json"), "utf8"));
1299
1600
  directDeps = /* @__PURE__ */ new Set([
1300
1601
  ...Object.keys(pkgRaw.dependencies ?? {}),
1301
1602
  ...Object.keys(pkgRaw.devDependencies ?? {})
@@ -1309,8 +1610,8 @@ async function queryOutdatedPackages(cwd, options = {}) {
1309
1610
  const wantedVersion = entry.wanted ?? currentVersion;
1310
1611
  const latestVersion = entry.latest ?? currentVersion;
1311
1612
  if (!currentVersion || !latestVersion) continue;
1312
- const dependencyScope = directDeps.has(name) ? "direct" : "indirect";
1313
- if (!options.includeTransitive && dependencyScope === "indirect") continue;
1613
+ const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
1614
+ if (!options.includeTransitive && dependencyScope === "transitive") continue;
1314
1615
  const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
1315
1616
  result.set(name, {
1316
1617
  currentVersion,
@@ -1324,24 +1625,23 @@ async function queryOutdatedPackages(cwd, options = {}) {
1324
1625
  }
1325
1626
 
1326
1627
  // src/remediation/tools/apply-version-bump.ts
1327
- import { tool as tool2 } from "ai";
1328
- import { z as z2 } from "zod";
1329
- import { join as join9 } from "path";
1330
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1628
+ import { z as z4 } from "zod";
1629
+ import { join as join10 } from "path";
1630
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
1331
1631
  import { execa as execa5 } from "execa";
1332
1632
  import semver2 from "semver";
1333
1633
 
1334
1634
  // src/platform/repo-lock.ts
1335
1635
  import { mkdir, rm } from "fs/promises";
1336
- import { join as join8 } from "path";
1636
+ import { join as join9 } from "path";
1337
1637
  async function sleep(ms) {
1338
1638
  await new Promise((resolve2) => setTimeout(resolve2, ms));
1339
1639
  }
1340
1640
  async function acquireRepoLock(cwd, options = {}) {
1341
1641
  const timeoutMs = options.timeoutMs ?? 15e3;
1342
1642
  const retryDelayMs = options.retryDelayMs ?? 125;
1343
- const lockRoot = join8(cwd, ".autoremediator", "locks");
1344
- const lockPath = join8(cwd, ".autoremediator", "locks", "remediation.lock");
1643
+ const lockRoot = join9(cwd, ".autoremediator", "locks");
1644
+ const lockPath = join9(cwd, ".autoremediator", "locks", "remediation.lock");
1345
1645
  const startedAt = Date.now();
1346
1646
  await mkdir(lockRoot, { recursive: true });
1347
1647
  while (true) {
@@ -1371,21 +1671,21 @@ async function withRepoLock(cwd, fn, options) {
1371
1671
  }
1372
1672
 
1373
1673
  // src/remediation/tools/apply-version-bump.ts
1374
- var applyVersionBumpTool = tool2({
1674
+ var applyVersionBumpTool = defineTool({
1375
1675
  description: "Update package.json to use the safe version of a vulnerable package and run the project's package manager install. In dry-run mode, only reports what would change.",
1376
- parameters: z2.object({
1377
- cwd: z2.string().describe("Absolute path to the consumer project root"),
1378
- packageManager: z2.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
1379
- packageName: z2.string().describe("The npm package to upgrade"),
1380
- fromVersion: z2.string().describe("The currently installed vulnerable version"),
1381
- toVersion: z2.string().describe("The safe target version to upgrade to"),
1382
- dryRun: z2.boolean().default(false).describe("If true, report changes but do not write"),
1383
- policy: z2.string().optional().describe("Optional path to .autoremediator policy file"),
1384
- runTests: z2.boolean().default(false).describe("If true, run test validation after applying the fix"),
1385
- installMode: z2.enum(["standard", "prefer-offline", "deterministic"]).optional(),
1386
- installPreferOffline: z2.boolean().optional(),
1387
- enforceFrozenLockfile: z2.boolean().optional(),
1388
- workspace: z2.string().optional()
1676
+ parameters: z4.object({
1677
+ cwd: z4.string().describe("Absolute path to the consumer project root"),
1678
+ packageManager: z4.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
1679
+ packageName: z4.string().describe("The npm package to upgrade"),
1680
+ fromVersion: z4.string().describe("The currently installed vulnerable version"),
1681
+ toVersion: z4.string().describe("The safe target version to upgrade to"),
1682
+ dryRun: z4.boolean().default(false).describe("If true, report changes but do not write"),
1683
+ policy: z4.string().optional().describe("Optional path to .autoremediator policy file"),
1684
+ runTests: z4.boolean().default(false).describe("If true, run test validation after applying the fix"),
1685
+ installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
1686
+ installPreferOffline: z4.boolean().optional(),
1687
+ enforceFrozenLockfile: z4.boolean().optional(),
1688
+ workspace: z4.string().optional()
1389
1689
  }),
1390
1690
  execute: async ({
1391
1691
  cwd,
@@ -1402,7 +1702,7 @@ var applyVersionBumpTool = tool2({
1402
1702
  workspace
1403
1703
  }) => {
1404
1704
  const pm = packageManager ?? detectPackageManager(cwd);
1405
- const pkgPath = join9(cwd, "package.json");
1705
+ const pkgPath = join10(cwd, "package.json");
1406
1706
  const loadedPolicy = loadPolicy(cwd, policy);
1407
1707
  const commandConstraints = {
1408
1708
  ...loadedPolicy.constraints,
@@ -1442,7 +1742,7 @@ var applyVersionBumpTool = tool2({
1442
1742
  }
1443
1743
  let pkgJson;
1444
1744
  try {
1445
- pkgJson = JSON.parse(readFileSync6(pkgPath, "utf8"));
1745
+ pkgJson = JSON.parse(readFileSync7(pkgPath, "utf8"));
1446
1746
  } catch {
1447
1747
  return {
1448
1748
  packageName,
@@ -1464,8 +1764,8 @@ var applyVersionBumpTool = tool2({
1464
1764
  fromVersion,
1465
1765
  applied: false,
1466
1766
  dryRun,
1467
- unresolvedReason: "indirect-dependency",
1468
- message: `"${packageName}" was not found in package.json dependencies (it may be a transitive dep). Cannot auto-bump.`
1767
+ unresolvedReason: "transitive-dependency",
1768
+ message: `"${packageName}" was not found in package.json dependencies (it may be a transitive dependency). Cannot auto-bump.`
1469
1769
  };
1470
1770
  }
1471
1771
  const currentRange = pkgJson[depField][packageName];
@@ -1565,10 +1865,9 @@ var applyVersionBumpTool = tool2({
1565
1865
  });
1566
1866
 
1567
1867
  // src/remediation/tools/apply-package-override/index.ts
1568
- import { tool as tool3 } from "ai";
1569
- import { z as z3 } from "zod";
1570
- import { join as join10 } from "path";
1571
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1868
+ import { z as z5 } from "zod";
1869
+ import { join as join11 } from "path";
1870
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1572
1871
  import { execa as execa7 } from "execa";
1573
1872
  import semver3 from "semver";
1574
1873
 
@@ -1648,22 +1947,22 @@ function restoreRecord(record, key, previousValue) {
1648
1947
  }
1649
1948
 
1650
1949
  // src/remediation/tools/apply-package-override/index.ts
1651
- var applyPackageOverrideTool = tool3({
1950
+ var applyPackageOverrideTool = defineTool({
1652
1951
  description: "Apply a package-manager-native package.json override for a vulnerable transitive dependency and reinstall. Uses npm overrides, pnpm.overrides, or yarn resolutions.",
1653
- parameters: z3.object({
1654
- cwd: z3.string().describe("Absolute path to the consumer project root"),
1655
- packageManager: z3.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
1656
- packageName: z3.string().describe("The npm package to override"),
1657
- selector: z3.string().optional().describe("Optional manager-native override selector key (for nested or scoped overrides)"),
1658
- fromVersion: z3.string().describe("The currently installed vulnerable version"),
1659
- toVersion: z3.string().describe("The safe target version to override to"),
1660
- dryRun: z3.boolean().default(false).describe("If true, report changes but do not write"),
1661
- policy: z3.string().optional().describe("Optional path to .autoremediator policy file"),
1662
- runTests: z3.boolean().default(false).describe("If true, run test validation after applying the override"),
1663
- installMode: z3.enum(["standard", "prefer-offline", "deterministic"]).optional(),
1664
- installPreferOffline: z3.boolean().optional(),
1665
- enforceFrozenLockfile: z3.boolean().optional(),
1666
- workspace: z3.string().optional()
1952
+ parameters: z5.object({
1953
+ cwd: z5.string().describe("Absolute path to the consumer project root"),
1954
+ packageManager: z5.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
1955
+ packageName: z5.string().describe("The npm package to override"),
1956
+ selector: z5.string().optional().describe("Optional manager-native override selector key (for nested or scoped overrides)"),
1957
+ fromVersion: z5.string().describe("The currently installed vulnerable version"),
1958
+ toVersion: z5.string().describe("The safe target version to override to"),
1959
+ dryRun: z5.boolean().default(false).describe("If true, report changes but do not write"),
1960
+ policy: z5.string().optional().describe("Optional path to .autoremediator policy file"),
1961
+ runTests: z5.boolean().default(false).describe("If true, run test validation after applying the override"),
1962
+ installMode: z5.enum(["standard", "prefer-offline", "deterministic"]).optional(),
1963
+ installPreferOffline: z5.boolean().optional(),
1964
+ enforceFrozenLockfile: z5.boolean().optional(),
1965
+ workspace: z5.string().optional()
1667
1966
  }),
1668
1967
  execute: async ({
1669
1968
  cwd,
@@ -1681,7 +1980,7 @@ var applyPackageOverrideTool = tool3({
1681
1980
  workspace
1682
1981
  }) => {
1683
1982
  const pm = packageManager ?? detectPackageManager(cwd);
1684
- const pkgPath = join10(cwd, "package.json");
1983
+ const pkgPath = join11(cwd, "package.json");
1685
1984
  const loadedPolicy = loadPolicy(cwd, policy);
1686
1985
  const commandConstraints = {
1687
1986
  ...loadedPolicy.constraints,
@@ -1722,7 +2021,7 @@ var applyPackageOverrideTool = tool3({
1722
2021
  }
1723
2022
  let pkgJson;
1724
2023
  try {
1725
- pkgJson = JSON.parse(readFileSync7(pkgPath, "utf8"));
2024
+ pkgJson = JSON.parse(readFileSync8(pkgPath, "utf8"));
1726
2025
  } catch {
1727
2026
  return {
1728
2027
  packageName,
@@ -1822,7 +2121,7 @@ async function resolvePrimaryResult(params) {
1822
2121
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
1823
2122
  const pkg = vulnerable.installed;
1824
2123
  const firstPatchedVersion = vulnerable.affected.firstPatchedVersion;
1825
- if (pkg.type === "indirect") {
2124
+ if (pkg.type === "transitive") {
1826
2125
  if (constraints.directDependenciesOnly) {
1827
2126
  return {
1828
2127
  steps: 0,
@@ -1833,7 +2132,7 @@ async function resolvePrimaryResult(params) {
1833
2132
  applied: false,
1834
2133
  dryRun,
1835
2134
  unresolvedReason: "constraint-blocked",
1836
- message: `Constraint blocked remediation for indirect dependency "${pkg.name}".`
2135
+ message: `Constraint blocked remediation for transitive dependency "${pkg.name}".`
1837
2136
  }
1838
2137
  };
1839
2138
  }
@@ -1959,17 +2258,16 @@ async function resolvePrimaryResult(params) {
1959
2258
  }
1960
2259
 
1961
2260
  // src/remediation/tools/fetch-package-source.ts
1962
- import { tool as tool4 } from "ai";
1963
- import { z as z4 } from "zod";
2261
+ import { z as z6 } from "zod";
1964
2262
  import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
1965
- import { join as join11 } from "path";
2263
+ import { join as join12 } from "path";
1966
2264
  import { execa as execa8 } from "execa";
1967
- var fetchPackageSourceTool = tool4({
2265
+ var fetchPackageSourceTool = defineTool({
1968
2266
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
1969
- parameters: z4.object({
1970
- packageName: z4.string().min(1).describe("The npm package name (e.g., 'lodash', '@scope/package')"),
1971
- version: z4.string().regex(/^\d+\.\d+\.\d+/, "Must be a valid semver version").describe("Exact package version to download"),
1972
- filePatterns: z4.array(z4.string()).optional().default(["*.js", "*.ts"]).describe(
2267
+ parameters: z6.object({
2268
+ packageName: z6.string().min(1).describe("The npm package name (e.g., 'lodash', '@scope/package')"),
2269
+ version: z6.string().regex(/^\d+\.\d+\.\d+/, "Must be a valid semver version").describe("Exact package version to download"),
2270
+ filePatterns: z6.array(z6.string()).optional().default(["*.js", "*.ts"]).describe(
1973
2271
  "File patterns to extract (glob patterns, default: *.js, *.ts)"
1974
2272
  )
1975
2273
  }),
@@ -1979,23 +2277,23 @@ var fetchPackageSourceTool = tool4({
1979
2277
  filePatterns
1980
2278
  }) => {
1981
2279
  const tempBaseDir = `/tmp/autoremediator-pkg-${Date.now()}`;
1982
- const extractDir = join11(tempBaseDir, "out");
2280
+ const extractDir = join12(tempBaseDir, "out");
1983
2281
  try {
1984
2282
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.split("/").pop()}-${version}.tgz`;
1985
2283
  await mkdir2(tempBaseDir, { recursive: true });
1986
- const tarballPath = join11(tempBaseDir, "package.tgz");
2284
+ const tarballPath = join12(tempBaseDir, "package.tgz");
1987
2285
  await execa8("curl", ["-L", "-o", tarballPath, npmUrl]);
1988
2286
  await mkdir2(extractDir, { recursive: true });
1989
2287
  await execa8("tar", ["-xzf", tarballPath, "-C", extractDir]);
1990
2288
  const extractedContents = await readdir2(extractDir);
1991
- const packageRootDir = extractedContents.includes("package") ? join11(extractDir, "package") : extractDir;
2289
+ const packageRootDir = extractedContents.includes("package") ? join12(extractDir, "package") : extractDir;
1992
2290
  const sourceCode = {};
1993
2291
  async function walkDir(dir, relativeBase) {
1994
2292
  try {
1995
2293
  const files = await readdir2(dir, { withFileTypes: true });
1996
2294
  for (const file of files) {
1997
- const fullPath = join11(dir, file.name);
1998
- const relPath = join11(relativeBase, file.name);
2295
+ const fullPath = join12(dir, file.name);
2296
+ const relPath = join12(relativeBase, file.name);
1999
2297
  if (file.isDirectory()) {
2000
2298
  if (![
2001
2299
  "node_modules",
@@ -2058,8 +2356,7 @@ var fetchPackageSourceTool = tool4({
2058
2356
  });
2059
2357
 
2060
2358
  // src/remediation/tools/generate-patch/index.ts
2061
- import { tool as tool5 } from "ai";
2062
- import { z as z5 } from "zod";
2359
+ import { z as z7 } from "zod";
2063
2360
  import { generateText } from "ai";
2064
2361
 
2065
2362
  // src/remediation/strategies/patch-synthesis-prompt.ts
@@ -2192,31 +2489,31 @@ function generateUnifiedDiff(original, fixed, filePath) {
2192
2489
  }
2193
2490
 
2194
2491
  // src/remediation/tools/generate-patch/index.ts
2195
- var generatePatchTool = tool5({
2492
+ var generatePatchTool = defineTool({
2196
2493
  description: "Generate a unified diff patch for a CVE using LLM analysis of vulnerable source code.",
2197
- parameters: z5.object({
2198
- packageName: z5.string().min(1).describe("The npm package name"),
2199
- vulnerableVersion: z5.string().describe("The vulnerable version string"),
2200
- cveId: z5.string().regex(/^CVE-\d{4}-\d+$/i).describe("CVE ID (e.g., CVE-2021-23337)"),
2201
- cveSummary: z5.string().min(10).describe("CVE description and impact"),
2202
- sourceFiles: z5.record(z5.string()).describe(
2494
+ parameters: z7.object({
2495
+ packageName: z7.string().min(1).describe("The npm package name"),
2496
+ vulnerableVersion: z7.string().describe("The vulnerable version string"),
2497
+ cveId: z7.string().regex(/^CVE-\d{4}-\d+$/i).describe("CVE ID (e.g., CVE-2021-23337)"),
2498
+ cveSummary: z7.string().min(10).describe("CVE description and impact"),
2499
+ sourceFiles: z7.record(z7.string(), z7.string()).describe(
2203
2500
  "Map of file paths to source code contents from fetch-package-source"
2204
2501
  ),
2205
- vulnerabilityCategory: z5.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
2206
- dryRun: z5.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
2207
- llmProvider: z5.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
2208
- model: z5.string().optional().describe("Optional model override for patch generation"),
2209
- policy: z5.string().optional().describe("Optional policy file path for model default resolution"),
2210
- cwd: z5.string().optional().describe("Optional working directory for policy/model resolution"),
2211
- providerSafetyProfile: z5.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
2212
- patchConfidenceThresholds: z5.object({
2213
- low: z5.number().min(0).max(1).optional(),
2214
- medium: z5.number().min(0).max(1).optional(),
2215
- high: z5.number().min(0).max(1).optional()
2502
+ vulnerabilityCategory: z7.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
2503
+ dryRun: z7.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
2504
+ llmProvider: z7.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
2505
+ model: z7.string().optional().describe("Optional model override for patch generation"),
2506
+ policy: z7.string().optional().describe("Optional policy file path for model default resolution"),
2507
+ cwd: z7.string().optional().describe("Optional working directory for policy/model resolution"),
2508
+ providerSafetyProfile: z7.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
2509
+ patchConfidenceThresholds: z7.object({
2510
+ low: z7.number().min(0).max(1).optional(),
2511
+ medium: z7.number().min(0).max(1).optional(),
2512
+ high: z7.number().min(0).max(1).optional()
2216
2513
  }).optional().describe("Optional per-risk confidence thresholds for patch acceptance"),
2217
- dynamicModelRouting: z5.boolean().optional().describe("Enable dynamic model routing by input size"),
2218
- dynamicRoutingThresholdChars: z5.number().int().positive().optional().describe("Threshold for dynamic model routing"),
2219
- modelPersonality: z5.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
2514
+ dynamicModelRouting: z7.boolean().optional().describe("Enable dynamic model routing by input size"),
2515
+ dynamicRoutingThresholdChars: z7.number().int().positive().optional().describe("Threshold for dynamic model routing"),
2516
+ modelPersonality: z7.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
2220
2517
  }),
2221
2518
  execute: async ({
2222
2519
  packageName,
@@ -2261,7 +2558,7 @@ var generatePatchTool = tool5({
2261
2558
  }
2262
2559
  const inputChars = JSON.stringify(resolvedSourceFiles).length + cveSummary.length;
2263
2560
  const modelInstance = await createModel(effectiveOptions, { inputChars });
2264
- const modelName = modelInstance.modelId || "unknown-model";
2561
+ const modelName = typeof modelInstance === "string" ? modelInstance : "modelId" in modelInstance && typeof modelInstance.modelId === "string" ? modelInstance.modelId : "unknown-model";
2265
2562
  const prompt = buildPatchPrompt({
2266
2563
  cveId,
2267
2564
  packageName,
@@ -2366,17 +2663,17 @@ var generatePatchTool = tool5({
2366
2663
  });
2367
2664
 
2368
2665
  // src/remediation/tools/apply-patch-file/index.ts
2369
- import { tool as tool6 } from "ai";
2370
- import { z as z6 } from "zod";
2666
+ import { z as z8 } from "zod";
2371
2667
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
2372
- import { join as join13 } from "path";
2668
+ import { join as join14 } from "path";
2373
2669
  import { execa as execa10 } from "execa";
2374
2670
 
2375
2671
  // src/remediation/tools/apply-patch-file/helpers.ts
2376
2672
  import { existsSync as existsSync7 } from "fs";
2377
2673
  import { mkdtemp, readFile as readFile4, rm as rm3, writeFile } from "fs/promises";
2674
+ import { createHash } from "crypto";
2378
2675
  import { tmpdir } from "os";
2379
- import { join as join12 } from "path";
2676
+ import { join as join13 } from "path";
2380
2677
  import { execa as execa9 } from "execa";
2381
2678
  async function resolvePatchMode(packageManager, cwd) {
2382
2679
  if (packageManager === "npm") return "patch-package";
@@ -2403,11 +2700,15 @@ function extractPatchedFiles(patchContent) {
2403
2700
  function countPatchHunks(patchContent) {
2404
2701
  return patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
2405
2702
  }
2703
+ function computePatchIntegrity(patchContent) {
2704
+ const hex = createHash("sha256").update(patchContent, "utf8").digest("hex");
2705
+ return `sha256:${hex}`;
2706
+ }
2406
2707
  async function writePatchManifest(manifestFilePath, artifact) {
2407
2708
  await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
2408
2709
  }
2409
2710
  async function configurePatchPackagePostinstall(cwd, packageManager) {
2410
- const pkgJsonPath = join12(cwd, "package.json");
2711
+ const pkgJsonPath = join13(cwd, "package.json");
2411
2712
  let pkgJson;
2412
2713
  try {
2413
2714
  pkgJson = JSON.parse(await readFile4(pkgJsonPath, "utf8"));
@@ -2447,7 +2748,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
2447
2748
  return { success: true };
2448
2749
  }
2449
2750
  async function capturePackageJsonSnapshot(cwd) {
2450
- const path = join12(cwd, "package.json");
2751
+ const path = join13(cwd, "package.json");
2451
2752
  try {
2452
2753
  const content = await readFile4(path, "utf8");
2453
2754
  return { path, content };
@@ -2517,8 +2818,8 @@ ${createResult.stderr}`);
2517
2818
  error: `Could not determine native patch directory for ${packageSpec}.`
2518
2819
  };
2519
2820
  }
2520
- const tempPatchDir = await mkdtemp(join12(tmpdir(), "autoremediator-native-patch-"));
2521
- const tempPatchFile = join12(tempPatchDir, "change.patch");
2821
+ const tempPatchDir = await mkdtemp(join13(tmpdir(), "autoremediator-native-patch-"));
2822
+ const tempPatchFile = join13(tempPatchDir, "change.patch");
2522
2823
  try {
2523
2824
  await writeFile(tempPatchFile, patchContent, "utf8");
2524
2825
  await execa9("patch", ["-p1", "-i", tempPatchFile], {
@@ -2603,31 +2904,31 @@ function extractFailedTests(output) {
2603
2904
  }
2604
2905
 
2605
2906
  // src/remediation/tools/apply-patch-file/index.ts
2606
- var applyPatchFileTool = tool6({
2907
+ var applyPatchFileTool = defineTool({
2607
2908
  description: "Write generated patch file and apply it using package-manager-native patch flow when available, falling back to patch-package when needed.",
2608
- parameters: z6.object({
2609
- packageName: z6.string().min(1).describe("The npm package name"),
2610
- vulnerableVersion: z6.string().describe("The vulnerable version string"),
2611
- patchContent: z6.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
2612
- cveId: z6.string().optional().describe("Optional CVE ID associated with this patch artifact"),
2613
- confidence: z6.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
2614
- riskLevel: z6.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
2615
- patches: z6.array(
2616
- z6.object({
2617
- filePath: z6.string().min(1),
2618
- unifiedDiff: z6.string().min(10)
2909
+ parameters: z8.object({
2910
+ packageName: z8.string().min(1).describe("The npm package name"),
2911
+ vulnerableVersion: z8.string().describe("The vulnerable version string"),
2912
+ patchContent: z8.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
2913
+ cveId: z8.string().optional().describe("Optional CVE ID associated with this patch artifact"),
2914
+ confidence: z8.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
2915
+ riskLevel: z8.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
2916
+ patches: z8.array(
2917
+ z8.object({
2918
+ filePath: z8.string().min(1),
2919
+ unifiedDiff: z8.string().min(10)
2619
2920
  })
2620
2921
  ).optional().describe("Patch list from generate-patch; first patch is applied"),
2621
- patchesDir: z6.string().optional().default("./patches").describe("Directory to store patch files"),
2622
- cwd: z6.string().describe("Project root directory (for package.json)"),
2623
- packageManager: z6.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
2624
- policy: z6.string().optional().describe("Optional path to .autoremediator policy file"),
2625
- installMode: z6.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2626
- installPreferOffline: z6.boolean().optional(),
2627
- enforceFrozenLockfile: z6.boolean().optional(),
2628
- workspace: z6.string().optional(),
2629
- validateWithTests: z6.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
2630
- dryRun: z6.boolean().optional().default(false).describe("If true, report but do not mutate files")
2922
+ patchesDir: z8.string().optional().default("./patches").describe("Directory to store patch files"),
2923
+ cwd: z8.string().describe("Project root directory (for package.json)"),
2924
+ packageManager: z8.enum(["npm", "pnpm", "yarn"]).optional().describe("Package manager used by the target project (auto-detected if omitted)"),
2925
+ policy: z8.string().optional().describe("Optional path to .autoremediator policy file"),
2926
+ installMode: z8.enum(["standard", "prefer-offline", "deterministic"]).optional(),
2927
+ installPreferOffline: z8.boolean().optional(),
2928
+ enforceFrozenLockfile: z8.boolean().optional(),
2929
+ workspace: z8.string().optional(),
2930
+ validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
2931
+ dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
2631
2932
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
2632
2933
  message: "Either patchContent or patches must be provided"
2633
2934
  }),
@@ -2698,7 +2999,7 @@ var applyPatchFileTool = tool6({
2698
2999
  };
2699
3000
  }
2700
3001
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
2701
- const patchFilePath = join13(cwd, patchesDir, patchFileName);
3002
+ const patchFilePath = join14(cwd, patchesDir, patchFileName);
2702
3003
  const manifestFilePath = `${patchFilePath}.json`;
2703
3004
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
2704
3005
  const baseArtifact = {
@@ -2717,6 +3018,7 @@ var applyPatchFileTool = tool6({
2717
3018
  hunkCount,
2718
3019
  applied: false,
2719
3020
  dryRun,
3021
+ integrity: computePatchIntegrity(selectedPatch),
2720
3022
  validationPhases
2721
3023
  };
2722
3024
  if (dryRun) {
@@ -2736,7 +3038,7 @@ var applyPatchFileTool = tool6({
2736
3038
  }
2737
3039
  return withRepoLock(cwd, async () => {
2738
3040
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
2739
- const patchesDirPath = join13(cwd, patchesDir);
3041
+ const patchesDirPath = join14(cwd, patchesDir);
2740
3042
  await mkdir3(patchesDirPath, { recursive: true });
2741
3043
  await writeFile2(patchFilePath, selectedPatch, "utf8");
2742
3044
  validationPhases.push({
@@ -2948,7 +3250,7 @@ function resolvePatchProvider(provider) {
2948
3250
  function shouldAttemptPatchFallback(result, preferVersionBump) {
2949
3251
  if (preferVersionBump) return false;
2950
3252
  if (result.applied || result.dryRun) return false;
2951
- return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "indirect-dependency";
3253
+ return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "transitive-dependency";
2952
3254
  }
2953
3255
  async function tryLocalPatchFallback(params) {
2954
3256
  const usage = [];
@@ -3171,7 +3473,14 @@ function resolveLocalRunOptions(options) {
3171
3473
  ...options.patchConfidenceThresholds
3172
3474
  },
3173
3475
  dynamicModelRouting: options.dynamicModelRouting ?? loadedPolicy.dynamicModelRouting ?? false,
3174
- dynamicRoutingThresholdChars: options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars
3476
+ dynamicRoutingThresholdChars: options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars,
3477
+ exploitSignalOverride: options.exploitSignalOverride ?? loadedPolicy.exploitSignalOverride,
3478
+ suppressions: loadedPolicy.suppressions ?? [],
3479
+ suppressionsFile: options.suppressionsFile,
3480
+ slaCheck: options.slaCheck ?? false,
3481
+ slaPolicy: loadedPolicy.sla,
3482
+ skipUnreachable: options.skipUnreachable ?? loadedPolicy.skipUnreachable ?? false,
3483
+ regressionCheck: options.regressionCheck ?? false
3175
3484
  };
3176
3485
  }
3177
3486
 
@@ -3228,7 +3537,14 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3228
3537
  consensusModel,
3229
3538
  patchConfidenceThresholds,
3230
3539
  dynamicModelRouting,
3231
- dynamicRoutingThresholdChars
3540
+ dynamicRoutingThresholdChars,
3541
+ exploitSignalOverride,
3542
+ suppressions,
3543
+ suppressionsFile,
3544
+ slaCheck,
3545
+ slaPolicy,
3546
+ skipUnreachable,
3547
+ regressionCheck
3232
3548
  } = resolved;
3233
3549
  const collectedResults = [];
3234
3550
  const llmUsage = [];
@@ -3267,6 +3583,29 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3267
3583
  cveDetails = mergeGhDataIntoCveDetails(cveDetails, ghPackages);
3268
3584
  }
3269
3585
  cveDetails = await enrichWithNvd(cveDetails);
3586
+ const preflight = await runSecOpsPreflight(normalizedId, cveDetails, {
3587
+ suppressions,
3588
+ suppressionsFile,
3589
+ exploitSignalOverride,
3590
+ slaCheck,
3591
+ slaPolicy
3592
+ });
3593
+ if (preflight.suppressed) {
3594
+ return {
3595
+ cveId,
3596
+ cveDetails,
3597
+ vulnerablePackages: [],
3598
+ results: [],
3599
+ agentSteps,
3600
+ summary: preflight.summary,
3601
+ correlation: {
3602
+ requestId: options.requestId,
3603
+ sessionId: options.sessionId,
3604
+ parentRunId: options.parentRunId
3605
+ }
3606
+ };
3607
+ }
3608
+ const { exploitSignalTriggered, slaBreaches } = preflight;
3270
3609
  if (cveDetails.affectedPackages.length === 0) {
3271
3610
  return {
3272
3611
  cveId,
@@ -3275,6 +3614,8 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3275
3614
  results: collectedResults,
3276
3615
  agentSteps,
3277
3616
  summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}.`,
3617
+ exploitSignalTriggered,
3618
+ slaBreaches,
3278
3619
  correlation: {
3279
3620
  requestId: options.requestId,
3280
3621
  sessionId: options.sessionId,
@@ -3297,6 +3638,8 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3297
3638
  results: collectedResults,
3298
3639
  agentSteps,
3299
3640
  summary: `Local mode failed at check-inventory: ${inventory.error}`,
3641
+ exploitSignalTriggered,
3642
+ slaBreaches,
3300
3643
  correlation: {
3301
3644
  requestId: options.requestId,
3302
3645
  sessionId: options.sessionId,
@@ -3308,6 +3651,21 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3308
3651
  vulnerablePackages = findVulnerablePackages(cveDetails, installedPackages);
3309
3652
  agentSteps += 1;
3310
3653
  for (const vulnerable of vulnerablePackages) {
3654
+ if (skipUnreachable) {
3655
+ const reach = assessPackageReachability(cwd, vulnerable.installed.name);
3656
+ if (reach.status === "not-reachable") {
3657
+ collectedResults.push({
3658
+ packageName: vulnerable.installed.name,
3659
+ fromVersion: vulnerable.installed.version,
3660
+ strategy: "none",
3661
+ applied: false,
3662
+ dryRun,
3663
+ message: `Skipped: '${vulnerable.installed.name}' is not reachable from source code.`,
3664
+ reachability: reach
3665
+ });
3666
+ continue;
3667
+ }
3668
+ }
3311
3669
  const primary = await resolvePrimaryResult({
3312
3670
  vulnerable,
3313
3671
  cwd,
@@ -3353,11 +3711,22 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3353
3711
  }
3354
3712
  continue;
3355
3713
  }
3356
- collectedResults.push({
3714
+ const primaryResult = {
3357
3715
  ...primary.result,
3358
3716
  dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
3359
- });
3717
+ };
3718
+ if (regressionCheck && primaryResult.applied && !dryRun && primaryResult.toVersion) {
3719
+ try {
3720
+ if (semver5.satisfies(primaryResult.toVersion, vulnerable.affected.vulnerableRange, { includePrerelease: false })) {
3721
+ primaryResult.regressionDetected = true;
3722
+ }
3723
+ } catch {
3724
+ }
3725
+ }
3726
+ collectedResults.push(primaryResult);
3360
3727
  }
3728
+ const vulnerableNames = new Set(vulnerablePackages.map((v) => v.installed.name));
3729
+ const sbom = buildSbom(installedPackages, vulnerableNames, collectedResults);
3361
3730
  return {
3362
3731
  cveId,
3363
3732
  cveDetails,
@@ -3366,6 +3735,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
3366
3735
  agentSteps,
3367
3736
  summary: buildLocalSummary(vulnerablePackages, collectedResults),
3368
3737
  llmUsage: llmUsage.length > 0 ? llmUsage : void 0,
3738
+ exploitSignalTriggered,
3739
+ slaBreaches,
3740
+ sbom,
3369
3741
  correlation: {
3370
3742
  requestId: options.requestId,
3371
3743
  sessionId: options.sessionId,
@@ -3440,8 +3812,8 @@ function accumulateStepResults(params) {
3440
3812
  }
3441
3813
 
3442
3814
  // src/remediation/orchestration-prompt.ts
3443
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
3444
- import { join as join14 } from "path";
3815
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
3816
+ import { join as join15 } from "path";
3445
3817
  function buildProviderAddendum(provider, personality = "balanced") {
3446
3818
  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.";
3447
3819
  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.";
@@ -3452,7 +3824,7 @@ Provider profile:
3452
3824
  - ${personalityDirective}`;
3453
3825
  }
3454
3826
  function loadOrchestrationPrompt(ctx) {
3455
- const promptPath = join14(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
3827
+ const promptPath = join15(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
3456
3828
  if (!existsSync8(promptPath)) {
3457
3829
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
3458
3830
  Working directory: ${ctx.cwd}
@@ -3479,7 +3851,7 @@ Fallback sequence (when neither version bump nor override can be applied):
3479
3851
 
3480
3852
  Always respect dryRun and policy constraints.`;
3481
3853
  }
3482
- const template = readFileSync8(promptPath, "utf8");
3854
+ const template = readFileSync9(promptPath, "utf8");
3483
3855
  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("{{directDependenciesOnly}}", String(ctx.constraints.directDependenciesOnly ?? false)).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
3484
3856
  }
3485
3857
 
@@ -3498,8 +3870,7 @@ function createProgressEmitter(options) {
3498
3870
  }
3499
3871
 
3500
3872
  // src/remediation/tools/lookup-cve.ts
3501
- import { tool as tool7 } from "ai";
3502
- import { z as z7 } from "zod";
3873
+ import { z as z9 } from "zod";
3503
3874
 
3504
3875
  // src/intelligence/sources/cisa-kev.ts
3505
3876
  var CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
@@ -3777,10 +4148,10 @@ async function enrichWithExternalFeeds(details) {
3777
4148
  }
3778
4149
 
3779
4150
  // src/remediation/tools/lookup-cve.ts
3780
- var lookupCveTool = tool7({
4151
+ var lookupCveTool = defineTool({
3781
4152
  description: "Look up a CVE ID and return the list of affected npm packages, their vulnerable version ranges, and the first patched version. Always call this first.",
3782
- parameters: z7.object({
3783
- cveId: z7.string().regex(/^CVE-\d{4}-\d+$/i, "Must be a valid CVE ID like CVE-2021-23337")
4153
+ parameters: z9.object({
4154
+ cveId: z9.string().regex(/^CVE-\d{4}-\d+$/i, "Must be a valid CVE ID like CVE-2021-23337")
3784
4155
  }),
3785
4156
  execute: async ({ cveId }) => {
3786
4157
  const normalizedId = cveId.toUpperCase();
@@ -3846,26 +4217,25 @@ var lookupCveTool = tool7({
3846
4217
  });
3847
4218
 
3848
4219
  // src/remediation/tools/check-version-match.ts
3849
- import { tool as tool8 } from "ai";
3850
- import { z as z8 } from "zod";
3851
- import semver5 from "semver";
3852
- var affectedPackageSchema = z8.object({
3853
- name: z8.string(),
3854
- ecosystem: z8.literal("npm"),
3855
- vulnerableRange: z8.string(),
3856
- firstPatchedVersion: z8.string().optional(),
3857
- source: z8.enum(["osv", "github-advisory"])
4220
+ import { z as z10 } from "zod";
4221
+ import semver6 from "semver";
4222
+ var affectedPackageSchema = z10.object({
4223
+ name: z10.string(),
4224
+ ecosystem: z10.literal("npm"),
4225
+ vulnerableRange: z10.string(),
4226
+ firstPatchedVersion: z10.string().optional(),
4227
+ source: z10.enum(["osv", "github-advisory"])
3858
4228
  });
3859
- var inventoryPackageSchema = z8.object({
3860
- name: z8.string(),
3861
- version: z8.string(),
3862
- type: z8.enum(["direct", "indirect"])
4229
+ var inventoryPackageSchema = z10.object({
4230
+ name: z10.string(),
4231
+ version: z10.string(),
4232
+ type: z10.enum(["direct", "transitive"])
3863
4233
  });
3864
- var checkVersionMatchTool = tool8({
4234
+ var checkVersionMatchTool = defineTool({
3865
4235
  description: "Check which of the project's installed packages fall within the CVE's vulnerable version ranges. Returns only the packages that are actually vulnerable.",
3866
- parameters: z8.object({
3867
- installedPackages: z8.array(inventoryPackageSchema).describe("Output from the check-inventory tool"),
3868
- affectedPackages: z8.array(affectedPackageSchema).describe("affectedPackages array from the lookup-cve tool result")
4236
+ parameters: z10.object({
4237
+ installedPackages: z10.array(inventoryPackageSchema).describe("Output from the check-inventory tool"),
4238
+ affectedPackages: z10.array(affectedPackageSchema).describe("affectedPackages array from the lookup-cve tool result")
3869
4239
  }),
3870
4240
  execute: async ({ installedPackages, affectedPackages }) => {
3871
4241
  const vulnerable = [];
@@ -3874,10 +4244,10 @@ var checkVersionMatchTool = tool8({
3874
4244
  (p) => p.name === affected.name
3875
4245
  );
3876
4246
  for (const installed of matches) {
3877
- if (!semver5.valid(installed.version)) continue;
4247
+ if (!semver6.valid(installed.version)) continue;
3878
4248
  let isVulnerable = false;
3879
4249
  try {
3880
- isVulnerable = semver5.satisfies(installed.version, affected.vulnerableRange, {
4250
+ isVulnerable = semver6.satisfies(installed.version, affected.vulnerableRange, {
3881
4251
  includePrerelease: false
3882
4252
  });
3883
4253
  } catch {
@@ -3896,17 +4266,16 @@ var checkVersionMatchTool = tool8({
3896
4266
  });
3897
4267
 
3898
4268
  // src/remediation/tools/find-fixed-version.ts
3899
- import { tool as tool9 } from "ai";
3900
- import { z as z9 } from "zod";
3901
- var findFixedVersionTool = tool9({
4269
+ import { z as z11 } from "zod";
4270
+ var findFixedVersionTool = defineTool({
3902
4271
  description: "Query the npm registry to find the safest published upgrade version for a package that is >= the first patched version. Prefer patch upgrades first, then minor, and only fall back to major when no same-major fix exists.",
3903
- parameters: z9.object({
3904
- packageName: z9.string().describe("The npm package name"),
3905
- installedVersion: z9.string().describe("The currently installed version (exact semver)"),
3906
- firstPatchedVersion: z9.string().describe(
4272
+ parameters: z11.object({
4273
+ packageName: z11.string().describe("The npm package name"),
4274
+ installedVersion: z11.string().describe("The currently installed version (exact semver)"),
4275
+ firstPatchedVersion: z11.string().describe(
3907
4276
  "The first version that is NOT vulnerable (from lookup-cve). Use this as the floor."
3908
4277
  ),
3909
- vulnerableRange: z9.string().optional().describe("Optional vulnerable semver range used to exclude still-vulnerable versions")
4278
+ vulnerableRange: z11.string().optional().describe("Optional vulnerable semver range used to exclude still-vulnerable versions")
3910
4279
  }),
3911
4280
  execute: async ({
3912
4281
  packageName,
@@ -3943,6 +4312,35 @@ var findFixedVersionTool = tool9({
3943
4312
  }
3944
4313
  });
3945
4314
 
4315
+ // src/remediation/tools/check-suppression.ts
4316
+ import { z as z12 } from "zod";
4317
+ var suppressionSchema = z12.object({
4318
+ cveId: z12.string(),
4319
+ justification: z12.enum(["not_affected", "fixed", "mitigated", "under_investigation"]),
4320
+ notes: z12.string().optional(),
4321
+ expiresAt: z12.string().optional()
4322
+ });
4323
+ var checkSuppressionTool = defineTool({
4324
+ description: "Check whether a CVE is suppressed by an active VEX suppression entry in the policy. Returns suppressed=true and the justification if an active match is found. Call this after check-version-match.",
4325
+ parameters: z12.object({
4326
+ cveId: z12.string().describe("The CVE ID to look up in suppressions"),
4327
+ suppressions: z12.array(suppressionSchema).describe("The suppressions array from the loaded policy")
4328
+ }),
4329
+ execute: async ({ cveId, suppressions }) => {
4330
+ const match = suppressions.find(
4331
+ (s) => s.cveId === cveId && isActiveSuppression(s)
4332
+ );
4333
+ if (!match) {
4334
+ return { suppressed: false };
4335
+ }
4336
+ return {
4337
+ suppressed: true,
4338
+ justification: match.justification,
4339
+ notes: match.notes
4340
+ };
4341
+ }
4342
+ });
4343
+
3946
4344
  // src/remediation/runtime-tools.ts
3947
4345
  function buildRuntimeTools(ctx) {
3948
4346
  const tools = {
@@ -3950,7 +4348,10 @@ function buildRuntimeTools(ctx) {
3950
4348
  "check-inventory": ctx.checkInventoryToolForRun,
3951
4349
  "check-version-match": checkVersionMatchTool,
3952
4350
  "find-fixed-version": findFixedVersionTool,
3953
- "apply-version-bump": ctx.applyVersionBumpToolForRun
4351
+ "apply-version-bump": ctx.applyVersionBumpToolForRun,
4352
+ "check-suppression": checkSuppressionTool,
4353
+ "check-exploit-signal": checkExploitSignalTool,
4354
+ "check-reachability": checkReachabilityTool
3954
4355
  };
3955
4356
  if (!ctx.constraints.directDependenciesOnly && !ctx.constraints.preferVersionBump) {
3956
4357
  tools["apply-package-override"] = ctx.applyPackageOverrideToolForRun;
@@ -4034,7 +4435,7 @@ async function createPipelineRuntime(cveId, options) {
4034
4435
  const constraints = options.constraints ?? {};
4035
4436
  const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
4036
4437
  const model = await createModel(options, { inputChars: prompt.length });
4037
- const modelName = model.modelId ?? "unknown-model";
4438
+ const modelName = typeof model === "string" ? model : "modelId" in model && typeof model.modelId === "string" ? model.modelId : "unknown-model";
4038
4439
  const systemPrompt = loadOrchestrationPrompt({
4039
4440
  cveId,
4040
4441
  cwd,
@@ -4086,7 +4487,7 @@ async function runRemediationPipeline(cveId, options = {}) {
4086
4487
  system: systemPrompt,
4087
4488
  prompt,
4088
4489
  tools,
4089
- maxSteps: 25,
4490
+ stopWhen: ({ steps }) => steps.length >= 25,
4090
4491
  onStepFinish(stepResult) {
4091
4492
  agentSteps += 1;
4092
4493
  const toolResults = stepResult.toolResults ?? [];
@@ -4141,6 +4542,188 @@ async function runRemediationPipeline(cveId, options = {}) {
4141
4542
  };
4142
4543
  }
4143
4544
 
4545
+ // src/api/change-request/index.ts
4546
+ import { execa as execa11 } from "execa";
4547
+ function sanitizeBranchToken(value) {
4548
+ return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
4549
+ }
4550
+ function buildPlan(reports) {
4551
+ const cveIds = Array.from(new Set(reports.map((report) => report.cveId)));
4552
+ const packageNames = Array.from(
4553
+ new Set(
4554
+ reports.flatMap((report) => report.results.map((result) => result.packageName))
4555
+ )
4556
+ );
4557
+ const titleSuffix = cveIds.length === 1 ? cveIds[0] : `${cveIds.length} CVEs`;
4558
+ const title = `security remediation: ${titleSuffix}`;
4559
+ const body = [
4560
+ "Automated remediation generated by autoremediator.",
4561
+ "",
4562
+ `CVEs: ${cveIds.join(", ")}`,
4563
+ `Packages: ${packageNames.join(", ")}`
4564
+ ].join("\n");
4565
+ return {
4566
+ cveIds,
4567
+ packageNames,
4568
+ title,
4569
+ body
4570
+ };
4571
+ }
4572
+ function resolveProvider2(options) {
4573
+ return options.provider ?? "github";
4574
+ }
4575
+ function resolveGrouping(options) {
4576
+ if (options.grouping && options.grouping !== "all") {
4577
+ throw new Error(
4578
+ "changeRequest.grouping currently supports only 'all' for deterministic branch creation."
4579
+ );
4580
+ }
4581
+ return "all";
4582
+ }
4583
+ async function detectCurrentBranch(cwd) {
4584
+ const result = await execa11("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
4585
+ cwd,
4586
+ stdio: "pipe"
4587
+ });
4588
+ return result.stdout.trim();
4589
+ }
4590
+ async function ensureRepoHasChanges(cwd) {
4591
+ const diff = await execa11("git", ["status", "--porcelain"], {
4592
+ cwd,
4593
+ stdio: "pipe"
4594
+ });
4595
+ return diff.stdout.trim().length > 0;
4596
+ }
4597
+ function toBranchName(prefix, cveIds) {
4598
+ const token = sanitizeBranchToken(cveIds.join("-") || "remediation");
4599
+ return `${sanitizeBranchToken(prefix)}/${token}-${Date.now()}`;
4600
+ }
4601
+ async function createGitHubRequest(params) {
4602
+ const args = ["pr", "create", "--head", params.head, "--base", params.base, "--title", params.title, "--body", params.body];
4603
+ if (params.repository) {
4604
+ args.push("--repo", params.repository);
4605
+ }
4606
+ if (params.draft) {
4607
+ args.push("--draft");
4608
+ }
4609
+ const result = await execa11("gh", args, {
4610
+ cwd: params.cwd,
4611
+ stdio: "pipe"
4612
+ });
4613
+ return result.stdout.trim() || void 0;
4614
+ }
4615
+ async function createGitLabRequest(params) {
4616
+ const args = [
4617
+ "mr",
4618
+ "create",
4619
+ "--source-branch",
4620
+ params.source,
4621
+ "--target-branch",
4622
+ params.target,
4623
+ "--title",
4624
+ params.title,
4625
+ "--description",
4626
+ params.body
4627
+ ];
4628
+ if (params.repository) {
4629
+ args.push("-R", params.repository);
4630
+ }
4631
+ if (params.draft) {
4632
+ args.push("--draft");
4633
+ }
4634
+ const result = await execa11("glab", args, {
4635
+ cwd: params.cwd,
4636
+ stdio: "pipe"
4637
+ });
4638
+ return result.stdout.trim() || void 0;
4639
+ }
4640
+ async function createChangeRequestsForReports(params) {
4641
+ const { cwd, options, reports } = params;
4642
+ const provider = resolveProvider2(options);
4643
+ const grouping = resolveGrouping(options);
4644
+ const plan = buildPlan(reports);
4645
+ const titlePrefix = options.titlePrefix?.trim();
4646
+ const title = titlePrefix ? `${titlePrefix} ${plan.title}` : plan.title;
4647
+ const body = options.bodyFooter ? `${plan.body}
4648
+
4649
+ ${options.bodyFooter}` : plan.body;
4650
+ try {
4651
+ const hasChanges = await ensureRepoHasChanges(cwd);
4652
+ if (!hasChanges) {
4653
+ return [
4654
+ {
4655
+ provider,
4656
+ grouping,
4657
+ repository: options.repository,
4658
+ branchName: "",
4659
+ title,
4660
+ body,
4661
+ created: false,
4662
+ cveIds: plan.cveIds,
4663
+ packageNames: plan.packageNames,
4664
+ error: "No repository changes to include in change request."
4665
+ }
4666
+ ];
4667
+ }
4668
+ const baseBranch = options.baseBranch ?? await detectCurrentBranch(cwd);
4669
+ const branchPrefix = options.branchPrefix ?? "autoremediator";
4670
+ const branchName = toBranchName(branchPrefix, plan.cveIds);
4671
+ const pushRemote = options.pushRemote ?? "origin";
4672
+ await execa11("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
4673
+ await execa11("git", ["add", "-A"], { cwd, stdio: "pipe" });
4674
+ await execa11("git", ["commit", "-m", title], { cwd, stdio: "pipe" });
4675
+ await execa11("git", ["push", "-u", pushRemote, branchName], { cwd, stdio: "pipe" });
4676
+ const url = provider === "github" ? await createGitHubRequest({
4677
+ cwd,
4678
+ repository: options.repository,
4679
+ head: branchName,
4680
+ base: baseBranch,
4681
+ title,
4682
+ body,
4683
+ draft: options.draft
4684
+ }) : await createGitLabRequest({
4685
+ cwd,
4686
+ repository: options.repository,
4687
+ source: branchName,
4688
+ target: baseBranch,
4689
+ title,
4690
+ body,
4691
+ draft: options.draft
4692
+ });
4693
+ return [
4694
+ {
4695
+ provider,
4696
+ grouping,
4697
+ repository: options.repository,
4698
+ branchName,
4699
+ title,
4700
+ body,
4701
+ created: true,
4702
+ draft: options.draft,
4703
+ url,
4704
+ cveIds: plan.cveIds,
4705
+ packageNames: plan.packageNames
4706
+ }
4707
+ ];
4708
+ } catch (error) {
4709
+ return [
4710
+ {
4711
+ provider,
4712
+ grouping,
4713
+ repository: options.repository,
4714
+ branchName: "",
4715
+ title,
4716
+ body,
4717
+ created: false,
4718
+ draft: options.draft,
4719
+ cveIds: plan.cveIds,
4720
+ packageNames: plan.packageNames,
4721
+ error: error instanceof Error ? error.message : String(error)
4722
+ }
4723
+ ];
4724
+ }
4725
+ }
4726
+
4144
4727
  // src/api/context.ts
4145
4728
  function buildRequestId() {
4146
4729
  return `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -4172,7 +4755,7 @@ function resolveConstraints(options, cwd) {
4172
4755
 
4173
4756
  // src/platform/evidence.ts
4174
4757
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
4175
- import { join as join15 } from "path";
4758
+ import { join as join16 } from "path";
4176
4759
  function createEvidenceLog(cwd, cveIds, context = {}) {
4177
4760
  return {
4178
4761
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -4203,9 +4786,9 @@ function finalizeEvidence(log) {
4203
4786
  return log;
4204
4787
  }
4205
4788
  function writeEvidenceLog(cwd, log) {
4206
- const dir = join15(cwd, ".autoremediator", "evidence");
4789
+ const dir = join16(cwd, ".autoremediator", "evidence");
4207
4790
  mkdirSync3(dir, { recursive: true });
4208
- const filePath = join15(dir, `${log.runId}.json`);
4791
+ const filePath = join16(dir, `${log.runId}.json`);
4209
4792
  writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
4210
4793
  return filePath;
4211
4794
  }
@@ -4261,7 +4844,9 @@ function addRemediateResultSteps(evidence, cveId, report) {
4261
4844
  dryRun: result.dryRun,
4262
4845
  unresolvedReason: result.unresolvedReason,
4263
4846
  reachability: result.reachability?.status,
4264
- hasAlternatives: Boolean(result.alternativeSuggestions?.length)
4847
+ hasAlternatives: Boolean(result.alternativeSuggestions?.length),
4848
+ suppressedBy: result.suppressedBy?.justification,
4849
+ regressionDetected: result.regressionDetected
4265
4850
  }
4266
4851
  );
4267
4852
  }
@@ -4272,7 +4857,11 @@ function addRemediateResultSteps(evidence, cveId, report) {
4272
4857
  {
4273
4858
  resultCount: report.results.length,
4274
4859
  vulnerableCount: report.vulnerablePackages.length,
4275
- llmUsage: report.llmUsage
4860
+ llmUsage: report.llmUsage,
4861
+ exploitSignalTriggered: report.exploitSignalTriggered ?? false,
4862
+ slaBreachCount: report.slaBreaches?.length ?? 0,
4863
+ regressionDetectedCount: report.results.filter((r) => r.regressionDetected).length,
4864
+ sbomEntryCount: report.sbom?.length ?? 0
4276
4865
  }
4277
4866
  );
4278
4867
  finalizeEvidence(evidence);
@@ -4367,12 +4956,24 @@ async function remediate(cveId, options = {}) {
4367
4956
  constraints,
4368
4957
  resumedFromCache: false
4369
4958
  };
4959
+ if (options.changeRequest?.enabled) {
4960
+ finalReport.changeRequests = await createChangeRequestsForReports({
4961
+ cwd,
4962
+ options: options.changeRequest,
4963
+ reports: [finalReport]
4964
+ });
4965
+ }
4370
4966
  if (options.idempotencyKey && !options.dryRun && !options.preview) {
4371
4967
  storeIdempotentReport(cwd, options.idempotencyKey, normalizedCveId, finalReport);
4372
4968
  }
4373
4969
  return finalReport;
4374
4970
  }
4375
4971
  async function planRemediation(cveId, options = {}) {
4972
+ if (Object.hasOwn(options, "dryRun") || Object.hasOwn(options, "preview")) {
4973
+ throw new Error(
4974
+ "planRemediation always runs with dryRun=true and preview=true. Remove dryRun/preview from options."
4975
+ );
4976
+ }
4376
4977
  return remediate(cveId, {
4377
4978
  ...options,
4378
4979
  preview: true,
@@ -4381,12 +4982,12 @@ async function planRemediation(cveId, options = {}) {
4381
4982
  }
4382
4983
 
4383
4984
  // src/scanner/parse-input.ts
4384
- import { extname } from "path";
4385
- import { readFileSync as readFileSync12 } from "fs";
4386
- import { execa as execa11 } from "execa";
4985
+ import { extname as extname2 } from "path";
4986
+ import { readFileSync as readFileSync13 } from "fs";
4987
+ import { execa as execa12 } from "execa";
4387
4988
 
4388
4989
  // src/scanner/adapters/npm-audit.ts
4389
- import { readFileSync as readFileSync9 } from "fs";
4990
+ import { readFileSync as readFileSync10 } from "fs";
4390
4991
  var CVE_REGEX = /CVE-\d{4}-\d+/gi;
4391
4992
  function normalizeSeverity(raw) {
4392
4993
  if (!raw) return "UNKNOWN";
@@ -4420,12 +5021,12 @@ function parseNpmAuditJsonFromString(content) {
4420
5021
  return findings;
4421
5022
  }
4422
5023
  function parseNpmAuditJsonFile(filePath) {
4423
- const content = readFileSync9(filePath, "utf8");
5024
+ const content = readFileSync10(filePath, "utf8");
4424
5025
  return parseNpmAuditJsonFromString(content);
4425
5026
  }
4426
5027
 
4427
5028
  // src/scanner/adapters/yarn-audit.ts
4428
- import { readFileSync as readFileSync10 } from "fs";
5029
+ import { readFileSync as readFileSync11 } from "fs";
4429
5030
  var CVE_REGEX2 = /CVE-\d{4}-\d+/gi;
4430
5031
  function normalizeSeverity2(raw) {
4431
5032
  if (!raw) return "UNKNOWN";
@@ -4468,12 +5069,12 @@ function parseYarnAuditJsonFromString(content) {
4468
5069
  return findings;
4469
5070
  }
4470
5071
  function parseYarnAuditJsonFile(filePath) {
4471
- const content = readFileSync10(filePath, "utf8");
5072
+ const content = readFileSync11(filePath, "utf8");
4472
5073
  return parseYarnAuditJsonFromString(content);
4473
5074
  }
4474
5075
 
4475
5076
  // src/scanner/adapters/sarif.ts
4476
- import { readFileSync as readFileSync11 } from "fs";
5077
+ import { readFileSync as readFileSync12 } from "fs";
4477
5078
  var CVE_REGEX3 = /CVE-\d{4}-\d+/gi;
4478
5079
  function extractPackageName(result) {
4479
5080
  const pkg = result.properties?.["packageName"];
@@ -4505,7 +5106,7 @@ function parseSarifFromString(content) {
4505
5106
  return findings;
4506
5107
  }
4507
5108
  function parseSarifFile(filePath) {
4508
- const content = readFileSync11(filePath, "utf8");
5109
+ const content = readFileSync12(filePath, "utf8");
4509
5110
  return parseSarifFromString(content);
4510
5111
  }
4511
5112
 
@@ -4532,7 +5133,7 @@ async function parseScanInputFromAudit(params) {
4532
5133
  }
4533
5134
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
4534
5135
  const [cmd, ...args] = command;
4535
- const result = await execa11(cmd, args, {
5136
+ const result = await execa12(cmd, args, {
4536
5137
  cwd: params.cwd,
4537
5138
  stdio: "pipe",
4538
5139
  reject: false
@@ -4568,10 +5169,10 @@ function ensureAuditFormatCompatibility(pm, resolved) {
4568
5169
  }
4569
5170
  }
4570
5171
  function inferFormat(filePath) {
4571
- const ext = extname(filePath).toLowerCase();
5172
+ const ext = extname2(filePath).toLowerCase();
4572
5173
  if (ext === ".sarif") return "sarif";
4573
5174
  try {
4574
- const content = readFileSync12(filePath, "utf8");
5175
+ const content = readFileSync13(filePath, "utf8");
4575
5176
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
4576
5177
  if (firstLine) {
4577
5178
  const parsed = JSON.parse(firstLine);
@@ -4839,6 +5440,11 @@ async function remediateFromScan(inputPath, options = {}) {
4839
5440
  };
4840
5441
  finalizeEvidence(evidence);
4841
5442
  const evidenceFile = options.evidence === false ? void 0 : writeEvidenceLog(cwd, evidence);
5443
+ const changeRequests = options.changeRequest?.enabled ? await createChangeRequestsForReports({
5444
+ cwd,
5445
+ options: options.changeRequest,
5446
+ reports
5447
+ }) : void 0;
4842
5448
  return {
4843
5449
  schemaVersion: "1.0",
4844
5450
  status,
@@ -4861,19 +5467,124 @@ async function remediateFromScan(inputPath, options = {}) {
4861
5467
  idempotencyKey: options.idempotencyKey,
4862
5468
  llmUsageCount: llmUsageTotals.llmUsageCount > 0 ? llmUsageTotals.llmUsageCount : void 0,
4863
5469
  estimatedCostUsd: llmUsageTotals.estimatedCostUsd,
4864
- totalLlmLatencyMs: llmUsageTotals.totalLlmLatencyMs
5470
+ totalLlmLatencyMs: llmUsageTotals.totalLlmLatencyMs,
5471
+ changeRequests
5472
+ };
5473
+ }
5474
+
5475
+ // src/api/portfolio/index.ts
5476
+ function toTargetStatusFromRemediation(report) {
5477
+ const failed = report.results.some((result) => !result.applied && !result.dryRun);
5478
+ const succeeded = report.results.some((result) => result.applied || result.dryRun);
5479
+ if (!failed) return "ok";
5480
+ return succeeded ? "partial" : "failed";
5481
+ }
5482
+ function normalizeTargetInput(target) {
5483
+ return {
5484
+ cveId: target.cveId?.trim() || void 0,
5485
+ inputPath: target.inputPath?.trim() || void 0,
5486
+ audit: target.audit ?? false
5487
+ };
5488
+ }
5489
+ function validateTarget(target, index) {
5490
+ if (!target.cwd || typeof target.cwd !== "string") {
5491
+ throw new Error(`Invalid portfolio target at index ${index}: cwd is required.`);
5492
+ }
5493
+ const { cveId, inputPath, audit } = normalizeTargetInput(target);
5494
+ const hasScanInput = Boolean(inputPath) || audit;
5495
+ if (Boolean(cveId) === hasScanInput) {
5496
+ throw new Error(
5497
+ `Invalid portfolio target at index ${index}: provide exactly one mode (cveId OR inputPath/audit).`
5498
+ );
5499
+ }
5500
+ }
5501
+ async function remediatePortfolio(targets, options = {}) {
5502
+ const normalizedTargets = Array.isArray(targets) ? targets : [];
5503
+ if (normalizedTargets.length === 0) {
5504
+ throw new Error("Portfolio requires at least one target.");
5505
+ }
5506
+ normalizedTargets.forEach((target, index) => validateTarget(target, index));
5507
+ const results = [];
5508
+ const changeRequests = [];
5509
+ let successCount = 0;
5510
+ let failedCount = 0;
5511
+ for (const target of normalizedTargets) {
5512
+ const { cveId, inputPath, audit } = normalizeTargetInput(target);
5513
+ try {
5514
+ if (cveId) {
5515
+ const remediationReport = await remediate(cveId, {
5516
+ ...options,
5517
+ cwd: target.cwd,
5518
+ source: options.source
5519
+ });
5520
+ const status = toTargetStatusFromRemediation(remediationReport);
5521
+ results.push({
5522
+ target,
5523
+ status,
5524
+ remediationReport,
5525
+ changeRequests: remediationReport.changeRequests
5526
+ });
5527
+ if (remediationReport.changeRequests?.length) {
5528
+ changeRequests.push(...remediationReport.changeRequests);
5529
+ }
5530
+ if (status === "failed") {
5531
+ failedCount += 1;
5532
+ } else {
5533
+ successCount += 1;
5534
+ }
5535
+ continue;
5536
+ }
5537
+ const scanReport = await remediateFromScan(inputPath ?? "", {
5538
+ ...options,
5539
+ cwd: target.cwd,
5540
+ audit,
5541
+ format: target.format,
5542
+ source: options.source
5543
+ });
5544
+ results.push({
5545
+ target,
5546
+ status: scanReport.status,
5547
+ scanReport,
5548
+ changeRequests: scanReport.changeRequests
5549
+ });
5550
+ if (scanReport.changeRequests?.length) {
5551
+ changeRequests.push(...scanReport.changeRequests);
5552
+ }
5553
+ if (scanReport.status === "failed") {
5554
+ failedCount += 1;
5555
+ } else {
5556
+ successCount += 1;
5557
+ }
5558
+ } catch (error) {
5559
+ results.push({
5560
+ target,
5561
+ status: "failed",
5562
+ error: error instanceof Error ? error.message : String(error)
5563
+ });
5564
+ failedCount += 1;
5565
+ }
5566
+ }
5567
+ const overallStatus = failedCount === 0 ? "ok" : successCount > 0 ? "partial" : "failed";
5568
+ return {
5569
+ schemaVersion: "1.0",
5570
+ status: overallStatus,
5571
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5572
+ targets: results,
5573
+ successCount,
5574
+ failedCount,
5575
+ changeRequests: changeRequests.length > 0 ? changeRequests : void 0
4865
5576
  };
4866
5577
  }
4867
5578
 
4868
5579
  // src/api/update-outdated/index.ts
4869
- import { join as join16 } from "path";
4870
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
4871
- import { execa as execa12 } from "execa";
5580
+ import { join as join17 } from "path";
5581
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
5582
+ import { execa as execa13 } from "execa";
4872
5583
  async function applyBump(params) {
4873
- const pkgPath = join16(params.cwd, "package.json");
5584
+ const pkgPath = join17(params.cwd, "package.json");
4874
5585
  let pkgJson;
4875
5586
  try {
4876
- pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
5587
+ pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
4877
5588
  } catch {
4878
5589
  return {
4879
5590
  applied: false,
@@ -4898,7 +5609,7 @@ async function applyBump(params) {
4898
5609
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4899
5610
  try {
4900
5611
  const [installCmd, ...installArgs] = params.installCommand;
4901
- await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
5612
+ await execa13(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
4902
5613
  } catch (err) {
4903
5614
  pkgJson[depField][params.packageName] = currentRange;
4904
5615
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -4911,13 +5622,13 @@ async function applyBump(params) {
4911
5622
  if (params.runTests) {
4912
5623
  try {
4913
5624
  const [testCmd, ...testArgs] = params.testCommand;
4914
- await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
5625
+ await execa13(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
4915
5626
  } catch (err) {
4916
5627
  pkgJson[depField][params.packageName] = currentRange;
4917
5628
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4918
5629
  try {
4919
5630
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
4920
- await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
5631
+ await execa13(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
4921
5632
  } catch {
4922
5633
  }
4923
5634
  const message = err instanceof Error ? err.message : String(err);
@@ -5070,6 +5781,28 @@ async function updateOutdated(options = {}) {
5070
5781
  };
5071
5782
  finalizeEvidence(evidence);
5072
5783
  const evidenceFile = options.evidence === false ? void 0 : writeEvidenceLog(cwd, evidence);
5784
+ const changeRequests = options.changeRequest?.enabled && successCount > 0 ? await createChangeRequestsForReports({
5785
+ cwd,
5786
+ options: options.changeRequest,
5787
+ reports: [
5788
+ {
5789
+ cveId: "UPDATE-OUTDATED",
5790
+ cveDetails: null,
5791
+ vulnerablePackages: [],
5792
+ results: outdatedPackages.filter((pkg) => !pkg.isMajorBump).map((pkg) => ({
5793
+ packageName: pkg.name,
5794
+ strategy: "version-bump",
5795
+ fromVersion: pkg.currentVersion,
5796
+ toVersion: pkg.latestVersion,
5797
+ applied: true,
5798
+ dryRun: Boolean(options.dryRun),
5799
+ message: `Updated ${pkg.name} to ${pkg.latestVersion}`
5800
+ })),
5801
+ agentSteps: 0,
5802
+ summary: "update-outdated"
5803
+ }
5804
+ ]
5805
+ }) : void 0;
5073
5806
  return {
5074
5807
  schemaVersion: "1.0",
5075
5808
  status,
@@ -5083,7 +5816,8 @@ async function updateOutdated(options = {}) {
5083
5816
  patchCount: 0,
5084
5817
  constraints,
5085
5818
  correlation,
5086
- provenance
5819
+ provenance,
5820
+ changeRequests
5087
5821
  };
5088
5822
  }
5089
5823
 
@@ -5151,9 +5885,9 @@ function toSarifOutput(report) {
5151
5885
  }
5152
5886
 
5153
5887
  // src/version.ts
5154
- import { readFileSync as readFileSync14 } from "fs";
5888
+ import { readFileSync as readFileSync15 } from "fs";
5155
5889
  function readPackageVersion() {
5156
- const raw = readFileSync14(new URL("../package.json", import.meta.url), "utf8");
5890
+ const raw = readFileSync15(new URL("../package.json", import.meta.url), "utf8");
5157
5891
  const metadata = JSON.parse(raw);
5158
5892
  if (!metadata.version) {
5159
5893
  throw new Error("packages/core/package.json is missing a version field.");
@@ -5177,7 +5911,8 @@ export {
5177
5911
  remediate,
5178
5912
  planRemediation,
5179
5913
  remediateFromScan,
5914
+ remediatePortfolio,
5180
5915
  updateOutdated,
5181
5916
  PACKAGE_VERSION
5182
5917
  };
5183
- //# sourceMappingURL=chunk-F7W4EYJL.js.map
5918
+ //# sourceMappingURL=chunk-575GEUAY.js.map