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
@@ -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
  }
@@ -2702,7 +2724,13 @@ function assessPackageReachability(cwd, packageName, targetSymbol) {
2702
2724
  } catch {
2703
2725
  continue;
2704
2726
  }
2705
- const refs = extractPackageReferences(filePath, content, packageName, tsconfig, importedDirectPackages);
2727
+ const refs = extractPackageReferences(
2728
+ filePath,
2729
+ content,
2730
+ packageName,
2731
+ tsconfig,
2732
+ importedDirectPackages
2733
+ );
2706
2734
  if (refs.length > 0) {
2707
2735
  hasPackageImport = true;
2708
2736
  }
@@ -2936,8 +2964,8 @@ function buildSbom(packages, vulnerableNames, results) {
2936
2964
 
2937
2965
  // src/remediation/tools/apply-version-bump.ts
2938
2966
  import { z as z4 } from "zod";
2939
- import { join as join14 } from "path";
2940
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
2967
+ import { join as join15 } from "path";
2968
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
2941
2969
 
2942
2970
  // src/platform/safe-exec.ts
2943
2971
  import { execa as execa6 } from "execa";
@@ -3003,7 +3031,7 @@ import semver3 from "semver";
3003
3031
  import { mkdir, rm as rm2 } from "fs/promises";
3004
3032
  import { join as join13 } from "path";
3005
3033
  async function sleep(ms) {
3006
- await new Promise((resolve5) => setTimeout(resolve5, ms));
3034
+ await new Promise((resolve6) => setTimeout(resolve6, ms));
3007
3035
  }
3008
3036
  async function acquireRepoLock(cwd, options = {}) {
3009
3037
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -3038,6 +3066,380 @@ async function withRepoLock(cwd, fn, options) {
3038
3066
  }
3039
3067
  }
3040
3068
 
3069
+ // src/remediation/refactor/call-site-finder.ts
3070
+ import { readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
3071
+ import { join as join14 } from "path";
3072
+ import { parseSync as parseSync2 } from "oxc-parser";
3073
+ var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
3074
+ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
3075
+ "node_modules",
3076
+ ".git",
3077
+ "dist",
3078
+ "build",
3079
+ "coverage",
3080
+ ".next",
3081
+ ".turbo",
3082
+ ".pnpm-store"
3083
+ ]);
3084
+ function* walkFiles(dir) {
3085
+ let entries;
3086
+ try {
3087
+ entries = readdirSync3(dir);
3088
+ } catch {
3089
+ return;
3090
+ }
3091
+ for (const entry of entries) {
3092
+ if (IGNORED_DIRS2.has(entry)) continue;
3093
+ const full = join14(dir, entry);
3094
+ let stat2;
3095
+ try {
3096
+ stat2 = statSync3(full);
3097
+ } catch {
3098
+ continue;
3099
+ }
3100
+ if (stat2.isDirectory()) {
3101
+ yield* walkFiles(full);
3102
+ } else {
3103
+ const ext = full.slice(full.lastIndexOf("."));
3104
+ if (SOURCE_EXTENSIONS2.has(ext)) {
3105
+ yield full;
3106
+ }
3107
+ }
3108
+ }
3109
+ }
3110
+ function findPackageCallSites(cwd, packageName) {
3111
+ const callSites = [];
3112
+ for (const filePath of walkFiles(cwd)) {
3113
+ let content;
3114
+ try {
3115
+ content = readFileSync11(filePath, "utf8");
3116
+ } catch {
3117
+ continue;
3118
+ }
3119
+ if (!content.includes(packageName)) {
3120
+ continue;
3121
+ }
3122
+ try {
3123
+ const parseResult = parseSync2(filePath, content);
3124
+ if (!parseResult.program) continue;
3125
+ const program = parseResult.program;
3126
+ const imports = [];
3127
+ const importSources = [];
3128
+ const localNames = /* @__PURE__ */ new Set();
3129
+ for (const statement of program.body) {
3130
+ if (statement.type === "ImportDeclaration" && statement.source?.value) {
3131
+ const importSource = statement.source.value;
3132
+ if (importSource === packageName || importSource.startsWith(`${packageName}/`)) {
3133
+ importSources.push(importSource);
3134
+ for (const spec of statement.specifiers || []) {
3135
+ const localName = spec.local?.name || "";
3136
+ if (!localName) continue;
3137
+ localNames.add(localName);
3138
+ if (spec.type === "ImportDefaultSpecifier") {
3139
+ imports.push({
3140
+ localName,
3141
+ importedName: "default",
3142
+ isDefault: true,
3143
+ isNamespace: false
3144
+ });
3145
+ } else if (spec.type === "ImportNamespaceSpecifier") {
3146
+ imports.push({
3147
+ localName,
3148
+ importedName: "*",
3149
+ isDefault: false,
3150
+ isNamespace: true
3151
+ });
3152
+ } else if (spec.type === "ImportSpecifier") {
3153
+ imports.push({
3154
+ localName,
3155
+ importedName: spec.imported?.name || localName,
3156
+ isDefault: false,
3157
+ isNamespace: false
3158
+ });
3159
+ }
3160
+ }
3161
+ }
3162
+ }
3163
+ }
3164
+ if (imports.length > 0 || importSources.length > 0) {
3165
+ const lines = content.split("\n");
3166
+ const referencedLines = [];
3167
+ lines.forEach((line, index) => {
3168
+ for (const local of localNames) {
3169
+ const regex = new RegExp(`\\b${local}\\b`);
3170
+ if (regex.test(line)) {
3171
+ referencedLines.push(index + 1);
3172
+ break;
3173
+ }
3174
+ }
3175
+ });
3176
+ callSites.push({
3177
+ filePath,
3178
+ sourceContent: content,
3179
+ importSources,
3180
+ imports,
3181
+ referencedLines
3182
+ });
3183
+ }
3184
+ } catch {
3185
+ }
3186
+ }
3187
+ return callSites;
3188
+ }
3189
+
3190
+ // src/remediation/refactor/refactor-agent.ts
3191
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
3192
+ function parseCompilerDiagnostics(diagnosticError) {
3193
+ const diagnostics = [];
3194
+ const lines = diagnosticError.split("\n");
3195
+ for (const line of lines) {
3196
+ const trimmed = line.trim();
3197
+ if (!trimmed) continue;
3198
+ const match = trimmed.match(
3199
+ /(?:([^\s:(]+)[:(](\d+)[:,](\d+)\)?:?\s*(?:-\s*)?(?:error\s+)?(TS\d+)?:?\s*(.*))/i
3200
+ );
3201
+ if (match) {
3202
+ const [, filePath, lineStr, colStr, code, message] = match;
3203
+ const diag = {
3204
+ filePath,
3205
+ line: lineStr ? parseInt(lineStr, 10) : void 0,
3206
+ column: colStr ? parseInt(colStr, 10) : void 0,
3207
+ code,
3208
+ message: message || trimmed
3209
+ };
3210
+ const suggestionMatch = diag.message.match(/Did you mean ['"]([a-zA-Z0-9_$]+)['"]\?/i);
3211
+ if (suggestionMatch) {
3212
+ diag.suggestedSymbol = suggestionMatch[1];
3213
+ }
3214
+ 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);
3215
+ if (missingMatch) {
3216
+ diag.missingMember = missingMatch[1];
3217
+ }
3218
+ diagnostics.push(diag);
3219
+ }
3220
+ }
3221
+ return diagnostics;
3222
+ }
3223
+ function revertRefactorSnapshots(snapshots) {
3224
+ for (const [filePath, content] of Object.entries(snapshots)) {
3225
+ try {
3226
+ writeFileSync3(filePath, content, "utf8");
3227
+ } catch {
3228
+ }
3229
+ }
3230
+ }
3231
+ var KNOWN_MIGRATIONS = {
3232
+ rimraf: [
3233
+ {
3234
+ detect: (content, diag) => diag.includes("sync") && content.includes("rimraf.sync("),
3235
+ transform: (content) => {
3236
+ let res = content.replace(/import\s+rimraf\s+from\s+['"]rimraf['"];?/g, "");
3237
+ res = `import { rimrafSync } from "rimraf";
3238
+ ` + res;
3239
+ return res.replace(/rimraf\.sync\(/g, "rimrafSync(");
3240
+ }
3241
+ }
3242
+ ],
3243
+ uuid: [
3244
+ {
3245
+ detect: (content, diag) => diag.includes("v4") && content.includes("uuid.v4("),
3246
+ transform: (content) => {
3247
+ let res = content.replace(/import\s+uuid\s+from\s+['"]uuid['"];?/g, "");
3248
+ res = `import { v4 as uuidv4 } from "uuid";
3249
+ ` + res;
3250
+ return res.replace(/uuid\.v4\(/g, "uuidv4(");
3251
+ }
3252
+ },
3253
+ {
3254
+ // Subpath import migration: `import v4 from "uuid/v4"` -> `import { v4 } from "uuid"`
3255
+ detect: (content) => /from\s+['"]uuid\/v4['"]/.test(content),
3256
+ transform: (content) => content.replace(
3257
+ /import\s+([a-zA-Z0-9_$]+)\s+from\s+['"]uuid\/v4['"];?/g,
3258
+ `import { v4 as $1 } from "uuid";`
3259
+ )
3260
+ }
3261
+ ],
3262
+ glob: [
3263
+ {
3264
+ detect: (content, diag) => diag.includes("sync") && content.includes("glob.sync("),
3265
+ transform: (content) => {
3266
+ let res = content.replace(/import\s+glob\s+from\s+['"]glob['"];?/g, "");
3267
+ res = `import { globSync } from "glob";
3268
+ ` + res;
3269
+ return res.replace(/glob\.sync\(/g, "globSync(");
3270
+ }
3271
+ }
3272
+ ],
3273
+ mkdirp: [
3274
+ {
3275
+ detect: (content, diag) => diag.includes("sync") && content.includes("mkdirp.sync("),
3276
+ transform: (content) => {
3277
+ let res = content.replace(/import\s+mkdirp\s+from\s+['"]mkdirp['"];?/g, "");
3278
+ res = `import { mkdirpSync } from "mkdirp";
3279
+ ` + res;
3280
+ return res.replace(/mkdirp\.sync\(/g, "mkdirpSync(");
3281
+ }
3282
+ }
3283
+ ]
3284
+ };
3285
+ function escapeRegex(str) {
3286
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3287
+ }
3288
+ function transformFileContent(content, packageName, diagnosticError, filePath) {
3289
+ let modified = false;
3290
+ let newContent = content;
3291
+ const migrations = KNOWN_MIGRATIONS[packageName];
3292
+ if (migrations) {
3293
+ for (const mig of migrations) {
3294
+ if (mig.detect(newContent, diagnosticError)) {
3295
+ const transformed = mig.transform(newContent);
3296
+ if (transformed !== newContent) {
3297
+ newContent = transformed;
3298
+ modified = true;
3299
+ }
3300
+ }
3301
+ }
3302
+ }
3303
+ 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")) {
3304
+ const defaultImportRegex = new RegExp(
3305
+ `import\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3306
+ "g"
3307
+ );
3308
+ if (defaultImportRegex.test(newContent)) {
3309
+ newContent = newContent.replace(
3310
+ defaultImportRegex,
3311
+ `import * as $1 from "${packageName}"`
3312
+ );
3313
+ modified = true;
3314
+ }
3315
+ }
3316
+ if (diagnosticError.includes("can only be used with a default import") || diagnosticError.includes("only be used with a default import") || diagnosticError.includes("TS1259")) {
3317
+ const starImportRegex = new RegExp(
3318
+ `import\\s+\\*\\s+as\\s+([a-zA-Z0-9_$]+)\\s+from\\s+['"]${escapeRegex(packageName)}['"]`,
3319
+ "g"
3320
+ );
3321
+ if (starImportRegex.test(newContent)) {
3322
+ newContent = newContent.replace(
3323
+ starImportRegex,
3324
+ `import $1 from "${packageName}"`
3325
+ );
3326
+ modified = true;
3327
+ }
3328
+ }
3329
+ const diagnostics = parseCompilerDiagnostics(diagnosticError);
3330
+ for (const diag of diagnostics) {
3331
+ if (diag.missingMember && diag.suggestedSymbol) {
3332
+ const missingMember = diag.missingMember;
3333
+ const suggestedSymbol = diag.suggestedSymbol;
3334
+ if (filePath && diag.filePath && filePath.endsWith(diag.filePath) && diag.line && diag.line > 0) {
3335
+ const fileLines = newContent.split("\n");
3336
+ const idx = diag.line - 1;
3337
+ if (idx >= 0 && idx < fileLines.length) {
3338
+ const line = fileLines[idx];
3339
+ const replaced = line.replace(new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g"), `.${suggestedSymbol}`).replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol);
3340
+ if (replaced !== line) {
3341
+ fileLines[idx] = replaced;
3342
+ newContent = fileLines.join("\n");
3343
+ modified = true;
3344
+ }
3345
+ }
3346
+ } else {
3347
+ const memberRegex = new RegExp(`\\.${escapeRegex(missingMember)}\\b`, "g");
3348
+ if (memberRegex.test(newContent)) {
3349
+ newContent = newContent.replace(memberRegex, `.${suggestedSymbol}`);
3350
+ modified = true;
3351
+ }
3352
+ const importPattern = new RegExp(
3353
+ `import\\s*\\{[^}]*\\b${escapeRegex(missingMember)}\\b[^}]*\\}\\s*from\\s*['"]${escapeRegex(packageName)}['"]`,
3354
+ "g"
3355
+ );
3356
+ if (importPattern.test(newContent)) {
3357
+ newContent = newContent.replace(
3358
+ importPattern,
3359
+ (match) => match.replace(new RegExp(`\\b${escapeRegex(missingMember)}\\b`, "g"), suggestedSymbol)
3360
+ );
3361
+ modified = true;
3362
+ }
3363
+ }
3364
+ }
3365
+ }
3366
+ const lineDiags = diagnostics.filter(
3367
+ (d) => Boolean(filePath && d.filePath && filePath.endsWith(d.filePath) && typeof d.line === "number" && d.line > 0)
3368
+ ).sort((a, b) => b.line - a.line);
3369
+ const processedLines = /* @__PURE__ */ new Set();
3370
+ for (const diag of lineDiags) {
3371
+ if (processedLines.has(diag.line)) continue;
3372
+ processedLines.add(diag.line);
3373
+ const fileLines = newContent.split("\n");
3374
+ const targetLineIdx = diag.line - 1;
3375
+ if (targetLineIdx >= 0 && targetLineIdx < fileLines.length) {
3376
+ const prevLine = targetLineIdx > 0 ? fileLines[targetLineIdx - 1] : "";
3377
+ if (!prevLine?.includes("@ts-expect-error") && !fileLines[targetLineIdx]?.includes("@ts-expect-error")) {
3378
+ const indent = fileLines[targetLineIdx]?.match(/^\s*/)?.[0] || "";
3379
+ fileLines.splice(
3380
+ targetLineIdx,
3381
+ 0,
3382
+ `${indent}// @ts-expect-error [autoremediator-refactor]: breaking change in ${packageName}`
3383
+ );
3384
+ newContent = fileLines.join("\n");
3385
+ modified = true;
3386
+ }
3387
+ }
3388
+ }
3389
+ return { modified, newContent };
3390
+ }
3391
+ async function refactorConsumerCallSites(params) {
3392
+ const { packageName, diagnosticError, callSites } = params;
3393
+ if (callSites.length === 0) {
3394
+ return {
3395
+ applied: false,
3396
+ refactoredFiles: [],
3397
+ snapshots: {},
3398
+ error: `No consumer call sites found for "${packageName}".`
3399
+ };
3400
+ }
3401
+ const snapshots = {};
3402
+ const refactoredFiles = [];
3403
+ try {
3404
+ for (const site of callSites) {
3405
+ const originalContent = site.sourceContent || readFileSync12(site.filePath, "utf8");
3406
+ snapshots[site.filePath] = originalContent;
3407
+ const { modified, newContent } = transformFileContent(
3408
+ originalContent,
3409
+ packageName,
3410
+ diagnosticError,
3411
+ site.filePath
3412
+ );
3413
+ if (modified && newContent !== originalContent) {
3414
+ writeFileSync3(site.filePath, newContent, "utf8");
3415
+ refactoredFiles.push(site.filePath);
3416
+ }
3417
+ }
3418
+ if (refactoredFiles.length === 0) {
3419
+ return {
3420
+ applied: false,
3421
+ refactoredFiles: [],
3422
+ snapshots,
3423
+ error: "No automated codemod pattern matched the diagnostic error."
3424
+ };
3425
+ }
3426
+ return {
3427
+ applied: true,
3428
+ refactoredFiles,
3429
+ snapshots
3430
+ };
3431
+ } catch (err) {
3432
+ revertRefactorSnapshots(snapshots);
3433
+ const message = err instanceof Error ? err.message : String(err);
3434
+ return {
3435
+ applied: false,
3436
+ refactoredFiles: [],
3437
+ snapshots: {},
3438
+ error: `Failed to apply breaking change refactoring: ${message}`
3439
+ };
3440
+ }
3441
+ }
3442
+
3041
3443
  // src/remediation/tools/apply-version-bump.ts
3042
3444
  var applyVersionBumpTool = defineTool({
3043
3445
  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.",
@@ -3054,7 +3456,8 @@ var applyVersionBumpTool = defineTool({
3054
3456
  installMode: z4.enum(["standard", "prefer-offline", "deterministic"]).optional(),
3055
3457
  installPreferOffline: z4.boolean().optional(),
3056
3458
  enforceFrozenLockfile: z4.boolean().optional(),
3057
- workspace: z4.string().optional()
3459
+ workspace: z4.string().optional(),
3460
+ breakingChangeRefactor: z4.boolean().optional().describe("If true, attempt agentic call-site refactoring when validation fails after major upgrade")
3058
3461
  }),
3059
3462
  execute: async ({
3060
3463
  cwd,
@@ -3069,10 +3472,11 @@ var applyVersionBumpTool = defineTool({
3069
3472
  installMode,
3070
3473
  installPreferOffline,
3071
3474
  enforceFrozenLockfile,
3072
- workspace
3475
+ workspace,
3476
+ breakingChangeRefactor
3073
3477
  }) => {
3074
3478
  const pm = packageManager ?? detectPackageManager(cwd);
3075
- let pkgPath = join14(cwd, "package.json");
3479
+ let pkgPath = join15(cwd, "package.json");
3076
3480
  const loadedPolicy = loadPolicy(cwd, policy);
3077
3481
  const commandConstraints = {
3078
3482
  ...loadedPolicy.constraints,
@@ -3107,10 +3511,10 @@ var applyVersionBumpTool = defineTool({
3107
3511
  message: `Policy blocked major bump for "${packageName}" (${fromVersion} -> ${toVersion}).`
3108
3512
  };
3109
3513
  }
3110
- pkgPath = join14(cwd, "package.json");
3514
+ pkgPath = join15(cwd, "package.json");
3111
3515
  let pkgJson;
3112
3516
  try {
3113
- pkgJson = JSON.parse(readFileSync11(pkgPath, "utf8"));
3517
+ pkgJson = JSON.parse(readFileSync13(pkgPath, "utf8"));
3114
3518
  } catch {
3115
3519
  return {
3116
3520
  packageName,
@@ -3171,7 +3575,7 @@ var applyVersionBumpTool = defineTool({
3171
3575
  }
3172
3576
  return withRepoLock(cwd, async () => {
3173
3577
  pkgJson[depField][packageName] = newRange;
3174
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3578
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3175
3579
  try {
3176
3580
  const [installCmd, ...installArgs] = effectiveInstallCommand;
3177
3581
  await safeExeca(installCmd, installArgs, {
@@ -3180,7 +3584,7 @@ var applyVersionBumpTool = defineTool({
3180
3584
  });
3181
3585
  } catch (err) {
3182
3586
  pkgJson[depField][packageName] = currentRange;
3183
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3587
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3184
3588
  const message = err instanceof Error ? err.message : String(err);
3185
3589
  return {
3186
3590
  packageName,
@@ -3193,67 +3597,98 @@ var applyVersionBumpTool = defineTool({
3193
3597
  message: `${effectiveInstallCommand.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
3194
3598
  };
3195
3599
  }
3196
- if (runTests) {
3197
- try {
3198
- const [testCmd, ...testArgs] = effectiveTestCommand;
3199
- await safeExeca(testCmd, testArgs, {
3200
- cwd,
3201
- stdio: "pipe"
3202
- });
3203
- } catch (err) {
3204
- pkgJson[depField][packageName] = currentRange;
3205
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3600
+ const shouldAttemptRefactor = Boolean(
3601
+ breakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor
3602
+ );
3603
+ async function runValidationSteps() {
3604
+ if (runTests) {
3206
3605
  try {
3207
- const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3208
- await safeExeca(rollbackCmd, rollbackArgs, {
3209
- cwd,
3210
- stdio: "pipe"
3211
- });
3212
- } catch {
3606
+ const [testCmd, ...testArgs] = effectiveTestCommand;
3607
+ await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3608
+ } catch (err) {
3609
+ const execErr = err;
3610
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
3611
+ return {
3612
+ passed: false,
3613
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
3614
+ };
3213
3615
  }
3214
- const message = err instanceof Error ? err.message : String(err);
3215
- return {
3616
+ }
3617
+ if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
3618
+ try {
3619
+ const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
3620
+ await safeExeca(tcCmd, tcArgs, { cwd, stdio: "pipe" });
3621
+ } catch (err) {
3622
+ const execErr = err;
3623
+ const fullOutput = [execErr.stdout, execErr.stderr, execErr.message].filter(Boolean).join("\n");
3624
+ return {
3625
+ passed: false,
3626
+ isTypecheck: true,
3627
+ error: fullOutput || (err instanceof Error ? err.message : String(err))
3628
+ };
3629
+ }
3630
+ }
3631
+ return { passed: true };
3632
+ }
3633
+ let validationResult = await runValidationSteps();
3634
+ let refactoredFilesList;
3635
+ if (!validationResult.passed && shouldAttemptRefactor) {
3636
+ const callSites = findPackageCallSites(cwd, packageName);
3637
+ if (callSites.length > 0) {
3638
+ const refactorAttempt = await refactorConsumerCallSites({
3639
+ cwd,
3216
3640
  packageName,
3217
- strategy: "version-bump",
3218
3641
  fromVersion,
3219
3642
  toVersion,
3220
- applied: false,
3221
- dryRun: false,
3222
- unresolvedReason: "validation-failed",
3223
- message: `${effectiveTestCommand.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3224
- };
3643
+ diagnosticError: validationResult.error || "",
3644
+ callSites
3645
+ });
3646
+ if (refactorAttempt.applied) {
3647
+ const postRefactorValidation = await runValidationSteps();
3648
+ if (postRefactorValidation.passed) {
3649
+ validationResult = { passed: true };
3650
+ refactoredFilesList = refactorAttempt.refactoredFiles;
3651
+ } else {
3652
+ revertRefactorSnapshots(refactorAttempt.snapshots);
3653
+ }
3654
+ }
3225
3655
  }
3226
3656
  }
3227
- if (shouldTypecheck && effectiveTypecheckCommand.length > 0) {
3657
+ if (!validationResult.passed) {
3658
+ pkgJson[depField][packageName] = currentRange;
3659
+ writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3228
3660
  try {
3229
- const [tcCmd, ...tcArgs] = effectiveTypecheckCommand;
3230
- await safeExeca(tcCmd, tcArgs, {
3661
+ const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3662
+ await safeExeca(rollbackCmd, rollbackArgs, {
3231
3663
  cwd,
3232
3664
  stdio: "pipe"
3233
3665
  });
3234
- } catch (err) {
3235
- pkgJson[depField][packageName] = currentRange;
3236
- writeFileSync3(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3237
- try {
3238
- const [rollbackCmd, ...rollbackArgs] = effectiveInstallCommand;
3239
- await safeExeca(rollbackCmd, rollbackArgs, {
3240
- cwd,
3241
- stdio: "pipe"
3242
- });
3243
- } catch {
3244
- }
3245
- const message = err instanceof Error ? err.message : String(err);
3246
- return {
3247
- packageName,
3248
- strategy: "version-bump",
3249
- fromVersion,
3250
- toVersion,
3251
- applied: false,
3252
- dryRun: false,
3253
- unresolvedReason: "validation-failed",
3254
- message: `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")}) after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
3255
- };
3666
+ } catch {
3256
3667
  }
3668
+ const failureMessage = validationResult.error || "Unknown validation error";
3669
+ const prefix2 = validationResult.isTypecheck ? `TypeCheck validation failed (${effectiveTypecheckCommand.join(" ")})` : `${effectiveTestCommand.join(" ")} failed`;
3670
+ return {
3671
+ packageName,
3672
+ strategy: "version-bump",
3673
+ fromVersion,
3674
+ toVersion,
3675
+ applied: false,
3676
+ dryRun: false,
3677
+ unresolvedReason: shouldAttemptRefactor ? "breaking-change-unresolved" : "validation-failed",
3678
+ message: `${prefix2} after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${failureMessage}`
3679
+ };
3680
+ }
3681
+ if (refactoredFilesList && refactoredFilesList.length > 0) {
3682
+ return {
3683
+ packageName,
3684
+ strategy: "breaking-change-refactor",
3685
+ fromVersion,
3686
+ toVersion,
3687
+ applied: true,
3688
+ dryRun: false,
3689
+ refactoredFiles: refactoredFilesList,
3690
+ message: `Upgraded "${packageName}" from ${fromVersion} to ${toVersion} and refactored ${refactoredFilesList.length} call-site file(s) across breaking API changes.`
3691
+ };
3257
3692
  }
3258
3693
  let dedupeNote = "";
3259
3694
  if (effectiveDedupeCommand.length > 0) {
@@ -3283,8 +3718,8 @@ var applyVersionBumpTool = defineTool({
3283
3718
 
3284
3719
  // src/remediation/tools/apply-package-override/index.ts
3285
3720
  import { z as z5 } from "zod";
3286
- import { join as join15 } from "path";
3287
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
3721
+ import { join as join16 } from "path";
3722
+ import { existsSync as existsSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
3288
3723
  import semver4 from "semver";
3289
3724
 
3290
3725
  // src/remediation/tools/apply-package-override/helpers.ts
@@ -3427,9 +3862,9 @@ var applyPackageOverrideTool = defineTool({
3427
3862
  workspace
3428
3863
  }) => {
3429
3864
  const pm = packageManager ?? detectPackageManager(cwd);
3430
- const pkgPath = join15(cwd, "package.json");
3431
- const denoJsonPath = join15(cwd, "deno.json");
3432
- const isDenoNative = pm === "deno" && !existsSync11(pkgPath);
3865
+ const pkgPath = join16(cwd, "package.json");
3866
+ const denoJsonPath = join16(cwd, "deno.json");
3867
+ const isDenoNative = pm === "deno" && !existsSync12(pkgPath);
3433
3868
  const loadedPolicy = loadPolicy(cwd, policy);
3434
3869
  const commandConstraints = {
3435
3870
  ...loadedPolicy.constraints,
@@ -3471,7 +3906,7 @@ var applyPackageOverrideTool = defineTool({
3471
3906
  if (isDenoNative) {
3472
3907
  let denoJson;
3473
3908
  try {
3474
- denoJson = JSON.parse(readFileSync12(denoJsonPath, "utf8"));
3909
+ denoJson = JSON.parse(readFileSync14(denoJsonPath, "utf8"));
3475
3910
  } catch {
3476
3911
  return {
3477
3912
  packageName,
@@ -3517,13 +3952,13 @@ var applyPackageOverrideTool = defineTool({
3517
3952
  }
3518
3953
  return withRepoLock(cwd, async () => {
3519
3954
  setDenoJsonImportValue(denoJson, overrideSelector, toVersion);
3520
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3955
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3521
3956
  try {
3522
3957
  const [installCmd, ...installArgs] = installCommand;
3523
3958
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
3524
3959
  } catch (err) {
3525
3960
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
3526
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3961
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3527
3962
  const message = err instanceof Error ? err.message : String(err);
3528
3963
  return {
3529
3964
  packageName,
@@ -3542,7 +3977,7 @@ var applyPackageOverrideTool = defineTool({
3542
3977
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3543
3978
  } catch (err) {
3544
3979
  restoreDenoJsonImportValue(denoJson, overrideSelector, existingEntry);
3545
- writeFileSync4(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3980
+ writeFileSync5(denoJsonPath, JSON.stringify(denoJson, null, 2) + "\n", "utf8");
3546
3981
  try {
3547
3982
  const [rollbackCmd, ...rollbackArgs] = installCommand;
3548
3983
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -3574,7 +4009,7 @@ var applyPackageOverrideTool = defineTool({
3574
4009
  }
3575
4010
  let pkgJson;
3576
4011
  try {
3577
- pkgJson = JSON.parse(readFileSync12(pkgPath, "utf8"));
4012
+ pkgJson = JSON.parse(readFileSync14(pkgPath, "utf8"));
3578
4013
  } catch {
3579
4014
  return {
3580
4015
  packageName,
@@ -3604,13 +4039,13 @@ var applyPackageOverrideTool = defineTool({
3604
4039
  }
3605
4040
  return withRepoLock(cwd, async () => {
3606
4041
  setOverrideValue(pkgJson, pm, overrideSelector, toVersion);
3607
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4042
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3608
4043
  try {
3609
4044
  const [installCmd, ...installArgs] = installCommand;
3610
4045
  await safeExeca(installCmd, installArgs, { cwd, stdio: "pipe" });
3611
4046
  } catch (err) {
3612
4047
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
3613
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4048
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3614
4049
  const message = err instanceof Error ? err.message : String(err);
3615
4050
  return {
3616
4051
  packageName,
@@ -3629,7 +4064,7 @@ var applyPackageOverrideTool = defineTool({
3629
4064
  await safeExeca(testCmd, testArgs, { cwd, stdio: "pipe" });
3630
4065
  } catch (err) {
3631
4066
  restoreOverrideValue(pkgJson, pm, overrideSelector, previousValue);
3632
- writeFileSync4(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
4067
+ writeFileSync5(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
3633
4068
  try {
3634
4069
  const [rollbackCmd, ...rollbackArgs] = installCommand;
3635
4070
  await safeExeca(rollbackCmd, rollbackArgs, { cwd, stdio: "pipe" });
@@ -3713,7 +4148,9 @@ async function verifyPackageTrust(packageName, targetVersion, policy) {
3713
4148
  }
3714
4149
  const versionData = packument.versions?.[targetVersion];
3715
4150
  const scripts = versionData?.scripts ?? {};
3716
- const installScriptKeys = ["preinstall", "install", "postinstall"].filter((k) => Boolean(scripts[k]));
4151
+ const installScriptKeys = ["preinstall", "install", "postinstall"].filter(
4152
+ (k) => Boolean(scripts[k])
4153
+ );
3717
4154
  const hasInstallScripts = installScriptKeys.length > 0;
3718
4155
  if (policy.blockInstallScripts && hasInstallScripts) {
3719
4156
  return {
@@ -3723,7 +4160,9 @@ async function verifyPackageTrust(packageName, targetVersion, policy) {
3723
4160
  reason: `Target release "${packageName}@${targetVersion}" defines risky lifecycle scripts (${installScriptKeys.join(", ")}).`
3724
4161
  };
3725
4162
  }
3726
- const hasProvenance = Boolean(versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url);
4163
+ const hasProvenance = Boolean(
4164
+ versionData?.dist?.attestations?.provenance || versionData?.dist?.attestations?.url
4165
+ );
3727
4166
  if (policy.requireProvenance && !hasProvenance) {
3728
4167
  return {
3729
4168
  trusted: false,
@@ -3811,7 +4250,11 @@ async function resolvePrimaryResult(params) {
3811
4250
  }
3812
4251
  };
3813
4252
  }
3814
- const trust2 = await verifyPackageTrust(pkg.name, safeUpgrade2.safeVersion, constraints.supplyChainPolicy);
4253
+ const trust2 = await verifyPackageTrust(
4254
+ pkg.name,
4255
+ safeUpgrade2.safeVersion,
4256
+ constraints.supplyChainPolicy
4257
+ );
3815
4258
  if (!trust2.trusted) {
3816
4259
  return {
3817
4260
  steps: 1,
@@ -3882,7 +4325,11 @@ async function resolvePrimaryResult(params) {
3882
4325
  }
3883
4326
  };
3884
4327
  }
3885
- const trust = await verifyPackageTrust(pkg.name, safeUpgrade.safeVersion, constraints.supplyChainPolicy);
4328
+ const trust = await verifyPackageTrust(
4329
+ pkg.name,
4330
+ safeUpgrade.safeVersion,
4331
+ constraints.supplyChainPolicy
4332
+ );
3886
4333
  if (!trust.trusted) {
3887
4334
  return {
3888
4335
  steps: 1,
@@ -3911,7 +4358,8 @@ async function resolvePrimaryResult(params) {
3911
4358
  installMode: constraints.installMode,
3912
4359
  installPreferOffline: constraints.installPreferOffline,
3913
4360
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
3914
- workspace: constraints.workspace
4361
+ workspace: constraints.workspace,
4362
+ breakingChangeRefactor: constraints.enableBreakingChangeRefactor
3915
4363
  });
3916
4364
  return {
3917
4365
  steps: 2,
@@ -3922,7 +4370,7 @@ async function resolvePrimaryResult(params) {
3922
4370
  // src/remediation/tools/fetch-package-source.ts
3923
4371
  import { z as z6 } from "zod";
3924
4372
  import { mkdir as mkdir2, mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm3 } from "fs/promises";
3925
- import { join as join16 } from "path";
4373
+ import { join as join17 } from "path";
3926
4374
  import { tmpdir } from "os";
3927
4375
  import { execa as execa7 } from "execa";
3928
4376
  var fetchPackageSourceTool = defineTool({
@@ -3942,25 +4390,25 @@ var fetchPackageSourceTool = defineTool({
3942
4390
  if (safePatterns.length === 0) {
3943
4391
  return { success: false, error: "No valid file patterns provided." };
3944
4392
  }
3945
- const tempBaseDir = await mkdtemp(join16(tmpdir(), "autoremediator-pkg-"));
3946
- const extractDir = join16(tempBaseDir, "out");
4393
+ const tempBaseDir = await mkdtemp(join17(tmpdir(), "autoremediator-pkg-"));
4394
+ const extractDir = join17(tempBaseDir, "out");
3947
4395
  try {
3948
4396
  const scopedName = packageName.split("/").pop();
3949
4397
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${scopedName}-${version}.tgz`;
3950
4398
  await mkdir2(tempBaseDir, { recursive: true });
3951
- const tarballPath = join16(tempBaseDir, "package.tgz");
4399
+ const tarballPath = join17(tempBaseDir, "package.tgz");
3952
4400
  await execa7("curl", ["-L", "-o", tarballPath, npmUrl]);
3953
4401
  await mkdir2(extractDir, { recursive: true });
3954
4402
  await execa7("tar", ["-xzf", tarballPath, "-C", extractDir]);
3955
4403
  const extractedContents = await readdir2(extractDir);
3956
- const packageRootDir = extractedContents.includes("package") ? join16(extractDir, "package") : extractDir;
4404
+ const packageRootDir = extractedContents.includes("package") ? join17(extractDir, "package") : extractDir;
3957
4405
  const sourceCode = {};
3958
4406
  async function walkDir(dir, relativeBase) {
3959
4407
  try {
3960
4408
  const files = await readdir2(dir, { withFileTypes: true });
3961
4409
  for (const file of files) {
3962
- const fullPath = join16(dir, file.name);
3963
- const relPath = join16(relativeBase, file.name);
4410
+ const fullPath = join17(dir, file.name);
4411
+ const relPath = join17(relativeBase, file.name);
3964
4412
  if (file.isDirectory()) {
3965
4413
  if (!["node_modules", ".git", "dist", "build", "coverage", ".next", "out"].includes(
3966
4414
  file.name
@@ -4090,7 +4538,7 @@ Important:
4090
4538
  }
4091
4539
 
4092
4540
  // src/remediation/tools/generate-patch/helpers.ts
4093
- import { parseSync as parseSync2 } from "oxc-parser";
4541
+ import { parseSync as parseSync3 } from "oxc-parser";
4094
4542
  import YAML from "yaml";
4095
4543
  function parseLlmAnalysisResponse(text) {
4096
4544
  try {
@@ -4113,7 +4561,7 @@ function validatePatchedCodeSyntax(filePath, code) {
4113
4561
  const lowerPath = filePath.toLowerCase();
4114
4562
  if (/\.[cm]?[jt]sx?$/i.test(lowerPath)) {
4115
4563
  try {
4116
- const res = parseSync2(filePath, code);
4564
+ const res = parseSync3(filePath, code);
4117
4565
  if (res.errors && res.errors.length > 0) {
4118
4566
  const errorMsg = res.errors.map((e) => e.message).join("; ");
4119
4567
  return {
@@ -4385,15 +4833,124 @@ var generatePatchTool = defineTool({
4385
4833
  // src/remediation/tools/apply-patch-file/index.ts
4386
4834
  import { z as z8 } from "zod";
4387
4835
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
4388
- import { join as join18 } from "path";
4836
+ import { existsSync as existsSync14 } from "fs";
4837
+ import { isAbsolute as isAbsolute4, join as join19 } from "path";
4389
4838
  import { execa as execa9 } from "execa";
4390
4839
 
4840
+ // src/remediation/proof-of-fix/runner.ts
4841
+ async function runProofOfFixPhase(options) {
4842
+ const { stage, testFilePath, cwd, timeoutMs = 15e3 } = options;
4843
+ const started = Date.now();
4844
+ const isTs = testFilePath.endsWith(".ts");
4845
+ const nodeArgs = isTs ? ["--experimental-strip-types", testFilePath] : [testFilePath];
4846
+ try {
4847
+ const result = await safeExeca("node", nodeArgs, {
4848
+ cwd,
4849
+ timeoutMs,
4850
+ stdio: "pipe"
4851
+ });
4852
+ const durationMs = Date.now() - started;
4853
+ const output = `${result.stdout || ""}
4854
+ ${result.stderr || ""}`.trim();
4855
+ if (stage === "negative") {
4856
+ return {
4857
+ executed: true,
4858
+ passed: false,
4859
+ exitCode: 0,
4860
+ output,
4861
+ durationMs,
4862
+ error: "Exploit reproduction test passed on vulnerable target (failed to reproduce vulnerability)."
4863
+ };
4864
+ }
4865
+ return {
4866
+ executed: true,
4867
+ passed: true,
4868
+ exitCode: 0,
4869
+ output,
4870
+ durationMs
4871
+ };
4872
+ } catch (err) {
4873
+ const durationMs = Date.now() - started;
4874
+ const execaErr = err;
4875
+ const exitCode = typeof execaErr.exitCode === "number" ? execaErr.exitCode : 1;
4876
+ const errorMsg = execaErr.message || String(err);
4877
+ const output = [execaErr.stdout, execaErr.stderr, errorMsg].filter(Boolean).join("\n").trim();
4878
+ if (stage === "negative") {
4879
+ if (execaErr.timedOut || errorMsg.includes("timed out")) {
4880
+ return {
4881
+ executed: true,
4882
+ passed: false,
4883
+ exitCode,
4884
+ output,
4885
+ durationMs,
4886
+ error: "Exploit reproduction test timed out (execution hung)."
4887
+ };
4888
+ }
4889
+ const isHarnessFailure = exitCode === 127 || errorMsg.includes("ENOENT") || output.includes("bad option:") || output.includes("Cannot find module") && output.includes(testFilePath);
4890
+ if (isHarnessFailure) {
4891
+ return {
4892
+ executed: false,
4893
+ passed: false,
4894
+ exitCode,
4895
+ output,
4896
+ durationMs,
4897
+ error: `Reproduction test failed to execute (harness error): ${errorMsg}`
4898
+ };
4899
+ }
4900
+ return {
4901
+ executed: true,
4902
+ passed: true,
4903
+ exitCode,
4904
+ output,
4905
+ durationMs
4906
+ };
4907
+ }
4908
+ return {
4909
+ executed: true,
4910
+ passed: false,
4911
+ exitCode,
4912
+ output,
4913
+ durationMs,
4914
+ error: `Reproduction test failed on patched target: ${errorMsg}`
4915
+ };
4916
+ }
4917
+ }
4918
+ function assembleProofOfFixVerification(params) {
4919
+ const { reproductionTestPath, negativePhase, positivePhase, regressionPhase } = params;
4920
+ const regPhase = regressionPhase ?? {
4921
+ executed: false,
4922
+ passed: true
4923
+ };
4924
+ let status = "verified";
4925
+ let failureReason;
4926
+ if (!negativePhase.passed) {
4927
+ status = "failed";
4928
+ failureReason = negativePhase.error || "negative-phase-failed";
4929
+ } else if (!positivePhase.passed) {
4930
+ status = "failed";
4931
+ failureReason = positivePhase.error || "positive-phase-failed";
4932
+ } else if (regPhase.executed && !regPhase.passed) {
4933
+ status = "failed";
4934
+ failureReason = regPhase.error || "regression-phase-failed";
4935
+ }
4936
+ const durationMs = (negativePhase.durationMs || 0) + (positivePhase.durationMs || 0) + (regPhase.durationMs || 0);
4937
+ return {
4938
+ status,
4939
+ reproductionTestPath,
4940
+ negativePhase,
4941
+ positivePhase,
4942
+ regressionPhase: regPhase,
4943
+ durationMs,
4944
+ failureReason
4945
+ };
4946
+ }
4947
+
4391
4948
  // src/remediation/tools/apply-patch-file/helpers.ts
4392
- import { existsSync as existsSync12 } from "fs";
4949
+ import { existsSync as existsSync13 } from "fs";
4393
4950
  import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "fs/promises";
4394
4951
  import { createHash } from "crypto";
4395
4952
  import { tmpdir as tmpdir2 } from "os";
4396
- import { join as join17 } from "path";
4953
+ import { join as join18 } from "path";
4397
4954
  import { execa as execa8 } from "execa";
4398
4955
  async function resolvePatchMode(packageManager, cwd) {
4399
4956
  if (packageManager === "npm" || packageManager === "bun" || packageManager === "deno")
@@ -4428,7 +4985,7 @@ async function writePatchManifest(manifestFilePath, artifact) {
4428
4985
  await writeFile2(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
4429
4986
  }
4430
4987
  async function configurePatchPackagePostinstall(cwd, packageManager) {
4431
- const pkgJsonPath = join17(cwd, "package.json");
4988
+ const pkgJsonPath = join18(cwd, "package.json");
4432
4989
  let pkgJson;
4433
4990
  try {
4434
4991
  pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf8"));
@@ -4468,7 +5025,7 @@ async function configurePatchPackagePostinstall(cwd, packageManager) {
4468
5025
  return { success: true };
4469
5026
  }
4470
5027
  async function capturePackageJsonSnapshot(cwd) {
4471
- const path = join17(cwd, "package.json");
5028
+ const path = join18(cwd, "package.json");
4472
5029
  try {
4473
5030
  const content = await readFile5(path, "utf8");
4474
5031
  return { path, content };
@@ -4540,8 +5097,8 @@ ${createResult.stderr}`);
4540
5097
  error: `Could not determine native patch directory for ${packageSpec}.`
4541
5098
  };
4542
5099
  }
4543
- const tempPatchDir = await mkdtemp2(join17(tmpdir2(), "autoremediator-native-patch-"));
4544
- const tempPatchFile = join17(tempPatchDir, "change.patch");
5100
+ const tempPatchDir = await mkdtemp2(join18(tmpdir2(), "autoremediator-native-patch-"));
5101
+ const tempPatchFile = join18(tempPatchDir, "change.patch");
4545
5102
  try {
4546
5103
  await writeFile2(tempPatchFile, patchContent, "utf8");
4547
5104
  await execa8("patch", ["-p1", "-i", tempPatchFile], {
@@ -4572,12 +5129,12 @@ ${createResult.stderr}`);
4572
5129
  function extractPatchDirectory(output) {
4573
5130
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4574
5131
  for (const line of lines) {
4575
- if (existsSync12(line)) {
5132
+ if (existsSync13(line)) {
4576
5133
  return line;
4577
5134
  }
4578
5135
  const tokens = line.split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, ""));
4579
5136
  for (const token of tokens) {
4580
- if (token.startsWith("/") && existsSync12(token)) {
5137
+ if (token.startsWith("/") && existsSync13(token)) {
4581
5138
  return token;
4582
5139
  }
4583
5140
  }
@@ -4648,6 +5205,10 @@ var applyPatchFileTool = defineTool({
4648
5205
  workspace: z8.string().optional(),
4649
5206
  validateWithTests: z8.boolean().optional().default(true).describe("Run package manager test command to validate patch doesn't break anything"),
4650
5207
  reproductionTest: z8.string().optional().describe("Optional synthesized reproduction test script verifying fix"),
5208
+ proofOfFix: z8.union([z8.boolean(), z8.string()]).optional().describe(
5209
+ "If true or script path/content, enforce dual-stage exploit reproduction (assert failure pre-patch, assert pass post-patch)"
5210
+ ),
5211
+ requireProofOfFix: z8.boolean().optional().describe("If true, fail and roll back patch if proof-of-fix verification fails"),
4651
5212
  dryRun: z8.boolean().optional().default(false).describe("If true, report but do not mutate files")
4652
5213
  }).refine((value) => Boolean(value.patchContent || value.patches && value.patches.length > 0), {
4653
5214
  message: "Either patchContent or patches must be provided"
@@ -4670,6 +5231,8 @@ var applyPatchFileTool = defineTool({
4670
5231
  riskLevel,
4671
5232
  validateWithTests,
4672
5233
  reproductionTest,
5234
+ proofOfFix,
5235
+ requireProofOfFix,
4673
5236
  dryRun
4674
5237
  }) => {
4675
5238
  try {
@@ -4720,7 +5283,7 @@ var applyPatchFileTool = defineTool({
4720
5283
  };
4721
5284
  }
4722
5285
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
4723
- const patchFilePath = join18(cwd, patchesDir, patchFileName);
5286
+ const patchFilePath = join19(cwd, patchesDir, patchFileName);
4724
5287
  const manifestFilePath = `${patchFilePath}.json`;
4725
5288
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4726
5289
  const baseArtifact = {
@@ -4759,7 +5322,7 @@ var applyPatchFileTool = defineTool({
4759
5322
  }
4760
5323
  return withRepoLock(cwd, async () => {
4761
5324
  const packageJsonSnapshot = patchModeRequiresPackageJsonSnapshot(pm) ? await capturePackageJsonSnapshot(cwd) : void 0;
4762
- const patchesDirPath = join18(cwd, patchesDir);
5325
+ const patchesDirPath = join19(cwd, patchesDir);
4763
5326
  await mkdir3(patchesDirPath, { recursive: true });
4764
5327
  await writeFile3(patchFilePath, selectedPatch, "utf8");
4765
5328
  validationPhases.push({
@@ -4782,6 +5345,101 @@ var applyPatchFileTool = defineTool({
4782
5345
  });
4783
5346
  artifact.validationPhases = validationPhases;
4784
5347
  await writePatchManifest(manifestFilePath, artifact);
5348
+ let reproductionTestPath;
5349
+ let negativePhaseResult;
5350
+ if (typeof proofOfFix === "string" && proofOfFix.trim().length > 0) {
5351
+ const resolvedPath = isAbsolute4(proofOfFix) ? proofOfFix : join19(cwd, proofOfFix);
5352
+ if (existsSync14(resolvedPath)) {
5353
+ reproductionTestPath = resolvedPath;
5354
+ } else {
5355
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5356
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5357
+ await writeFile3(reproductionTestPath, proofOfFix, "utf8");
5358
+ }
5359
+ } else if (reproductionTest) {
5360
+ const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
5361
+ reproductionTestPath = join19(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
5362
+ await writeFile3(reproductionTestPath, reproductionTest, "utf8");
5363
+ }
5364
+ const isProofOfFixActive = Boolean(proofOfFix || requireProofOfFix);
5365
+ if (isProofOfFixActive) {
5366
+ if (!reproductionTestPath) {
5367
+ if (requireProofOfFix) {
5368
+ await cleanupPatchArtifacts({
5369
+ cwd,
5370
+ patchFilePath,
5371
+ manifestFilePath,
5372
+ patchMode,
5373
+ packageJsonSnapshot,
5374
+ installCommand,
5375
+ rerunInstall: false
5376
+ });
5377
+ return {
5378
+ success: false,
5379
+ packageName,
5380
+ vulnerableVersion,
5381
+ applied: false,
5382
+ dryRun: false,
5383
+ message: "Proof-of-fix verification required, but no reproduction test or script was supplied.",
5384
+ patchFilePath,
5385
+ manifestFilePath,
5386
+ patchPath: patchFilePath,
5387
+ patchMode,
5388
+ postinstallConfigured: false,
5389
+ patchArtifact: {
5390
+ ...artifact,
5391
+ applied: false,
5392
+ validationPhases
5393
+ },
5394
+ validationPhases,
5395
+ error: "Proof-of-fix verification required, but no reproduction test or script was supplied."
5396
+ };
5397
+ }
5398
+ } else {
5399
+ negativePhaseResult = await runProofOfFixPhase({
5400
+ stage: "negative",
5401
+ testFilePath: reproductionTestPath,
5402
+ cwd
5403
+ });
5404
+ validationPhases.push({
5405
+ phase: "proof-of-fix-negative",
5406
+ passed: negativePhaseResult.passed,
5407
+ message: negativePhaseResult.passed ? "Reproduction test reproduced vulnerability on unpatched code." : void 0,
5408
+ error: negativePhaseResult.passed ? void 0 : negativePhaseResult.error
5409
+ });
5410
+ if (!negativePhaseResult.passed && requireProofOfFix) {
5411
+ await cleanupPatchArtifacts({
5412
+ cwd,
5413
+ patchFilePath,
5414
+ manifestFilePath,
5415
+ patchMode,
5416
+ packageJsonSnapshot,
5417
+ installCommand,
5418
+ rerunInstall: false
5419
+ });
5420
+ return {
5421
+ success: false,
5422
+ packageName,
5423
+ vulnerableVersion,
5424
+ applied: false,
5425
+ dryRun: false,
5426
+ message: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`,
5427
+ patchFilePath,
5428
+ manifestFilePath,
5429
+ patchPath: patchFilePath,
5430
+ patchMode,
5431
+ postinstallConfigured: false,
5432
+ patchArtifact: {
5433
+ ...artifact,
5434
+ applied: false,
5435
+ validationPhases
5436
+ },
5437
+ validationPhases,
5438
+ error: `Proof-of-fix negative phase failed: ${negativePhaseResult.error}`
5439
+ };
5440
+ }
5441
+ }
5442
+ }
4785
5443
  const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
4786
5444
  cwd,
4787
5445
  packageName,
@@ -4922,34 +5580,90 @@ var applyPatchFileTool = defineTool({
4922
5580
  message: "Patch validation tests passed."
4923
5581
  });
4924
5582
  }
4925
- let reproductionTestPath;
4926
- if (reproductionTest) {
4927
- const safeName = packageName.replace(/^@/, "").replace(/\//g, "+");
4928
- reproductionTestPath = join18(patchesDirPath, `${safeName}+${vulnerableVersion}.test.cjs`);
4929
- await writeFile3(reproductionTestPath, reproductionTest, "utf8");
4930
- try {
4931
- const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
4932
- validationPhases.push({
4933
- phase: "reproduction-test",
4934
- passed: reproRun.exitCode === 0,
4935
- message: "Reproduction test verified fix successfully."
5583
+ let positivePhaseResult;
5584
+ if (reproductionTestPath) {
5585
+ if (isProofOfFixActive) {
5586
+ positivePhaseResult = await runProofOfFixPhase({
5587
+ stage: "positive",
5588
+ testFilePath: reproductionTestPath,
5589
+ cwd
4936
5590
  });
4937
- } catch (err) {
4938
- const reproErr = err instanceof Error ? err.message : String(err);
4939
5591
  validationPhases.push({
4940
- phase: "reproduction-test",
4941
- passed: false,
4942
- error: `Reproduction test failed: ${reproErr}`
5592
+ phase: "proof-of-fix-positive",
5593
+ passed: positivePhaseResult.passed,
5594
+ message: positivePhaseResult.passed ? "Reproduction test passed on patched code (vulnerability mitigated)." : void 0,
5595
+ error: positivePhaseResult.passed ? void 0 : positivePhaseResult.error
4943
5596
  });
5597
+ if (!positivePhaseResult.passed && (requireProofOfFix || validateWithTests)) {
5598
+ await cleanupPatchArtifacts({
5599
+ cwd,
5600
+ patchFilePath,
5601
+ manifestFilePath,
5602
+ patchMode,
5603
+ packageJsonSnapshot,
5604
+ installCommand,
5605
+ rerunInstall: patchMode === "patch-package"
5606
+ });
5607
+ return {
5608
+ success: false,
5609
+ packageName,
5610
+ vulnerableVersion,
5611
+ applied: false,
5612
+ dryRun: false,
5613
+ message: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`,
5614
+ patchFilePath,
5615
+ manifestFilePath,
5616
+ patchPath: patchFilePath,
5617
+ patchMode,
5618
+ postinstallConfigured: false,
5619
+ patchArtifact: {
5620
+ ...artifact,
5621
+ applied: false,
5622
+ validationPhases
5623
+ },
5624
+ validationPhases,
5625
+ error: `Proof-of-fix positive phase failed: ${positivePhaseResult.error}`
5626
+ };
5627
+ }
5628
+ } else {
5629
+ try {
5630
+ const reproRun = await execa9("node", [reproductionTestPath], { cwd, stdio: "pipe" });
5631
+ validationPhases.push({
5632
+ phase: "reproduction-test",
5633
+ passed: reproRun.exitCode === 0,
5634
+ message: "Reproduction test verified fix successfully."
5635
+ });
5636
+ } catch (err) {
5637
+ const reproErr = err instanceof Error ? err.message : String(err);
5638
+ validationPhases.push({
5639
+ phase: "reproduction-test",
5640
+ passed: false,
5641
+ error: `Reproduction test failed: ${reproErr}`
5642
+ });
5643
+ }
4944
5644
  }
4945
5645
  }
5646
+ let proofOfFixVerification;
5647
+ if (negativePhaseResult && positivePhaseResult) {
5648
+ proofOfFixVerification = assembleProofOfFixVerification({
5649
+ reproductionTestPath,
5650
+ negativePhase: negativePhaseResult,
5651
+ positivePhase: positivePhaseResult,
5652
+ regressionPhase: validationResult ? {
5653
+ executed: true,
5654
+ passed: validationResult.passed,
5655
+ error: validationResult.error
5656
+ } : void 0
5657
+ });
5658
+ }
4946
5659
  const finalArtifact = {
4947
5660
  ...artifact,
4948
5661
  applied: true,
4949
5662
  dryRun: false,
4950
5663
  reproductionTest,
4951
5664
  reproductionTestPath,
4952
- validationPhases
5665
+ validationPhases,
5666
+ proofOfFix: proofOfFixVerification
4953
5667
  };
4954
5668
  await writePatchManifest(manifestFilePath, finalArtifact);
4955
5669
  return {
@@ -4966,7 +5680,8 @@ var applyPatchFileTool = defineTool({
4966
5680
  postinstallConfigured: patchMode === "patch-package",
4967
5681
  patchArtifact: finalArtifact,
4968
5682
  validation: validationResult,
4969
- validationPhases
5683
+ validationPhases,
5684
+ proofOfFix: proofOfFixVerification
4970
5685
  };
4971
5686
  });
4972
5687
  } catch (err) {
@@ -5210,6 +5925,9 @@ async function tryLocalPatchFallback(params) {
5210
5925
  workspace: params.workspace,
5211
5926
  riskLevel: patchResult.riskLevel,
5212
5927
  validateWithTests: params.runTests,
5928
+ reproductionTest: patchResult.reproductionTest,
5929
+ proofOfFix: params.proofOfFix,
5930
+ requireProofOfFix: params.requireProofOfFix,
5213
5931
  dryRun: params.dryRun
5214
5932
  });
5215
5933
  steps += 1;
@@ -5258,12 +5976,14 @@ function resolveLocalRunOptions(options) {
5258
5976
  const runTests = options.runTests ?? false;
5259
5977
  const policy = options.policy ?? "";
5260
5978
  const patchesDir = options.patchesDir || "./patches";
5979
+ const loadedPolicy = loadPolicy(cwd, options.policy);
5261
5980
  const constraints = {
5262
5981
  ...options.constraints,
5263
- typecheck: options.typecheck ?? options.constraints?.typecheck,
5264
- supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy
5982
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? loadedPolicy.constraints?.typecheck,
5983
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? loadedPolicy.constraints?.supplyChainPolicy,
5984
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? loadedPolicy.constraints?.enableBreakingChangeRefactor,
5985
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? loadedPolicy.constraints?.requireProofOfFix
5265
5986
  };
5266
- const loadedPolicy = loadPolicy(cwd, options.policy);
5267
5987
  const llmProvider = resolveProvider(options);
5268
5988
  return {
5269
5989
  cwd,
@@ -5294,7 +6014,10 @@ function resolveLocalRunOptions(options) {
5294
6014
  regressionCheck: options.regressionCheck ?? false,
5295
6015
  escalationGraph: options.escalationGraph ?? loadedPolicy.escalationGraph,
5296
6016
  offlineIntelligence: options.offlineIntelligence ?? false,
5297
- intelligenceSnapshotPath: options.intelligenceSnapshotPath
6017
+ intelligenceSnapshotPath: options.intelligenceSnapshotPath,
6018
+ proofOfFix: options.proofOfFix,
6019
+ requireProofOfFix: options.requireProofOfFix ?? constraints.requireProofOfFix,
6020
+ breakingChangeRefactor: options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor
5298
6021
  };
5299
6022
  }
5300
6023
 
@@ -5462,7 +6185,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5462
6185
  regressionCheck,
5463
6186
  escalationGraph,
5464
6187
  offlineIntelligence,
5465
- intelligenceSnapshotPath
6188
+ intelligenceSnapshotPath,
6189
+ proofOfFix,
6190
+ requireProofOfFix
5466
6191
  } = resolved;
5467
6192
  const correlation = {
5468
6193
  requestId: options.requestId,
@@ -5639,7 +6364,9 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
5639
6364
  installPreferOffline: constraints.installPreferOffline,
5640
6365
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
5641
6366
  workspace: constraints.workspace,
5642
- vulnerableRange: vulnerable.affected.vulnerableRange
6367
+ vulnerableRange: vulnerable.affected.vulnerableRange,
6368
+ proofOfFix,
6369
+ requireProofOfFix
5643
6370
  });
5644
6371
  agentSteps += fallback.steps;
5645
6372
  collectedResults.push(finalizePatchResult(fallback.result));
@@ -5745,8 +6472,8 @@ function accumulateStepResults(params) {
5745
6472
  }
5746
6473
 
5747
6474
  // src/remediation/orchestration-prompt.ts
5748
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
5749
- import { join as join19 } from "path";
6475
+ import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
6476
+ import { join as join20 } from "path";
5750
6477
  function buildProviderAddendum(provider, personality = "balanced") {
5751
6478
  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.";
5752
6479
  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.";
@@ -5757,13 +6484,13 @@ Provider profile:
5757
6484
  - ${personalityDirective}`;
5758
6485
  }
5759
6486
  function loadOrchestrationPrompt(ctx) {
5760
- const promptPath = join19(
6487
+ const promptPath = join20(
5761
6488
  process.cwd(),
5762
6489
  ".github",
5763
6490
  "instructions",
5764
6491
  "orchestration.instructions.md"
5765
6492
  );
5766
- if (!existsSync13(promptPath)) {
6493
+ if (!existsSync15(promptPath)) {
5767
6494
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
5768
6495
  Working directory: ${ctx.cwd}
5769
6496
  Package manager: ${ctx.packageManager}
@@ -5789,7 +6516,7 @@ Fallback sequence (when neither version bump nor override can be applied):
5789
6516
 
5790
6517
  Always respect dryRun and policy constraints.`;
5791
6518
  }
5792
- const template = readFileSync13(promptPath, "utf8");
6519
+ const template = readFileSync15(promptPath, "utf8");
5793
6520
  return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll(
5794
6521
  "{{directDependenciesOnly}}",
5795
6522
  String(ctx.constraints.directDependenciesOnly ?? false)
@@ -6268,6 +6995,8 @@ function createRuntimeToolsForRun(options) {
6268
6995
  installPreferOffline: constraints.installPreferOffline,
6269
6996
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6270
6997
  workspace: constraints.workspace,
6998
+ typecheck: input.typecheck ?? options.typecheck ?? constraints.typecheck,
6999
+ breakingChangeRefactor: input.breakingChangeRefactor ?? options.breakingChangeRefactor ?? constraints.enableBreakingChangeRefactor,
6271
7000
  dryRun: preview ? true : input.dryRun
6272
7001
  })
6273
7002
  };
@@ -6292,6 +7021,8 @@ function createRuntimeToolsForRun(options) {
6292
7021
  installPreferOffline: constraints.installPreferOffline,
6293
7022
  enforceFrozenLockfile: constraints.enforceFrozenLockfile,
6294
7023
  workspace: constraints.workspace,
7024
+ proofOfFix: input.proofOfFix ?? options.proofOfFix,
7025
+ requireProofOfFix: input.requireProofOfFix ?? options.requireProofOfFix ?? constraints.requireProofOfFix,
6295
7026
  dryRun: preview ? true : input.dryRun
6296
7027
  })
6297
7028
  };
@@ -6722,13 +7453,17 @@ function resolveConstraints(options, cwd) {
6722
7453
  installMode: options.constraints?.installMode ?? policy.constraints?.installMode ?? "deterministic",
6723
7454
  installPreferOffline: options.constraints?.installPreferOffline ?? policy.constraints?.installPreferOffline,
6724
7455
  enforceFrozenLockfile: options.constraints?.enforceFrozenLockfile ?? policy.constraints?.enforceFrozenLockfile,
6725
- workspace: options.constraints?.workspace ?? policy.constraints?.workspace
7456
+ workspace: options.constraints?.workspace ?? policy.constraints?.workspace,
7457
+ typecheck: options.typecheck ?? options.constraints?.typecheck ?? policy.constraints?.typecheck,
7458
+ requireProofOfFix: options.requireProofOfFix ?? options.constraints?.requireProofOfFix ?? policy.constraints?.requireProofOfFix,
7459
+ enableBreakingChangeRefactor: options.breakingChangeRefactor ?? options.constraints?.enableBreakingChangeRefactor ?? policy.constraints?.enableBreakingChangeRefactor,
7460
+ supplyChainPolicy: options.supplyChainPolicy ?? options.constraints?.supplyChainPolicy ?? policy.constraints?.supplyChainPolicy
6726
7461
  };
6727
7462
  }
6728
7463
 
6729
7464
  // src/platform/evidence.ts
6730
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6731
- import { join as join20 } from "path";
7465
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync6 } from "fs";
7466
+ import { join as join21 } from "path";
6732
7467
  function createEvidenceLog(cwd, cveIds, context = {}) {
6733
7468
  return {
6734
7469
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -6760,10 +7495,10 @@ function finalizeEvidence(log) {
6760
7495
  return log;
6761
7496
  }
6762
7497
  function writeEvidenceLog(cwd, log) {
6763
- const dir = join20(cwd, ".autoremediator", "evidence");
7498
+ const dir = join21(cwd, ".autoremediator", "evidence");
6764
7499
  mkdirSync3(dir, { recursive: true });
6765
- const filePath = join20(dir, `${log.runId}.json`);
6766
- writeFileSync5(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
7500
+ const filePath = join21(dir, `${log.runId}.json`);
7501
+ writeFileSync6(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
6767
7502
  return filePath;
6768
7503
  }
6769
7504
 
@@ -7366,12 +8101,12 @@ async function planRemediation(cveId, options = {}) {
7366
8101
  }
7367
8102
 
7368
8103
  // src/scanner/parse-input.ts
7369
- import { extname, resolve as resolve4 } from "path";
7370
- import { readFileSync as readFileSync17 } from "fs";
8104
+ import { extname, resolve as resolve5 } from "path";
8105
+ import { readFileSync as readFileSync19 } from "fs";
7371
8106
  import { execa as execa11 } from "execa";
7372
8107
 
7373
8108
  // src/scanner/adapters/npm-audit.ts
7374
- import { readFileSync as readFileSync14 } from "fs";
8109
+ import { readFileSync as readFileSync16 } from "fs";
7375
8110
  var CVE_REGEX = /CVE-\d{4}-\d{1,7}/gi;
7376
8111
  var GHSA_REGEX = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
7377
8112
  function normalizeSeverity(raw) {
@@ -7407,12 +8142,12 @@ function parseNpmAuditJsonFromString(content) {
7407
8142
  return findings;
7408
8143
  }
7409
8144
  function parseNpmAuditJsonFile(filePath) {
7410
- const content = readFileSync14(filePath, "utf8");
8145
+ const content = readFileSync16(filePath, "utf8");
7411
8146
  return parseNpmAuditJsonFromString(content);
7412
8147
  }
7413
8148
 
7414
8149
  // src/scanner/adapters/yarn-audit.ts
7415
- import { readFileSync as readFileSync15 } from "fs";
8150
+ import { readFileSync as readFileSync17 } from "fs";
7416
8151
  var CVE_REGEX2 = /CVE-\d{4}-\d{1,7}/gi;
7417
8152
  var GHSA_REGEX2 = /GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}/gi;
7418
8153
  function normalizeSeverity2(raw) {
@@ -7457,12 +8192,12 @@ function parseYarnAuditJsonFromString(content) {
7457
8192
  return findings;
7458
8193
  }
7459
8194
  function parseYarnAuditJsonFile(filePath) {
7460
- const content = readFileSync15(filePath, "utf8");
8195
+ const content = readFileSync17(filePath, "utf8");
7461
8196
  return parseYarnAuditJsonFromString(content);
7462
8197
  }
7463
8198
 
7464
8199
  // src/scanner/adapters/sarif.ts
7465
- import { readFileSync as readFileSync16 } from "fs";
8200
+ import { readFileSync as readFileSync18 } from "fs";
7466
8201
  var CVE_REGEX3 = /CVE-\d{4}-\d{1,7}/gi;
7467
8202
  function extractPackageName(result) {
7468
8203
  const pkg = result.properties?.["packageName"];
@@ -7500,7 +8235,7 @@ function parseSarifFromString(content) {
7500
8235
  return findings;
7501
8236
  }
7502
8237
  function parseSarifFile(filePath) {
7503
- const content = readFileSync16(filePath, "utf8");
8238
+ const content = readFileSync18(filePath, "utf8");
7504
8239
  return parseSarifFromString(content);
7505
8240
  }
7506
8241
 
@@ -7509,7 +8244,7 @@ function parseScanInput(filePath, format) {
7509
8244
  if (filePath.includes("\0")) {
7510
8245
  throw new Error("Invalid scan input path: path contains null bytes");
7511
8246
  }
7512
- const resolvedPath = resolve4(filePath);
8247
+ const resolvedPath = resolve5(filePath);
7513
8248
  const resolved = format === "auto" ? inferFormat(resolvedPath) : format;
7514
8249
  if (resolved === "npm-audit") {
7515
8250
  return parseNpmAuditJsonFile(resolvedPath);
@@ -7585,7 +8320,7 @@ function inferFormat(filePath) {
7585
8320
  const ext = extname(filePath).toLowerCase();
7586
8321
  if (ext === ".sarif") return "sarif";
7587
8322
  try {
7588
- const content = readFileSync17(filePath, "utf8");
8323
+ const content = readFileSync19(filePath, "utf8");
7589
8324
  const firstLine = content.split("\n").find((line) => line.trim().startsWith("{"));
7590
8325
  if (firstLine) {
7591
8326
  const parsed = JSON.parse(firstLine);
@@ -7996,14 +8731,14 @@ async function remediatePortfolio(targets, options = {}) {
7996
8731
  }
7997
8732
 
7998
8733
  // src/api/update-outdated/index.ts
7999
- import { join as join21 } from "path";
8000
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "fs";
8734
+ import { join as join22 } from "path";
8735
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "fs";
8001
8736
  import { execa as execa12 } from "execa";
8002
8737
  async function applyBump(params) {
8003
- const pkgPath = join21(params.cwd, "package.json");
8738
+ const pkgPath = join22(params.cwd, "package.json");
8004
8739
  let pkgJson;
8005
8740
  try {
8006
- pkgJson = JSON.parse(readFileSync18(pkgPath, "utf8"));
8741
+ pkgJson = JSON.parse(readFileSync20(pkgPath, "utf8"));
8007
8742
  } catch {
8008
8743
  return {
8009
8744
  applied: false,
@@ -8025,13 +8760,13 @@ async function applyBump(params) {
8025
8760
  const newRange = `${prefix}${params.toVersion}`;
8026
8761
  return withRepoLock(params.cwd, async () => {
8027
8762
  pkgJson[depField][params.packageName] = newRange;
8028
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8763
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8029
8764
  try {
8030
8765
  const [installCmd, ...installArgs] = params.installCommand;
8031
8766
  await execa12(installCmd, installArgs, { cwd: params.cwd, stdio: "pipe" });
8032
8767
  } catch (err) {
8033
8768
  pkgJson[depField][params.packageName] = currentRange;
8034
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8769
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8035
8770
  const message = err instanceof Error ? err.message : String(err);
8036
8771
  return {
8037
8772
  applied: false,
@@ -8044,7 +8779,7 @@ async function applyBump(params) {
8044
8779
  await execa12(testCmd, testArgs, { cwd: params.cwd, stdio: "pipe" });
8045
8780
  } catch (err) {
8046
8781
  pkgJson[depField][params.packageName] = currentRange;
8047
- writeFileSync6(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8782
+ writeFileSync7(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
8048
8783
  try {
8049
8784
  const [rollbackCmd, ...rollbackArgs] = params.installCommand;
8050
8785
  await execa12(rollbackCmd, rollbackArgs, { cwd: params.cwd, stdio: "pipe" });
@@ -8249,8 +8984,8 @@ async function checkReachability(options) {
8249
8984
  }
8250
8985
 
8251
8986
  // src/scanner/delta-scan.ts
8252
- import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
8253
- import { join as join22 } from "path";
8987
+ import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
8988
+ import { join as join23 } from "path";
8254
8989
  import { execa as execa13 } from "execa";
8255
8990
  import semver9 from "semver";
8256
8991
 
@@ -8443,7 +9178,7 @@ async function getChangedManifestPaths(cwd, baseRef) {
8443
9178
  }
8444
9179
  }
8445
9180
  } catch {
8446
- if (existsSync14(join22(cwd, "package.json"))) {
9181
+ if (existsSync16(join23(cwd, "package.json"))) {
8447
9182
  manifests.add("package.json");
8448
9183
  }
8449
9184
  }
@@ -8462,10 +9197,10 @@ async function getOldManifestContent(cwd, baseRef, relPath) {
8462
9197
  }
8463
9198
  }
8464
9199
  function getNewManifestContent(cwd, relPath) {
8465
- const fullPath = join22(cwd, relPath);
8466
- if (!existsSync14(fullPath)) return null;
9200
+ const fullPath = join23(cwd, relPath);
9201
+ if (!existsSync16(fullPath)) return null;
8467
9202
  try {
8468
- return JSON.parse(readFileSync19(fullPath, "utf8"));
9203
+ return JSON.parse(readFileSync21(fullPath, "utf8"));
8469
9204
  } catch {
8470
9205
  return null;
8471
9206
  }
@@ -8579,8 +9314,8 @@ async function scanDelta(options = {}) {
8579
9314
  }
8580
9315
  for (const lockfileRel of changedLockfiles) {
8581
9316
  const oldContent = await getFileContentAtRef(cwd, baseRef, lockfileRel);
8582
- const fullPath = join22(cwd, lockfileRel);
8583
- const newContent = existsSync14(fullPath) ? readFileSync19(fullPath, "utf8") : null;
9317
+ const fullPath = join23(cwd, lockfileRel);
9318
+ const newContent = existsSync16(fullPath) ? readFileSync21(fullPath, "utf8") : null;
8584
9319
  const lockDiffs = computeLockfileDiff(lockfileRel, oldContent, newContent);
8585
9320
  for (const change of lockDiffs) {
8586
9321
  if (!directPkgs.has(change.packageName)) {
@@ -8783,9 +9518,9 @@ function toSarifOutput(report) {
8783
9518
  }
8784
9519
 
8785
9520
  // src/version.ts
8786
- import { readFileSync as readFileSync20 } from "fs";
9521
+ import { readFileSync as readFileSync22 } from "fs";
8787
9522
  function readPackageVersion() {
8788
- const raw = readFileSync20(new URL("../package.json", import.meta.url), "utf8");
9523
+ const raw = readFileSync22(new URL("../package.json", import.meta.url), "utf8");
8789
9524
  const metadata = JSON.parse(raw);
8790
9525
  if (!metadata.version) {
8791
9526
  throw new Error("packages/core/package.json is missing a version field.");