autoremediator 0.7.0 → 0.8.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,57 +1,139 @@
1
1
  // src/remediation/pipeline.ts
2
2
  import { generateText as generateText2 } from "ai";
3
3
 
4
+ // src/platform/policy.ts
5
+ import { existsSync, readFileSync } from "fs";
6
+ import { join } from "path";
7
+ var DEFAULT_POLICY = {
8
+ allowMajorBumps: false,
9
+ denyPackages: [],
10
+ allowPackages: [],
11
+ constraints: {
12
+ directDependenciesOnly: false,
13
+ preferVersionBump: false
14
+ },
15
+ modelDefaults: {},
16
+ providerSafetyProfile: "relaxed",
17
+ requireConsensusForHighRisk: false,
18
+ dynamicModelRouting: false,
19
+ dynamicRoutingThresholdChars: 18e3
20
+ };
21
+ function loadPolicy(cwd, explicitPath) {
22
+ const candidate = explicitPath ?? join(cwd, ".autoremediator.json");
23
+ if (!existsSync(candidate)) return DEFAULT_POLICY;
24
+ try {
25
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
26
+ return {
27
+ allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
28
+ denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
29
+ allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
30
+ constraints: {
31
+ directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
32
+ preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false
33
+ },
34
+ modelDefaults: {
35
+ remote: parsed.modelDefaults?.remote ?? DEFAULT_POLICY.modelDefaults?.remote,
36
+ local: parsed.modelDefaults?.local ?? DEFAULT_POLICY.modelDefaults?.local
37
+ },
38
+ providerSafetyProfile: parsed.providerSafetyProfile ?? DEFAULT_POLICY.providerSafetyProfile,
39
+ requireConsensusForHighRisk: parsed.requireConsensusForHighRisk ?? DEFAULT_POLICY.requireConsensusForHighRisk,
40
+ dynamicModelRouting: parsed.dynamicModelRouting ?? DEFAULT_POLICY.dynamicModelRouting,
41
+ dynamicRoutingThresholdChars: parsed.dynamicRoutingThresholdChars ?? DEFAULT_POLICY.dynamicRoutingThresholdChars
42
+ };
43
+ } catch {
44
+ return DEFAULT_POLICY;
45
+ }
46
+ }
47
+ function isPackageAllowed(policy, packageName) {
48
+ if (policy.denyPackages.includes(packageName)) return false;
49
+ if (policy.allowPackages.length > 0 && !policy.allowPackages.includes(packageName)) {
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+
4
55
  // src/platform/config.ts
5
56
  function resolveProvider(options = {}) {
6
- const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "openai";
7
- if (raw !== "openai" && raw !== "anthropic" && raw !== "local") {
57
+ const raw = options.llmProvider ?? process.env.AUTOREMEDIATOR_LLM_PROVIDER ?? "remote";
58
+ if (raw !== "remote" && raw !== "local") {
8
59
  throw new Error(
9
- `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "openai", "anthropic", or "local".`
60
+ `Unsupported LLM provider "${raw}". Set AUTOREMEDIATOR_LLM_PROVIDER to "remote" or "local".`
10
61
  );
11
62
  }
12
63
  return raw;
13
64
  }
14
- function resolveModelName(provider, options = {}) {
65
+ function resolveModelName(provider, options = {}, routing = {}) {
15
66
  if (options.model) return options.model;
16
67
  if (process.env.AUTOREMEDIATOR_MODEL) return process.env.AUTOREMEDIATOR_MODEL;
68
+ const cwd = options.cwd ?? process.cwd();
69
+ const policy = loadPolicy(cwd, options.policy);
70
+ const providerEnvModel = provider === "remote" ? process.env.AUTOREMEDIATOR_MODEL_REMOTE : process.env.AUTOREMEDIATOR_MODEL_LOCAL;
71
+ if (providerEnvModel) return providerEnvModel;
72
+ const policyPinnedModel = provider === "remote" ? policy.modelDefaults?.remote : policy.modelDefaults?.local;
73
+ if (policyPinnedModel) return policyPinnedModel;
17
74
  const defaults = {
18
- openai: "gpt-4o",
19
- anthropic: "claude-sonnet-4-5",
75
+ remote: "remote-default",
20
76
  local: "local"
21
77
  };
78
+ const routingEnabled = options.dynamicModelRouting ?? policy.dynamicModelRouting ?? false;
79
+ const threshold = options.dynamicRoutingThresholdChars ?? policy.dynamicRoutingThresholdChars ?? 18e3;
80
+ if (provider === "remote" && routingEnabled && typeof routing.inputChars === "number" && routing.inputChars >= threshold) {
81
+ return process.env.AUTOREMEDIATOR_MODEL_REMOTE_LARGE ?? "remote-large";
82
+ }
22
83
  return defaults[provider];
23
84
  }
24
- async function createModel(options = {}) {
85
+ async function loadRemoteFactory() {
86
+ const moduleName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_MODULE;
87
+ const exportName = process.env.AUTOREMEDIATOR_REMOTE_CLIENT_FACTORY ?? "createRemoteClient";
88
+ if (!moduleName) {
89
+ throw new Error(
90
+ "AUTOREMEDIATOR_REMOTE_CLIENT_MODULE is required for remote provider model loading."
91
+ );
92
+ }
93
+ const loaded = await import(moduleName);
94
+ const factory = loaded[exportName];
95
+ if (typeof factory !== "function") {
96
+ throw new Error(
97
+ `Remote client factory "${exportName}" was not found in module "${moduleName}".`
98
+ );
99
+ }
100
+ return factory;
101
+ }
102
+ async function createModel(options = {}, routing = {}) {
25
103
  const provider = resolveProvider(options);
26
104
  if (provider === "local") {
27
105
  throw new Error(
28
106
  "Local provider does not create a language model. Use the deterministic pipeline path instead."
29
107
  );
30
108
  }
31
- const modelName = resolveModelName(provider, options);
32
- if (provider === "openai") {
33
- const apiKey = process.env.OPENAI_API_KEY;
34
- if (!apiKey) {
35
- throw new Error(
36
- "OPENAI_API_KEY environment variable is required when using the openai provider."
37
- );
38
- }
39
- const { createOpenAI } = await import("@ai-sdk/openai");
40
- const openai = createOpenAI({ apiKey });
41
- return openai(modelName);
42
- }
43
- if (provider === "anthropic") {
44
- const apiKey = process.env.ANTHROPIC_API_KEY;
45
- if (!apiKey) {
46
- throw new Error(
47
- "ANTHROPIC_API_KEY environment variable is required when using the anthropic provider."
48
- );
49
- }
50
- const { createAnthropic } = await import("@ai-sdk/anthropic");
51
- const anthropic = createAnthropic({ apiKey });
52
- return anthropic(modelName);
109
+ const apiKey = process.env.AUTOREMEDIATOR_REMOTE_API_KEY;
110
+ if (!apiKey) {
111
+ throw new Error(
112
+ "AUTOREMEDIATOR_REMOTE_API_KEY environment variable is required for remote provider."
113
+ );
53
114
  }
54
- throw new Error(`Unhandled provider: ${provider}`);
115
+ const modelName = resolveModelName(provider, options, routing);
116
+ const createRemoteClient = await loadRemoteFactory();
117
+ const remoteClient = createRemoteClient({ apiKey });
118
+ return remoteClient(modelName);
119
+ }
120
+ function getPatchConfidenceThreshold(provider, safetyProfile = "relaxed") {
121
+ void provider;
122
+ const relaxed = {
123
+ remote: 0.7,
124
+ local: 0.7
125
+ };
126
+ const strict = {
127
+ remote: 0.85,
128
+ local: 0.85
129
+ };
130
+ return safetyProfile === "strict" ? strict[provider] : relaxed[provider];
131
+ }
132
+ function estimateModelCostUsd(params) {
133
+ const inputTokens = params.promptChars / 4;
134
+ const outputTokens = params.completionChars / 4;
135
+ const pricePerThousand = params.provider === "remote" ? { input: 6e-3, output: 0.02 } : { input: 0, output: 0 };
136
+ return inputTokens / 1e3 * pricePerThousand.input + outputTokens / 1e3 * pricePerThousand.output;
55
137
  }
56
138
  function getNvdConfig() {
57
139
  return {
@@ -76,11 +158,11 @@ function getIntelligenceSourceConfig() {
76
158
  }
77
159
 
78
160
  // src/platform/package-manager.ts
79
- import { existsSync } from "fs";
80
- import { join } from "path";
161
+ import { existsSync as existsSync2 } from "fs";
162
+ import { join as join2 } from "path";
81
163
  function detectPackageManager(cwd) {
82
- if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
83
- if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
164
+ if (existsSync2(join2(cwd, "pnpm-lock.yaml"))) return "pnpm";
165
+ if (existsSync2(join2(cwd, "yarn.lock"))) return "yarn";
84
166
  return "npm";
85
167
  }
86
168
  function getPackageManagerCommands(pm) {
@@ -167,49 +249,11 @@ import { readFileSync as readFileSync2, writeFileSync } from "fs";
167
249
  import { execa } from "execa";
168
250
  import semver from "semver";
169
251
 
170
- // src/platform/policy.ts
171
- import { existsSync as existsSync2, readFileSync } from "fs";
172
- import { join as join2 } from "path";
173
- var DEFAULT_POLICY = {
174
- allowMajorBumps: false,
175
- denyPackages: [],
176
- allowPackages: [],
177
- constraints: {
178
- directDependenciesOnly: false,
179
- preferVersionBump: false
180
- }
181
- };
182
- function loadPolicy(cwd, explicitPath) {
183
- const candidate = explicitPath ?? join2(cwd, ".autoremediator.json");
184
- if (!existsSync2(candidate)) return DEFAULT_POLICY;
185
- try {
186
- const parsed = JSON.parse(readFileSync(candidate, "utf8"));
187
- return {
188
- allowMajorBumps: parsed.allowMajorBumps ?? DEFAULT_POLICY.allowMajorBumps,
189
- denyPackages: parsed.denyPackages ?? DEFAULT_POLICY.denyPackages,
190
- allowPackages: parsed.allowPackages ?? DEFAULT_POLICY.allowPackages,
191
- constraints: {
192
- directDependenciesOnly: parsed.constraints?.directDependenciesOnly ?? DEFAULT_POLICY.constraints?.directDependenciesOnly ?? false,
193
- preferVersionBump: parsed.constraints?.preferVersionBump ?? DEFAULT_POLICY.constraints?.preferVersionBump ?? false
194
- }
195
- };
196
- } catch {
197
- return DEFAULT_POLICY;
198
- }
199
- }
200
- function isPackageAllowed(policy, packageName) {
201
- if (policy.denyPackages.includes(packageName)) return false;
202
- if (policy.allowPackages.length > 0 && !policy.allowPackages.includes(packageName)) {
203
- return false;
204
- }
205
- return true;
206
- }
207
-
208
252
  // src/platform/repo-lock.ts
209
253
  import { mkdir, rm } from "fs/promises";
210
254
  import { join as join3 } from "path";
211
255
  async function sleep(ms) {
212
- await new Promise((resolve) => setTimeout(resolve, ms));
256
+ await new Promise((resolve2) => setTimeout(resolve2, ms));
213
257
  }
214
258
  async function acquireRepoLock(cwd, options = {}) {
215
259
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -661,6 +705,9 @@ var applyPatchFileTool = tool3({
661
705
  packageName: z3.string().min(1).describe("The npm package name"),
662
706
  vulnerableVersion: z3.string().describe("The vulnerable version string"),
663
707
  patchContent: z3.string().min(10).optional().describe("Unified diff patch content from generate-patch"),
708
+ cveId: z3.string().optional().describe("Optional CVE ID associated with this patch artifact"),
709
+ confidence: z3.number().min(0).max(1).optional().describe("Optional patch confidence score from generate-patch"),
710
+ riskLevel: z3.enum(["low", "medium", "high"]).optional().describe("Optional risk level from generate-patch"),
664
711
  patches: z3.array(
665
712
  z3.object({
666
713
  filePath: z3.string().min(1),
@@ -679,16 +726,22 @@ var applyPatchFileTool = tool3({
679
726
  packageName,
680
727
  vulnerableVersion,
681
728
  patchContent,
729
+ cveId,
730
+ confidence,
682
731
  patches,
683
732
  patchesDir,
684
733
  cwd,
685
734
  packageManager,
735
+ riskLevel,
686
736
  validateWithTests,
687
737
  dryRun
688
738
  }) => {
689
739
  try {
690
740
  const pm = packageManager ?? detectPackageManager(cwd);
691
741
  const selectedPatch = patchContent ?? patches?.[0]?.unifiedDiff;
742
+ const patchFiles = extractPatchedFiles(selectedPatch ?? "");
743
+ const hunkCount = countPatchHunks(selectedPatch ?? "");
744
+ const validationPhases = [];
692
745
  if (!selectedPatch) {
693
746
  return {
694
747
  success: false,
@@ -701,6 +754,12 @@ var applyPatchFileTool = tool3({
701
754
  };
702
755
  }
703
756
  const patchValidation = validatePatchDiff(selectedPatch);
757
+ validationPhases.push({
758
+ phase: "diff-format",
759
+ passed: patchValidation.valid,
760
+ error: patchValidation.valid ? void 0 : patchValidation.error,
761
+ message: patchValidation.valid ? "Patch content is a valid unified diff." : void 0
762
+ });
704
763
  if (!patchValidation.valid) {
705
764
  return {
706
765
  success: false,
@@ -709,11 +768,32 @@ var applyPatchFileTool = tool3({
709
768
  applied: false,
710
769
  dryRun,
711
770
  message: patchValidation.error ?? "Patch content is not a valid unified diff.",
771
+ validationPhases,
712
772
  error: patchValidation.error ?? "Patch content is not a valid unified diff."
713
773
  };
714
774
  }
715
775
  const patchFileName = buildPatchFileName(packageName, vulnerableVersion);
716
776
  const patchFilePath = join7(cwd, patchesDir, patchFileName);
777
+ const manifestFilePath = `${patchFilePath}.json`;
778
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
779
+ const baseArtifact = {
780
+ schemaVersion: "1.0",
781
+ cveId,
782
+ packageName,
783
+ vulnerableVersion,
784
+ patchFilePath,
785
+ manifestFilePath,
786
+ patchFileName,
787
+ patchesDir,
788
+ confidence,
789
+ riskLevel,
790
+ generatedAt,
791
+ files: patchFiles,
792
+ hunkCount,
793
+ applied: false,
794
+ dryRun,
795
+ validationPhases
796
+ };
717
797
  if (dryRun) {
718
798
  return {
719
799
  success: true,
@@ -723,7 +803,10 @@ var applyPatchFileTool = tool3({
723
803
  dryRun: true,
724
804
  message: `[DRY RUN] Would write and configure patch at ${patchFilePath}.`,
725
805
  patchFilePath,
726
- patchPath: patchFilePath
806
+ manifestFilePath,
807
+ patchPath: patchFilePath,
808
+ patchArtifact: baseArtifact,
809
+ validationPhases
727
810
  };
728
811
  }
729
812
  return withRepoLock(cwd, async () => {
@@ -731,21 +814,41 @@ var applyPatchFileTool = tool3({
731
814
  const patchesDirPath = join7(cwd, patchesDir);
732
815
  await mkdir2(patchesDirPath, { recursive: true });
733
816
  await writeFile(patchFilePath, selectedPatch, "utf8");
817
+ validationPhases.push({
818
+ phase: "patch-write",
819
+ passed: true,
820
+ message: `Patch file written to ${patchFilePath}.`
821
+ });
734
822
  let validationResult;
735
823
  const patchMode = await resolvePatchMode(pm, cwd);
736
824
  const commands = getPackageManagerCommands(pm);
825
+ const artifact = {
826
+ ...baseArtifact,
827
+ patchMode,
828
+ validationPhases
829
+ };
830
+ await writePatchManifest(manifestFilePath, artifact);
831
+ validationPhases.push({
832
+ phase: "manifest-write",
833
+ passed: true,
834
+ message: `Patch manifest written to ${manifestFilePath}.`
835
+ });
836
+ artifact.validationPhases = validationPhases;
837
+ await writePatchManifest(manifestFilePath, artifact);
737
838
  const applyResult = patchMode === "patch-package" ? await configurePatchPackagePostinstall(cwd, pm) : await applyNativePatch({
738
839
  cwd,
739
840
  packageName,
740
841
  vulnerableVersion,
741
842
  patchContent: selectedPatch,
742
- patchMode
843
+ patchMode,
844
+ validationPhases
743
845
  });
744
846
  if (!applyResult.success) {
745
847
  await cleanupPatchArtifacts({
746
848
  cwd,
747
849
  packageManager: pm,
748
850
  patchFilePath,
851
+ manifestFilePath,
749
852
  patchMode,
750
853
  packageJsonSnapshot,
751
854
  rerunInstall: patchMode === "patch-package"
@@ -758,12 +861,24 @@ var applyPatchFileTool = tool3({
758
861
  dryRun: false,
759
862
  message: applyResult.error,
760
863
  patchFilePath,
864
+ manifestFilePath,
761
865
  patchPath: patchFilePath,
762
866
  patchMode,
763
867
  postinstallConfigured: patchMode === "patch-package" ? false : void 0,
868
+ patchArtifact: {
869
+ ...artifact,
870
+ applied: false,
871
+ validationPhases
872
+ },
873
+ validationPhases,
764
874
  error: applyResult.error
765
875
  };
766
876
  }
877
+ validationPhases.push({
878
+ phase: "apply",
879
+ passed: true,
880
+ message: `Patch applied using ${patchMode}.`
881
+ });
767
882
  if (patchMode === "patch-package") {
768
883
  try {
769
884
  const [installCmd, ...installArgs] = commands.installPreferOffline;
@@ -771,16 +886,27 @@ var applyPatchFileTool = tool3({
771
886
  cwd,
772
887
  stdio: "pipe"
773
888
  });
889
+ validationPhases.push({
890
+ phase: "install",
891
+ passed: true,
892
+ message: `${commands.installPreferOffline.join(" ")} completed successfully.`
893
+ });
774
894
  } catch (err) {
775
895
  await cleanupPatchArtifacts({
776
896
  cwd,
777
897
  packageManager: pm,
778
898
  patchFilePath,
899
+ manifestFilePath,
779
900
  patchMode,
780
901
  packageJsonSnapshot,
781
902
  rerunInstall: true
782
903
  });
783
904
  const error = err instanceof Error ? err.message : String(err);
905
+ validationPhases.push({
906
+ phase: "install",
907
+ passed: false,
908
+ error
909
+ });
784
910
  return {
785
911
  success: false,
786
912
  packageName,
@@ -789,9 +915,16 @@ var applyPatchFileTool = tool3({
789
915
  dryRun: false,
790
916
  message: `Failed to apply patch-package workflow for ${packageName}@${vulnerableVersion}: ${error}`,
791
917
  patchFilePath,
918
+ manifestFilePath,
792
919
  patchPath: patchFilePath,
793
920
  patchMode,
794
921
  postinstallConfigured: false,
922
+ patchArtifact: {
923
+ ...artifact,
924
+ applied: false,
925
+ validationPhases
926
+ },
927
+ validationPhases,
795
928
  error: `Failed to apply patch-package workflow for ${packageName}@${vulnerableVersion}: ${error}`
796
929
  };
797
930
  }
@@ -803,11 +936,17 @@ var applyPatchFileTool = tool3({
803
936
  cwd,
804
937
  packageManager: pm,
805
938
  patchFilePath,
939
+ manifestFilePath,
806
940
  patchMode,
807
941
  packageJsonSnapshot,
808
942
  rerunInstall: patchMode === "patch-package"
809
943
  });
810
944
  const validationError = "Patch validation failed after apply; patch marked unresolved.";
945
+ validationPhases.push({
946
+ phase: "test",
947
+ passed: false,
948
+ error: validationResult.error
949
+ });
811
950
  return {
812
951
  success: false,
813
952
  packageName,
@@ -816,14 +955,33 @@ var applyPatchFileTool = tool3({
816
955
  dryRun: false,
817
956
  message: validationError,
818
957
  patchFilePath,
958
+ manifestFilePath,
819
959
  patchPath: patchFilePath,
820
960
  patchMode,
821
961
  postinstallConfigured: false,
962
+ patchArtifact: {
963
+ ...artifact,
964
+ applied: false,
965
+ validationPhases
966
+ },
822
967
  validation: validationResult,
968
+ validationPhases,
823
969
  error: validationError
824
970
  };
825
971
  }
972
+ validationPhases.push({
973
+ phase: "test",
974
+ passed: true,
975
+ message: "Patch validation tests passed."
976
+ });
826
977
  }
978
+ const finalArtifact = {
979
+ ...artifact,
980
+ applied: true,
981
+ dryRun: false,
982
+ validationPhases
983
+ };
984
+ await writePatchManifest(manifestFilePath, finalArtifact);
827
985
  return {
828
986
  success: true,
829
987
  packageName,
@@ -832,10 +990,13 @@ var applyPatchFileTool = tool3({
832
990
  dryRun: false,
833
991
  message: `Patch applied successfully for ${packageName}@${vulnerableVersion}.`,
834
992
  patchFilePath,
993
+ manifestFilePath,
835
994
  patchPath: patchFilePath,
836
995
  patchMode,
837
996
  postinstallConfigured: patchMode === "patch-package",
838
- validation: validationResult
997
+ patchArtifact: finalArtifact,
998
+ validation: validationResult,
999
+ validationPhases
839
1000
  };
840
1001
  });
841
1002
  } catch (err) {
@@ -926,8 +1087,11 @@ async function capturePackageJsonSnapshot(cwd) {
926
1087
  }
927
1088
  }
928
1089
  async function cleanupPatchArtifacts(params) {
929
- const { cwd, packageManager, patchFilePath, patchMode, packageJsonSnapshot, rerunInstall } = params;
1090
+ const { cwd, packageManager, patchFilePath, manifestFilePath, patchMode, packageJsonSnapshot, rerunInstall } = params;
930
1091
  await rm2(patchFilePath, { force: true }).catch(() => void 0);
1092
+ if (manifestFilePath) {
1093
+ await rm2(manifestFilePath, { force: true }).catch(() => void 0);
1094
+ }
931
1095
  if (patchMode === "patch-package" && packageJsonSnapshot) {
932
1096
  await writeFile(packageJsonSnapshot.path, packageJsonSnapshot.content, "utf8").catch(() => void 0);
933
1097
  }
@@ -943,7 +1107,7 @@ async function cleanupPatchArtifacts(params) {
943
1107
  }
944
1108
  }
945
1109
  async function applyNativePatch(params) {
946
- const { cwd, packageName, vulnerableVersion, patchContent, patchMode } = params;
1110
+ const { cwd, packageName, vulnerableVersion, patchContent, patchMode, validationPhases } = params;
947
1111
  const packageSpec = `${packageName}@${vulnerableVersion}`;
948
1112
  const createCommand = patchMode === "native-pnpm" ? "pnpm" : "yarn";
949
1113
  const createArgs = ["patch", packageSpec];
@@ -956,12 +1120,22 @@ async function applyNativePatch(params) {
956
1120
  patchDir = extractPatchDirectory(`${createResult.stdout}
957
1121
  ${createResult.stderr}`);
958
1122
  } catch (err) {
1123
+ validationPhases.push({
1124
+ phase: "apply",
1125
+ passed: false,
1126
+ error: err instanceof Error ? err.message : String(err)
1127
+ });
959
1128
  return {
960
1129
  success: false,
961
1130
  error: `Failed to create native patch workspace for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
962
1131
  };
963
1132
  }
964
1133
  if (!patchDir) {
1134
+ validationPhases.push({
1135
+ phase: "apply",
1136
+ passed: false,
1137
+ error: `Could not determine native patch directory for ${packageSpec}.`
1138
+ });
965
1139
  return {
966
1140
  success: false,
967
1141
  error: `Could not determine native patch directory for ${packageSpec}.`
@@ -982,6 +1156,11 @@ ${createResult.stderr}`);
982
1156
  stdio: "pipe"
983
1157
  });
984
1158
  } catch (err) {
1159
+ validationPhases.push({
1160
+ phase: "apply",
1161
+ passed: false,
1162
+ error: err instanceof Error ? err.message : String(err)
1163
+ });
985
1164
  return {
986
1165
  success: false,
987
1166
  error: `Failed to apply native patch for ${packageSpec}: ${err instanceof Error ? err.message : String(err)}`
@@ -991,6 +1170,19 @@ ${createResult.stderr}`);
991
1170
  }
992
1171
  return { success: true };
993
1172
  }
1173
+ async function writePatchManifest(manifestFilePath, artifact) {
1174
+ await writeFile(manifestFilePath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
1175
+ }
1176
+ function extractPatchedFiles(patchContent) {
1177
+ return Array.from(
1178
+ new Set(
1179
+ patchContent.split(/\r?\n/).filter((line) => line.startsWith("+++ b/")).map((line) => line.slice("+++ b/".length))
1180
+ )
1181
+ );
1182
+ }
1183
+ function countPatchHunks(patchContent) {
1184
+ return patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
1185
+ }
994
1186
  function extractPatchDirectory(output) {
995
1187
  const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
996
1188
  for (const line of lines) {
@@ -1640,7 +1832,15 @@ var generatePatchTool = tool6({
1640
1832
  "Map of file paths to source code contents from fetch-package-source"
1641
1833
  ),
1642
1834
  vulnerabilityCategory: z6.enum(["redos", "code-injection", "path-traversal", "unknown"]).optional().default("unknown").describe("Category of the vulnerability for better context"),
1643
- dryRun: z6.boolean().optional().default(false).describe("If true, return analysis without generating patches")
1835
+ dryRun: z6.boolean().optional().default(false).describe("If true, return analysis without generating patches"),
1836
+ llmProvider: z6.enum(["remote", "local"]).optional().describe("Optional provider override for patch generation"),
1837
+ model: z6.string().optional().describe("Optional model override for patch generation"),
1838
+ policy: z6.string().optional().describe("Optional policy file path for model default resolution"),
1839
+ cwd: z6.string().optional().describe("Optional working directory for policy/model resolution"),
1840
+ providerSafetyProfile: z6.enum(["strict", "relaxed"]).optional().describe("Confidence threshold profile for patch acceptance"),
1841
+ dynamicModelRouting: z6.boolean().optional().describe("Enable dynamic model routing by input size"),
1842
+ dynamicRoutingThresholdChars: z6.number().int().positive().optional().describe("Threshold for dynamic model routing"),
1843
+ modelPersonality: z6.enum(["analytical", "pragmatic", "balanced"]).optional().describe("Prompt personality for patch-generation guidance")
1644
1844
  }),
1645
1845
  execute: async ({
1646
1846
  packageName,
@@ -1649,27 +1849,49 @@ var generatePatchTool = tool6({
1649
1849
  cveSummary,
1650
1850
  sourceFiles,
1651
1851
  vulnerabilityCategory,
1652
- dryRun
1852
+ dryRun,
1853
+ llmProvider,
1854
+ model,
1855
+ policy,
1856
+ cwd,
1857
+ providerSafetyProfile,
1858
+ dynamicModelRouting,
1859
+ dynamicRoutingThresholdChars,
1860
+ modelPersonality
1653
1861
  }) => {
1654
1862
  try {
1655
1863
  const resolvedSourceFiles = sourceFiles;
1864
+ const effectiveOptions = {
1865
+ llmProvider,
1866
+ model,
1867
+ policy,
1868
+ cwd,
1869
+ providerSafetyProfile,
1870
+ dynamicModelRouting,
1871
+ dynamicRoutingThresholdChars,
1872
+ modelPersonality
1873
+ };
1874
+ const provider = resolveProvider(effectiveOptions);
1656
1875
  if (Object.keys(resolvedSourceFiles).length === 0) {
1657
1876
  return {
1658
1877
  success: false,
1878
+ llmProvider: provider,
1659
1879
  llmModel: "unknown",
1660
1880
  confidence: 0,
1661
1881
  riskLevel: "high",
1662
1882
  error: "No source files were provided. Call fetch-package-source first and pass sourceFiles."
1663
1883
  };
1664
1884
  }
1665
- const model = await createModel();
1666
- const modelName = model.modelId || "unknown-model";
1885
+ const inputChars = JSON.stringify(resolvedSourceFiles).length + cveSummary.length;
1886
+ const modelInstance = await createModel(effectiveOptions, { inputChars });
1887
+ const modelName = modelInstance.modelId || "unknown-model";
1667
1888
  const sourceContext = Object.entries(resolvedSourceFiles).map(([filePath, content]) => `
1668
1889
  ### File: ${filePath}
1669
1890
  \`\`\`typescript
1670
1891
  ${content}
1671
1892
  \`\`\``).join("\n");
1672
1893
  const vulnerabilityContext = VULNERABILITY_DESCRIPTIONS[vulnerabilityCategory] || VULNERABILITY_DESCRIPTIONS.unknown;
1894
+ const personalityDirective = modelPersonality === "analytical" ? "Provide concise analysis with explicit risk tradeoffs." : modelPersonality === "pragmatic" ? "Prioritize minimal, safe changes with low operational risk." : "Balance analytical explanation with practical remediation.";
1673
1895
  const prompt = `You are a security expert tasked with analyzing a CVE vulnerability and generating a secure patch.
1674
1896
 
1675
1897
  ## CVE Information
@@ -1683,6 +1905,9 @@ ${cveSummary}
1683
1905
  ## Vulnerability Type Context
1684
1906
  ${vulnerabilityContext}
1685
1907
 
1908
+ ## Model Behavior
1909
+ ${personalityDirective}
1910
+
1686
1911
  ## Vulnerable Source Code
1687
1912
  ${sourceContext}
1688
1913
 
@@ -1710,12 +1935,14 @@ Important:
1710
1935
  - riskLevel: "low", "medium", or "high" - assess the risk of the proposed fix breaking functionality
1711
1936
  - fixedCode: must contain the COMPLETE file contents (not just diffs), with the vulnerability addressed
1712
1937
  - Only include files that need modification`;
1938
+ const started = Date.now();
1713
1939
  const { text } = await generateText({
1714
- model,
1940
+ model: modelInstance,
1715
1941
  prompt,
1716
1942
  temperature: 0.3
1717
1943
  // Lower temperature for more consistent code generation
1718
1944
  });
1945
+ const latencyMs = Date.now() - started;
1719
1946
  let analysis;
1720
1947
  try {
1721
1948
  const jsonMatch = text.match(/\{[\s\S]*\}/);
@@ -1726,25 +1953,42 @@ Important:
1726
1953
  } catch (err) {
1727
1954
  return {
1728
1955
  success: false,
1956
+ llmProvider: provider,
1729
1957
  llmModel: modelName,
1730
1958
  confidence: 0,
1731
1959
  riskLevel: "high",
1960
+ latencyMs,
1732
1961
  error: `Failed to parse LLM response: ${err instanceof Error ? err.message : "unknown error"}`
1733
1962
  };
1734
1963
  }
1735
1964
  if (!analysis.analysis || !analysis.fixedCode || typeof analysis.confidence !== "number" || !["low", "medium", "high"].includes(analysis.riskLevel)) {
1736
1965
  return {
1737
1966
  success: false,
1967
+ llmProvider: provider,
1738
1968
  llmModel: modelName,
1739
1969
  confidence: 0,
1740
1970
  riskLevel: "high",
1971
+ latencyMs,
1741
1972
  error: "LLM response missing required fields (analysis, fixedCode, confidence, riskLevel)"
1742
1973
  };
1743
1974
  }
1975
+ const confidenceThreshold = getPatchConfidenceThreshold(
1976
+ provider,
1977
+ providerSafetyProfile ?? "relaxed"
1978
+ );
1979
+ const estimatedCostUsd = estimateModelCostUsd({
1980
+ provider,
1981
+ promptChars: prompt.length,
1982
+ completionChars: text.length
1983
+ });
1744
1984
  if (dryRun) {
1745
1985
  return {
1746
1986
  success: true,
1987
+ llmProvider: provider,
1747
1988
  llmModel: modelName,
1989
+ latencyMs,
1990
+ estimatedCostUsd,
1991
+ confidenceThreshold,
1748
1992
  confidence: analysis.confidence,
1749
1993
  riskLevel: analysis.riskLevel
1750
1994
  };
@@ -1772,7 +2016,11 @@ Important:
1772
2016
  if (patches.length === 0) {
1773
2017
  return {
1774
2018
  success: false,
2019
+ llmProvider: provider,
1775
2020
  llmModel: modelName,
2021
+ latencyMs,
2022
+ estimatedCostUsd,
2023
+ confidenceThreshold,
1776
2024
  confidence: analysis.confidence,
1777
2025
  riskLevel: analysis.riskLevel,
1778
2026
  error: "No valid patches could be generated from LLM response"
@@ -1782,7 +2030,11 @@ Important:
1782
2030
  success: true,
1783
2031
  patches,
1784
2032
  patchContent: patches[0]?.unifiedDiff,
2033
+ llmProvider: provider,
1785
2034
  llmModel: modelName,
2035
+ latencyMs,
2036
+ estimatedCostUsd,
2037
+ confidenceThreshold,
1786
2038
  confidence: analysis.confidence,
1787
2039
  riskLevel: analysis.riskLevel
1788
2040
  };
@@ -1790,6 +2042,7 @@ Important:
1790
2042
  const message = err instanceof Error ? err.message : String(err);
1791
2043
  return {
1792
2044
  success: false,
2045
+ llmProvider: "local",
1793
2046
  llmModel: "unknown",
1794
2047
  confidence: 0,
1795
2048
  riskLevel: "high",
@@ -1827,12 +2080,17 @@ function generateUnifiedDiff(original, fixed, filePath) {
1827
2080
  }
1828
2081
 
1829
2082
  // src/remediation/local/fallback.ts
2083
+ function resolvePatchProvider(provider) {
2084
+ void provider;
2085
+ return "remote";
2086
+ }
1830
2087
  function shouldAttemptPatchFallback(result, preferVersionBump) {
1831
2088
  if (preferVersionBump) return false;
1832
2089
  if (result.applied || result.dryRun) return false;
1833
2090
  return result.unresolvedReason === "no-safe-version" || result.unresolvedReason === "install-failed" || result.unresolvedReason === "override-apply-failed" || result.unresolvedReason === "validation-failed" || result.unresolvedReason === "major-bump-required" || result.unresolvedReason === "indirect-dependency";
1834
2091
  }
1835
2092
  async function tryLocalPatchFallback(params) {
2093
+ const usage = [];
1836
2094
  let steps = 0;
1837
2095
  const sourceResult = await fetchPackageSourceTool.execute({
1838
2096
  packageName: params.packageName,
@@ -1842,17 +2100,20 @@ async function tryLocalPatchFallback(params) {
1842
2100
  if (!sourceResult?.success || !sourceResult.sourceFiles) {
1843
2101
  return {
1844
2102
  steps,
2103
+ usage,
1845
2104
  result: {
1846
2105
  packageName: params.packageName,
1847
2106
  strategy: "none",
1848
2107
  fromVersion: params.vulnerableVersion,
1849
2108
  applied: false,
1850
2109
  dryRun: params.dryRun,
2110
+ dependencyScope: params.dependencyScope,
1851
2111
  unresolvedReason: "source-fetch-failed",
1852
2112
  message: sourceResult?.error ?? `Failed to fetch source for ${params.packageName}@${params.vulnerableVersion}.`
1853
2113
  }
1854
2114
  };
1855
2115
  }
2116
+ const primaryProvider = resolvePatchProvider(params.llmProvider);
1856
2117
  const patchResult = await generatePatchTool.execute({
1857
2118
  packageName: params.packageName,
1858
2119
  vulnerableVersion: params.vulnerableVersion,
@@ -1860,7 +2121,15 @@ async function tryLocalPatchFallback(params) {
1860
2121
  cveSummary: params.cveSummary,
1861
2122
  sourceFiles: sourceResult.sourceFiles,
1862
2123
  vulnerabilityCategory: "unknown",
1863
- dryRun: params.dryRun
2124
+ dryRun: params.dryRun,
2125
+ llmProvider: primaryProvider,
2126
+ model: params.model,
2127
+ policy: params.policy,
2128
+ cwd: params.cwd,
2129
+ modelPersonality: params.modelPersonality,
2130
+ providerSafetyProfile: params.providerSafetyProfile,
2131
+ dynamicModelRouting: params.dynamicModelRouting,
2132
+ dynamicRoutingThresholdChars: params.dynamicRoutingThresholdChars
1864
2133
  });
1865
2134
  steps += 1;
1866
2135
  if (!patchResult?.success) {
@@ -1868,58 +2137,121 @@ async function tryLocalPatchFallback(params) {
1868
2137
  const unresolvedReason = error.includes("API_KEY") || error.includes("does not create a language model") ? "requires-llm-fallback" : "patch-generation-failed";
1869
2138
  return {
1870
2139
  steps,
2140
+ usage,
1871
2141
  result: {
1872
2142
  packageName: params.packageName,
1873
2143
  strategy: "none",
1874
2144
  fromVersion: params.vulnerableVersion,
1875
2145
  applied: false,
1876
2146
  dryRun: params.dryRun,
2147
+ dependencyScope: params.dependencyScope,
1877
2148
  unresolvedReason,
1878
2149
  message: error
1879
2150
  }
1880
2151
  };
1881
2152
  }
1882
- if (typeof patchResult.confidence === "number" && patchResult.confidence < 0.7) {
2153
+ const effectiveProvider = patchResult.llmProvider ?? primaryProvider;
2154
+ const confidenceThreshold = patchResult.confidenceThreshold ?? getPatchConfidenceThreshold(effectiveProvider, params.providerSafetyProfile ?? "relaxed");
2155
+ if (typeof patchResult.confidence === "number" && patchResult.confidence < confidenceThreshold) {
1883
2156
  return {
1884
2157
  steps,
2158
+ usage,
1885
2159
  result: {
1886
2160
  packageName: params.packageName,
1887
2161
  strategy: "none",
1888
2162
  fromVersion: params.vulnerableVersion,
1889
2163
  applied: false,
1890
2164
  dryRun: params.dryRun,
2165
+ dependencyScope: params.dependencyScope,
2166
+ confidence: patchResult.confidence,
2167
+ riskLevel: patchResult.riskLevel,
1891
2168
  unresolvedReason: "patch-confidence-too-low",
1892
- message: `Patch confidence ${patchResult.confidence.toFixed(2)} is below threshold 0.70.`
2169
+ message: `Patch confidence ${patchResult.confidence.toFixed(2)} is below threshold ${confidenceThreshold.toFixed(2)}.`
1893
2170
  }
1894
2171
  };
1895
2172
  }
2173
+ if (params.requireConsensusForHighRisk && patchResult.riskLevel === "high" && !params.dryRun) {
2174
+ const consensus = await generatePatchTool.execute({
2175
+ packageName: params.packageName,
2176
+ vulnerableVersion: params.vulnerableVersion,
2177
+ cveId: params.cveId,
2178
+ cveSummary: params.cveSummary,
2179
+ sourceFiles: sourceResult.sourceFiles,
2180
+ vulnerabilityCategory: "unknown",
2181
+ dryRun: false,
2182
+ llmProvider: "remote",
2183
+ policy: params.policy,
2184
+ cwd: params.cwd,
2185
+ modelPersonality: params.modelPersonality,
2186
+ providerSafetyProfile: params.providerSafetyProfile,
2187
+ dynamicModelRouting: params.dynamicModelRouting,
2188
+ dynamicRoutingThresholdChars: params.dynamicRoutingThresholdChars
2189
+ });
2190
+ steps += 1;
2191
+ const primaryDiff = patchResult.patches?.[0]?.unifiedDiff;
2192
+ const secondaryDiff = consensus.patches?.[0]?.unifiedDiff;
2193
+ if (!consensus.success || !primaryDiff || !secondaryDiff || primaryDiff !== secondaryDiff) {
2194
+ return {
2195
+ steps,
2196
+ usage,
2197
+ result: {
2198
+ packageName: params.packageName,
2199
+ strategy: "none",
2200
+ fromVersion: params.vulnerableVersion,
2201
+ applied: false,
2202
+ dryRun: params.dryRun,
2203
+ dependencyScope: params.dependencyScope,
2204
+ unresolvedReason: "consensus-failed",
2205
+ message: consensus.error ?? "High-risk patch did not pass consensus verification."
2206
+ }
2207
+ };
2208
+ }
2209
+ }
1896
2210
  const applyResult = await applyPatchFileTool.execute({
1897
2211
  packageName: params.packageName,
1898
2212
  vulnerableVersion: params.vulnerableVersion,
2213
+ cveId: params.cveId,
2214
+ confidence: patchResult.confidence,
1899
2215
  patchContent: patchResult.patchContent,
1900
2216
  patches: patchResult.patches,
1901
2217
  patchesDir: params.patchesDir,
1902
2218
  cwd: params.cwd,
1903
2219
  packageManager: params.packageManager,
2220
+ riskLevel: patchResult.riskLevel,
1904
2221
  validateWithTests: params.runTests,
1905
2222
  dryRun: params.dryRun
1906
2223
  });
1907
2224
  steps += 1;
2225
+ if (patchResult.llmProvider && patchResult.llmModel) {
2226
+ usage.push({
2227
+ purpose: "patch-generation",
2228
+ provider: patchResult.llmProvider,
2229
+ model: patchResult.llmModel,
2230
+ latencyMs: patchResult.latencyMs,
2231
+ estimatedCostUsd: patchResult.estimatedCostUsd
2232
+ });
2233
+ }
1908
2234
  return {
1909
2235
  steps,
2236
+ usage,
1910
2237
  result: {
1911
2238
  packageName: params.packageName,
1912
2239
  strategy: "patch-file",
1913
2240
  fromVersion: params.vulnerableVersion,
1914
2241
  patchFilePath: applyResult.patchFilePath ?? applyResult.patchPath,
2242
+ patchArtifact: applyResult.patchArtifact,
1915
2243
  applied: Boolean(applyResult.applied),
1916
2244
  dryRun: Boolean(applyResult.dryRun),
2245
+ dependencyScope: params.dependencyScope,
2246
+ confidence: patchResult.confidence,
2247
+ riskLevel: patchResult.riskLevel,
1917
2248
  unresolvedReason: !Boolean(applyResult.applied) && !Boolean(applyResult.dryRun) ? applyResult.validation?.passed === false ? "patch-validation-failed" : "patch-apply-failed" : void 0,
1918
2249
  message: applyResult.message ?? applyResult.error ?? "Patch-file strategy finished.",
1919
2250
  validation: typeof applyResult.validation?.passed === "boolean" ? {
1920
2251
  passed: applyResult.validation.passed,
1921
2252
  error: applyResult.validation.error
1922
- } : void 0
2253
+ } : void 0,
2254
+ validationPhases: applyResult.validationPhases
1923
2255
  }
1924
2256
  };
1925
2257
  }
@@ -1934,7 +2266,14 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
1934
2266
  const policy = options.policy ?? "";
1935
2267
  const patchesDir = options.patchesDir || "./patches";
1936
2268
  const constraints = options.constraints ?? {};
2269
+ const loadedPolicy = loadPolicy(cwd, options.policy);
2270
+ const llmProvider = resolveProvider(options);
2271
+ const providerSafetyProfile = options.providerSafetyProfile ?? loadedPolicy.providerSafetyProfile ?? "relaxed";
2272
+ const requireConsensusForHighRisk = options.requireConsensusForHighRisk ?? loadedPolicy.requireConsensusForHighRisk ?? false;
2273
+ const dynamicModelRouting = options.dynamicModelRouting ?? loadedPolicy.dynamicModelRouting ?? false;
2274
+ const dynamicRoutingThresholdChars = options.dynamicRoutingThresholdChars ?? loadedPolicy.dynamicRoutingThresholdChars;
1937
2275
  const collectedResults = [];
2276
+ const llmUsage = [];
1938
2277
  const vulnerablePackages = [];
1939
2278
  let cveDetails = null;
1940
2279
  let agentSteps = 0;
@@ -2043,15 +2382,30 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
2043
2382
  vulnerableVersion: vulnerable.installed.version,
2044
2383
  cveId: normalizedId,
2045
2384
  cveSummary: cveDetails?.summary ?? normalizedId,
2385
+ dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive",
2046
2386
  dryRun,
2047
2387
  runTests,
2048
- patchesDir
2388
+ patchesDir,
2389
+ llmProvider,
2390
+ model: options.model,
2391
+ policy: options.policy,
2392
+ modelPersonality: options.modelPersonality,
2393
+ providerSafetyProfile,
2394
+ requireConsensusForHighRisk,
2395
+ dynamicModelRouting,
2396
+ dynamicRoutingThresholdChars
2049
2397
  });
2050
2398
  agentSteps += fallback.steps;
2051
2399
  collectedResults.push(fallback.result);
2400
+ if (fallback.usage) {
2401
+ llmUsage.push(...fallback.usage);
2402
+ }
2052
2403
  continue;
2053
2404
  }
2054
- collectedResults.push(primary.result);
2405
+ collectedResults.push({
2406
+ ...primary.result,
2407
+ dependencyScope: vulnerable.installed.type === "direct" ? "direct" : "transitive"
2408
+ });
2055
2409
  }
2056
2410
  const appliedCount = collectedResults.filter((result) => result.applied).length;
2057
2411
  const unresolvedCount = collectedResults.filter((result) => !result.applied && !result.dryRun).length;
@@ -2063,6 +2417,7 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
2063
2417
  results: collectedResults,
2064
2418
  agentSteps,
2065
2419
  summary: `Local mode completed: vulnerable=${vulnerablePackages.length}, applied=${appliedCount}, dryRun=${dryRunCount}, unresolved=${unresolvedCount}`,
2420
+ llmUsage: llmUsage.length > 0 ? llmUsage : void 0,
2066
2421
  correlation: {
2067
2422
  requestId: options.requestId,
2068
2423
  sessionId: options.sessionId,
@@ -2071,6 +2426,50 @@ async function runLocalRemediationPipeline(cveId, options = {}) {
2071
2426
  };
2072
2427
  }
2073
2428
 
2429
+ // src/remediation/orchestration-prompt.ts
2430
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2431
+ import { join as join10 } from "path";
2432
+ function buildProviderAddendum(provider, personality = "balanced") {
2433
+ 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.";
2434
+ 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.";
2435
+ return `
2436
+ Provider profile:
2437
+ - Provider: ${provider}
2438
+ - ${providerDirective}
2439
+ - ${personalityDirective}`;
2440
+ }
2441
+ function loadOrchestrationPrompt(ctx) {
2442
+ const promptPath = join10(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
2443
+ if (!existsSync5(promptPath)) {
2444
+ return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
2445
+ Working directory: ${ctx.cwd}
2446
+ Package manager: ${ctx.packageManager}
2447
+ Dry run: ${ctx.dryRun}
2448
+ Run tests: ${ctx.runTests}
2449
+ Policy: ${ctx.policy || "undefined"}
2450
+ Patches dir: ${ctx.patchesDir}
2451
+ Direct dependencies only: ${String(ctx.constraints.directDependenciesOnly ?? false)}
2452
+ Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
2453
+
2454
+ Required sequence:
2455
+ 1. lookup-cve
2456
+ 2. check-inventory
2457
+ 3. check-version-match
2458
+ 4. find-fixed-version
2459
+ 5. apply-version-bump
2460
+ 6. apply-package-override
2461
+
2462
+ Fallback sequence (when neither version bump nor override can be applied):
2463
+ 1. fetch-package-source
2464
+ 2. generate-patch
2465
+ 3. apply-patch-file
2466
+
2467
+ Always respect dryRun and policy constraints.`;
2468
+ }
2469
+ const template = readFileSync6(promptPath, "utf8");
2470
+ return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll("{{directDependenciesOnly}}", String(ctx.constraints.directDependenciesOnly ?? false)).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false)) + buildProviderAddendum(ctx.llmProvider, ctx.modelPersonality);
2471
+ }
2472
+
2074
2473
  // src/remediation/tools/lookup-cve.ts
2075
2474
  import { tool as tool7 } from "ai";
2076
2475
  import { z as z7 } from "zod";
@@ -2544,47 +2943,23 @@ function buildRuntimeTools(ctx) {
2544
2943
  return tools;
2545
2944
  }
2546
2945
 
2547
- // src/remediation/orchestration-prompt.ts
2548
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2549
- import { join as join10 } from "path";
2550
- function loadOrchestrationPrompt(ctx) {
2551
- const promptPath = join10(process.cwd(), ".github", "instructions", "orchestration.instructions.md");
2552
- if (!existsSync5(promptPath)) {
2553
- return `You are autoremediator, an agentic security remediation system for Node.js package dependencies.
2554
- Working directory: ${ctx.cwd}
2555
- Package manager: ${ctx.packageManager}
2556
- Dry run: ${ctx.dryRun}
2557
- Run tests: ${ctx.runTests}
2558
- Policy: ${ctx.policy || "undefined"}
2559
- Patches dir: ${ctx.patchesDir}
2560
- Direct dependencies only: ${String(ctx.constraints.directDependenciesOnly ?? false)}
2561
- Prefer version bump: ${String(ctx.constraints.preferVersionBump ?? false)}
2562
-
2563
- Required sequence:
2564
- 1. lookup-cve
2565
- 2. check-inventory
2566
- 3. check-version-match
2567
- 4. find-fixed-version
2568
- 5. apply-version-bump
2569
- 6. apply-package-override
2570
-
2571
- Fallback sequence (when neither version bump nor override can be applied):
2572
- 1. fetch-package-source
2573
- 2. generate-patch
2574
- 3. apply-patch-file
2575
-
2576
- Always respect dryRun and policy constraints.`;
2577
- }
2578
- const template = readFileSync6(promptPath, "utf8");
2579
- return template.replaceAll("{{cveId}}", ctx.cveId).replaceAll("{{cwd}}", ctx.cwd).replaceAll("{{packageManager}}", ctx.packageManager).replaceAll("{{dryRun}}", String(ctx.dryRun)).replaceAll("{{runTests}}", String(ctx.runTests)).replaceAll("{{policy}}", ctx.policy || "undefined").replaceAll("{{patchesDir}}", ctx.patchesDir).replaceAll("{{directDependenciesOnly}}", String(ctx.constraints.directDependenciesOnly ?? false)).replaceAll("{{preferVersionBump}}", String(ctx.constraints.preferVersionBump ?? false));
2580
- }
2581
-
2582
2946
  // src/remediation/pipeline.ts
2583
2947
  async function runRemediationPipeline(cveId, options = {}) {
2584
2948
  const provider = resolveProvider(options);
2585
2949
  if (provider === "local") {
2586
2950
  return runLocalRemediationPipeline(cveId, options);
2587
2951
  }
2952
+ const emitProgress = (stage, detail, extra) => {
2953
+ if (!options.onProgress) return;
2954
+ options.onProgress({
2955
+ stage,
2956
+ detail,
2957
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2958
+ provider: extra?.provider,
2959
+ model: extra?.model
2960
+ });
2961
+ };
2962
+ emitProgress("pipeline-start", `Starting remediation for ${cveId}.`, { provider });
2588
2963
  const cwd = options.cwd ?? process.cwd();
2589
2964
  const packageManager = options.packageManager ?? detectPackageManager(cwd);
2590
2965
  const preview = options.preview ?? false;
@@ -2593,10 +2968,15 @@ async function runRemediationPipeline(cveId, options = {}) {
2593
2968
  const policy = options.policy ?? "";
2594
2969
  const patchesDir = options.patchesDir || "./patches";
2595
2970
  const constraints = options.constraints ?? {};
2596
- const model = await createModel(options);
2971
+ const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
2972
+ const model = await createModel(options, { inputChars: prompt.length });
2973
+ const modelName = model.modelId ?? "unknown-model";
2974
+ emitProgress("model-selected", `Selected model ${modelName}.`, { provider, model: modelName });
2597
2975
  const systemPrompt = loadOrchestrationPrompt({
2598
2976
  cveId,
2599
2977
  cwd,
2978
+ llmProvider: provider,
2979
+ modelPersonality: options.modelPersonality,
2600
2980
  dryRun,
2601
2981
  runTests,
2602
2982
  policy,
@@ -2604,11 +2984,15 @@ async function runRemediationPipeline(cveId, options = {}) {
2604
2984
  packageManager,
2605
2985
  constraints
2606
2986
  });
2607
- const prompt = `Patch vulnerable dependencies affected by ${cveId} in the project at: ${cwd}. Package manager: ${packageManager}.`;
2608
2987
  const collectedResults = [];
2609
2988
  const vulnerablePackages = [];
2610
2989
  let cveDetails = null;
2611
2990
  let agentSteps = 0;
2991
+ function getDependencyScope(packageName) {
2992
+ const match = vulnerablePackages.find((pkg) => pkg.installed.name === packageName);
2993
+ if (!match) return void 0;
2994
+ return match.installed.type === "direct" ? "direct" : "transitive";
2995
+ }
2612
2996
  const applyVersionBumpToolForRun = preview ? {
2613
2997
  ...applyVersionBumpTool,
2614
2998
  execute: async (input) => applyVersionBumpTool.execute({ ...input, dryRun: true })
@@ -2627,6 +3011,7 @@ async function runRemediationPipeline(cveId, options = {}) {
2627
3011
  applyPatchFileToolForRun,
2628
3012
  constraints
2629
3013
  });
3014
+ const started = Date.now();
2630
3015
  const result = await generateText2({
2631
3016
  model,
2632
3017
  system: systemPrompt,
@@ -2645,10 +3030,18 @@ async function runRemediationPipeline(cveId, options = {}) {
2645
3030
  vulnerablePackages.push(...toolResult.vulnerablePackages);
2646
3031
  }
2647
3032
  if (tr.toolName === "apply-version-bump") {
2648
- collectedResults.push(toolResult);
3033
+ const typed = toolResult;
3034
+ collectedResults.push({
3035
+ ...typed,
3036
+ dependencyScope: typed.packageName ? getDependencyScope(typed.packageName) : typed.dependencyScope
3037
+ });
2649
3038
  }
2650
3039
  if (tr.toolName === "apply-package-override") {
2651
- collectedResults.push(toolResult);
3040
+ const typed = toolResult;
3041
+ collectedResults.push({
3042
+ ...typed,
3043
+ dependencyScope: typed.packageName ? getDependencyScope(typed.packageName) : typed.dependencyScope
3044
+ });
2652
3045
  }
2653
3046
  if (tr.toolName === "apply-patch-file" && toolResult) {
2654
3047
  const validation = toolResult.validation;
@@ -2658,19 +3051,47 @@ async function runRemediationPipeline(cveId, options = {}) {
2658
3051
  strategy: "patch-file",
2659
3052
  fromVersion: typeof toolResult.vulnerableVersion === "string" ? toolResult.vulnerableVersion : "unknown",
2660
3053
  patchFilePath: typeof toolResult.patchFilePath === "string" ? toolResult.patchFilePath : typeof toolResult.patchPath === "string" ? toolResult.patchPath : void 0,
3054
+ patchArtifact: typeof toolResult.patchArtifact === "object" && toolResult.patchArtifact !== null ? toolResult.patchArtifact : void 0,
2661
3055
  applied: Boolean(toolResult.applied),
2662
3056
  dryRun: Boolean(toolResult.dryRun),
3057
+ dependencyScope: typeof toolResult.packageName === "string" ? getDependencyScope(toolResult.packageName) : void 0,
3058
+ confidence: typeof toolResult.patchArtifact === "object" && toolResult.patchArtifact !== null && typeof toolResult.patchArtifact.confidence === "number" ? toolResult.patchArtifact.confidence : void 0,
3059
+ riskLevel: typeof toolResult.patchArtifact === "object" && toolResult.patchArtifact !== null && typeof toolResult.patchArtifact.riskLevel === "string" ? toolResult.patchArtifact.riskLevel : void 0,
2663
3060
  unresolvedReason: !Boolean(toolResult.applied) && !Boolean(toolResult.dryRun) ? validation && validation.passed === false ? "patch-validation-failed" : "patch-apply-failed" : void 0,
2664
3061
  message,
2665
3062
  validation: validation && typeof validation.passed === "boolean" ? {
2666
3063
  passed: validation.passed,
2667
3064
  error: typeof validation.error === "string" ? validation.error : void 0
2668
- } : void 0
3065
+ } : void 0,
3066
+ validationPhases: Array.isArray(toolResult.validationPhases) ? toolResult.validationPhases : void 0
2669
3067
  });
2670
3068
  }
2671
3069
  }
3070
+ emitProgress("agent-step", `Completed agent step ${agentSteps} with ${toolResults.length} tool result(s).`, {
3071
+ provider,
3072
+ model: modelName
3073
+ });
2672
3074
  }
2673
3075
  });
3076
+ const llmUsage = [
3077
+ {
3078
+ purpose: "orchestration",
3079
+ provider,
3080
+ model: modelName,
3081
+ latencyMs: Date.now() - started,
3082
+ promptChars: prompt.length + systemPrompt.length,
3083
+ completionChars: result.text.length,
3084
+ estimatedCostUsd: estimateModelCostUsd({
3085
+ provider,
3086
+ promptChars: prompt.length + systemPrompt.length,
3087
+ completionChars: result.text.length
3088
+ })
3089
+ }
3090
+ ];
3091
+ emitProgress("pipeline-finish", `Completed remediation with ${collectedResults.length} result(s).`, {
3092
+ provider,
3093
+ model: modelName
3094
+ });
2674
3095
  return {
2675
3096
  cveId,
2676
3097
  cveDetails,
@@ -2678,6 +3099,7 @@ async function runRemediationPipeline(cveId, options = {}) {
2678
3099
  results: collectedResults,
2679
3100
  agentSteps,
2680
3101
  summary: result.text,
3102
+ llmUsage,
2681
3103
  correlation: {
2682
3104
  requestId: options.requestId,
2683
3105
  sessionId: options.sessionId,
@@ -2688,7 +3110,7 @@ async function runRemediationPipeline(cveId, options = {}) {
2688
3110
 
2689
3111
  // src/api/options-schema.ts
2690
3112
  var PACKAGE_MANAGER_VALUES = ["npm", "pnpm", "yarn"];
2691
- var LLM_PROVIDER_VALUES = ["openai", "anthropic", "local"];
3113
+ var LLM_PROVIDER_VALUES = ["remote", "local"];
2692
3114
  var PROVENANCE_SOURCE_VALUES = ["cli", "sdk", "mcp", "openapi", "unknown"];
2693
3115
  var OPTION_DESCRIPTIONS = {
2694
3116
  cveId: "CVE ID, e.g. CVE-2021-23337",
@@ -2698,7 +3120,13 @@ var OPTION_DESCRIPTIONS = {
2698
3120
  dryRun: "If true, plan changes but write nothing",
2699
3121
  preview: "If true, enforce non-mutating preview mode",
2700
3122
  runTests: "Run package-manager test command after applying fix",
2701
- llmProvider: "LLM provider override",
3123
+ llmProvider: "LLM provider override (remote|local)",
3124
+ model: "LLM model override",
3125
+ modelPersonality: "Prompt behavior profile: analytical|pragmatic|balanced",
3126
+ providerSafetyProfile: "Safety posture profile for confidence gates: strict|relaxed",
3127
+ requireConsensusForHighRisk: "Require second-provider agreement for high-risk generated patches",
3128
+ dynamicModelRouting: "Enable dynamic model selection by input size",
3129
+ dynamicRoutingThresholdChars: "Input size threshold used by dynamic model routing",
2702
3130
  patchesDir: "Directory to write .patch files (default: ./patches)",
2703
3131
  policy: "Optional path to .autoremediator policy file",
2704
3132
  requestId: "Request correlation ID",
@@ -2730,6 +3158,12 @@ function createRemediateOptionSchemaProperties(options) {
2730
3158
  ...includePreview ? { preview: { type: "boolean", description: OPTION_DESCRIPTIONS.preview } } : {},
2731
3159
  runTests: { type: "boolean", description: OPTION_DESCRIPTIONS.runTests },
2732
3160
  llmProvider: { type: "string", enum: [...LLM_PROVIDER_VALUES], description: OPTION_DESCRIPTIONS.llmProvider },
3161
+ model: { type: "string", description: OPTION_DESCRIPTIONS.model },
3162
+ modelPersonality: { type: "string", enum: ["analytical", "pragmatic", "balanced"], description: OPTION_DESCRIPTIONS.modelPersonality },
3163
+ providerSafetyProfile: { type: "string", enum: ["strict", "relaxed"], description: OPTION_DESCRIPTIONS.providerSafetyProfile },
3164
+ requireConsensusForHighRisk: { type: "boolean", description: OPTION_DESCRIPTIONS.requireConsensusForHighRisk },
3165
+ dynamicModelRouting: { type: "boolean", description: OPTION_DESCRIPTIONS.dynamicModelRouting },
3166
+ dynamicRoutingThresholdChars: { type: "number", description: OPTION_DESCRIPTIONS.dynamicRoutingThresholdChars },
2733
3167
  patchesDir: { type: "string", description: OPTION_DESCRIPTIONS.patchesDir },
2734
3168
  policy: { type: "string", description: OPTION_DESCRIPTIONS.policy },
2735
3169
  ...includeEvidence ? { evidence: { type: "boolean", description: OPTION_DESCRIPTIONS.evidence } } : {},
@@ -2781,7 +3215,10 @@ function createScanReportSchemaProperties() {
2781
3215
  correlation: { type: "object" },
2782
3216
  provenance: { type: "object" },
2783
3217
  constraints: { type: "object" },
2784
- idempotencyKey: { type: "string" }
3218
+ idempotencyKey: { type: "string" },
3219
+ llmUsageCount: { type: "number" },
3220
+ estimatedCostUsd: { type: "number" },
3221
+ totalLlmLatencyMs: { type: "number" }
2785
3222
  };
2786
3223
  }
2787
3224
 
@@ -2851,7 +3288,10 @@ function toCiSummary(report) {
2851
3288
  correlation: report.correlation,
2852
3289
  provenance: report.provenance,
2853
3290
  constraints: report.constraints,
2854
- idempotencyKey: report.idempotencyKey
3291
+ idempotencyKey: report.idempotencyKey,
3292
+ llmUsageCount: report.llmUsageCount,
3293
+ estimatedCostUsd: report.estimatedCostUsd,
3294
+ totalLlmLatencyMs: report.totalLlmLatencyMs
2855
3295
  };
2856
3296
  }
2857
3297
  function ciExitCode(summary) {
@@ -2921,9 +3361,219 @@ function toSarifOutput(report) {
2921
3361
  };
2922
3362
  }
2923
3363
 
3364
+ // src/api/patches.ts
3365
+ import { existsSync as existsSync6 } from "fs";
3366
+ import { readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
3367
+ import { isAbsolute, join as join11, resolve } from "path";
3368
+ var DEFAULT_PATCHES_DIR = "./patches";
3369
+ async function listPatchArtifacts(options = {}) {
3370
+ const cwd = options.cwd ?? process.cwd();
3371
+ const patchesDirPath = resolvePatchesDir(cwd, options.patchesDir);
3372
+ if (!existsSync6(patchesDirPath)) {
3373
+ return [];
3374
+ }
3375
+ const entries = await readdir2(patchesDirPath, { withFileTypes: true });
3376
+ const patchFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".patch")).map((entry) => join11(patchesDirPath, entry.name)).sort((left, right) => left.localeCompare(right));
3377
+ const summaries = await Promise.all(
3378
+ patchFiles.map(async (patchFilePath) => {
3379
+ const inspection = await inspectPatchArtifact(patchFilePath, options);
3380
+ return toSummary(inspection);
3381
+ })
3382
+ );
3383
+ return summaries;
3384
+ }
3385
+ async function inspectPatchArtifact(patchFilePath, options = {}) {
3386
+ const cwd = options.cwd ?? process.cwd();
3387
+ const resolvedPatchPath = resolveArtifactPath(cwd, patchFilePath);
3388
+ const patchFileName = resolvedPatchPath.split("/").pop() ?? resolvedPatchPath;
3389
+ const manifestFilePath = `${resolvedPatchPath}.json`;
3390
+ if (!existsSync6(resolvedPatchPath)) {
3391
+ return {
3392
+ patchFilePath: resolvedPatchPath,
3393
+ manifestFilePath: existsSync6(manifestFilePath) ? manifestFilePath : void 0,
3394
+ patchFileName,
3395
+ exists: false,
3396
+ diffValid: false,
3397
+ formatError: "Patch file does not exist."
3398
+ };
3399
+ }
3400
+ const [patchContent, fileStats, manifest] = await Promise.all([
3401
+ readFile3(resolvedPatchPath, "utf8"),
3402
+ stat(resolvedPatchPath),
3403
+ readManifest(manifestFilePath)
3404
+ ]);
3405
+ const format = validatePatchDiff(patchContent);
3406
+ const derived = derivePatchMetadata(patchContent);
3407
+ return {
3408
+ patchFilePath: resolvedPatchPath,
3409
+ manifestFilePath: manifest ? manifestFilePath : void 0,
3410
+ patchFileName,
3411
+ cveId: manifest?.cveId,
3412
+ packageName: manifest?.packageName,
3413
+ vulnerableVersion: manifest?.vulnerableVersion,
3414
+ patchMode: manifest?.patchMode,
3415
+ confidence: manifest?.confidence,
3416
+ riskLevel: manifest?.riskLevel,
3417
+ generatedAt: manifest?.generatedAt,
3418
+ files: manifest?.files ?? derived.files,
3419
+ hunkCount: manifest?.hunkCount ?? derived.hunkCount,
3420
+ exists: true,
3421
+ diffValid: format.valid,
3422
+ formatError: format.error,
3423
+ patchSizeBytes: fileStats.size,
3424
+ lineCount: patchContent.split(/\r?\n/).length,
3425
+ manifest
3426
+ };
3427
+ }
3428
+ async function validatePatchArtifact(patchFilePath, options = {}) {
3429
+ const inspection = await inspectPatchArtifact(patchFilePath, options);
3430
+ const validationPhases = [
3431
+ {
3432
+ phase: "diff-format",
3433
+ passed: inspection.diffValid,
3434
+ error: inspection.diffValid ? void 0 : inspection.formatError,
3435
+ message: inspection.diffValid ? "Patch content is a valid unified diff." : void 0
3436
+ }
3437
+ ];
3438
+ if (!inspection.exists) {
3439
+ return {
3440
+ patchFilePath: inspection.patchFilePath,
3441
+ manifestFilePath: inspection.manifestFilePath,
3442
+ exists: false,
3443
+ manifestFound: false,
3444
+ diffValid: false,
3445
+ formatError: inspection.formatError,
3446
+ driftDetected: false,
3447
+ validationPhases
3448
+ };
3449
+ }
3450
+ const manifest = inspection.manifest;
3451
+ const manifestFound = Boolean(manifest);
3452
+ if (!manifest) {
3453
+ validationPhases.push({
3454
+ phase: "manifest-write",
3455
+ passed: false,
3456
+ error: "No patch manifest found for this patch artifact."
3457
+ });
3458
+ return {
3459
+ patchFilePath: inspection.patchFilePath,
3460
+ manifestFilePath: inspection.manifestFilePath,
3461
+ exists: true,
3462
+ manifestFound,
3463
+ diffValid: inspection.diffValid,
3464
+ formatError: inspection.formatError,
3465
+ driftDetected: false,
3466
+ validationPhases
3467
+ };
3468
+ }
3469
+ validationPhases.push({
3470
+ phase: "manifest-write",
3471
+ passed: true,
3472
+ message: "Patch manifest is present."
3473
+ });
3474
+ const cwd = options.cwd ?? process.cwd();
3475
+ const packageManager = options.packageManager ?? detectPackageManager(cwd);
3476
+ const inventory = await checkInventoryTool.execute({
3477
+ cwd,
3478
+ packageManager
3479
+ });
3480
+ if (inventory.error) {
3481
+ validationPhases.push({
3482
+ phase: "drift",
3483
+ passed: false,
3484
+ error: inventory.error
3485
+ });
3486
+ return {
3487
+ patchFilePath: inspection.patchFilePath,
3488
+ manifestFilePath: inspection.manifestFilePath,
3489
+ exists: true,
3490
+ manifestFound,
3491
+ diffValid: inspection.diffValid,
3492
+ formatError: inspection.formatError,
3493
+ driftDetected: false,
3494
+ cveId: manifest.cveId,
3495
+ packageName: manifest.packageName,
3496
+ vulnerableVersion: manifest.vulnerableVersion,
3497
+ validationPhases
3498
+ };
3499
+ }
3500
+ const matchingPackages = (inventory.packages ?? []).filter(
3501
+ (pkg) => pkg.name === manifest.packageName
3502
+ );
3503
+ const installedVersion = matchingPackages[0]?.version;
3504
+ const inventoryMatch = matchingPackages.some(
3505
+ (pkg) => pkg.version === manifest.vulnerableVersion
3506
+ );
3507
+ const driftDetected = matchingPackages.length > 0 && !inventoryMatch;
3508
+ validationPhases.push({
3509
+ phase: "drift",
3510
+ passed: !driftDetected,
3511
+ message: matchingPackages.length === 0 ? `Package ${manifest.packageName} is not currently installed.` : inventoryMatch ? `Installed version matches manifest target ${manifest.vulnerableVersion}.` : `Installed version ${installedVersion} does not match manifest target ${manifest.vulnerableVersion}.`,
3512
+ error: driftDetected ? "Patch manifest does not match the installed dependency version." : void 0
3513
+ });
3514
+ return {
3515
+ patchFilePath: inspection.patchFilePath,
3516
+ manifestFilePath: inspection.manifestFilePath,
3517
+ exists: true,
3518
+ manifestFound,
3519
+ diffValid: inspection.diffValid,
3520
+ formatError: inspection.formatError,
3521
+ driftDetected,
3522
+ cveId: manifest.cveId,
3523
+ packageName: manifest.packageName,
3524
+ vulnerableVersion: manifest.vulnerableVersion,
3525
+ installedVersion,
3526
+ inventoryMatch,
3527
+ validationPhases
3528
+ };
3529
+ }
3530
+ function resolvePatchesDir(cwd, patchesDir = DEFAULT_PATCHES_DIR) {
3531
+ return isAbsolute(patchesDir) ? patchesDir : resolve(cwd, patchesDir);
3532
+ }
3533
+ function resolveArtifactPath(cwd, patchFilePath) {
3534
+ return isAbsolute(patchFilePath) ? patchFilePath : resolve(cwd, patchFilePath);
3535
+ }
3536
+ async function readManifest(manifestFilePath) {
3537
+ if (!existsSync6(manifestFilePath)) {
3538
+ return void 0;
3539
+ }
3540
+ try {
3541
+ const raw = await readFile3(manifestFilePath, "utf8");
3542
+ return JSON.parse(raw);
3543
+ } catch {
3544
+ return void 0;
3545
+ }
3546
+ }
3547
+ function derivePatchMetadata(patchContent) {
3548
+ const files = Array.from(
3549
+ new Set(
3550
+ patchContent.split(/\r?\n/).filter((line) => line.startsWith("+++ b/")).map((line) => line.slice("+++ b/".length))
3551
+ )
3552
+ );
3553
+ const hunkCount = patchContent.split(/\r?\n/).filter((line) => line.startsWith("@@ ")).length;
3554
+ return { files, hunkCount };
3555
+ }
3556
+ function toSummary(inspection) {
3557
+ return {
3558
+ patchFilePath: inspection.patchFilePath,
3559
+ manifestFilePath: inspection.manifestFilePath,
3560
+ patchFileName: inspection.patchFileName,
3561
+ cveId: inspection.cveId,
3562
+ packageName: inspection.packageName,
3563
+ vulnerableVersion: inspection.vulnerableVersion,
3564
+ patchMode: inspection.patchMode,
3565
+ confidence: inspection.confidence,
3566
+ riskLevel: inspection.riskLevel,
3567
+ generatedAt: inspection.generatedAt,
3568
+ files: inspection.files,
3569
+ hunkCount: inspection.hunkCount,
3570
+ diffValid: inspection.diffValid
3571
+ };
3572
+ }
3573
+
2924
3574
  // src/platform/evidence.ts
2925
3575
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
2926
- import { join as join11 } from "path";
3576
+ import { join as join12 } from "path";
2927
3577
  function createEvidenceLog(cwd, cveIds, context = {}) {
2928
3578
  return {
2929
3579
  runId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -2932,6 +3582,7 @@ function createEvidenceLog(cwd, cveIds, context = {}) {
2932
3582
  parentRunId: context.parentRunId,
2933
3583
  actor: context.actor,
2934
3584
  source: context.source,
3585
+ llmProvider: context.llmProvider,
2935
3586
  idempotencyKey: context.idempotencyKey,
2936
3587
  cveIds,
2937
3588
  cwd,
@@ -2953,29 +3604,29 @@ function finalizeEvidence(log) {
2953
3604
  return log;
2954
3605
  }
2955
3606
  function writeEvidenceLog(cwd, log) {
2956
- const dir = join11(cwd, ".autoremediator", "evidence");
3607
+ const dir = join12(cwd, ".autoremediator", "evidence");
2957
3608
  mkdirSync2(dir, { recursive: true });
2958
- const filePath = join11(dir, `${log.runId}.json`);
3609
+ const filePath = join12(dir, `${log.runId}.json`);
2959
3610
  writeFileSync4(filePath, JSON.stringify(log, null, 2) + "\n", "utf8");
2960
3611
  return filePath;
2961
3612
  }
2962
3613
 
2963
3614
  // src/platform/idempotency.ts
2964
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
2965
- import { join as join12 } from "path";
3615
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3616
+ import { join as join13 } from "path";
2966
3617
  var DEFAULT_INDEX = {
2967
3618
  schemaVersion: "1.0",
2968
3619
  entries: {}
2969
3620
  };
2970
3621
  function indexFilePath(cwd) {
2971
- return join12(cwd, ".autoremediator", "state", "idempotency.json");
3622
+ return join13(cwd, ".autoremediator", "state", "idempotency.json");
2972
3623
  }
2973
3624
  function entryKey(idempotencyKey, cveId) {
2974
3625
  return `${idempotencyKey}::${cveId.toUpperCase()}`;
2975
3626
  }
2976
3627
  function loadIndex(cwd) {
2977
3628
  const filePath = indexFilePath(cwd);
2978
- if (!existsSync6(filePath)) return DEFAULT_INDEX;
3629
+ if (!existsSync7(filePath)) return DEFAULT_INDEX;
2979
3630
  try {
2980
3631
  const parsed = JSON.parse(readFileSync7(filePath, "utf8"));
2981
3632
  if (parsed && parsed.schemaVersion === "1.0" && parsed.entries) {
@@ -2988,7 +3639,7 @@ function loadIndex(cwd) {
2988
3639
  }
2989
3640
  function saveIndex(cwd, index) {
2990
3641
  const filePath = indexFilePath(cwd);
2991
- mkdirSync3(join12(cwd, ".autoremediator", "state"), { recursive: true });
3642
+ mkdirSync3(join13(cwd, ".autoremediator", "state"), { recursive: true });
2992
3643
  writeFileSync5(filePath, JSON.stringify(index, null, 2) + "\n", "utf8");
2993
3644
  }
2994
3645
  function readIdempotentReport(cwd, idempotencyKey, cveId) {
@@ -3045,11 +3696,13 @@ async function remediate(cveId, options = {}) {
3045
3696
  const constraints = resolveConstraints(options, cwd);
3046
3697
  const provenance = resolveProvenanceContext(options);
3047
3698
  const correlation = resolveCorrelationContext(options);
3699
+ const llmProvider = resolveProvider(options);
3048
3700
  const evidenceEnabled = options.evidence !== false;
3049
3701
  const evidence = evidenceEnabled ? createEvidenceLog(cwd, [normalizedCveId], {
3050
3702
  ...correlation,
3051
3703
  actor: provenance.actor,
3052
3704
  source: provenance.source,
3705
+ llmProvider,
3053
3706
  idempotencyKey: options.idempotencyKey
3054
3707
  }) : void 0;
3055
3708
  if (options.resume && options.idempotencyKey) {
@@ -3078,7 +3731,8 @@ async function remediate(cveId, options = {}) {
3078
3731
  {
3079
3732
  cveId: normalizedCveId,
3080
3733
  dryRun: Boolean(options.dryRun),
3081
- preview: Boolean(options.preview)
3734
+ preview: Boolean(options.preview),
3735
+ llmProvider
3082
3736
  },
3083
3737
  {
3084
3738
  directDependenciesOnly: Boolean(constraints.directDependenciesOnly),
@@ -3126,7 +3780,8 @@ async function remediate(cveId, options = {}) {
3126
3780
  { cveId: normalizedCveId },
3127
3781
  {
3128
3782
  resultCount: report.results.length,
3129
- vulnerableCount: report.vulnerablePackages.length
3783
+ vulnerableCount: report.vulnerablePackages.length,
3784
+ llmUsage: report.llmUsage
3130
3785
  }
3131
3786
  );
3132
3787
  finalizeEvidence(evidence);
@@ -3409,6 +4064,7 @@ async function remediateFromScan(inputPath, options = {}) {
3409
4064
  const findings = parseScanInput(inputPath, format);
3410
4065
  const cveIds = uniqueCveIds(findings);
3411
4066
  const policy = loadPolicy(cwd, options.policy);
4067
+ const llmProvider = resolveProvider(options);
3412
4068
  const correlation = resolveCorrelationContext(options);
3413
4069
  const provenance = resolveProvenanceContext(options);
3414
4070
  const constraints = resolveConstraints(options, cwd);
@@ -3416,6 +4072,7 @@ async function remediateFromScan(inputPath, options = {}) {
3416
4072
  ...correlation,
3417
4073
  actor: provenance.actor,
3418
4074
  source: provenance.source,
4075
+ llmProvider,
3419
4076
  idempotencyKey: options.idempotencyKey
3420
4077
  });
3421
4078
  addEvidenceStep(evidence, "scan.parse", { inputPath, format }, { findingCount: findings.length, cveCount: cveIds.length });
@@ -3433,6 +4090,16 @@ async function remediateFromScan(inputPath, options = {}) {
3433
4090
  const errors = execution.errors;
3434
4091
  const patchCount = execution.patchCount;
3435
4092
  const patchValidationFailures = execution.patchValidationFailures;
4093
+ let llmUsageCount = 0;
4094
+ let estimatedCostUsd = 0;
4095
+ let totalLlmLatencyMs = 0;
4096
+ for (const report of reports) {
4097
+ for (const usage of report.llmUsage ?? []) {
4098
+ llmUsageCount += 1;
4099
+ estimatedCostUsd += usage.estimatedCostUsd ?? 0;
4100
+ totalLlmLatencyMs += usage.latencyMs ?? 0;
4101
+ }
4102
+ }
3436
4103
  const outcome = buildScanOutcome({ reports, errors });
3437
4104
  const { status, successCount, failedCount, strategyCounts, dependencyScopeCounts, unresolvedByReason, remediationCount } = outcome;
3438
4105
  evidence.summary = {
@@ -3446,7 +4113,10 @@ async function remediateFromScan(inputPath, options = {}) {
3446
4113
  strategyCounts,
3447
4114
  dependencyScopeCounts,
3448
4115
  unresolvedByReason,
3449
- patchesDir: patchCount > 0 ? patchesDir : void 0
4116
+ patchesDir: patchCount > 0 ? patchesDir : void 0,
4117
+ llmUsageCount: llmUsageCount > 0 ? llmUsageCount : void 0,
4118
+ estimatedCostUsd: llmUsageCount > 0 ? Number(estimatedCostUsd.toFixed(6)) : void 0,
4119
+ totalLlmLatencyMs: llmUsageCount > 0 ? totalLlmLatencyMs : void 0
3450
4120
  };
3451
4121
  finalizeEvidence(evidence);
3452
4122
  const evidenceFile = options.evidence === false ? void 0 : writeEvidenceLog(cwd, evidence);
@@ -3469,7 +4139,10 @@ async function remediateFromScan(inputPath, options = {}) {
3469
4139
  correlation,
3470
4140
  provenance,
3471
4141
  constraints,
3472
- idempotencyKey: options.idempotencyKey
4142
+ idempotencyKey: options.idempotencyKey,
4143
+ llmUsageCount: llmUsageCount > 0 ? llmUsageCount : void 0,
4144
+ estimatedCostUsd: llmUsageCount > 0 ? Number(estimatedCostUsd.toFixed(6)) : void 0,
4145
+ totalLlmLatencyMs: llmUsageCount > 0 ? totalLlmLatencyMs : void 0
3473
4146
  };
3474
4147
  }
3475
4148
 
@@ -3486,8 +4159,11 @@ export {
3486
4159
  toCiSummary,
3487
4160
  ciExitCode,
3488
4161
  toSarifOutput,
4162
+ listPatchArtifacts,
4163
+ inspectPatchArtifact,
4164
+ validatePatchArtifact,
3489
4165
  remediate,
3490
4166
  planRemediation,
3491
4167
  remediateFromScan
3492
4168
  };
3493
- //# sourceMappingURL=chunk-MUFP2DQX.js.map
4169
+ //# sourceMappingURL=chunk-5S4Y3WVZ.js.map