autoremediator 0.11.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,7 +40,14 @@ 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.",
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.",
44
51
  updateOutdated: "Run in update-outdated mode: bump all outdated npm packages without requiring a CVE.",
45
52
  kevMandatory: "If true, CVEs with active CISA KEV status bypass severity filtering and are treated as mandatory",
46
53
  epssThreshold: "EPSS probability threshold (0..1) above which a CVE is treated as mandatory regardless of severity",
@@ -104,6 +111,26 @@ function createRemediateOptionSchemaProperties(options) {
104
111
  type: "object",
105
112
  properties: createConstraintSchemaProperties()
106
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
+ },
107
134
  kevMandatory: { type: "boolean", description: OPTION_DESCRIPTIONS.kevMandatory },
108
135
  epssThreshold: { type: "number", minimum: 0, maximum: 1, description: OPTION_DESCRIPTIONS.epssThreshold },
109
136
  suppressionsFile: { type: "string", description: OPTION_DESCRIPTIONS.suppressionsFile },
@@ -152,7 +179,8 @@ function createScanReportSchemaProperties() {
152
179
  idempotencyKey: { type: "string" },
153
180
  llmUsageCount: { type: "number" },
154
181
  estimatedCostUsd: { type: "number" },
155
- totalLlmLatencyMs: { type: "number" }
182
+ totalLlmLatencyMs: { type: "number" },
183
+ changeRequests: { type: "array", items: { type: "object" } }
156
184
  };
157
185
  }
158
186
  function createUpdateOutdatedOptionSchemaProperties() {
@@ -477,8 +505,13 @@ async function inspectPatchArtifact(patchFilePath, options = {}) {
477
505
  };
478
506
  }
479
507
 
480
- // src/remediation/tools/check-inventory.ts
508
+ // src/remediation/tools/tool-compat.ts
481
509
  import { tool } from "ai";
510
+ function defineTool(config) {
511
+ return tool(config);
512
+ }
513
+
514
+ // src/remediation/tools/check-inventory.ts
482
515
  import { z } from "zod";
483
516
  import { readFileSync as readFileSync3 } from "fs";
484
517
  import { join as join5 } from "path";
@@ -602,7 +635,7 @@ function checkSlaBreach(cveId, severity, publishedAt, slaPolicy) {
602
635
  }
603
636
 
604
637
  // src/remediation/tools/check-inventory.ts
605
- var checkInventoryTool = tool({
638
+ var checkInventoryTool = defineTool({
606
639
  description: "Read the project's package.json and installed dependencies to list packages and exact versions. Must be called before checking version matches.",
607
640
  parameters: z.object({
608
641
  cwd: z.string().describe("Absolute path to the consumer project's root directory"),
@@ -643,7 +676,7 @@ var checkInventoryTool = tool({
643
676
  packages.push({
644
677
  name,
645
678
  version,
646
- type: isDirect ? "direct" : "indirect"
679
+ type: isDirect ? "direct" : "transitive"
647
680
  });
648
681
  }
649
682
  if (packages.length === 0) {
@@ -1188,7 +1221,6 @@ async function enrichWithNvd(details) {
1188
1221
  }
1189
1222
 
1190
1223
  // src/remediation/tools/check-reachability.ts
1191
- import { tool as tool2 } from "ai";
1192
1224
  import { z as z2 } from "zod";
1193
1225
  import { readdirSync, readFileSync as readFileSync5, statSync } from "fs";
1194
1226
  import { join as join7, extname } from "path";
@@ -1264,7 +1296,7 @@ function assessPackageReachability(cwd, packageName) {
1264
1296
  reason: `No import or require of '${packageName}' found in ${files.length} source file(s).`
1265
1297
  };
1266
1298
  }
1267
- var checkReachabilityTool = tool2({
1299
+ var checkReachabilityTool = defineTool({
1268
1300
  description: "Assess whether an npm package is statically reachable (imported) from the project's source files. Returns reachable, not-reachable, or unknown.",
1269
1301
  parameters: z2.object({
1270
1302
  cwd: z2.string().describe("Project root directory"),
@@ -1276,7 +1308,6 @@ var checkReachabilityTool = tool2({
1276
1308
  });
1277
1309
 
1278
1310
  // src/remediation/tools/check-exploit-signal.ts
1279
- import { tool as tool3 } from "ai";
1280
1311
  import { z as z3 } from "zod";
1281
1312
  var kevSchema = z3.object({
1282
1313
  knownExploited: z3.boolean(),
@@ -1294,7 +1325,7 @@ var exploitSignalPolicySchema = z3.object({
1294
1325
  kev: z3.object({ mandatory: z3.boolean() }).optional(),
1295
1326
  epss: z3.object({ mandatory: z3.boolean(), threshold: z3.number() }).optional()
1296
1327
  }).optional();
1297
- var checkExploitSignalTool = tool3({
1328
+ var checkExploitSignalTool = defineTool({
1298
1329
  description: "Evaluate KEV and EPSS exploit signals for a CVE against policy thresholds. Returns whether the exploit signal overrides severity filtering (mandatory gate).",
1299
1330
  parameters: z3.object({
1300
1331
  cveDetails: z3.object({
@@ -1385,7 +1416,7 @@ function buildSbom(packages, vulnerableNames, results) {
1385
1416
  const entry = {
1386
1417
  name: pkg.name,
1387
1418
  version: pkg.version,
1388
- scope: pkg.type === "direct" ? "direct" : "indirect"
1419
+ scope: pkg.type === "direct" ? "direct" : "transitive"
1389
1420
  };
1390
1421
  if (isVulnerable) {
1391
1422
  entry.status = statusByPackage.get(pkg.name) ?? "unpatched";
@@ -1579,8 +1610,8 @@ async function queryOutdatedPackages(cwd, options = {}) {
1579
1610
  const wantedVersion = entry.wanted ?? currentVersion;
1580
1611
  const latestVersion = entry.latest ?? currentVersion;
1581
1612
  if (!currentVersion || !latestVersion) continue;
1582
- const dependencyScope = directDeps.has(name) ? "direct" : "indirect";
1583
- if (!options.includeTransitive && dependencyScope === "indirect") continue;
1613
+ const dependencyScope = directDeps.has(name) ? "direct" : "transitive";
1614
+ if (!options.includeTransitive && dependencyScope === "transitive") continue;
1584
1615
  const isMajorBump = semver.valid(currentVersion) !== null && semver.valid(latestVersion) !== null && semver.major(latestVersion) > semver.major(currentVersion);
1585
1616
  result.set(name, {
1586
1617
  currentVersion,
@@ -1594,7 +1625,6 @@ async function queryOutdatedPackages(cwd, options = {}) {
1594
1625
  }
1595
1626
 
1596
1627
  // src/remediation/tools/apply-version-bump.ts
1597
- import { tool as tool4 } from "ai";
1598
1628
  import { z as z4 } from "zod";
1599
1629
  import { join as join10 } from "path";
1600
1630
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
@@ -1641,7 +1671,7 @@ async function withRepoLock(cwd, fn, options) {
1641
1671
  }
1642
1672
 
1643
1673
  // src/remediation/tools/apply-version-bump.ts
1644
- var applyVersionBumpTool = tool4({
1674
+ var applyVersionBumpTool = defineTool({
1645
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.",
1646
1676
  parameters: z4.object({
1647
1677
  cwd: z4.string().describe("Absolute path to the consumer project root"),
@@ -1734,8 +1764,8 @@ var applyVersionBumpTool = tool4({
1734
1764
  fromVersion,
1735
1765
  applied: false,
1736
1766
  dryRun,
1737
- unresolvedReason: "indirect-dependency",
1738
- 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.`
1739
1769
  };
1740
1770
  }
1741
1771
  const currentRange = pkgJson[depField][packageName];
@@ -1835,7 +1865,6 @@ var applyVersionBumpTool = tool4({
1835
1865
  });
1836
1866
 
1837
1867
  // src/remediation/tools/apply-package-override/index.ts
1838
- import { tool as tool5 } from "ai";
1839
1868
  import { z as z5 } from "zod";
1840
1869
  import { join as join11 } from "path";
1841
1870
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
@@ -1918,7 +1947,7 @@ function restoreRecord(record, key, previousValue) {
1918
1947
  }
1919
1948
 
1920
1949
  // src/remediation/tools/apply-package-override/index.ts
1921
- var applyPackageOverrideTool = tool5({
1950
+ var applyPackageOverrideTool = defineTool({
1922
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.",
1923
1952
  parameters: z5.object({
1924
1953
  cwd: z5.string().describe("Absolute path to the consumer project root"),
@@ -2092,7 +2121,7 @@ async function resolvePrimaryResult(params) {
2092
2121
  const { vulnerable, cwd, packageManager, dryRun, policy, runTests, constraints } = params;
2093
2122
  const pkg = vulnerable.installed;
2094
2123
  const firstPatchedVersion = vulnerable.affected.firstPatchedVersion;
2095
- if (pkg.type === "indirect") {
2124
+ if (pkg.type === "transitive") {
2096
2125
  if (constraints.directDependenciesOnly) {
2097
2126
  return {
2098
2127
  steps: 0,
@@ -2103,7 +2132,7 @@ async function resolvePrimaryResult(params) {
2103
2132
  applied: false,
2104
2133
  dryRun,
2105
2134
  unresolvedReason: "constraint-blocked",
2106
- message: `Constraint blocked remediation for indirect dependency "${pkg.name}".`
2135
+ message: `Constraint blocked remediation for transitive dependency "${pkg.name}".`
2107
2136
  }
2108
2137
  };
2109
2138
  }
@@ -2229,12 +2258,11 @@ async function resolvePrimaryResult(params) {
2229
2258
  }
2230
2259
 
2231
2260
  // src/remediation/tools/fetch-package-source.ts
2232
- import { tool as tool6 } from "ai";
2233
2261
  import { z as z6 } from "zod";
2234
2262
  import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2 } from "fs/promises";
2235
2263
  import { join as join12 } from "path";
2236
2264
  import { execa as execa8 } from "execa";
2237
- var fetchPackageSourceTool = tool6({
2265
+ var fetchPackageSourceTool = defineTool({
2238
2266
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
2239
2267
  parameters: z6.object({
2240
2268
  packageName: z6.string().min(1).describe("The npm package name (e.g., 'lodash', '@scope/package')"),
@@ -2328,7 +2356,6 @@ var fetchPackageSourceTool = tool6({
2328
2356
  });
2329
2357
 
2330
2358
  // src/remediation/tools/generate-patch/index.ts
2331
- import { tool as tool7 } from "ai";
2332
2359
  import { z as z7 } from "zod";
2333
2360
  import { generateText } from "ai";
2334
2361
 
@@ -2462,14 +2489,14 @@ function generateUnifiedDiff(original, fixed, filePath) {
2462
2489
  }
2463
2490
 
2464
2491
  // src/remediation/tools/generate-patch/index.ts
2465
- var generatePatchTool = tool7({
2492
+ var generatePatchTool = defineTool({
2466
2493
  description: "Generate a unified diff patch for a CVE using LLM analysis of vulnerable source code.",
2467
2494
  parameters: z7.object({
2468
2495
  packageName: z7.string().min(1).describe("The npm package name"),
2469
2496
  vulnerableVersion: z7.string().describe("The vulnerable version string"),
2470
2497
  cveId: z7.string().regex(/^CVE-\d{4}-\d+$/i).describe("CVE ID (e.g., CVE-2021-23337)"),
2471
2498
  cveSummary: z7.string().min(10).describe("CVE description and impact"),
2472
- sourceFiles: z7.record(z7.string()).describe(
2499
+ sourceFiles: z7.record(z7.string(), z7.string()).describe(
2473
2500
  "Map of file paths to source code contents from fetch-package-source"
2474
2501
  ),
2475
2502
  vulnerabilityCategory: z7.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
@@ -2531,7 +2558,7 @@ var generatePatchTool = tool7({
2531
2558
  }
2532
2559
  const inputChars = JSON.stringify(resolvedSourceFiles).length + cveSummary.length;
2533
2560
  const modelInstance = await createModel(effectiveOptions, { inputChars });
2534
- const modelName = modelInstance.modelId || "unknown-model";
2561
+ const modelName = typeof modelInstance === "string" ? modelInstance : "modelId" in modelInstance && typeof modelInstance.modelId === "string" ? modelInstance.modelId : "unknown-model";
2535
2562
  const prompt = buildPatchPrompt({
2536
2563
  cveId,
2537
2564
  packageName,
@@ -2636,7 +2663,6 @@ var generatePatchTool = tool7({
2636
2663
  });
2637
2664
 
2638
2665
  // src/remediation/tools/apply-patch-file/index.ts
2639
- import { tool as tool8 } from "ai";
2640
2666
  import { z as z8 } from "zod";
2641
2667
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
2642
2668
  import { join as join14 } from "path";
@@ -2878,7 +2904,7 @@ function extractFailedTests(output) {
2878
2904
  }
2879
2905
 
2880
2906
  // src/remediation/tools/apply-patch-file/index.ts
2881
- var applyPatchFileTool = tool8({
2907
+ var applyPatchFileTool = defineTool({
2882
2908
  description: "Write generated patch file and apply it using package-manager-native patch flow when available, falling back to patch-package when needed.",
2883
2909
  parameters: z8.object({
2884
2910
  packageName: z8.string().min(1).describe("The npm package name"),
@@ -3224,7 +3250,7 @@ function resolvePatchProvider(provider) {
3224
3250
  function shouldAttemptPatchFallback(result, preferVersionBump) {
3225
3251
  if (preferVersionBump) return false;
3226
3252
  if (result.applied || result.dryRun) return false;
3227
- 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";
3228
3254
  }
3229
3255
  async function tryLocalPatchFallback(params) {
3230
3256
  const usage = [];
@@ -3844,7 +3870,6 @@ function createProgressEmitter(options) {
3844
3870
  }
3845
3871
 
3846
3872
  // src/remediation/tools/lookup-cve.ts
3847
- import { tool as tool9 } from "ai";
3848
3873
  import { z as z9 } from "zod";
3849
3874
 
3850
3875
  // src/intelligence/sources/cisa-kev.ts
@@ -4123,7 +4148,7 @@ async function enrichWithExternalFeeds(details) {
4123
4148
  }
4124
4149
 
4125
4150
  // src/remediation/tools/lookup-cve.ts
4126
- var lookupCveTool = tool9({
4151
+ var lookupCveTool = defineTool({
4127
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.",
4128
4153
  parameters: z9.object({
4129
4154
  cveId: z9.string().regex(/^CVE-\d{4}-\d+$/i, "Must be a valid CVE ID like CVE-2021-23337")
@@ -4192,7 +4217,6 @@ var lookupCveTool = tool9({
4192
4217
  });
4193
4218
 
4194
4219
  // src/remediation/tools/check-version-match.ts
4195
- import { tool as tool10 } from "ai";
4196
4220
  import { z as z10 } from "zod";
4197
4221
  import semver6 from "semver";
4198
4222
  var affectedPackageSchema = z10.object({
@@ -4205,9 +4229,9 @@ var affectedPackageSchema = z10.object({
4205
4229
  var inventoryPackageSchema = z10.object({
4206
4230
  name: z10.string(),
4207
4231
  version: z10.string(),
4208
- type: z10.enum(["direct", "indirect"])
4232
+ type: z10.enum(["direct", "transitive"])
4209
4233
  });
4210
- var checkVersionMatchTool = tool10({
4234
+ var checkVersionMatchTool = defineTool({
4211
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.",
4212
4236
  parameters: z10.object({
4213
4237
  installedPackages: z10.array(inventoryPackageSchema).describe("Output from the check-inventory tool"),
@@ -4242,9 +4266,8 @@ var checkVersionMatchTool = tool10({
4242
4266
  });
4243
4267
 
4244
4268
  // src/remediation/tools/find-fixed-version.ts
4245
- import { tool as tool11 } from "ai";
4246
4269
  import { z as z11 } from "zod";
4247
- var findFixedVersionTool = tool11({
4270
+ var findFixedVersionTool = defineTool({
4248
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.",
4249
4272
  parameters: z11.object({
4250
4273
  packageName: z11.string().describe("The npm package name"),
@@ -4290,7 +4313,6 @@ var findFixedVersionTool = tool11({
4290
4313
  });
4291
4314
 
4292
4315
  // src/remediation/tools/check-suppression.ts
4293
- import { tool as tool12 } from "ai";
4294
4316
  import { z as z12 } from "zod";
4295
4317
  var suppressionSchema = z12.object({
4296
4318
  cveId: z12.string(),
@@ -4298,7 +4320,7 @@ var suppressionSchema = z12.object({
4298
4320
  notes: z12.string().optional(),
4299
4321
  expiresAt: z12.string().optional()
4300
4322
  });
4301
- var checkSuppressionTool = tool12({
4323
+ var checkSuppressionTool = defineTool({
4302
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.",
4303
4325
  parameters: z12.object({
4304
4326
  cveId: z12.string().describe("The CVE ID to look up in suppressions"),
@@ -4413,7 +4435,7 @@ async function createPipelineRuntime(cveId, options) {
4413
4435
  const constraints = options.constraints ?? {};
4414
4436
  const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
4415
4437
  const model = await createModel(options, { inputChars: prompt.length });
4416
- const modelName = model.modelId ?? "unknown-model";
4438
+ const modelName = typeof model === "string" ? model : "modelId" in model && typeof model.modelId === "string" ? model.modelId : "unknown-model";
4417
4439
  const systemPrompt = loadOrchestrationPrompt({
4418
4440
  cveId,
4419
4441
  cwd,
@@ -4465,7 +4487,7 @@ async function runRemediationPipeline(cveId, options = {}) {
4465
4487
  system: systemPrompt,
4466
4488
  prompt,
4467
4489
  tools,
4468
- maxSteps: 25,
4490
+ stopWhen: ({ steps }) => steps.length >= 25,
4469
4491
  onStepFinish(stepResult) {
4470
4492
  agentSteps += 1;
4471
4493
  const toolResults = stepResult.toolResults ?? [];
@@ -4520,6 +4542,188 @@ async function runRemediationPipeline(cveId, options = {}) {
4520
4542
  };
4521
4543
  }
4522
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
+
4523
4727
  // src/api/context.ts
4524
4728
  function buildRequestId() {
4525
4729
  return `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -4752,12 +4956,24 @@ async function remediate(cveId, options = {}) {
4752
4956
  constraints,
4753
4957
  resumedFromCache: false
4754
4958
  };
4959
+ if (options.changeRequest?.enabled) {
4960
+ finalReport.changeRequests = await createChangeRequestsForReports({
4961
+ cwd,
4962
+ options: options.changeRequest,
4963
+ reports: [finalReport]
4964
+ });
4965
+ }
4755
4966
  if (options.idempotencyKey && !options.dryRun && !options.preview) {
4756
4967
  storeIdempotentReport(cwd, options.idempotencyKey, normalizedCveId, finalReport);
4757
4968
  }
4758
4969
  return finalReport;
4759
4970
  }
4760
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
+ }
4761
4977
  return remediate(cveId, {
4762
4978
  ...options,
4763
4979
  preview: true,
@@ -4768,7 +4984,7 @@ async function planRemediation(cveId, options = {}) {
4768
4984
  // src/scanner/parse-input.ts
4769
4985
  import { extname as extname2 } from "path";
4770
4986
  import { readFileSync as readFileSync13 } from "fs";
4771
- import { execa as execa11 } from "execa";
4987
+ import { execa as execa12 } from "execa";
4772
4988
 
4773
4989
  // src/scanner/adapters/npm-audit.ts
4774
4990
  import { readFileSync as readFileSync10 } from "fs";
@@ -4917,7 +5133,7 @@ async function parseScanInputFromAudit(params) {
4917
5133
  }
4918
5134
  const command = resolveAuditCommand(pm, { workspace: params.workspace });
4919
5135
  const [cmd, ...args] = command;
4920
- const result = await execa11(cmd, args, {
5136
+ const result = await execa12(cmd, args, {
4921
5137
  cwd: params.cwd,
4922
5138
  stdio: "pipe",
4923
5139
  reject: false
@@ -5224,6 +5440,11 @@ async function remediateFromScan(inputPath, options = {}) {
5224
5440
  };
5225
5441
  finalizeEvidence(evidence);
5226
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;
5227
5448
  return {
5228
5449
  schemaVersion: "1.0",
5229
5450
  status,
@@ -5246,14 +5467,119 @@ async function remediateFromScan(inputPath, options = {}) {
5246
5467
  idempotencyKey: options.idempotencyKey,
5247
5468
  llmUsageCount: llmUsageTotals.llmUsageCount > 0 ? llmUsageTotals.llmUsageCount : void 0,
5248
5469
  estimatedCostUsd: llmUsageTotals.estimatedCostUsd,
5249
- 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
5250
5576
  };
5251
5577
  }
5252
5578
 
5253
5579
  // src/api/update-outdated/index.ts
5254
5580
  import { join as join17 } from "path";
5255
5581
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
5256
- import { execa as execa12 } from "execa";
5582
+ import { execa as execa13 } from "execa";
5257
5583
  async function applyBump(params) {
5258
5584
  const pkgPath = join17(params.cwd, "package.json");
5259
5585
  let pkgJson;
@@ -5283,7 +5609,7 @@ async function applyBump(params) {
5283
5609
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5284
5610
  try {
5285
5611
  const [installCmd, ...installArgs] = params.installCommand;
5286
- await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
5612
+ await execa13(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
5287
5613
  } catch (err) {
5288
5614
  pkgJson[depField][params.packageName] = currentRange;
5289
5615
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
@@ -5296,13 +5622,13 @@ async function applyBump(params) {
5296
5622
  if (params.runTests) {
5297
5623
  try {
5298
5624
  const [testCmd, ...testArgs] = params.testCommand;
5299
- await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
5625
+ await execa13(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
5300
5626
  } catch (err) {
5301
5627
  pkgJson[depField][params.packageName] = currentRange;
5302
5628
  writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
5303
5629
  try {
5304
5630
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
5305
- await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
5631
+ await execa13(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
5306
5632
  } catch {
5307
5633
  }
5308
5634
  const message = err instanceof Error ? err.message : String(err);
@@ -5455,6 +5781,28 @@ async function updateOutdated(options = {}) {
5455
5781
  };
5456
5782
  finalizeEvidence(evidence);
5457
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;
5458
5806
  return {
5459
5807
  schemaVersion: "1.0",
5460
5808
  status,
@@ -5468,7 +5816,8 @@ async function updateOutdated(options = {}) {
5468
5816
  patchCount: 0,
5469
5817
  constraints,
5470
5818
  correlation,
5471
- provenance
5819
+ provenance,
5820
+ changeRequests
5472
5821
  };
5473
5822
  }
5474
5823
 
@@ -5562,7 +5911,8 @@ export {
5562
5911
  remediate,
5563
5912
  planRemediation,
5564
5913
  remediateFromScan,
5914
+ remediatePortfolio,
5565
5915
  updateOutdated,
5566
5916
  PACKAGE_VERSION
5567
5917
  };
5568
- //# sourceMappingURL=chunk-GYCZ6L3O.js.map
5918
+ //# sourceMappingURL=chunk-575GEUAY.js.map