autoremediator 0.2.1 → 0.3.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.
@@ -1,7 +1,7 @@
1
1
  // src/remediation/pipeline.ts
2
2
  import { generateText as generateText2 } from "ai";
3
3
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
4
- import { join as join7 } from "path";
4
+ import { join as join8 } from "path";
5
5
  import semver4 from "semver";
6
6
 
7
7
  // src/platform/config.ts
@@ -574,7 +574,7 @@ var findFixedVersionTool = tool4({
574
574
  // src/remediation/tools/apply-version-bump.ts
575
575
  import { tool as tool5 } from "ai";
576
576
  import { z as z5 } from "zod";
577
- import { join as join4 } from "path";
577
+ import { join as join5 } from "path";
578
578
  import { readFileSync as readFileSync3, writeFileSync } from "fs";
579
579
  import { execa as execa2 } from "execa";
580
580
  import semver3 from "semver";
@@ -585,7 +585,11 @@ import { join as join3 } from "path";
585
585
  var DEFAULT_POLICY = {
586
586
  allowMajorBumps: false,
587
587
  denyPackages: [],
588
- allowPackages: []
588
+ allowPackages: [],
589
+ constraints: {
590
+ directDependenciesOnly: false,
591
+ preferVersionBump: false
592
+ }
589
593
  };
590
594
  function loadPolicy(cwd, explicitPath) {
591
595
  const candidate = explicitPath ?? join3(cwd, ".autoremediator.json");
@@ -595,7 +599,11 @@ function loadPolicy(cwd, explicitPath) {
595
599
  return {
596
600
  allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
597
601
  denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
598
- allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages
602
+ allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
603
+ constraints: {
604
+ directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
605
+ preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false
606
+ }
599
607
  };
600
608
  } catch {
601
609
  return DEFAULT_POLICY;
@@ -609,6 +617,45 @@ function isPackageAllowed(policy, packageName) {
609
617
  return true;
610
618
  }
611
619
 
620
+ // src/platform/repo-lock.ts
621
+ import { mkdir, rm } from "fs/promises";
622
+ import { join as join4 } from "path";
623
+ async function sleep(ms) {
624
+ await new Promise((resolve) => setTimeout(resolve, ms));
625
+ }
626
+ async function acquireRepoLock(cwd, options = {}) {
627
+ const timeoutMs = options.timeoutMs ?? 15e3;
628
+ const retryDelayMs = options.retryDelayMs ?? 125;
629
+ const lockRoot = join4(cwd, ".autoremediator", "locks");
630
+ const lockPath = join4(cwd, ".autoremediator", "locks", "remediation.lock");
631
+ const startedAt = Date.now();
632
+ await mkdir(lockRoot, { recursive: true });
633
+ while (true) {
634
+ try {
635
+ await mkdir(lockPath, { recursive: false });
636
+ return {
637
+ lockPath,
638
+ release: async () => {
639
+ await rm(lockPath, { recursive: true, force: true });
640
+ }
641
+ };
642
+ } catch {
643
+ if (Date.now() - startedAt > timeoutMs) {
644
+ throw new Error(`Timed out waiting for repository lock at ${lockPath}.`);
645
+ }
646
+ await sleep(retryDelayMs);
647
+ }
648
+ }
649
+ }
650
+ async function withRepoLock(cwd, fn, options) {
651
+ const lock = await acquireRepoLock(cwd, options);
652
+ try {
653
+ return await fn();
654
+ } finally {
655
+ await lock.release();
656
+ }
657
+ }
658
+
612
659
  // src/remediation/tools/apply-version-bump.ts
613
660
  var applyVersionBumpTool = tool5({
614
661
  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.",
@@ -634,7 +681,7 @@ var applyVersionBumpTool = tool5({
634
681
  }) => {
635
682
  const pm = packageManager ?? detectPackageManager(cwd);
636
683
  const commands = getPackageManagerCommands(pm);
637
- const pkgPath = join4(cwd, "package.json");
684
+ const pkgPath = join5(cwd, "package.json");
638
685
  const policy = loadPolicy(cwd, policyPath);
639
686
  if (!isPackageAllowed(policy, packageName)) {
640
687
  return {
@@ -702,46 +749,18 @@ var applyVersionBumpTool = tool5({
702
749
  message: `[DRY RUN] Would update ${depField}.${packageName}: "${currentRange}" \u2192 "${newRange}", then run ${installCmd}${skipTests ? "" : ` and ${testCmd}`}.`
703
750
  };
704
751
  }
705
- pkgJson[depField][packageName] = newRange;
706
- writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
707
- try {
708
- const [installCmd, ...installArgs] = commands.installPreferOffline;
709
- await execa2(installCmd, installArgs, {
710
- cwd,
711
- stdio: "pipe"
712
- });
713
- } catch (err) {
714
- pkgJson[depField][packageName] = currentRange;
752
+ return withRepoLock(cwd, async () => {
753
+ pkgJson[depField][packageName] = newRange;
715
754
  writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
716
- const message = err instanceof Error ? err.message : String(err);
717
- return {
718
- packageName,
719
- strategy: "version-bump",
720
- fromVersion,
721
- toVersion,
722
- applied: false,
723
- dryRun: false,
724
- message: `${commands.installPreferOffline.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
725
- };
726
- }
727
- if (!skipTests) {
728
755
  try {
729
- const [testCmd, ...testArgs] = commands.test;
730
- await execa2(testCmd, testArgs, {
756
+ const [installCmd, ...installArgs] = commands.installPreferOffline;
757
+ await execa2(installCmd, installArgs, {
731
758
  cwd,
732
759
  stdio: "pipe"
733
760
  });
734
761
  } catch (err) {
735
762
  pkgJson[depField][packageName] = currentRange;
736
763
  writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
737
- try {
738
- const [rollbackCmd, ...rollbackArgs] = commands.installPreferOffline;
739
- await execa2(rollbackCmd, rollbackArgs, {
740
- cwd,
741
- stdio: "pipe"
742
- });
743
- } catch {
744
- }
745
764
  const message = err instanceof Error ? err.message : String(err);
746
765
  return {
747
766
  packageName,
@@ -750,27 +769,57 @@ var applyVersionBumpTool = tool5({
750
769
  toVersion,
751
770
  applied: false,
752
771
  dryRun: false,
753
- message: `${commands.test.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
772
+ message: `${commands.installPreferOffline.join(" ")} failed after updating "${packageName}" to ${toVersion}. Reverted. Error: ${message}`
754
773
  };
755
774
  }
756
- }
757
- return {
758
- packageName,
759
- strategy: "version-bump",
760
- fromVersion,
761
- toVersion,
762
- applied: true,
763
- dryRun: false,
764
- message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${commands.installPreferOffline.join(" ")}${skipTests ? "" : `, and passed ${commands.test.join(" ")}`}.`
765
- };
775
+ if (!skipTests) {
776
+ try {
777
+ const [testCmd, ...testArgs] = commands.test;
778
+ await execa2(testCmd, testArgs, {
779
+ cwd,
780
+ stdio: "pipe"
781
+ });
782
+ } catch (err) {
783
+ pkgJson[depField][packageName] = currentRange;
784
+ writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf8");
785
+ try {
786
+ const [rollbackCmd, ...rollbackArgs] = commands.installPreferOffline;
787
+ await execa2(rollbackCmd, rollbackArgs, {
788
+ cwd,
789
+ stdio: "pipe"
790
+ });
791
+ } catch {
792
+ }
793
+ const message = err instanceof Error ? err.message : String(err);
794
+ return {
795
+ packageName,
796
+ strategy: "version-bump",
797
+ fromVersion,
798
+ toVersion,
799
+ applied: false,
800
+ dryRun: false,
801
+ message: `${commands.test.join(" ")} failed after upgrading "${packageName}" to ${toVersion}. Rolled back to ${currentRange}. Error: ${message}`
802
+ };
803
+ }
804
+ }
805
+ return {
806
+ packageName,
807
+ strategy: "version-bump",
808
+ fromVersion,
809
+ toVersion,
810
+ applied: true,
811
+ dryRun: false,
812
+ message: `Successfully upgraded "${packageName}" from ${fromVersion} to ${toVersion}, ran ${commands.installPreferOffline.join(" ")}${skipTests ? "" : `, and passed ${commands.test.join(" ")}`}.`
813
+ };
814
+ });
766
815
  }
767
816
  });
768
817
 
769
818
  // src/remediation/tools/fetch-package-source.ts
770
819
  import { tool as tool6 } from "ai";
771
820
  import { z as z6 } from "zod";
772
- import { mkdir, readdir, readFile, rm } from "fs/promises";
773
- import { join as join5 } from "path";
821
+ import { mkdir as mkdir2, readdir, readFile, rm as rm2 } from "fs/promises";
822
+ import { join as join6 } from "path";
774
823
  import { execa as execa3 } from "execa";
775
824
  var fetchPackageSourceTool = tool6({
776
825
  description: "Download package tarball from npm and extract source files for CVE analysis. Supports custom file patterns (default: *.js, *.ts).",
@@ -787,23 +836,23 @@ var fetchPackageSourceTool = tool6({
787
836
  filePatterns
788
837
  }) => {
789
838
  const tempBaseDir = `/tmp/autoremediator-pkg-${Date.now()}`;
790
- const extractDir = join5(tempBaseDir, "out");
839
+ const extractDir = join6(tempBaseDir, "out");
791
840
  try {
792
841
  const npmUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.split("/").pop()}-${version}.tgz`;
793
- await mkdir(tempBaseDir, { recursive: true });
794
- const tarballPath = join5(tempBaseDir, "package.tgz");
842
+ await mkdir2(tempBaseDir, { recursive: true });
843
+ const tarballPath = join6(tempBaseDir, "package.tgz");
795
844
  await execa3("curl", ["-L", "-o", tarballPath, npmUrl]);
796
- await mkdir(extractDir, { recursive: true });
845
+ await mkdir2(extractDir, { recursive: true });
797
846
  await execa3("tar", ["-xzf", tarballPath, "-C", extractDir]);
798
847
  const extractedContents = await readdir(extractDir);
799
- const packageRootDir = extractedContents.includes("package") ? join5(extractDir, "package") : extractDir;
848
+ const packageRootDir = extractedContents.includes("package") ? join6(extractDir, "package") : extractDir;
800
849
  const sourceCode = {};
801
850
  async function walkDir(dir, relativeBase) {
802
851
  try {
803
852
  const files = await readdir(dir, { withFileTypes: true });
804
853
  for (const file of files) {
805
- const fullPath = join5(dir, file.name);
806
- const relPath = join5(relativeBase, file.name);
854
+ const fullPath = join6(dir, file.name);
855
+ const relPath = join6(relativeBase, file.name);
807
856
  if (file.isDirectory()) {
808
857
  if (![
809
858
  "node_modules",
@@ -860,7 +909,7 @@ var fetchPackageSourceTool = tool6({
860
909
  error: `Failed to fetch and extract package ${packageName}@${version}: ${message}`
861
910
  };
862
911
  } finally {
863
- await rm(tempBaseDir, { recursive: true, force: true });
912
+ await rm2(tempBaseDir, { recursive: true, force: true });
864
913
  }
865
914
  }
866
915
  });
@@ -1076,9 +1125,9 @@ function generateUnifiedDiff(original, fixed, filePath) {
1076
1125
  import { tool as tool8 } from "ai";
1077
1126
  import { z as z8 } from "zod";
1078
1127
  import { existsSync as existsSync3 } from "fs";
1079
- import { mkdir as mkdir2, mkdtemp, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
1128
+ import { mkdir as mkdir3, mkdtemp, readFile as readFile2, rm as rm3, writeFile } from "fs/promises";
1080
1129
  import { tmpdir } from "os";
1081
- import { join as join6 } from "path";
1130
+ import { join as join7 } from "path";
1082
1131
  import { execa as execa4 } from "execa";
1083
1132
  var applyPatchFileTool = tool8({
1084
1133
  description: "Write generated patch file and apply it using package-manager-native patch flow when available, falling back to patch-package when needed.",
@@ -1126,7 +1175,7 @@ var applyPatchFileTool = tool8({
1126
1175
  };
1127
1176
  }
1128
1177
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
1129
- const patchFilePath = join6(cwd, patchesDir, patchFileName);
1178
+ const patchFilePath = join7(cwd, patchesDir, patchFileName);
1130
1179
  if (dryRun) {
1131
1180
  return {
1132
1181
  success: true,
@@ -1139,66 +1188,68 @@ var applyPatchFileTool = tool8({
1139
1188
  patchPath: patchFilePath
1140
1189
  };
1141
1190
  }
1142
- const patchesDirPath = join6(cwd, patchesDir);
1143
- await mkdir2(patchesDirPath, { recursive: true });
1144
- await writeFile(patchFilePath, selectedPatch, "utf8");
1145
- let validationResult;
1146
- const patchMode = await resolvePatchMode(pm, cwd);
1147
- const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
1148
- cwd,
1149
- packageName,
1150
- vulnerableVersion,
1151
- patchContent: selectedPatch,
1152
- patchMode
1153
- });
1154
- if (!applyResult.success) {
1155
- return {
1156
- success: false,
1191
+ return withRepoLock(cwd, async () => {
1192
+ const patchesDirPath = join7(cwd, patchesDir);
1193
+ await mkdir3(patchesDirPath, { recursive: true });
1194
+ await writeFile(patchFilePath, selectedPatch, "utf8");
1195
+ let validationResult;
1196
+ const patchMode = await resolvePatchMode(pm, cwd);
1197
+ const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
1198
+ cwd,
1157
1199
  packageName,
1158
1200
  vulnerableVersion,
1159
- applied: false,
1160
- dryRun: false,
1161
- message: applyResult.error,
1162
- patchFilePath,
1163
- patchPath: patchFilePath,
1164
- patchMode,
1165
- postinstallConfigured: patchMode === "patch-package" ? false : void 0,
1166
- error: applyResult.error
1167
- };
1168
- }
1169
- if (validateWithTests) {
1170
- validationResult = await validatePatchWithTests(cwd, pm);
1171
- if (!validationResult.passed) {
1172
- const validationError = "Patch validation failed after apply; patch marked unresolved.";
1201
+ patchContent: selectedPatch,
1202
+ patchMode
1203
+ });
1204
+ if (!applyResult.success) {
1173
1205
  return {
1174
1206
  success: false,
1175
1207
  packageName,
1176
1208
  vulnerableVersion,
1177
1209
  applied: false,
1178
1210
  dryRun: false,
1179
- message: validationError,
1211
+ message: applyResult.error,
1180
1212
  patchFilePath,
1181
1213
  patchPath: patchFilePath,
1182
1214
  patchMode,
1183
- postinstallConfigured: patchMode === "patch-package",
1184
- validation: validationResult,
1185
- error: validationError
1215
+ postinstallConfigured: patchMode === "patch-package" ? false : void 0,
1216
+ error: applyResult.error
1186
1217
  };
1187
1218
  }
1188
- }
1189
- return {
1190
- success: true,
1191
- packageName,
1192
- vulnerableVersion,
1193
- applied: true,
1194
- dryRun: false,
1195
- message: `Patch applied successfully for ${packageName}@${vulnerableVersion}.`,
1196
- patchFilePath,
1197
- patchPath: patchFilePath,
1198
- patchMode,
1199
- postinstallConfigured: patchMode === "patch-package",
1200
- validation: validationResult
1201
- };
1219
+ if (validateWithTests) {
1220
+ validationResult = await validatePatchWithTests(cwd, pm);
1221
+ if (!validationResult.passed) {
1222
+ const validationError = "Patch validation failed after apply; patch marked unresolved.";
1223
+ return {
1224
+ success: false,
1225
+ packageName,
1226
+ vulnerableVersion,
1227
+ applied: false,
1228
+ dryRun: false,
1229
+ message: validationError,
1230
+ patchFilePath,
1231
+ patchPath: patchFilePath,
1232
+ patchMode,
1233
+ postinstallConfigured: patchMode === "patch-package",
1234
+ validation: validationResult,
1235
+ error: validationError
1236
+ };
1237
+ }
1238
+ }
1239
+ return {
1240
+ success: true,
1241
+ packageName,
1242
+ vulnerableVersion,
1243
+ applied: true,
1244
+ dryRun: false,
1245
+ message: `Patch applied successfully for ${packageName}@${vulnerableVersion}.`,
1246
+ patchFilePath,
1247
+ patchPath: patchFilePath,
1248
+ patchMode,
1249
+ postinstallConfigured: patchMode === "patch-package",
1250
+ validation: validationResult
1251
+ };
1252
+ });
1202
1253
  } catch (err) {
1203
1254
  const message = err instanceof Error ? err.message : String(err);
1204
1255
  return {
@@ -1233,7 +1284,7 @@ function buildPatchFileName(packageName, vulnerableVersion) {
1233
1284
  return `${safeName}+${vulnerableVersion}.patch`;
1234
1285
  }
1235
1286
  async function configurePatchPackagePostinstall(cwd, packageManager) {
1236
- const pkgJsonPath = join6(cwd, "package.json");
1287
+ const pkgJsonPath = join7(cwd, "package.json");
1237
1288
  let pkgJson;
1238
1289
  try {
1239
1290
  pkgJson = JSON.parse(await readFile2(pkgJsonPath, "utf8"));
@@ -1297,8 +1348,8 @@ ${createResult.stderr}`);
1297
1348
  error: `Could not determine native patch directory for ${packageSpec}.`
1298
1349
  };
1299
1350
  }
1300
- const tempPatchDir = await mkdtemp(join6(tmpdir(), "autoremediator-native-patch-"));
1301
- const tempPatchFile = join6(tempPatchDir, "change.patch");
1351
+ const tempPatchDir = await mkdtemp(join7(tmpdir(), "autoremediator-native-patch-"));
1352
+ const tempPatchFile = join7(tempPatchDir, "change.patch");
1302
1353
  try {
1303
1354
  await writeFile(tempPatchFile, patchContent, "utf8");
1304
1355
  await execa4("patch", ["-p1", "-i", tempPatchFile], {
@@ -1317,7 +1368,7 @@ ${createResult.stderr}`);
1317
1368
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
1318
1369
  };
1319
1370
  } finally {
1320
- await rm2(tempPatchDir, { recursive: true, force: true });
1371
+ await rm3(tempPatchDir, { recursive: true, force: true });
1321
1372
  }
1322
1373
  return { success: true };
1323
1374
  }
@@ -1389,7 +1440,8 @@ async function runRemediationPipeline(cveId, options = {}) {
1389
1440
  }
1390
1441
  const cwd = options.cwd ?? process.cwd();
1391
1442
  const packageManager = options.packageManager ?? detectPackageManager(cwd);
1392
- const dryRun = options.dryRun ?? false;
1443
+ const preview = options.preview ?? false;
1444
+ const dryRun = (options.dryRun ?? false) || preview;
1393
1445
  const skipTests = options.skipTests ?? true;
1394
1446
  const policyPath = options.policyPath ?? "";
1395
1447
  const patchesDir = options.patchesDir || "./patches";
@@ -1408,6 +1460,14 @@ async function runRemediationPipeline(cveId, options = {}) {
1408
1460
  const vulnerablePackages = [];
1409
1461
  let cveDetails = null;
1410
1462
  let agentSteps = 0;
1463
+ const applyVersionBumpToolForRun = preview ? {
1464
+ ...applyVersionBumpTool,
1465
+ execute: async (input) => applyVersionBumpTool.execute({ ...input, dryRun: true })
1466
+ } : applyVersionBumpTool;
1467
+ const applyPatchFileToolForRun = preview ? {
1468
+ ...applyPatchFileTool,
1469
+ execute: async (input) => applyPatchFileTool.execute({ ...input, dryRun: true })
1470
+ } : applyPatchFileTool;
1411
1471
  const result = await generateText2({
1412
1472
  model,
1413
1473
  system: systemPrompt,
@@ -1417,10 +1477,10 @@ async function runRemediationPipeline(cveId, options = {}) {
1417
1477
  "check-inventory": checkInventoryTool,
1418
1478
  "check-version-match": checkVersionMatchTool,
1419
1479
  "find-fixed-version": findFixedVersionTool,
1420
- "apply-version-bump": applyVersionBumpTool,
1480
+ "apply-version-bump": applyVersionBumpToolForRun,
1421
1481
  "fetch-package-source": fetchPackageSourceTool,
1422
1482
  "generate-patch": generatePatchTool,
1423
- "apply-patch-file": applyPatchFileTool
1483
+ "apply-patch-file": applyPatchFileToolForRun
1424
1484
  },
1425
1485
  maxSteps: 25,
1426
1486
  onStepFinish(stepResult) {
@@ -1463,13 +1523,19 @@ async function runRemediationPipeline(cveId, options = {}) {
1463
1523
  vulnerablePackages,
1464
1524
  results: collectedResults,
1465
1525
  agentSteps,
1466
- summary: result.text
1526
+ summary: result.text,
1527
+ correlation: {
1528
+ requestId: options.requestId,
1529
+ sessionId: options.sessionId,
1530
+ parentRunId: options.parentRunId
1531
+ }
1467
1532
  };
1468
1533
  }
1469
1534
  async function runLocalRemediationPipeline(cveId, options = {}) {
1470
1535
  const cwd = options.cwd ?? process.cwd();
1471
1536
  const packageManager = options.packageManager ?? detectPackageManager(cwd);
1472
- const dryRun = options.dryRun ?? false;
1537
+ const preview = options.preview ?? false;
1538
+ const dryRun = (options.dryRun ?? false) || preview;
1473
1539
  const skipTests = options.skipTests ?? true;
1474
1540
  const policyPath = options.policyPath ?? "";
1475
1541
  const collectedResults = [];
@@ -1489,7 +1555,12 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
1489
1555
  vulnerablePackages,
1490
1556
  results: collectedResults,
1491
1557
  agentSteps,
1492
- summary: `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`
1558
+ summary: `Local mode failed at lookup-cve: ${normalizedId} not found in OSV or GitHub advisory data.`,
1559
+ correlation: {
1560
+ requestId: options.requestId,
1561
+ sessionId: options.sessionId,
1562
+ parentRunId: options.parentRunId
1563
+ }
1493
1564
  };
1494
1565
  }
1495
1566
  cveDetails = osvDetails ?? {
@@ -1510,7 +1581,12 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
1510
1581
  vulnerablePackages,
1511
1582
  results: collectedResults,
1512
1583
  agentSteps,
1513
- summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}.`
1584
+ summary: `Local mode lookup succeeded but no npm affected packages were found for ${normalizedId}.`,
1585
+ correlation: {
1586
+ requestId: options.requestId,
1587
+ sessionId: options.sessionId,
1588
+ parentRunId: options.parentRunId
1589
+ }
1514
1590
  };
1515
1591
  }
1516
1592
  const inventory = await checkInventoryTool.execute({ cwd, packageManager });
@@ -1522,7 +1598,12 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
1522
1598
  vulnerablePackages,
1523
1599
  results: collectedResults,
1524
1600
  agentSteps,
1525
- summary: `Local mode failed at check-inventory: ${inventory.error}`
1601
+ summary: `Local mode failed at check-inventory: ${inventory.error}`,
1602
+ correlation: {
1603
+ requestId: options.requestId,
1604
+ sessionId: options.sessionId,
1605
+ parentRunId: options.parentRunId
1606
+ }
1526
1607
  };
1527
1608
  }
1528
1609
  const installedPackages = inventory.packages ?? [];
@@ -1612,11 +1693,16 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
1612
1693
  vulnerablePackages,
1613
1694
  results: collectedResults,
1614
1695
  agentSteps,
1615
- summary: `Local mode completed: vulnerable=${vulnerablePackages.length}, applied=${appliedCount}, dryRun=${dryRunCount}, unresolved=${unresolvedCount}`
1696
+ summary: `Local mode completed: vulnerable=${vulnerablePackages.length}, applied=${appliedCount}, dryRun=${dryRunCount}, unresolved=${unresolvedCount}`,
1697
+ correlation: {
1698
+ requestId: options.requestId,
1699
+ sessionId: options.sessionId,
1700
+ parentRunId: options.parentRunId
1701
+ }
1616
1702
  };
1617
1703
  }
1618
1704
  function loadOrchestrationPrompt(ctx) {
1619
- const promptPath = join7(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
1705
+ const promptPath = join8(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
1620
1706
  if (!existsSync4(promptPath)) {
1621
1707
  return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
1622
1708
  Working directory: ${ctx.cwd}
@@ -1810,10 +1896,16 @@ function uniqueCveIds(findings) {
1810
1896
 
1811
1897
  // src/platform/evidence.ts
1812
1898
  import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
1813
- import { join as join8 } from "path";
1814
- function createEvidenceLog(cwd, cveIds) {
1899
+ import { join as join9 } from "path";
1900
+ function createEvidenceLog(cwd, cveIds, context = {}) {
1815
1901
  return {
1816
- runId: `${Date.now()}`,
1902
+ runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1903
+ requestId: context.requestId,
1904
+ sessionId: context.sessionId,
1905
+ parentRunId: context.parentRunId,
1906
+ actor: context.actor,
1907
+ source: context.source,
1908
+ idempotencyKey: context.idempotencyKey,
1817
1909
  cveIds,
1818
1910
  cwd,
1819
1911
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1834,21 +1926,163 @@ function finalizeEvidence(log) {
1834
1926
  return log;
1835
1927
  }
1836
1928
  function writeEvidenceLog(cwd, log) {
1837
- const dir = join8(cwd, ".autoremediator", "evidence");
1929
+ const dir = join9(cwd, ".autoremediator", "evidence");
1838
1930
  mkdirSync(dir, { recursive: true });
1839
- const filePath = join8(dir, `${log.runId}.json`);
1931
+ const filePath = join9(dir, `${log.runId}.json`);
1840
1932
  writeFileSync2(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
1841
1933
  return filePath;
1842
1934
  }
1843
1935
 
1936
+ // src/platform/idempotency.ts
1937
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
1938
+ import { join as join10 } from "path";
1939
+ var DEFAULT_INDEX = {
1940
+ schemaVersion: "1.0",
1941
+ entries: {}
1942
+ };
1943
+ function indexFilePath(cwd) {
1944
+ return join10(cwd, ".autoremediator", "state", "idempotency.json");
1945
+ }
1946
+ function entryKey(idempotencyKey, cveId) {
1947
+ return `${idempotencyKey}::${cveId.toUpperCase()}`;
1948
+ }
1949
+ function loadIndex(cwd) {
1950
+ const filePath = indexFilePath(cwd);
1951
+ if (!existsSync5(filePath)) return DEFAULT_INDEX;
1952
+ try {
1953
+ const parsed = JSON.parse(readFileSync9(filePath, "utf8"));
1954
+ if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
1955
+ return parsed;
1956
+ }
1957
+ return DEFAULT_INDEX;
1958
+ } catch {
1959
+ return DEFAULT_INDEX;
1960
+ }
1961
+ }
1962
+ function saveIndex(cwd, index) {
1963
+ const filePath = indexFilePath(cwd);
1964
+ mkdirSync2(join10(cwd, ".autoremediator", "state"), { recursive: true });
1965
+ writeFileSync3(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
1966
+ }
1967
+ function readIdempotentReport(cwd, idempotencyKey, cveId) {
1968
+ const index = loadIndex(cwd);
1969
+ const key = entryKey(idempotencyKey, cveId);
1970
+ return index.entries[key]?.report;
1971
+ }
1972
+ function storeIdempotentReport(cwd, idempotencyKey, cveId, report) {
1973
+ const index = loadIndex(cwd);
1974
+ const key = entryKey(idempotencyKey, cveId);
1975
+ index.entries[key] = {
1976
+ key: idempotencyKey,
1977
+ cveId: cveId.toUpperCase(),
1978
+ report,
1979
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1980
+ };
1981
+ saveIndex(cwd, index);
1982
+ }
1983
+
1844
1984
  // src/api.ts
1985
+ function buildRequestId() {
1986
+ return `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1987
+ }
1988
+ function resolveCorrelationContext(options) {
1989
+ return {
1990
+ requestId: options.requestId ?? buildRequestId(),
1991
+ sessionId: options.sessionId,
1992
+ parentRunId: options.parentRunId
1993
+ };
1994
+ }
1995
+ function resolveProvenanceContext(options) {
1996
+ return {
1997
+ actor: options.actor,
1998
+ source: options.source ?? "sdk"
1999
+ };
2000
+ }
2001
+ function resolveConstraints(options, cwd) {
2002
+ const policy = loadPolicy(cwd, options.policyPath);
2003
+ return {
2004
+ directDependenciesOnly: options.constraints?.directDependenciesOnly ?? policy.constraints?.directDependenciesOnly ?? false,
2005
+ preferVersionBump: options.constraints?.preferVersionBump ?? policy.constraints?.preferVersionBump ?? false
2006
+ };
2007
+ }
2008
+ function enforceConstraints(report, constraints) {
2009
+ const indirectPackages = new Set(
2010
+ report.vulnerablePackages.filter((vp) => vp.installed.type === "indirect").map((vp) => vp.installed.name)
2011
+ );
2012
+ const nextResults = report.results.map((result) => {
2013
+ if (constraints.directDependenciesOnly && indirectPackages.has(result.packageName)) {
2014
+ return {
2015
+ ...result,
2016
+ strategy: "none",
2017
+ applied: false,
2018
+ message: `Constraint blocked remediation for indirect dependency "${result.packageName}".`
2019
+ };
2020
+ }
2021
+ if (constraints.preferVersionBump && result.strategy === "patch-file") {
2022
+ return {
2023
+ ...result,
2024
+ strategy: "none",
2025
+ applied: false,
2026
+ message: `Constraint prefers version-bump and rejected patch-file remediation for "${result.packageName}".`
2027
+ };
2028
+ }
2029
+ return result;
2030
+ });
2031
+ return {
2032
+ ...report,
2033
+ results: nextResults,
2034
+ constraints
2035
+ };
2036
+ }
1845
2037
  async function remediate(cveId, options = {}) {
1846
2038
  if (!/^CVE-\d{4}-\d+$/i.test(cveId)) {
1847
2039
  throw new Error(
1848
2040
  `Invalid CVE ID: "${cveId}". Expected format: CVE-YYYY-NNNNN (e.g. CVE-2021-23337).`
1849
2041
  );
1850
2042
  }
1851
- return runRemediationPipeline(cveId.toUpperCase(), options);
2043
+ const cwd = options.cwd ?? process.cwd();
2044
+ const constraints = resolveConstraints(options, cwd);
2045
+ const provenance = resolveProvenanceContext(options);
2046
+ const correlation = resolveCorrelationContext(options);
2047
+ if (options.resume && options.idempotencyKey) {
2048
+ const cached = readIdempotentReport(cwd, options.idempotencyKey, cveId.toUpperCase());
2049
+ if (cached) {
2050
+ return {
2051
+ ...cached,
2052
+ summary: `${cached.summary} (resumed from idempotency cache)`,
2053
+ correlation,
2054
+ provenance,
2055
+ constraints,
2056
+ resumedFromCache: true
2057
+ };
2058
+ }
2059
+ }
2060
+ const report = await runRemediationPipeline(cveId.toUpperCase(), {
2061
+ ...options,
2062
+ ...correlation,
2063
+ constraints
2064
+ });
2065
+ const constrainedReport = enforceConstraints(report, constraints);
2066
+ const finalReport = {
2067
+ ...constrainedReport,
2068
+ correlation,
2069
+ provenance,
2070
+ constraints,
2071
+ resumedFromCache: false
2072
+ };
2073
+ if (options.idempotencyKey && !options.dryRun && !options.preview) {
2074
+ storeIdempotentReport(cwd, options.idempotencyKey, cveId.toUpperCase(), finalReport);
2075
+ }
2076
+ return {
2077
+ ...finalReport
2078
+ };
2079
+ }
2080
+ async function planRemediation(cveId, options = {}) {
2081
+ return remediate(cveId, {
2082
+ ...options,
2083
+ preview: true,
2084
+ dryRun: true
2085
+ });
1852
2086
  }
1853
2087
  async function remediateFromScan(inputPath, options = {}) {
1854
2088
  const cwd = options.cwd ?? process.cwd();
@@ -1857,7 +2091,15 @@ async function remediateFromScan(inputPath, options = {}) {
1857
2091
  const findings = parseScanInput(inputPath, format);
1858
2092
  const cveIds = uniqueCveIds(findings);
1859
2093
  const policy = loadPolicy(cwd, options.policyPath);
1860
- const evidence = createEvidenceLog(cwd, cveIds);
2094
+ const correlation = resolveCorrelationContext(options);
2095
+ const provenance = resolveProvenanceContext(options);
2096
+ const constraints = resolveConstraints(options, cwd);
2097
+ const evidence = createEvidenceLog(cwd, cveIds, {
2098
+ ...correlation,
2099
+ actor: provenance.actor,
2100
+ source: provenance.source,
2101
+ idempotencyKey: options.idempotencyKey
2102
+ });
1861
2103
  addEvidenceStep(evidence, "scan.parse", { inputPath, format }, { findingCount: findings.length, cveCount: cveIds.length });
1862
2104
  const reports = [];
1863
2105
  const errors = [];
@@ -1868,7 +2110,11 @@ async function remediateFromScan(inputPath, options = {}) {
1868
2110
  addEvidenceStep(evidence, "remediate.start", { cveId });
1869
2111
  const report = await remediate(cveId, {
1870
2112
  ...options,
1871
- patchesDir
2113
+ patchesDir,
2114
+ ...correlation,
2115
+ actor: provenance.actor,
2116
+ source: provenance.source,
2117
+ constraints
1872
2118
  });
1873
2119
  report.results = report.results.filter((r) => isPackageAllowed(policy, r.packageName));
1874
2120
  for (const result of report.results) {
@@ -1923,7 +2169,11 @@ async function remediateFromScan(inputPath, options = {}) {
1923
2169
  evidenceFile,
1924
2170
  patchFileCount,
1925
2171
  patchValidationFailures: patchValidationFailures.length > 0 ? patchValidationFailures : void 0,
1926
- patchStorageDir: patchFileCount > 0 ? patchesDir : void 0
2172
+ patchStorageDir: patchFileCount > 0 ? patchesDir : void 0,
2173
+ correlation,
2174
+ provenance,
2175
+ constraints,
2176
+ idempotencyKey: options.idempotencyKey
1927
2177
  };
1928
2178
  }
1929
2179
  function toCiSummary(report) {
@@ -1943,7 +2193,11 @@ function toCiSummary(report) {
1943
2193
  evidenceFile: report.evidenceFile,
1944
2194
  patchFileCount: report.patchFileCount || 0,
1945
2195
  patchValidationFailures: report.patchValidationFailures,
1946
- patchStorageDir: report.patchStorageDir
2196
+ patchStorageDir: report.patchStorageDir,
2197
+ correlation: report.correlation,
2198
+ provenance: report.provenance,
2199
+ constraints: report.constraints,
2200
+ idempotencyKey: report.idempotencyKey
1947
2201
  };
1948
2202
  }
1949
2203
  function ciExitCode(summary) {
@@ -1953,8 +2207,9 @@ function ciExitCode(summary) {
1953
2207
  export {
1954
2208
  runRemediationPipeline,
1955
2209
  remediate,
2210
+ planRemediation,
1956
2211
  remediateFromScan,
1957
2212
  toCiSummary,
1958
2213
  ciExitCode
1959
2214
  };
1960
- //# sourceMappingURL=chunk-DQKT2CUG.js.map
2215
+ //# sourceMappingURL=chunk-URM53GSJ.js.map