autoremediator 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +20 -28
  2. package/dist/api/change-request/index.d.ts.map +1 -1
  3. package/dist/api/context.d.ts.map +1 -1
  4. package/dist/api/options-schema.d.ts +3 -0
  5. package/dist/api/options-schema.d.ts.map +1 -1
  6. package/dist/{chunk-FXOUJDQ3.js → chunk-7BORIBPY.js} +897 -162
  7. package/dist/cli/program.d.ts.map +1 -1
  8. package/dist/cli/runners.d.ts.map +1 -1
  9. package/dist/cli/types.d.ts +3 -0
  10. package/dist/cli/types.d.ts.map +1 -1
  11. package/dist/cli.js +14 -4
  12. package/dist/index.js +895 -160
  13. package/dist/index.js.map +1 -1
  14. package/dist/intelligence/sources/github-advisory.d.ts.map +1 -1
  15. package/dist/mcp/server.js +1 -1
  16. package/dist/openapi/server.js +1 -1
  17. package/dist/platform/evidence.d.ts +2 -0
  18. package/dist/platform/evidence.d.ts.map +1 -1
  19. package/dist/platform/package-manager/index.d.ts.map +1 -1
  20. package/dist/platform/supply-chain.d.ts.map +1 -1
  21. package/dist/platform/types.d.ts +32 -3
  22. package/dist/platform/types.d.ts.map +1 -1
  23. package/dist/remediation/local/fallback.d.ts +2 -0
  24. package/dist/remediation/local/fallback.d.ts.map +1 -1
  25. package/dist/remediation/local/options.d.ts +3 -0
  26. package/dist/remediation/local/options.d.ts.map +1 -1
  27. package/dist/remediation/local/primary-strategy.d.ts +2 -11
  28. package/dist/remediation/local/primary-strategy.d.ts.map +1 -1
  29. package/dist/remediation/local/run.d.ts.map +1 -1
  30. package/dist/remediation/pipeline-runtime/tool-wrappers.d.ts.map +1 -1
  31. package/dist/remediation/proof-of-fix/runner.d.ts +16 -0
  32. package/dist/remediation/proof-of-fix/runner.d.ts.map +1 -0
  33. package/dist/remediation/proof-of-fix/runner.test.d.ts +2 -0
  34. package/dist/remediation/proof-of-fix/runner.test.d.ts.map +1 -0
  35. package/dist/remediation/refactor/call-site-finder.d.ts +15 -0
  36. package/dist/remediation/refactor/call-site-finder.d.ts.map +1 -0
  37. package/dist/remediation/refactor/refactor-agent.d.ts +34 -0
  38. package/dist/remediation/refactor/refactor-agent.d.ts.map +1 -0
  39. package/dist/remediation/refactor/refactor.test.d.ts +2 -0
  40. package/dist/remediation/refactor/refactor.test.d.ts.map +1 -0
  41. package/dist/remediation/tools/apply-patch-file/index.d.ts +4 -1
  42. package/dist/remediation/tools/apply-patch-file/index.d.ts.map +1 -1
  43. package/dist/remediation/tools/apply-version-bump.d.ts +1 -0
  44. package/dist/remediation/tools/apply-version-bump.d.ts.map +1 -1
  45. package/dist/remediation/tools/check-inventory.d.ts.map +1 -1
  46. package/dist/remediation/tools/check-reachability.d.ts.map +1 -1
  47. package/dist/scanner/adapters/yarn-audit.d.ts.map +1 -1
  48. package/dist/scanner/lockfile-diff.d.ts.map +1 -1
  49. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -65,7 +65,10 @@ var OPTION_DESCRIPTIONS = {
65
65
  campaignMode: "Enable portfolio campaign mode to risk-rank targets before execution",
66
66
  escalationGraph: "Optional mapping from unresolved reasons to intended escalation actions",
67
67
  offlineIntelligence: "Skip all network calls to intelligence sources. Only local inventory and npm-registry version resolution proceed.",
68
- intelligenceSnapshotPath: "Path to a local JSON snapshot of pre-fetched CVE intelligence (keyed by CVE ID) used when offline mode is active."
68
+ intelligenceSnapshotPath: "Path to a local JSON snapshot of pre-fetched CVE intelligence (keyed by CVE ID) used when offline mode is active.",
69
+ proofOfFix: "If true, enforce dual-stage exploit reproduction proof-of-fix verification (assert failure pre-patch, assert pass post-patch)",
70
+ requireProofOfFix: "If true, reject patches and roll back if proof-of-fix verification fails",
71
+ breakingChangeRefactor: "If true, attempt agentic call-site refactoring when major version upgrades introduce breaking API changes"
69
72
  };
70
73
  function createConstraintSchemaProperties() {
71
74
  return {
@@ -87,7 +90,12 @@ function createConstraintSchemaProperties() {
87
90
  type: "boolean",
88
91
  description: OPTION_DESCRIPTIONS.enforceFrozenLockfile
89
92
  },
90
- workspace: { type: "string", description: OPTION_DESCRIPTIONS.workspace }
93
+ workspace: { type: "string", description: OPTION_DESCRIPTIONS.workspace },
94
+ requireProofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.requireProofOfFix },
95
+ enableBreakingChangeRefactor: {
96
+ type: "boolean",
97
+ description: OPTION_DESCRIPTIONS.breakingChangeRefactor
98
+ }
91
99
  };
92
100
  }
93
101
  function createRemediateOptionSchemaProperties(options) {
@@ -306,6 +314,14 @@ function createRemediateOptionSchemaProperties(options) {
306
314
  "validation-failed": {
307
315
  type: "string",
308
316
  enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
317
+ },
318
+ "proof-of-fix-failed": {
319
+ type: "string",
320
+ enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
321
+ },
322
+ "breaking-change-unresolved": {
323
+ type: "string",
324
+ enum: ["open-issue", "notify-channel", "create-draft-pr", "hold-branch", "none"]
309
325
  }
310
326
  },
311
327
  additionalProperties: false
@@ -314,6 +330,12 @@ function createRemediateOptionSchemaProperties(options) {
314
330
  intelligenceSnapshotPath: {
315
331
  type: "string",
316
332
  description: OPTION_DESCRIPTIONS.intelligenceSnapshotPath
333
+ },
334
+ proofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.proofOfFix },
335
+ requireProofOfFix: { type: "boolean", description: OPTION_DESCRIPTIONS.requireProofOfFix },
336
+ breakingChangeRefactor: {
337
+ type: "boolean",
338
+ description: OPTION_DESCRIPTIONS.breakingChangeRefactor
317
339
  }
318
340
  };
319
341
  }
@@ -3128,7 +3150,13 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
3128
3150
  } catch {
3129
3151
  continue;
3130
3152
  }
3131
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
3153
+ const refs = extractPackageReferences(
3154
+ filePath,
3155
+ content,
3156
+ packageName,
3157
+ tsconfig,
3158
+ importedDirectPackages
3159
+ );
3132
3160
  if (refs.length > 0) {
3133
3161
  hasPackageImport = true;
3134
3162
  }
@@ -3362,8 +3390,8 @@ function buildSbom(packages, vulnerableNames, results) {
3362
3390
 
3363
3391
  // src/remediation/tools/apply-version-bump.ts
3364
3392
  import { z as z4 } from "zod";
3365
- import { join as join14 } from "path";
3366
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
3393
+ import { join as join15 } from "path";
3394
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
3367
3395
 
3368
3396
  // src/platform/safe-exec.ts
3369
3397
  import { execa as execa6 } from "execa";
@@ -3429,7 +3457,7 @@ import semver3 from "semver";
3429
3457
  import { mkdir, rm as rm2 } from "fs/promises";
3430
3458
  import { join as join13 } from "path";
3431
3459
  async function sleep(ms) {
3432
- await new Promise((resolve5) => setTimeout(resolve5, ms));
3460
+ await new Promise((resolve6) => setTimeout(resolve6, ms));
3433
3461
  }
3434
3462
  async function acquireRepoLock(cwd, options = {}) {
3435
3463
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -3464,6 +3492,380 @@ async function withRepoLock(cwd, fn, options) {
3464
3492
  }
3465
3493
  }
3466
3494
 
3495
+ // src/remediation/refactor/call-site-finder.ts
3496
+ import { readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
3497
+ import { join as join14 } from "path";
3498
+ import { parseSync as parseSync2 } from "oxc-parser";
3499
+ var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
3500
+ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
3501
+ "node_modules",
3502
+ ".git",
3503
+ "dist",
3504
+ "build",
3505
+ "coverage",
3506
+ ".next",
3507
+ ".turbo",
3508
+ ".pnpm-store"
3509
+ ]);
3510
+ function* walkFiles(dir) {
3511
+ let entries;
3512
+ try {
3513
+ entries = readdirSync3(dir);
3514
+ } catch {
3515
+ return;
3516
+ }
3517
+ for (const entry of entries) {
3518
+ if (IGNORED_DIRS2.has(entry)) continue;
3519
+ const full = join14(dir, entry);
3520
+ let stat2;
3521
+ try {
3522
+ stat2 = statSync3(full);
3523
+ } catch {
3524
+ continue;
3525
+ }
3526
+ if (stat2.isDirectory()) {
3527
+ yield* walkFiles(full);
3528
+ } else {
3529
+ const ext = full.slice(full.lastIndexOf("."));
3530
+ if (SOURCE_EXTENSIONS2.has(ext)) {
3531
+ yield full;
3532
+ }
3533
+ }
3534
+ }
3535
+ }
3536
+ function findPackageCallSites(cwd, packageName) {
3537
+ const callSites = [];
3538
+ for (const filePath of walkFiles(cwd)) {
3539
+ let content;
3540
+ try {
3541
+ content = readFileSync11(filePath, "utf8");
3542
+ } catch {
3543
+ continue;
3544
+ }
3545
+ if (!content.includes(packageName)) {
3546
+ continue;
3547
+ }
3548
+ try {
3549
+ const parseResult = parseSync2(filePath, content);
3550
+ if (!parseResult.program) continue;
3551
+ const program = parseResult.program;
3552
+ const imports = [];
3553
+ const importSources = [];
3554
+ const localNames = /* @__PURE__ */ new Set();
3555
+ for (const statement of program.body) {
3556
+ if (statement.type === "ImportDeclaration" && statement.source?.value) {
3557
+ const importSource = statement.source.value;
3558
+ if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
3559
+ importSources.push(importSource);
3560
+ for (const spec of statement.specifiers || []) {
3561
+ const localName = spec.local?.name || "";
3562
+ if (!localName) continue;
3563
+ localNames.add(localName);
3564
+ if (spec.type === "ImportDefaultSpecifier") {
3565
+ imports.push({
3566
+ localName,
3567
+ importedName: "default",
3568
+ isDefault: true,
3569
+ isNamespace: false
3570
+ });
3571
+ } else if (spec.type === "ImportNamespaceSpecifier") {
3572
+ imports.push({
3573
+ localName,
3574
+ importedName: "*",
3575
+ isDefault: false,
3576
+ isNamespace: true
3577
+ });
3578
+ } else if (spec.type === "ImportSpecifier") {
3579
+ imports.push({
3580
+ localName,
3581
+ importedName: spec.imported?.name || localName,
3582
+ isDefault: false,
3583
+ isNamespace: false
3584
+ });
3585
+ }
3586
+ }
3587
+ }
3588
+ }
3589
+ }
3590
+ if (imports.length > 0 || importSources.length > 0) {
3591
+ const lines = content.split("\n");
3592
+ const referencedLines = [];
3593
+ lines.forEach((line, index) => {
3594
+ for (const local of localNames) {
3595
+ const regex = new RegExp(`\\b${local}\\b`);
3596
+ if (regex.test(line)) {
3597
+ referencedLines.push(index + 1);
3598
+ break;
3599
+ }
3600
+ }
3601
+ });
3602
+ callSites.push({
3603
+ filePath,
3604
+ sourceContent: content,
3605
+ importSources,
3606
+ imports,
3607
+ referencedLines
3608
+ });
3609
+ }
3610
+ } catch {
3611
+ }
3612
+ }
3613
+ return callSites;
3614
+ }
3615
+
3616
+ // src/remediation/refactor/refactor-agent.ts
3617
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
3618
+ function parseCompilerDiagnostics(diagnosticError) {
3619
+ const diagnostics = [];
3620
+ const lines = diagnosticError.split("\n");
3621
+ for (const line of lines) {
3622
+ const trimmed = line.trim();
3623
+ if (!trimmed) continue;
3624
+ const match = trimmed.match(
3625
+ /(?:([^\s:(]+)[:(](\d+)[:,](\d+)\)?:?\s*(?:-\s*)?(?:error\s+)?(TS\d+)?:?\s*(.*))/i
3626
+ );
3627
+ if (match) {
3628
+ const [, filePath, lineStr, colStr, code, message] = match;
3629
+ const diag = {
3630
+ filePath,
3631
+ line: lineStr ? parseInt(lineStr, 10) : void 0,
3632
+ column: colStr ? parseInt(colStr, 10) : void 0,
3633
+ code,
3634
+ message: message || trimmed
3635
+ };
3636
+ const suggestionMatch = diag.message.match(/Did you mean ['"]([a-zA-Z0-9_$]+)['"]\?/i);
3637
+ if (suggestionMatch) {
3638
+ diag.suggestedSymbol = suggestionMatch[1];
3639
+ }
3640
+ const missingMatch = diag.message.match(/has no exported member ['"]([a-zA-Z0-9_$]+)['"]/i) || diag.message.match(/Property ['"]([a-zA-Z0-9_$]+)['"] does not exist/i);
3641
+ if (missingMatch) {
3642
+ diag.missingMember = missingMatch[1];
3643
+ }
3644
+ diagnostics.push(diag);
3645
+ }
3646
+ }
3647
+ return diagnostics;
3648
+ }
3649
+ function revertRefactorSnapshots(snapshots) {
3650
+ for (const [filePath, content] of Object.entries(snapshots)) {
3651
+ try {
3652
+ writeFileSync3(filePath, content, "utf8");
3653
+ } catch {
3654
+ }
3655
+ }
3656
+ }
3657
+ var KNOWN_MIGRATIONS = {
3658
+ rimraf: [
3659
+ {
3660
+ detect: (content, diag) => diag.includes("sync") && content.includes("rimraf.sync("),
3661
+ transform: (content) => {
3662
+ let res = content.replace(/import\s+rimraf\s+from\s+['"]rimraf['"];?/g, "");
3663
+ res = `import { rimrafSync } from "rimraf";
3664
+ ` + res;
3665
+ return res.replace(/rimraf\.sync\(/g, "rimrafSync(");
3666
+ }
3667
+ }
3668
+ ],
3669
+ uuid: [
3670
+ {
3671
+ detect: (content, diag) => diag.includes("v4") && content.includes("uuid.v4("),
3672
+ transform: (content) => {
3673
+ let res = content.replace(/import\s+uuid\s+from\s+['"]uuid['"];?/g, "");
3674
+ res = `import { v4 as uuidv4 } from "uuid";
3675
+ ` + res;
3676
+ return res.replace(/uuid\.v4\(/g, "uuidv4(");
3677
+ }
3678
+ },
3679
+ {
3680
+ // Subpath import migration: `import v4 from "uuid/v4"` -> `import { v4 } from "uuid"`
3681
+ detect: (content) => /from\s+['"]uuid\/v4['"]/.test(content),
3682
+ transform: (content) => content.replace(
3683
+ /import\s+([a-zA-Z0-9_$]+)\s+from\s+['"]uuid\/v4['"];?/g,
3684
+ `import { v4 as $1 } from "uuid";`
3685
+ )
3686
+ }
3687
+ ],
3688
+ glob: [
3689
+ {
3690
+ detect: (content, diag) => diag.includes("sync") && content.includes("glob.sync("),
3691
+ transform: (content) => {
3692
+ let res = content.replace(/import\s+glob\s+from\s+['"]glob['"];?/g, "");
3693
+ res = `import { globSync } from "glob";
3694
+ ` + res;
3695
+ return res.replace(/glob\.sync\(/g, "globSync(");
3696
+ }
3697
+ }
3698
+ ],
3699
+ mkdirp: [
3700
+ {
3701
+ detect: (content, diag) => diag.includes("sync") && content.includes("mkdirp.sync("),
3702
+ transform: (content) => {
3703
+ let res = content.replace(/import\s+mkdirp\s+from\s+['"]mkdirp['"];?/g, "");
3704
+ res = `import { mkdirpSync } from "mkdirp";
3705
+ ` + res;
3706
+ return res.replace(/mkdirp\.sync\(/g, "mkdirpSync(");
3707
+ }
3708
+ }
3709
+ ]
3710
+ };
3711
+ function escapeRegex(str) {
3712
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3713
+ }
3714
+ function transformFileContent(content, packageName, diagnosticError, filePath) {
3715
+ let modified = false;
3716
+ let newContent = content;
3717
+ const migrations = KNOWN_MIGRATIONS[packageName];
3718
+ if (migrations) {
3719
+ for (const mig of migrations) {
3720
+ if (mig.detect(newContent, diagnosticError)) {
3721
+ const transformed = mig.transform(newContent);
3722
+ if (transformed !== newContent) {
3723
+ newContent = transformed;
3724
+ modified = true;
3725
+ }
3726
+ }
3727
+ }
3728
+ }
3729
+ if (diagnosticError.includes("no default export") || diagnosticError.includes("has no exported member 'default'") || diagnosticError.includes("has no exported member default") || diagnosticError.includes("TS2614") || diagnosticError.includes("TS1192")) {
3730
+ const defaultImportRegex = new RegExp(
3731
+ `import\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3732
+ "g"
3733
+ );
3734
+ if (defaultImportRegex.test(newContent)) {
3735
+ newContent = newContent.replace(
3736
+ defaultImportRegex,
3737
+ `import * as $1 from "${packageName}"`
3738
+ );
3739
+ modified = true;
3740
+ }
3741
+ }
3742
+ if (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259")) {
3743
+ const starImportRegex = new RegExp(
3744
+ `import\\s+\\*\\s+as\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3745
+ "g"
3746
+ );
3747
+ if (starImportRegex.test(newContent)) {
3748
+ newContent = newContent.replace(
3749
+ starImportRegex,
3750
+ `import $1 from "${packageName}"`
3751
+ );
3752
+ modified = true;
3753
+ }
3754
+ }
3755
+ const diagnostics = parseCompilerDiagnostics(diagnosticError);
3756
+ for (const diag of diagnostics) {
3757
+ if (diag.missingMember && diag.suggestedSymbol) {
3758
+ const missingMember = diag.missingMember;
3759
+ const suggestedSymbol = diag.suggestedSymbol;
3760
+ if (filePath && diag.filePath && filePath.endsWith(diag.filePath) && diag.line && diag.line > 0) {
3761
+ const fileLines = newContent.split("\n");
3762
+ const idx = diag.line - 1;
3763
+ if (idx >= 0 && idx < fileLines.length) {
3764
+ const line = fileLines[idx];
3765
+ const replaced = line.replace(new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g"), `.${suggestedSymbol}`).replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol);
3766
+ if (replaced !== line) {
3767
+ fileLines[idx] = replaced;
3768
+ newContent = fileLines.join("\n");
3769
+ modified = true;
3770
+ }
3771
+ }
3772
+ } else {
3773
+ const memberRegex = new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g");
3774
+ if (memberRegex.test(newContent)) {
3775
+ newContent = newContent.replace(memberRegex, `.${suggestedSymbol}`);
3776
+ modified = true;
3777
+ }
3778
+ const importPattern = new RegExp(
3779
+ `import\\s*\\{[^}]*\\b${escapeRegex(missingMember)}\\b[^}]*\\}\\s*from\\s*['"]${escapeRegex(packageName)}['"]`,
3780
+ "g"
3781
+ );
3782
+ if (importPattern.test(newContent)) {
3783
+ newContent = newContent.replace(
3784
+ importPattern,
3785
+ (match) => match.replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol)
3786
+ );
3787
+ modified = true;
3788
+ }
3789
+ }
3790
+ }
3791
+ }
3792
+ const lineDiags = diagnostics.filter(
3793
+ (d) => Boolean(filePath && d.filePath && filePath.endsWith(d.filePath) && typeof d.line === "number" && d.line > 0)
3794
+ ).sort((a, b) => b.line - a.line);
3795
+ const processedLines = /* @__PURE__ */ new Set();
3796
+ for (const diag of lineDiags) {
3797
+ if (processedLines.has(diag.line)) continue;
3798
+ processedLines.add(diag.line);
3799
+ const fileLines = newContent.split("\n");
3800
+ const targetLineIdx = diag.line - 1;
3801
+ if (targetLineIdx >= 0 && targetLineIdx < fileLines.length) {
3802
+ const prevLine = targetLineIdx > 0 ? fileLines[targetLineIdx - 1] : "";
3803
+ if (!prevLine?.includes("@ts-expect-error") && !fileLines[targetLineIdx]?.includes("@ts-expect-error")) {
3804
+ const indent = fileLines[targetLineIdx]?.match(/^\s*/)?.[0] || "";
3805
+ fileLines.splice(
3806
+ targetLineIdx,
3807
+ 0,
3808
+ `${indent}// @ts-expect-error [autoremediator-refactor]: breaking change in ${packageName}`
3809
+ );
3810
+ newContent = fileLines.join("\n");
3811
+ modified = true;
3812
+ }
3813
+ }
3814
+ }
3815
+ return { modified, newContent };
3816
+ }
3817
+ async function refactorConsumerCallSites(params) {
3818
+ const { packageName, diagnosticError, callSites } = params;
3819
+ if (callSites.length === 0) {
3820
+ return {
3821
+ applied: false,
3822
+ refactoredFiles: [],
3823
+ snapshots: {},
3824
+ error: `No consumer call sites found for "${packageName}".`
3825
+ };
3826
+ }
3827
+ const snapshots = {};
3828
+ const refactoredFiles = [];
3829
+ try {
3830
+ for (const site of callSites) {
3831
+ const originalContent = site.sourceContent || readFileSync12(site.filePath, "utf8");
3832
+ snapshots[site.filePath] = originalContent;
3833
+ const { modified, newContent } = transformFileContent(
3834
+ originalContent,
3835
+ packageName,
3836
+ diagnosticError,
3837
+ site.filePath
3838
+ );
3839
+ if (modified && newContent !== originalContent) {
3840
+ writeFileSync3(site.filePath, newContent, "utf8");
3841
+ refactoredFiles.push(site.filePath);
3842
+ }
3843
+ }
3844
+ if (refactoredFiles.length === 0) {
3845
+ return {
3846
+ applied: false,
3847
+ refactoredFiles: [],
3848
+ snapshots,
3849
+ error: "No automated codemod pattern matched the diagnostic error."
3850
+ };
3851
+ }
3852
+ return {
3853
+ applied: true,
3854
+ refactoredFiles,
3855
+ snapshots
3856
+ };
3857
+ } catch (err) {
3858
+ revertRefactorSnapshots(snapshots);
3859
+ const message = err instanceof Error ? err.message : String(err);
3860
+ return {
3861
+ applied: false,
3862
+ refactoredFiles: [],
3863
+ snapshots: {},
3864
+ error: `Failed to apply breaking change refactoring: ${message}`
3865
+ };
3866
+ }
3867
+ }
3868
+
3467
3869
  // src/remediation/tools/apply-version-bump.ts
3468
3870
  var applyVersionBumpTool = defineTool({
3469
3871
  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.",
@@ -3480,7 +3882,8 @@ var applyVersionBumpTool = defineTool({
3480
3882
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
3481
3883
  installPreferOffline: z4.boolean().optional(),
3482
3884
  enforceFrozenLockfile: z4.boolean().optional(),
3483
- workspace: z4.string().optional()
3885
+ workspace: z4.string().optional(),
3886
+ breakingChangeRefactor: z4.boolean().optional().describe("If true, attempt agentic call-site refactoring when validation fails after major upgrade")
3484
3887
  }),
3485
3888
  execute: async ({
3486
3889
  cwd,
@@ -3495,10 +3898,11 @@ var applyVersionBumpTool = defineTool({
3495
3898
  installMode,
3496
3899
  installPreferOffline,
3497
3900
  enforceFrozenLockfile,
3498
- workspace
3901
+ workspace,
3902
+ breakingChangeRefactor
3499
3903
  }) => {
3500
3904
  const pm = packageManager ?? detectPackageManager(cwd);
3501
- let pkgPath = join14(cwd, "package.json");
3905
+ let pkgPath = join15(cwd, "package.json");
3502
3906
  const loadedPolicy = loadPolicy(cwd, policy);
3503
3907
  const commandConstraints = {
3504
3908
  ...loadedPolicy.constraints,
@@ -3533,10 +3937,10 @@ var applyVersionBumpTool = defineTool({
3533
3937
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
3534
3938
  };
3535
3939
  }
3536
- pkgPath = join14(cwd, "package.json");
3940
+ pkgPath = join15(cwd, "package.json");
3537
3941
  let pkgJson;
3538
3942
  try {
3539
- pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
3943
+ pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
3540
3944
  } catch {
3541
3945
  return {
3542
3946
  packageName,
@@ -3597,7 +4001,7 @@ var applyVersionBumpTool = defineTool({
3597
4001
  }
3598
4002
  return withRepoLock(cwd, async () => {
3599
4003
  pkgJson[depField][packageName] = newRange;
3600
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4004
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3601
4005
  try {
3602
4006
  const [installCmd, ...installArgs] = effectiveInstallCommand;
3603
4007
  await safeExeca(installCmd, installArgs, {
@@ -3606,7 +4010,7 @@ var applyVersionBumpTool = defineTool({
3606
4010
  });
3607
4011
  } catch (err) {
3608
4012
  pkgJson[depField][packageName] = currentRange;
3609
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4013
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3610
4014
  const message = err instanceof Error ? err.message : String(err);
3611
4015
  return {
3612
4016
  packageName,
@@ -3619,67 +4023,98 @@ var applyVersionBumpTool = defineTool({
3619
4023
  message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3620
4024
  };
3621
4025
  }
3622
- if (runTests) {
3623
- try {
3624
- const [testCmd, ...testArgs] = effectiveTestCommand;
3625
- await safeExeca(testCmd, testArgs, {
3626
- cwd,
3627
- stdio: "pipe"
3628
- });
3629
- } catch (err) {
3630
- pkgJson[depField][packageName] = currentRange;
3631
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4026
+ const shouldAttemptRefactor = Boolean(
4027
+ breakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor
4028
+ );
4029
+ async function runValidationSteps() {
4030
+ if (runTests) {
3632
4031
  try {
3633
- const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3634
- await safeExeca(rollbackCmd, rollbackArgs, {
3635
- cwd,
3636
- stdio: "pipe"
3637
- });
3638
- } catch {
4032
+ const [testCmd, ...testArgs] = effectiveTestCommand;
4033
+ await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
4034
+ } catch (err) {
4035
+ const execErr = err;
4036
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
4037
+ return {
4038
+ passed: false,
4039
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
4040
+ };
3639
4041
  }
3640
- const message = err instanceof Error ? err.message : String(err);
3641
- return {
4042
+ }
4043
+ if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
4044
+ try {
4045
+ const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
4046
+ await safeExeca(tcCmd, tcArgs, { cwd, stdio: "pipe" });
4047
+ } catch (err) {
4048
+ const execErr = err;
4049
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
4050
+ return {
4051
+ passed: false,
4052
+ isTypecheck: true,
4053
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
4054
+ };
4055
+ }
4056
+ }
4057
+ return { passed: true };
4058
+ }
4059
+ let validationResult = await runValidationSteps();
4060
+ let refactoredFilesList;
4061
+ if (!validationResult.passed && shouldAttemptRefactor) {
4062
+ const callSites = findPackageCallSites(cwd, packageName);
4063
+ if (callSites.length > 0) {
4064
+ const refactorAttempt = await refactorConsumerCallSites({
4065
+ cwd,
3642
4066
  packageName,
3643
- strategy: "version-bump",
3644
4067
  fromVersion,
3645
4068
  toVersion,
3646
- applied: false,
3647
- dryRun: false,
3648
- unresolvedReason: "validation-failed",
3649
- message: `${effectiveTestCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3650
- };
4069
+ diagnosticError: validationResult.error || "",
4070
+ callSites
4071
+ });
4072
+ if (refactorAttempt.applied) {
4073
+ const postRefactorValidation = await runValidationSteps();
4074
+ if (postRefactorValidation.passed) {
4075
+ validationResult = { passed: true };
4076
+ refactoredFilesList = refactorAttempt.refactoredFiles;
4077
+ } else {
4078
+ revertRefactorSnapshots(refactorAttempt.snapshots);
4079
+ }
4080
+ }
3651
4081
  }
3652
4082
  }
3653
- if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
4083
+ if (!validationResult.passed) {
4084
+ pkgJson[depField][packageName] = currentRange;
4085
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3654
4086
  try {
3655
- const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
3656
- await safeExeca(tcCmd, tcArgs, {
4087
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
4088
+ await safeExeca(rollbackCmd, rollbackArgs, {
3657
4089
  cwd,
3658
4090
  stdio: "pipe"
3659
4091
  });
3660
- } catch (err) {
3661
- pkgJson[depField][packageName] = currentRange;
3662
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3663
- try {
3664
- const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3665
- await safeExeca(rollbackCmd, rollbackArgs, {
3666
- cwd,
3667
- stdio: "pipe"
3668
- });
3669
- } catch {
3670
- }
3671
- const message = err instanceof Error ? err.message : String(err);
3672
- return {
3673
- packageName,
3674
- strategy: "version-bump",
3675
- fromVersion,
3676
- toVersion,
3677
- applied: false,
3678
- dryRun: false,
3679
- unresolvedReason: "validation-failed",
3680
- message: `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")}) after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3681
- };
4092
+ } catch {
3682
4093
  }
4094
+ const failureMessage = validationResult.error || "Unknown validation error";
4095
+ const prefix2 = validationResult.isTypecheck ? `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")})` : `${effectiveTestCommand.join(" ")} failed`;
4096
+ return {
4097
+ packageName,
4098
+ strategy: "version-bump",
4099
+ fromVersion,
4100
+ toVersion,
4101
+ applied: false,
4102
+ dryRun: false,
4103
+ unresolvedReason: shouldAttemptRefactor ? "breaking-change-unresolved" : "validation-failed",
4104
+ message: `${prefix2} after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${failureMessage}`
4105
+ };
4106
+ }
4107
+ if (refactoredFilesList && refactoredFilesList.length > 0) {
4108
+ return {
4109
+ packageName,
4110
+ strategy: "breaking-change-refactor",
4111
+ fromVersion,
4112
+ toVersion,
4113
+ applied: true,
4114
+ dryRun: false,
4115
+ refactoredFiles: refactoredFilesList,
4116
+ message: `Upgraded "${packageName}" from ${fromVersion} to ${toVersion} and refactored ${refactoredFilesList.length} call-site file(s) across breaking API changes.`
4117
+ };
3683
4118
  }
3684
4119
  let dedupeNote = "";
3685
4120
  if (effectiveDedupeCommand.length > 0) {
@@ -3709,8 +4144,8 @@ var applyVersionBumpTool = defineTool({
3709
4144
 
3710
4145
  // src/remediation/tools/apply-package-override/index.ts
3711
4146
  import { z as z5 } from "zod";
3712
- import { join as join15 } from "path";
3713
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
4147
+ import { join as join16 } from "path";
4148
+ import { existsSync as existsSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
3714
4149
  import semver4 from "semver";
3715
4150
 
3716
4151
  // src/remediation/tools/apply-package-override/helpers.ts
@@ -3853,9 +4288,9 @@ var applyPackageOverrideTool = defineTool({
3853
4288
  workspace
3854
4289
  }) => {
3855
4290
  const pm = packageManager ?? detectPackageManager(cwd);
3856
- const pkgPath = join15(cwd, "package.json");
3857
- const denoJsonPath = join15(cwd, "deno.json");
3858
- const isDenoNative = pm === "deno" && !existsSync11(pkgPath);
4291
+ const pkgPath = join16(cwd, "package.json");
4292
+ const denoJsonPath = join16(cwd, "deno.json");
4293
+ const isDenoNative = pm === "deno" && !existsSync12(pkgPath);
3859
4294
  const loadedPolicy = loadPolicy(cwd, policy);
3860
4295
  const commandConstraints = {
3861
4296
  ...loadedPolicy.constraints,
@@ -3897,7 +4332,7 @@ var applyPackageOverrideTool = defineTool({
3897
4332
  if (isDenoNative) {
3898
4333
  let denoJson;
3899
4334
  try {
3900
- denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
4335
+ denoJson = JSON.parse(readFileSync14(denoJsonPath, "utf8"));
3901
4336
  } catch {
3902
4337
  return {
3903
4338
  packageName,
@@ -3943,13 +4378,13 @@ var applyPackageOverrideTool = defineTool({
3943
4378
  }
3944
4379
  return withRepoLock(cwd, async () => {
3945
4380
  setDenoJsonImportValue(denoJson, overrideSelector, toVersion);
3946
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4381
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3947
4382
  try {
3948
4383
  const [installCmd, ...installArgs] = installCommand;
3949
4384
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
3950
4385
  } catch (err) {
3951
4386
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
3952
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4387
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3953
4388
  const message = err instanceof Error ? err.message : String(err);
3954
4389
  return {
3955
4390
  packageName,
@@ -3968,7 +4403,7 @@ var applyPackageOverrideTool = defineTool({
3968
4403
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3969
4404
  } catch (err) {
3970
4405
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
3971
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
4406
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3972
4407
  try {
3973
4408
  const [rollbackCmd, ...rollbackArgs] = installCommand;
3974
4409
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -4000,7 +4435,7 @@ var applyPackageOverrideTool = defineTool({
4000
4435
  }
4001
4436
  let pkgJson;
4002
4437
  try {
4003
- pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
4438
+ pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
4004
4439
  } catch {
4005
4440
  return {
4006
4441
  packageName,
@@ -4030,13 +4465,13 @@ var applyPackageOverrideTool = defineTool({
4030
4465
  }
4031
4466
  return withRepoLock(cwd, async () => {
4032
4467
  setOverrideValue(pkgJson, pm, overrideSelector, toVersion);
4033
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4468
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4034
4469
  try {
4035
4470
  const [installCmd, ...installArgs] = installCommand;
4036
4471
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
4037
4472
  } catch (err) {
4038
4473
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
4039
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4474
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4040
4475
  const message = err instanceof Error ? err.message : String(err);
4041
4476
  return {
4042
4477
  packageName,
@@ -4055,7 +4490,7 @@ var applyPackageOverrideTool = defineTool({
4055
4490
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
4056
4491
  } catch (err) {
4057
4492
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
4058
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4493
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4059
4494
  try {
4060
4495
  const [rollbackCmd, ...rollbackArgs] = installCommand;
4061
4496
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -4139,7 +4574,9 @@ async function verifyPackageTrust(packageName, targetVersion, policy) {
4139
4574
  }
4140
4575
  const versionData = packument.versions?.[targetVersion];
4141
4576
  const scripts = versionData?.scripts ?? {};
4142
- const installScriptKeys = ["preinstall", "install", "postinstall"].filter((k) => Boolean(scripts[k]));
4577
+ const installScriptKeys = ["preinstall", "install", "postinstall"].filter(
4578
+ (k) => Boolean(scripts[k])
4579
+ );
4143
4580
  const hasInstallScripts = installScriptKeys.length > 0;
4144
4581
  if (policy.blockInstallScripts && hasInstallScripts) {
4145
4582
  return {
@@ -4149,7 +4586,9 @@ async function verifyPackageTrust(packageName, targetVersion, policy) {
4149
4586
  reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
4150
4587
  };
4151
4588
  }
4152
- const hasProvenance = Boolean(versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url);
4589
+ const hasProvenance = Boolean(
4590
+ versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url
4591
+ );
4153
4592
  if (policy.requireProvenance && !hasProvenance) {
4154
4593
  return {
4155
4594
  trusted: false,
@@ -4237,7 +4676,11 @@ async function resolvePrimaryResult(params) {
4237
4676
  }
4238
4677
  };
4239
4678
  }
4240
- const trust2 = await verifyPackageTrust(pkg.name, safeUpgrade2.safeVersion, constraints.supplyChainPolicy);
4679
+ const trust2 = await verifyPackageTrust(
4680
+ pkg.name,
4681
+ safeUpgrade2.safeVersion,
4682
+ constraints.supplyChainPolicy
4683
+ );
4241
4684
  if (!trust2.trusted) {
4242
4685
  return {
4243
4686
  steps: 1,
@@ -4308,7 +4751,11 @@ async function resolvePrimaryResult(params) {
4308
4751
  }
4309
4752
  };
4310
4753
  }
4311
- const trust = await verifyPackageTrust(pkg.name, safeUpgrade.safeVersion, constraints.supplyChainPolicy);
4754
+ const trust = await verifyPackageTrust(
4755
+ pkg.name,
4756
+ safeUpgrade.safeVersion,
4757
+ constraints.supplyChainPolicy
4758
+ );
4312
4759
  if (!trust.trusted) {
4313
4760
  return {
4314
4761
  steps: 1,
@@ -4337,7 +4784,8 @@ async function resolvePrimaryResult(params) {
4337
4784
  installMode: constraints.installMode,
4338
4785
  installPreferOffline: constraints.installPreferOffline,
4339
4786
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
4340
- workspace: constraints.workspace
4787
+ workspace: constraints.workspace,
4788
+ breakingChangeRefactor: constraints.enableBreakingChangeRefactor
4341
4789
  });
4342
4790
  return {
4343
4791
  steps: 2,
@@ -4348,7 +4796,7 @@ async function resolvePrimaryResult(params) {
4348
4796
  // src/remediation/tools/fetch-package-source.ts
4349
4797
  import { z as z6 } from "zod";
4350
4798
  import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
4351
- import { join as join16 } from "path";
4799
+ import { join as join17 } from "path";
4352
4800
  import { tmpdir } from "os";
4353
4801
  import { execa as execa7 } from "execa";
4354
4802
  var fetchPackageSourceTool = defineTool({
@@ -4368,25 +4816,25 @@ var fetchPackageSourceTool = defineTool({
4368
4816
  if (safePatterns.length === 0) {
4369
4817
  return { success: false, error: "No valid file patterns provided." };
4370
4818
  }
4371
- const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
4372
- const extractDir = join16(tempBaseDir, "out");
4819
+ const tempBaseDir = await mkdtemp(join17(tmpdir(), "autoremediator-pkg-"));
4820
+ const extractDir = join17(tempBaseDir, "out");
4373
4821
  try {
4374
4822
  const scopedName = packageName.split("/").pop();
4375
4823
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
4376
4824
  await mkdir2(tempBaseDir, { recursive: true });
4377
- const tarballPath = join16(tempBaseDir, "package.tgz");
4825
+ const tarballPath = join17(tempBaseDir, "package.tgz");
4378
4826
  await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
4379
4827
  await mkdir2(extractDir, { recursive: true });
4380
4828
  await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
4381
4829
  const extractedContents = await readdir2(extractDir);
4382
- const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
4830
+ const packageRootDir = extractedContents.includes("package") ? join17(extractDir, "package") : extractDir;
4383
4831
  const sourceCode = {};
4384
4832
  async function walkDir(dir, relativeBase) {
4385
4833
  try {
4386
4834
  const files = await readdir2(dir, { withFileTypes: true });
4387
4835
  for (const file of files) {
4388
- const fullPath = join16(dir, file.name);
4389
- const relPath = join16(relativeBase, file.name);
4836
+ const fullPath = join17(dir, file.name);
4837
+ const relPath = join17(relativeBase, file.name);
4390
4838
  if (file.isDirectory()) {
4391
4839
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
4392
4840
  file.name
@@ -4516,7 +4964,7 @@ Important:
4516
4964
  }
4517
4965
 
4518
4966
  // src/remediation/tools/generate-patch/helpers.ts
4519
- import { parseSync as parseSync2 } from "oxc-parser";
4967
+ import { parseSync as parseSync3 } from "oxc-parser";
4520
4968
  import YAML from "yaml";
4521
4969
  function parseLlmAnalysisResponse(text) {
4522
4970
  try {
@@ -4539,7 +4987,7 @@ function validatePatchedCodeSyntax(filePath, code) {
4539
4987
  const lowerPath = filePath.toLowerCase();
4540
4988
  if (/\.[cm]?[jt]sx?$/i.test(lowerPath)) {
4541
4989
  try {
4542
- const res = parseSync2(filePath, code);
4990
+ const res = parseSync3(filePath, code);
4543
4991
  if (res.errors && res.errors.length > 0) {
4544
4992
  const errorMsg = res.errors.map((e) => e.message).join("; ");
4545
4993
  return {
@@ -4811,15 +5259,124 @@ var generatePatchTool = defineTool({
4811
5259
  // src/remediation/tools/apply-patch-file/index.ts
4812
5260
  import { z as z8 } from "zod";
4813
5261
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
4814
- import { join as join18 } from "path";
5262
+ import { existsSync as existsSync14 } from "fs";
5263
+ import { isAbsolute as isAbsolute4, join as join19 } from "path";
4815
5264
  import { execa as execa9 } from "execa";
4816
5265
 
5266
+ // src/remediation/proof-of-fix/runner.ts
5267
+ async function runProofOfFixPhase(options) {
5268
+ const { stage, testFilePath, cwd, timeoutMs = 15e3 } = options;
5269
+ const started = Date.now();
5270
+ const isTs = testFilePath.endsWith(".ts");
5271
+ const nodeArgs = isTs ? ["--experimental-strip-types", testFilePath] : [testFilePath];
5272
+ try {
5273
+ const result = await safeExeca("node", nodeArgs, {
5274
+ cwd,
5275
+ timeoutMs,
5276
+ stdio: "pipe"
5277
+ });
5278
+ const durationMs = Date.now() - started;
5279
+ const output = `${result.stdout || ""}
5280
+ ${result.stderr || ""}`.trim();
5281
+ if (stage === "negative") {
5282
+ return {
5283
+ executed: true,
5284
+ passed: false,
5285
+ exitCode: 0,
5286
+ output,
5287
+ durationMs,
5288
+ error: "Exploit reproduction test passed on vulnerable target (failed to reproduce vulnerability)."
5289
+ };
5290
+ }
5291
+ return {
5292
+ executed: true,
5293
+ passed: true,
5294
+ exitCode: 0,
5295
+ output,
5296
+ durationMs
5297
+ };
5298
+ } catch (err) {
5299
+ const durationMs = Date.now() - started;
5300
+ const execaErr = err;
5301
+ const exitCode = typeof execaErr.exitCode === "number" ? execaErr.exitCode : 1;
5302
+ const errorMsg = execaErr.message || String(err);
5303
+ const output = [execaErr.stdout, execaErr.stderr, errorMsg].filter(Boolean).join("\n").trim();
5304
+ if (stage === "negative") {
5305
+ if (execaErr.timedOut || errorMsg.includes("timed out")) {
5306
+ return {
5307
+ executed: true,
5308
+ passed: false,
5309
+ exitCode,
5310
+ output,
5311
+ durationMs,
5312
+ error: "Exploit reproduction test timed out (execution hung)."
5313
+ };
5314
+ }
5315
+ const isHarnessFailure = exitCode === 127 || errorMsg.includes("ENOENT") || output.includes("bad option:") || output.includes("Cannot find module") && output.includes(testFilePath);
5316
+ if (isHarnessFailure) {
5317
+ return {
5318
+ executed: false,
5319
+ passed: false,
5320
+ exitCode,
5321
+ output,
5322
+ durationMs,
5323
+ error: `Reproduction test failed to execute (harness error): ${errorMsg}`
5324
+ };
5325
+ }
5326
+ return {
5327
+ executed: true,
5328
+ passed: true,
5329
+ exitCode,
5330
+ output,
5331
+ durationMs
5332
+ };
5333
+ }
5334
+ return {
5335
+ executed: true,
5336
+ passed: false,
5337
+ exitCode,
5338
+ output,
5339
+ durationMs,
5340
+ error: `Reproduction test failed on patched target: ${errorMsg}`
5341
+ };
5342
+ }
5343
+ }
5344
+ function assembleProofOfFixVerification(params) {
5345
+ const { reproductionTestPath, negativePhase, positivePhase, regressionPhase } = params;
5346
+ const regPhase = regressionPhase ?? {
5347
+ executed: false,
5348
+ passed: true
5349
+ };
5350
+ let status = "verified";
5351
+ let failureReason;
5352
+ if (!negativePhase.passed) {
5353
+ status = "failed";
5354
+ failureReason = negativePhase.error || "negative-phase-failed";
5355
+ } else if (!positivePhase.passed) {
5356
+ status = "failed";
5357
+ failureReason = positivePhase.error || "positive-phase-failed";
5358
+ } else if (regPhase.executed && !regPhase.passed) {
5359
+ status = "failed";
5360
+ failureReason = regPhase.error || "regression-phase-failed";
5361
+ }
5362
+ const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
5363
+ return {
5364
+ status,
5365
+ reproductionTestPath,
5366
+ negativePhase,
5367
+ positivePhase,
5368
+ regressionPhase: regPhase,
5369
+ durationMs,
5370
+ failureReason
5371
+ };
5372
+ }
5373
+
4817
5374
  // src/remediation/tools/apply-patch-file/helpers.ts
4818
- import { existsSync as existsSync12 } from "fs";
5375
+ import { existsSync as existsSync13 } from "fs";
4819
5376
  import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
4820
5377
  import { createHash } from "crypto";
4821
5378
  import { tmpdir as tmpdir2 } from "os";
4822
- import { join as join17 } from "path";
5379
+ import { join as join18 } from "path";
4823
5380
  import { execa as execa8 } from "execa";
4824
5381
  async function resolvePatchMode(packageManager, cwd) {
4825
5382
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
@@ -4854,7 +5411,7 @@ async function writePatchManifest(manifestFilePath, artifact) {
4854
5411
  await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4855
5412
  }
4856
5413
  async function configurePatchPackagePostinstall(cwd, packageManager) {
4857
- const pkgJsonPath = join17(cwd, "package.json");
5414
+ const pkgJsonPath = join18(cwd, "package.json");
4858
5415
  let pkgJson;
4859
5416
  try {
4860
5417
  pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
@@ -4894,7 +5451,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4894
5451
  return { success: true };
4895
5452
  }
4896
5453
  async function capturePackageJsonSnapshot(cwd) {
4897
- const path = join17(cwd, "package.json");
5454
+ const path = join18(cwd, "package.json");
4898
5455
  try {
4899
5456
  const content = await readFile5(path, "utf8");
4900
5457
  return { path, content };
@@ -4966,8 +5523,8 @@ ${createResult.stderr}`);
4966
5523
  error: `Could not determine native patch directory for ${packageSpec}.`
4967
5524
  };
4968
5525
  }
4969
- const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
4970
- const tempPatchFile = join17(tempPatchDir, "change.patch");
5526
+ const tempPatchDir = await mkdtemp2(join18(tmpdir2(), "autoremediator-native-patch-"));
5527
+ const tempPatchFile = join18(tempPatchDir, "change.patch");
4971
5528
  try {
4972
5529
  await writeFile2(tempPatchFile, patchContent, "utf8");
4973
5530
  await execa8("patch", ["-p1", "-i", tempPatchFile], {
@@ -4998,12 +5555,12 @@ ${createResult.stderr}`);
4998
5555
  function extractPatchDirectory(output) {
4999
5556
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5000
5557
  for (const line of lines) {
5001
- if (existsSync12(line)) {
5558
+ if (existsSync13(line)) {
5002
5559
  return line;
5003
5560
  }
5004
5561
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
5005
5562
  for (const token of tokens) {
5006
- if (token.startsWith("/") && existsSync12(token)) {
5563
+ if (token.startsWith("/") && existsSync13(token)) {
5007
5564
  return token;
5008
5565
  }
5009
5566
  }
@@ -5074,6 +5631,10 @@ var applyPatchFileTool = defineTool({
5074
5631
  workspace: z8.string().optional(),
5075
5632
  validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
5076
5633
  reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
5634
+ proofOfFix: z8.union([z8.boolean(), z8.string()]).optional().describe(
5635
+ "If true or script path/content, enforce dual-stage exploit reproduction (assert failure pre-patch, assert pass post-patch)"
5636
+ ),
5637
+ requireProofOfFix: z8.boolean().optional().describe("If true, fail and roll back patch if proof-of-fix verification fails"),
5077
5638
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
5078
5639
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
5079
5640
  message: "Either patchContent or patches must be provided"
@@ -5096,6 +5657,8 @@ var applyPatchFileTool = defineTool({
5096
5657
  riskLevel,
5097
5658
  validateWithTests,
5098
5659
  reproductionTest,
5660
+ proofOfFix,
5661
+ requireProofOfFix,
5099
5662
  dryRun
5100
5663
  }) => {
5101
5664
  try {
@@ -5146,7 +5709,7 @@ var applyPatchFileTool = defineTool({
5146
5709
  };
5147
5710
  }
5148
5711
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
5149
- const patchFilePath = join18(cwd, patchesDir, patchFileName);
5712
+ const patchFilePath = join19(cwd, patchesDir, patchFileName);
5150
5713
  const manifestFilePath = `${patchFilePath}.json`;
5151
5714
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
5152
5715
  const baseArtifact = {
@@ -5185,7 +5748,7 @@ var applyPatchFileTool = defineTool({
5185
5748
  }
5186
5749
  return withRepoLock(cwd, async () => {
5187
5750
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
5188
- const patchesDirPath = join18(cwd, patchesDir);
5751
+ const patchesDirPath = join19(cwd, patchesDir);
5189
5752
  await mkdir3(patchesDirPath, { recursive: true });
5190
5753
  await writeFile3(patchFilePath, selectedPatch, "utf8");
5191
5754
  validationPhases.push({
@@ -5208,6 +5771,101 @@ var applyPatchFileTool = defineTool({
5208
5771
  });
5209
5772
  artifact.validationPhases = validationPhases;
5210
5773
  await writePatchManifest(manifestFilePath, artifact);
5774
+ let reproductionTestPath;
5775
+ let negativePhaseResult;
5776
+ if (typeof proofOfFix === "string" && proofOfFix.trim().length > 0) {
5777
+ const resolvedPath = isAbsolute4(proofOfFix) ? proofOfFix : join19(cwd, proofOfFix);
5778
+ if (existsSync14(resolvedPath)) {
5779
+ reproductionTestPath = resolvedPath;
5780
+ } else {
5781
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5782
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5783
+ await writeFile3(reproductionTestPath, proofOfFix, "utf8");
5784
+ }
5785
+ } else if (reproductionTest) {
5786
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5787
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5788
+ await writeFile3(reproductionTestPath, reproductionTest, "utf8");
5789
+ }
5790
+ const isProofOfFixActive = Boolean(proofOfFix || requireProofOfFix);
5791
+ if (isProofOfFixActive) {
5792
+ if (!reproductionTestPath) {
5793
+ if (requireProofOfFix) {
5794
+ await cleanupPatchArtifacts({
5795
+ cwd,
5796
+ patchFilePath,
5797
+ manifestFilePath,
5798
+ patchMode,
5799
+ packageJsonSnapshot,
5800
+ installCommand,
5801
+ rerunInstall: false
5802
+ });
5803
+ return {
5804
+ success: false,
5805
+ packageName,
5806
+ vulnerableVersion,
5807
+ applied: false,
5808
+ dryRun: false,
5809
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied.",
5810
+ patchFilePath,
5811
+ manifestFilePath,
5812
+ patchPath: patchFilePath,
5813
+ patchMode,
5814
+ postinstallConfigured: false,
5815
+ patchArtifact: {
5816
+ ...artifact,
5817
+ applied: false,
5818
+ validationPhases
5819
+ },
5820
+ validationPhases,
5821
+ error: "Proof-of-fix verification required, but no reproduction test or script was supplied."
5822
+ };
5823
+ }
5824
+ } else {
5825
+ negativePhaseResult = await runProofOfFixPhase({
5826
+ stage: "negative",
5827
+ testFilePath: reproductionTestPath,
5828
+ cwd
5829
+ });
5830
+ validationPhases.push({
5831
+ phase: "proof-of-fix-negative",
5832
+ passed: negativePhaseResult.passed,
5833
+ message: negativePhaseResult.passed ? "Reproduction test reproduced vulnerability on unpatched code." : void 0,
5834
+ error: negativePhaseResult.passed ? void 0 : negativePhaseResult.error
5835
+ });
5836
+ if (!negativePhaseResult.passed && requireProofOfFix) {
5837
+ await cleanupPatchArtifacts({
5838
+ cwd,
5839
+ patchFilePath,
5840
+ manifestFilePath,
5841
+ patchMode,
5842
+ packageJsonSnapshot,
5843
+ installCommand,
5844
+ rerunInstall: false
5845
+ });
5846
+ return {
5847
+ success: false,
5848
+ packageName,
5849
+ vulnerableVersion,
5850
+ applied: false,
5851
+ dryRun: false,
5852
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`,
5853
+ patchFilePath,
5854
+ manifestFilePath,
5855
+ patchPath: patchFilePath,
5856
+ patchMode,
5857
+ postinstallConfigured: false,
5858
+ patchArtifact: {
5859
+ ...artifact,
5860
+ applied: false,
5861
+ validationPhases
5862
+ },
5863
+ validationPhases,
5864
+ error: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
5865
+ };
5866
+ }
5867
+ }
5868
+ }
5211
5869
  const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
5212
5870
  cwd,
5213
5871
  packageName,
@@ -5348,34 +6006,90 @@ var applyPatchFileTool = defineTool({
5348
6006
  message: "Patch validation tests passed."
5349
6007
  });
5350
6008
  }
5351
- let reproductionTestPath;
5352
- if (reproductionTest) {
5353
- const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5354
- reproductionTestPath = join18(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5355
- await writeFile3(reproductionTestPath, reproductionTest, "utf8");
5356
- try {
5357
- const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
5358
- validationPhases.push({
5359
- phase: "reproduction-test",
5360
- passed: reproRun.exitCode === 0,
5361
- message: "Reproduction test verified fix successfully."
6009
+ let positivePhaseResult;
6010
+ if (reproductionTestPath) {
6011
+ if (isProofOfFixActive) {
6012
+ positivePhaseResult = await runProofOfFixPhase({
6013
+ stage: "positive",
6014
+ testFilePath: reproductionTestPath,
6015
+ cwd
5362
6016
  });
5363
- } catch (err) {
5364
- const reproErr = err instanceof Error ? err.message : String(err);
5365
6017
  validationPhases.push({
5366
- phase: "reproduction-test",
5367
- passed: false,
5368
- error: `Reproduction test failed: ${reproErr}`
6018
+ phase: "proof-of-fix-positive",
6019
+ passed: positivePhaseResult.passed,
6020
+ message: positivePhaseResult.passed ? "Reproduction test passed on patched code (vulnerability mitigated)." : void 0,
6021
+ error: positivePhaseResult.passed ? void 0 : positivePhaseResult.error
5369
6022
  });
6023
+ if (!positivePhaseResult.passed && (requireProofOfFix || validateWithTests)) {
6024
+ await cleanupPatchArtifacts({
6025
+ cwd,
6026
+ patchFilePath,
6027
+ manifestFilePath,
6028
+ patchMode,
6029
+ packageJsonSnapshot,
6030
+ installCommand,
6031
+ rerunInstall: patchMode === "patch-package"
6032
+ });
6033
+ return {
6034
+ success: false,
6035
+ packageName,
6036
+ vulnerableVersion,
6037
+ applied: false,
6038
+ dryRun: false,
6039
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`,
6040
+ patchFilePath,
6041
+ manifestFilePath,
6042
+ patchPath: patchFilePath,
6043
+ patchMode,
6044
+ postinstallConfigured: false,
6045
+ patchArtifact: {
6046
+ ...artifact,
6047
+ applied: false,
6048
+ validationPhases
6049
+ },
6050
+ validationPhases,
6051
+ error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
6052
+ };
6053
+ }
6054
+ } else {
6055
+ try {
6056
+ const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
6057
+ validationPhases.push({
6058
+ phase: "reproduction-test",
6059
+ passed: reproRun.exitCode === 0,
6060
+ message: "Reproduction test verified fix successfully."
6061
+ });
6062
+ } catch (err) {
6063
+ const reproErr = err instanceof Error ? err.message : String(err);
6064
+ validationPhases.push({
6065
+ phase: "reproduction-test",
6066
+ passed: false,
6067
+ error: `Reproduction test failed: ${reproErr}`
6068
+ });
6069
+ }
5370
6070
  }
5371
6071
  }
6072
+ let proofOfFixVerification;
6073
+ if (negativePhaseResult && positivePhaseResult) {
6074
+ proofOfFixVerification = assembleProofOfFixVerification({
6075
+ reproductionTestPath,
6076
+ negativePhase: negativePhaseResult,
6077
+ positivePhase: positivePhaseResult,
6078
+ regressionPhase: validationResult ? {
6079
+ executed: true,
6080
+ passed: validationResult.passed,
6081
+ error: validationResult.error
6082
+ } : void 0
6083
+ });
6084
+ }
5372
6085
  const finalArtifact = {
5373
6086
  ...artifact,
5374
6087
  applied: true,
5375
6088
  dryRun: false,
5376
6089
  reproductionTest,
5377
6090
  reproductionTestPath,
5378
- validationPhases
6091
+ validationPhases,
6092
+ proofOfFix: proofOfFixVerification
5379
6093
  };
5380
6094
  await writePatchManifest(manifestFilePath, finalArtifact);
5381
6095
  return {
@@ -5392,7 +6106,8 @@ var applyPatchFileTool = defineTool({
5392
6106
  postinstallConfigured: patchMode === "patch-package",
5393
6107
  patchArtifact: finalArtifact,
5394
6108
  validation: validationResult,
5395
- validationPhases
6109
+ validationPhases,
6110
+ proofOfFix: proofOfFixVerification
5396
6111
  };
5397
6112
  });
5398
6113
  } catch (err) {
@@ -5636,6 +6351,9 @@ async function tryLocalPatchFallback(params) {
5636
6351
  workspace: params.workspace,
5637
6352
  riskLevel: patchResult.riskLevel,
5638
6353
  validateWithTests: params.runTests,
6354
+ reproductionTest: patchResult.reproductionTest,
6355
+ proofOfFix: params.proofOfFix,
6356
+ requireProofOfFix: params.requireProofOfFix,
5639
6357
  dryRun: params.dryRun
5640
6358
  });
5641
6359
  steps += 1;
@@ -5684,12 +6402,14 @@ function resolveLocalRunOptions(options) {
5684
6402
  const runTests = options.runTests ?? false;
5685
6403
  const policy = options.policy ?? "";
5686
6404
  const patchesDir = options.patchesDir || "./patches";
6405
+ const loadedPolicy = loadPolicy(cwd, options.policy);
5687
6406
  const constraints = {
5688
6407
  ...options.constraints,
5689
- typecheck: options.typecheck ?? options.constraints?.typecheck,
5690
- supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy
6408
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? loadedPolicy.constraints?.typecheck,
6409
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? loadedPolicy.constraints?.supplyChainPolicy,
6410
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor,
6411
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? loadedPolicy.constraints?.requireProofOfFix
5691
6412
  };
5692
- const loadedPolicy = loadPolicy(cwd, options.policy);
5693
6413
  const llmProvider = resolveProvider(options);
5694
6414
  return {
5695
6415
  cwd,
@@ -5720,7 +6440,10 @@ function resolveLocalRunOptions(options) {
5720
6440
  regressionCheck: options.regressionCheck ?? false,
5721
6441
  escalationGraph: options.escalationGraph ?? loadedPolicy.escalationGraph,
5722
6442
  offlineIntelligence: options.offlineIntelligence ?? false,
5723
- intelligenceSnapshotPath: options.intelligenceSnapshotPath
6443
+ intelligenceSnapshotPath: options.intelligenceSnapshotPath,
6444
+ proofOfFix: options.proofOfFix,
6445
+ requireProofOfFix: options.requireProofOfFix ?? constraints.requireProofOfFix,
6446
+ breakingChangeRefactor: options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor
5724
6447
  };
5725
6448
  }
5726
6449
 
@@ -5888,7 +6611,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5888
6611
  regressionCheck,
5889
6612
  escalationGraph,
5890
6613
  offlineIntelligence,
5891
- intelligenceSnapshotPath
6614
+ intelligenceSnapshotPath,
6615
+ proofOfFix,
6616
+ requireProofOfFix
5892
6617
  } = resolved;
5893
6618
  const correlation = {
5894
6619
  requestId: options.requestId,
@@ -6065,7 +6790,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
6065
6790
  installPreferOffline: constraints.installPreferOffline,
6066
6791
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6067
6792
  workspace: constraints.workspace,
6068
- vulnerableRange: vulnerable.affected.vulnerableRange
6793
+ vulnerableRange: vulnerable.affected.vulnerableRange,
6794
+ proofOfFix,
6795
+ requireProofOfFix
6069
6796
  });
6070
6797
  agentSteps += fallback.steps;
6071
6798
  collectedResults.push(finalizePatchResult(fallback.result));
@@ -6171,8 +6898,8 @@ function accumulateStepResults(params) {
6171
6898
  }
6172
6899
 
6173
6900
  // src/remediation/orchestration-prompt.ts
6174
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
6175
- import { join as join19 } from "path";
6901
+ import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
6902
+ import { join as join20 } from "path";
6176
6903
  function buildProviderAddendum(provider, personality = "balanced") {
6177
6904
  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.";
6178
6905
  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.";
@@ -6183,13 +6910,13 @@ Provider profile:
6183
6910
  - ${personalityDirective}`;
6184
6911
  }
6185
6912
  function loadOrchestrationPrompt(ctx) {
6186
- const promptPath = join19(
6913
+ const promptPath = join20(
6187
6914
  process.cwd(),
6188
6915
  ".github",
6189
6916
  "instructions",
6190
6917
  "orchestration.instructions.md"
6191
6918
  );
6192
- if (!existsSync13(promptPath)) {
6919
+ if (!existsSync15(promptPath)) {
6193
6920
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
6194
6921
  Working directory: ${ctx.cwd}
6195
6922
  Package manager: ${ctx.packageManager}
@@ -6215,7 +6942,7 @@ Fallback sequence (when neither version bump nor override can be applied):
6215
6942
 
6216
6943
  Always respect dryRun and policy constraints.`;
6217
6944
  }
6218
- const template = readFileSync13(promptPath, "utf8");
6945
+ const template = readFileSync15(promptPath, "utf8");
6219
6946
  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(
6220
6947
  "{{directDependenciesOnly}}",
6221
6948
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -6694,6 +7421,8 @@ function createRuntimeToolsForRun(options) {
6694
7421
  installPreferOffline: constraints.installPreferOffline,
6695
7422
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6696
7423
  workspace: constraints.workspace,
7424
+ typecheck: input.typecheck ?? options.typecheck ?? constraints.typecheck,
7425
+ breakingChangeRefactor: input.breakingChangeRefactor ?? options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor,
6697
7426
  dryRun: preview ? true : input.dryRun
6698
7427
  })
6699
7428
  };
@@ -6718,6 +7447,8 @@ function createRuntimeToolsForRun(options) {
6718
7447
  installPreferOffline: constraints.installPreferOffline,
6719
7448
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6720
7449
  workspace: constraints.workspace,
7450
+ proofOfFix: input.proofOfFix ?? options.proofOfFix,
7451
+ requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
6721
7452
  dryRun: preview ? true : input.dryRun
6722
7453
  })
6723
7454
  };
@@ -7148,13 +7879,17 @@ function resolveConstraints(options, cwd) {
7148
7879
  installMode: options.constraints?.installMode ?? policy.constraints?.installMode ?? "deterministic",
7149
7880
  installPreferOffline: options.constraints?.installPreferOffline ?? policy.constraints?.installPreferOffline,
7150
7881
  enforceFrozenLockfile: options.constraints?.enforceFrozenLockfile ?? policy.constraints?.enforceFrozenLockfile,
7151
- workspace: options.constraints?.workspace ?? policy.constraints?.workspace
7882
+ workspace: options.constraints?.workspace ?? policy.constraints?.workspace,
7883
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? policy.constraints?.typecheck,
7884
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? policy.constraints?.requireProofOfFix,
7885
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? policy.constraints?.enableBreakingChangeRefactor,
7886
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? policy.constraints?.supplyChainPolicy
7152
7887
  };
7153
7888
  }
7154
7889
 
7155
7890
  // src/platform/evidence.ts
7156
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
7157
- import { join as join20 } from "path";
7891
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync6 } from "fs";
7892
+ import { join as join21 } from "path";
7158
7893
  function createEvidenceLog(cwd, cveIds, context = {}) {
7159
7894
  return {
7160
7895
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -7186,10 +7921,10 @@ function finalizeEvidence(log) {
7186
7921
  return log;
7187
7922
  }
7188
7923
  function writeEvidenceLog(cwd, log) {
7189
- const dir = join20(cwd, ".autoremediator", "evidence");
7924
+ const dir = join21(cwd, ".autoremediator", "evidence");
7190
7925
  mkdirSync3(dir, { recursive: true });
7191
- const filePath = join20(dir, `${log.runId}.json`);
7192
- writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
7926
+ const filePath = join21(dir, `${log.runId}.json`);
7927
+ writeFileSync6(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
7193
7928
  return filePath;
7194
7929
  }
7195
7930
 
@@ -7431,12 +8166,12 @@ async function planRemediation(cveId, options = {}) {
7431
8166
  }
7432
8167
 
7433
8168
  // src/scanner/parse-input.ts
7434
- import { extname, resolve as resolve4 } from "path";
7435
- import { readFileSync as readFileSync17 } from "fs";
8169
+ import { extname, resolve as resolve5 } from "path";
8170
+ import { readFileSync as readFileSync19 } from "fs";
7436
8171
  import { execa as execa11 } from "execa";
7437
8172
 
7438
8173
  // src/scanner/adapters/npm-audit.ts
7439
- import { readFileSync as readFileSync14 } from "fs";
8174
+ import { readFileSync as readFileSync16 } from "fs";
7440
8175
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
7441
8176
  var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
7442
8177
  function normalizeSeverity(raw) {
@@ -7472,12 +8207,12 @@ function parseNpmAuditJsonFromString(content) {
7472
8207
  return findings;
7473
8208
  }
7474
8209
  function parseNpmAuditJsonFile(filePath) {
7475
- const content = readFileSync14(filePath, "utf8");
8210
+ const content = readFileSync16(filePath, "utf8");
7476
8211
  return parseNpmAuditJsonFromString(content);
7477
8212
  }
7478
8213
 
7479
8214
  // src/scanner/adapters/yarn-audit.ts
7480
- import { readFileSync as readFileSync15 } from "fs";
8215
+ import { readFileSync as readFileSync17 } from "fs";
7481
8216
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
7482
8217
  var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
7483
8218
  function normalizeSeverity2(raw) {
@@ -7522,12 +8257,12 @@ function parseYarnAuditJsonFromString(content) {
7522
8257
  return findings;
7523
8258
  }
7524
8259
  function parseYarnAuditJsonFile(filePath) {
7525
- const content = readFileSync15(filePath, "utf8");
8260
+ const content = readFileSync17(filePath, "utf8");
7526
8261
  return parseYarnAuditJsonFromString(content);
7527
8262
  }
7528
8263
 
7529
8264
  // src/scanner/adapters/sarif.ts
7530
- import { readFileSync as readFileSync16 } from "fs";
8265
+ import { readFileSync as readFileSync18 } from "fs";
7531
8266
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
7532
8267
  function extractPackageName(result) {
7533
8268
  const pkg = result.properties?.["packageName"];
@@ -7565,7 +8300,7 @@ function parseSarifFromString(content) {
7565
8300
  return findings;
7566
8301
  }
7567
8302
  function parseSarifFile(filePath) {
7568
- const content = readFileSync16(filePath, "utf8");
8303
+ const content = readFileSync18(filePath, "utf8");
7569
8304
  return parseSarifFromString(content);
7570
8305
  }
7571
8306
 
@@ -7574,7 +8309,7 @@ function parseScanInput(filePath, format) {
7574
8309
  if (filePath.includes("\0")) {
7575
8310
  throw new Error("Invalid scan input path: path contains null bytes");
7576
8311
  }
7577
- const resolvedPath = resolve4(filePath);
8312
+ const resolvedPath = resolve5(filePath);
7578
8313
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
7579
8314
  if (resolved === "npm-audit") {
7580
8315
  return parseNpmAuditJsonFile(resolvedPath);
@@ -7650,7 +8385,7 @@ function inferFormat(filePath) {
7650
8385
  const ext = extname(filePath).toLowerCase();
7651
8386
  if (ext === ".sarif") return "sarif";
7652
8387
  try {
7653
- const content = readFileSync17(filePath, "utf8");
8388
+ const content = readFileSync19(filePath, "utf8");
7654
8389
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
7655
8390
  if (firstLine) {
7656
8391
  const parsed = JSON.parse(firstLine);
@@ -8061,14 +8796,14 @@ async function remediatePortfolio(targets, options = {}) {
8061
8796
  }
8062
8797
 
8063
8798
  // src/api/update-outdated/index.ts
8064
- import { join as join21 } from "path";
8065
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "fs";
8799
+ import { join as join22 } from "path";
8800
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
8066
8801
  import { execa as execa12 } from "execa";
8067
8802
  async function applyBump(params) {
8068
- const pkgPath = join21(params.cwd, "package.json");
8803
+ const pkgPath = join22(params.cwd, "package.json");
8069
8804
  let pkgJson;
8070
8805
  try {
8071
- pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
8806
+ pkgJson = JSON.parse(readFileSync20(pkgPath, "utf8"));
8072
8807
  } catch {
8073
8808
  return {
8074
8809
  applied: false,
@@ -8090,13 +8825,13 @@ async function applyBump(params) {
8090
8825
  const newRange = `${prefix}${params.toVersion}`;
8091
8826
  return withRepoLock(params.cwd, async () => {
8092
8827
  pkgJson[depField][params.packageName] = newRange;
8093
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8828
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8094
8829
  try {
8095
8830
  const [installCmd, ...installArgs] = params.installCommand;
8096
8831
  await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8097
8832
  } catch (err) {
8098
8833
  pkgJson[depField][params.packageName] = currentRange;
8099
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8834
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8100
8835
  const message = err instanceof Error ? err.message : String(err);
8101
8836
  return {
8102
8837
  applied: false,
@@ -8109,7 +8844,7 @@ async function applyBump(params) {
8109
8844
  await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8110
8845
  } catch (err) {
8111
8846
  pkgJson[depField][params.packageName] = currentRange;
8112
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8847
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8113
8848
  try {
8114
8849
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
8115
8850
  await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
@@ -8314,8 +9049,8 @@ async function checkReachability(options) {
8314
9049
  }
8315
9050
 
8316
9051
  // src/scanner/delta-scan.ts
8317
- import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
8318
- import { join as join22 } from "path";
9052
+ import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
9053
+ import { join as join23 } from "path";
8319
9054
  import { execa as execa13 } from "execa";
8320
9055
  import semver9 from "semver";
8321
9056
 
@@ -8508,7 +9243,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
8508
9243
  }
8509
9244
  }
8510
9245
  } catch {
8511
- if (existsSync14(join22(cwd, "package.json"))) {
9246
+ if (existsSync16(join23(cwd, "package.json"))) {
8512
9247
  manifests.add("package.json");
8513
9248
  }
8514
9249
  }
@@ -8527,10 +9262,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
8527
9262
  }
8528
9263
  }
8529
9264
  function getNewManifestContent(cwd, relPath) {
8530
- const fullPath = join22(cwd, relPath);
8531
- if (!existsSync14(fullPath)) return null;
9265
+ const fullPath = join23(cwd, relPath);
9266
+ if (!existsSync16(fullPath)) return null;
8532
9267
  try {
8533
- return JSON.parse(readFileSync19(fullPath, "utf8"));
9268
+ return JSON.parse(readFileSync21(fullPath, "utf8"));
8534
9269
  } catch {
8535
9270
  return null;
8536
9271
  }
@@ -8644,8 +9379,8 @@ async function scanDelta(options = {}) {
8644
9379
  }
8645
9380
  for (const lockfileRel of changedLockfiles) {
8646
9381
  const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
8647
- const fullPath = join22(cwd, lockfileRel);
8648
- const newContent = existsSync14(fullPath) ? readFileSync19(fullPath, "utf8") : null;
9382
+ const fullPath = join23(cwd, lockfileRel);
9383
+ const newContent = existsSync16(fullPath) ? readFileSync21(fullPath, "utf8") : null;
8649
9384
  const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
8650
9385
  for (const change of lockDiffs) {
8651
9386
  if (!directPkgs.has(change.packageName)) {