guardrail-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/__tests__/autopilot.test.d.ts +7 -0
  2. package/dist/__tests__/autopilot.test.d.ts.map +1 -0
  3. package/dist/__tests__/autopilot.test.js +156 -0
  4. package/dist/__tests__/tier-config.test.d.ts +9 -0
  5. package/dist/__tests__/tier-config.test.d.ts.map +1 -0
  6. package/dist/__tests__/tier-config.test.js +230 -0
  7. package/dist/__tests__/utils/hash-inline.test.d.ts +2 -0
  8. package/dist/__tests__/utils/hash-inline.test.d.ts.map +1 -0
  9. package/dist/__tests__/utils/hash-inline.test.js +62 -0
  10. package/dist/__tests__/utils/hash.test.d.ts +3 -0
  11. package/dist/__tests__/utils/hash.test.d.ts.map +1 -0
  12. package/dist/__tests__/utils/hash.test.js +95 -0
  13. package/dist/__tests__/utils/simple.test.d.ts +1 -0
  14. package/dist/__tests__/utils/simple.test.d.ts.map +1 -0
  15. package/dist/__tests__/utils/simple.test.js +10 -0
  16. package/dist/__tests__/utils/utils-simple.test.d.ts +1 -0
  17. package/dist/__tests__/utils/utils-simple.test.d.ts.map +1 -0
  18. package/dist/__tests__/utils/utils-simple.test.js +6 -0
  19. package/dist/__tests__/utils/utils.test.d.ts +15 -0
  20. package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/utils.test.js +172 -0
  22. package/dist/autopilot/autopilot-runner.d.ts +33 -0
  23. package/dist/autopilot/autopilot-runner.d.ts.map +1 -0
  24. package/dist/autopilot/autopilot-runner.js +479 -0
  25. package/dist/autopilot/index.d.ts +6 -0
  26. package/dist/autopilot/index.d.ts.map +1 -0
  27. package/dist/autopilot/index.js +25 -0
  28. package/dist/autopilot/types.d.ts +102 -0
  29. package/dist/autopilot/types.d.ts.map +1 -0
  30. package/dist/autopilot/types.js +18 -0
  31. package/dist/cache/index.d.ts +7 -0
  32. package/dist/cache/index.d.ts.map +1 -0
  33. package/dist/cache/index.js +22 -0
  34. package/dist/cache/redis-cache.d.ts +145 -0
  35. package/dist/cache/redis-cache.d.ts.map +1 -0
  36. package/dist/cache/redis-cache.js +459 -0
  37. package/dist/ci/github-actions.d.ts +77 -0
  38. package/dist/ci/github-actions.d.ts.map +1 -0
  39. package/dist/ci/github-actions.js +277 -0
  40. package/dist/ci/index.d.ts +12 -0
  41. package/dist/ci/index.d.ts.map +1 -0
  42. package/dist/ci/index.js +27 -0
  43. package/dist/ci/pre-commit.d.ts +65 -0
  44. package/dist/ci/pre-commit.d.ts.map +1 -0
  45. package/dist/ci/pre-commit.js +286 -0
  46. package/dist/entitlements.d.ts +149 -0
  47. package/dist/entitlements.d.ts.map +1 -0
  48. package/dist/entitlements.js +464 -0
  49. package/dist/env.d.ts +113 -0
  50. package/dist/env.d.ts.map +1 -0
  51. package/dist/env.js +204 -0
  52. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts +7 -0
  53. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts.map +1 -0
  54. package/dist/fix-packs/__tests__/generate-fix-packs.test.js +250 -0
  55. package/dist/fix-packs/generate-fix-packs.d.ts +15 -0
  56. package/dist/fix-packs/generate-fix-packs.d.ts.map +1 -0
  57. package/dist/fix-packs/generate-fix-packs.js +505 -0
  58. package/dist/fix-packs/index.d.ts +8 -0
  59. package/dist/fix-packs/index.d.ts.map +1 -0
  60. package/dist/fix-packs/index.js +23 -0
  61. package/dist/fix-packs/types.d.ts +113 -0
  62. package/dist/fix-packs/types.d.ts.map +1 -0
  63. package/dist/fix-packs/types.js +71 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +28 -0
  67. package/dist/metrics/prometheus.d.ts +99 -0
  68. package/dist/metrics/prometheus.d.ts.map +1 -0
  69. package/dist/metrics/prometheus.js +306 -0
  70. package/dist/quota-ledger.d.ts +119 -0
  71. package/dist/quota-ledger.d.ts.map +1 -0
  72. package/dist/quota-ledger.js +462 -0
  73. package/dist/rbac/__tests__/permissions.test.d.ts +8 -0
  74. package/dist/rbac/__tests__/permissions.test.d.ts.map +1 -0
  75. package/dist/rbac/__tests__/permissions.test.js +350 -0
  76. package/dist/rbac/index.d.ts +9 -0
  77. package/dist/rbac/index.d.ts.map +1 -0
  78. package/dist/rbac/index.js +32 -0
  79. package/dist/rbac/permissions.d.ts +71 -0
  80. package/dist/rbac/permissions.d.ts.map +1 -0
  81. package/dist/rbac/permissions.js +247 -0
  82. package/dist/rbac/types.d.ts +69 -0
  83. package/dist/rbac/types.d.ts.map +1 -0
  84. package/dist/rbac/types.js +213 -0
  85. package/dist/tier-config.d.ts +203 -0
  86. package/dist/tier-config.d.ts.map +1 -0
  87. package/dist/tier-config.js +675 -0
  88. package/dist/types.d.ts +365 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +5 -0
  91. package/dist/utils.d.ts +36 -0
  92. package/dist/utils.d.ts.map +1 -0
  93. package/dist/utils.js +127 -0
  94. package/dist/verified-autofix/__tests__/format-validator.test.d.ts +11 -0
  95. package/dist/verified-autofix/__tests__/format-validator.test.d.ts.map +1 -0
  96. package/dist/verified-autofix/__tests__/format-validator.test.js +285 -0
  97. package/dist/verified-autofix/__tests__/pipeline.test.d.ts +11 -0
  98. package/dist/verified-autofix/__tests__/pipeline.test.d.ts.map +1 -0
  99. package/dist/verified-autofix/__tests__/pipeline.test.js +389 -0
  100. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts +11 -0
  101. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts.map +1 -0
  102. package/dist/verified-autofix/__tests__/repo-fingerprint.test.js +236 -0
  103. package/dist/verified-autofix/__tests__/workspace.test.d.ts +11 -0
  104. package/dist/verified-autofix/__tests__/workspace.test.d.ts.map +1 -0
  105. package/dist/verified-autofix/__tests__/workspace.test.js +314 -0
  106. package/dist/verified-autofix/format-validator.d.ts +101 -0
  107. package/dist/verified-autofix/format-validator.d.ts.map +1 -0
  108. package/dist/verified-autofix/format-validator.js +446 -0
  109. package/dist/verified-autofix/index.d.ts +14 -0
  110. package/dist/verified-autofix/index.d.ts.map +1 -0
  111. package/dist/verified-autofix/index.js +39 -0
  112. package/dist/verified-autofix/pipeline.d.ts +68 -0
  113. package/dist/verified-autofix/pipeline.d.ts.map +1 -0
  114. package/dist/verified-autofix/pipeline.js +330 -0
  115. package/dist/verified-autofix/repo-fingerprint.d.ts +56 -0
  116. package/dist/verified-autofix/repo-fingerprint.d.ts.map +1 -0
  117. package/dist/verified-autofix/repo-fingerprint.js +396 -0
  118. package/dist/verified-autofix/workspace.d.ts +83 -0
  119. package/dist/verified-autofix/workspace.d.ts.map +1 -0
  120. package/dist/verified-autofix/workspace.js +454 -0
  121. package/dist/verified-autofix.d.ts +182 -0
  122. package/dist/verified-autofix.d.ts.map +1 -0
  123. package/dist/verified-autofix.js +1021 -0
  124. package/dist/visualization/dependency-graph.d.ts +79 -0
  125. package/dist/visualization/dependency-graph.d.ts.map +1 -0
  126. package/dist/visualization/dependency-graph.js +399 -0
  127. package/dist/visualization/index.d.ts +5 -0
  128. package/dist/visualization/index.d.ts.map +1 -0
  129. package/dist/visualization/index.js +20 -0
  130. package/package.json +29 -0
  131. package/src/__tests__/autopilot.test.ts +196 -0
  132. package/src/__tests__/tier-config.test.ts +289 -0
  133. package/src/__tests__/utils/hash-inline.test.ts +76 -0
  134. package/src/__tests__/utils/hash.test.ts +119 -0
  135. package/src/__tests__/utils/simple.test.ts +10 -0
  136. package/src/__tests__/utils/utils-simple.test.ts +5 -0
  137. package/src/__tests__/utils/utils.test.ts +203 -0
  138. package/src/autopilot/autopilot-runner.ts +503 -0
  139. package/src/autopilot/index.ts +6 -0
  140. package/src/autopilot/types.ts +119 -0
  141. package/src/cache/index.ts +7 -0
  142. package/src/cache/redis-cache.d.ts +155 -0
  143. package/src/cache/redis-cache.d.ts.map +1 -0
  144. package/src/cache/redis-cache.ts +517 -0
  145. package/src/ci/github-actions.ts +335 -0
  146. package/src/ci/index.ts +12 -0
  147. package/src/ci/pre-commit.ts +338 -0
  148. package/src/db/usage-schema.prisma +114 -0
  149. package/src/entitlements.ts +570 -0
  150. package/src/env.d.ts +68 -0
  151. package/src/env.d.ts.map +1 -0
  152. package/src/env.ts +247 -0
  153. package/src/fix-packs/__tests__/generate-fix-packs.test.ts +317 -0
  154. package/src/fix-packs/generate-fix-packs.ts +577 -0
  155. package/src/fix-packs/index.ts +8 -0
  156. package/src/fix-packs/types.ts +206 -0
  157. package/src/index.d.ts +7 -0
  158. package/src/index.d.ts.map +1 -0
  159. package/src/index.ts +12 -0
  160. package/src/metrics/prometheus.d.ts +104 -0
  161. package/src/metrics/prometheus.d.ts.map +1 -0
  162. package/src/metrics/prometheus.ts +446 -0
  163. package/src/quota-ledger.ts +548 -0
  164. package/src/rbac/__tests__/permissions.test.ts +446 -0
  165. package/src/rbac/index.ts +46 -0
  166. package/src/rbac/permissions.ts +301 -0
  167. package/src/rbac/types.ts +298 -0
  168. package/src/tier-config.json +157 -0
  169. package/src/tier-config.ts +815 -0
  170. package/src/types.d.ts +365 -0
  171. package/src/types.d.ts.map +1 -0
  172. package/src/types.ts +441 -0
  173. package/src/utils.d.ts +36 -0
  174. package/src/utils.d.ts.map +1 -0
  175. package/src/utils.ts +140 -0
  176. package/src/verified-autofix/__tests__/format-validator.test.ts +335 -0
  177. package/src/verified-autofix/__tests__/pipeline.test.ts +419 -0
  178. package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +241 -0
  179. package/src/verified-autofix/__tests__/workspace.test.ts +373 -0
  180. package/src/verified-autofix/format-validator.ts +517 -0
  181. package/src/verified-autofix/index.ts +63 -0
  182. package/src/verified-autofix/pipeline.ts +403 -0
  183. package/src/verified-autofix/repo-fingerprint.ts +459 -0
  184. package/src/verified-autofix/workspace.ts +531 -0
  185. package/src/verified-autofix.ts +1187 -0
  186. package/src/visualization/dependency-graph.d.ts +85 -0
  187. package/src/visualization/dependency-graph.d.ts.map +1 -0
  188. package/src/visualization/dependency-graph.ts +495 -0
  189. package/src/visualization/index.ts +5 -0
@@ -0,0 +1,1021 @@
1
+ "use strict";
2
+ /**
3
+ * Verified Autofix System - PRO+ Feature
4
+ *
5
+ * Core monetization feature that provides:
6
+ * 1. Strict Build Mode prompts requiring JSON output with unified diff
7
+ * 2. Validation of strict output protocol
8
+ * 3. Temp workspace application with full verification pipeline
9
+ * 4. Auto-reprompt on failure with tight failure context
10
+ * 5. Apply patch only if verification passes
11
+ *
12
+ * PRICING: This is a PRO+ feature. Prompts alone are free.
13
+ * Paid value = prompts + strict diff protocol + verification + apply-only-if-pass
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.runVerifiedAutofix = exports.verifiedAutofix = exports.VerifiedAutofixRunner = exports.VerificationPipeline = exports.TempWorkspaceManager = exports.FIX_PACKS = void 0;
53
+ exports.validateStrictOutput = validateStrictOutput;
54
+ exports.generateBuildModePromptWithContext = generateBuildModePromptWithContext;
55
+ exports.generateBuildModePrompt = generateBuildModePrompt;
56
+ exports.generateRepromptWithFailures = generateRepromptWithFailures;
57
+ exports.estimateCost = estimateCost;
58
+ exports.listBackups = listBackups;
59
+ exports.restoreBackups = restoreBackups;
60
+ exports.cleanBackups = cleanBackups;
61
+ const fs = __importStar(require("fs"));
62
+ const path = __importStar(require("path"));
63
+ const crypto = __importStar(require("crypto"));
64
+ const child_process_1 = require("child_process");
65
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
66
+ const openai_1 = __importDefault(require("openai"));
67
+ // ============================================================================
68
+ // FIX PACK CONFIGURATIONS
69
+ // ============================================================================
70
+ exports.FIX_PACKS = {
71
+ 'route-integrity': {
72
+ type: 'route-integrity',
73
+ name: 'Route Integrity',
74
+ description: 'Fix dead links and orphan routes',
75
+ scanCommand: 'guardrail scan --truth --json',
76
+ verifyCommands: [
77
+ 'npx tsc --noEmit', // Required: TypeScript must pass
78
+ ],
79
+ maxAttempts: 3,
80
+ requiredTier: 'pro',
81
+ },
82
+ 'placeholders': {
83
+ type: 'placeholders',
84
+ name: 'Placeholder Removal',
85
+ description: 'Remove lorem ipsum, mock data, and placeholder content',
86
+ scanCommand: 'guardrail scan --json',
87
+ verifyCommands: [
88
+ 'npx tsc --noEmit', // Required: TypeScript must pass
89
+ ],
90
+ maxAttempts: 3,
91
+ requiredTier: 'pro',
92
+ },
93
+ 'type-errors': {
94
+ type: 'type-errors',
95
+ name: 'Type Error Fix',
96
+ description: 'Fix TypeScript type errors',
97
+ scanCommand: 'npx tsc --noEmit 2>&1 || true',
98
+ verifyCommands: [
99
+ 'npx tsc --noEmit',
100
+ ],
101
+ maxAttempts: 5,
102
+ requiredTier: 'pro',
103
+ },
104
+ 'build-blockers': {
105
+ type: 'build-blockers',
106
+ name: 'Build Blockers',
107
+ description: 'Fix issues preventing successful builds',
108
+ scanCommand: 'npm run build 2>&1 || true',
109
+ verifyCommands: [
110
+ 'npm run build',
111
+ ],
112
+ maxAttempts: 5,
113
+ requiredTier: 'pro',
114
+ },
115
+ 'test-failures': {
116
+ type: 'test-failures',
117
+ name: 'Test Failures',
118
+ description: 'Fix failing tests',
119
+ scanCommand: 'npm test 2>&1 || true',
120
+ verifyCommands: [
121
+ 'npm test',
122
+ ],
123
+ maxAttempts: 5,
124
+ requiredTier: 'pro',
125
+ },
126
+ };
127
+ // ============================================================================
128
+ // STRICT OUTPUT PROTOCOL
129
+ // ============================================================================
130
+ const STRICT_OUTPUT_SCHEMA = {
131
+ type: 'object',
132
+ required: ['success', 'explanation', 'diffs', 'filesModified', 'confidence'],
133
+ properties: {
134
+ success: { type: 'boolean' },
135
+ explanation: { type: 'string' },
136
+ diffs: {
137
+ type: 'array',
138
+ items: {
139
+ type: 'object',
140
+ required: ['file', 'oldStart', 'oldLines', 'newStart', 'newLines', 'content'],
141
+ properties: {
142
+ file: { type: 'string' },
143
+ oldStart: { type: 'number' },
144
+ oldLines: { type: 'number' },
145
+ newStart: { type: 'number' },
146
+ newLines: { type: 'number' },
147
+ content: { type: 'string' },
148
+ },
149
+ },
150
+ },
151
+ filesModified: { type: 'array', items: { type: 'string' } },
152
+ confidence: { type: 'number', minimum: 0, maximum: 100 },
153
+ warnings: { type: 'array', items: { type: 'string' } },
154
+ },
155
+ };
156
+ /**
157
+ * Validate strict agent output format
158
+ */
159
+ function validateStrictOutput(output) {
160
+ const errors = [];
161
+ if (!output || typeof output !== 'object') {
162
+ return { valid: false, errors: ['Output must be a JSON object'] };
163
+ }
164
+ const obj = output;
165
+ // Check required fields
166
+ for (const field of STRICT_OUTPUT_SCHEMA.required) {
167
+ if (!(field in obj)) {
168
+ errors.push(`Missing required field: ${field}`);
169
+ }
170
+ }
171
+ // Type checks
172
+ if (typeof obj['success'] !== 'boolean') {
173
+ errors.push('Field "success" must be boolean');
174
+ }
175
+ if (typeof obj['explanation'] !== 'string') {
176
+ errors.push('Field "explanation" must be string');
177
+ }
178
+ if (!Array.isArray(obj['diffs'])) {
179
+ errors.push('Field "diffs" must be array');
180
+ }
181
+ if (!Array.isArray(obj['filesModified'])) {
182
+ errors.push('Field "filesModified" must be array');
183
+ }
184
+ const confidence = obj['confidence'];
185
+ if (typeof confidence !== 'number' || confidence < 0 || confidence > 100) {
186
+ errors.push('Field "confidence" must be number 0-100');
187
+ }
188
+ // Validate each diff
189
+ const diffs = obj['diffs'];
190
+ if (Array.isArray(diffs)) {
191
+ for (let i = 0; i < diffs.length; i++) {
192
+ const diff = diffs[i];
193
+ if (!diff['file'] || typeof diff['file'] !== 'string') {
194
+ errors.push(`diffs[${i}].file must be string`);
195
+ }
196
+ if (typeof diff['oldStart'] !== 'number') {
197
+ errors.push(`diffs[${i}].oldStart must be number`);
198
+ }
199
+ if (typeof diff['content'] !== 'string') {
200
+ errors.push(`diffs[${i}].content must be string`);
201
+ }
202
+ }
203
+ }
204
+ return { valid: errors.length === 0, errors };
205
+ }
206
+ // ============================================================================
207
+ // BUILD MODE PROMPT GENERATOR
208
+ // ============================================================================
209
+ /**
210
+ * Extract affected file paths from scan output
211
+ */
212
+ function extractAffectedFiles(scanOutput) {
213
+ const files = new Set();
214
+ // Match file paths in various formats
215
+ const patterns = [
216
+ /["']([^"']+\.(tsx?|jsx?|vue|svelte))["']/g,
217
+ /(\S+\.(tsx?|jsx?|vue|svelte)):/g,
218
+ /File:\s*(\S+\.(tsx?|jsx?|vue|svelte))/g,
219
+ ];
220
+ for (const pattern of patterns) {
221
+ let match;
222
+ while ((match = pattern.exec(scanOutput)) !== null) {
223
+ const file = match[1];
224
+ if (file && !file.includes('node_modules') && !file.startsWith('http')) {
225
+ files.add(file);
226
+ }
227
+ }
228
+ }
229
+ return Array.from(files).slice(0, 5); // Limit to 5 files
230
+ }
231
+ /**
232
+ * Read file content safely
233
+ */
234
+ async function readFileContent(projectPath, filePath) {
235
+ try {
236
+ const fullPath = path.join(projectPath, filePath);
237
+ const content = await fs.promises.readFile(fullPath, 'utf8');
238
+ // Limit content size
239
+ return content.slice(0, 2000);
240
+ }
241
+ catch {
242
+ return null;
243
+ }
244
+ }
245
+ /**
246
+ * Generate strict Build Mode prompt for agent with file context
247
+ */
248
+ async function generateBuildModePromptWithContext(fixPack, scanOutput, context) {
249
+ const config = exports.FIX_PACKS[fixPack];
250
+ // Extract and read affected files
251
+ const affectedFiles = extractAffectedFiles(scanOutput);
252
+ const fileContents = [];
253
+ for (const file of affectedFiles) {
254
+ const content = await readFileContent(context.projectPath, file);
255
+ if (content) {
256
+ fileContents.push(`### ${file}\n\`\`\`typescript\n${content}\n\`\`\``);
257
+ }
258
+ }
259
+ const fileContextSection = fileContents.length > 0
260
+ ? `## AFFECTED FILES (current content)\n\n${fileContents.join('\n\n')}\n\n`
261
+ : '';
262
+ return `# STRICT BUILD MODE - ${config.name}
263
+
264
+ ## TASK
265
+ ${config.description}
266
+
267
+ ## SCAN OUTPUT (issues to fix)
268
+ \`\`\`
269
+ ${scanOutput.slice(0, 3000)}
270
+ \`\`\`
271
+
272
+ ${fileContextSection}## PROJECT CONTEXT
273
+ - Path: ${context.projectPath}
274
+ ${context.framework ? `- Framework: ${context.framework}` : ''}
275
+
276
+ ## REQUIRED OUTPUT FORMAT
277
+ You MUST respond with ONLY a valid JSON object matching this schema:
278
+
279
+ \`\`\`json
280
+ {
281
+ "success": boolean,
282
+ "explanation": "Brief explanation of changes",
283
+ "diffs": [
284
+ {
285
+ "file": "relative/path/to/file.ts",
286
+ "oldStart": 1,
287
+ "oldLines": 3,
288
+ "newStart": 1,
289
+ "newLines": 4,
290
+ "content": "@@ -1,3 +1,4 @@\\n context line\\n-old line\\n+new line\\n+added line\\n context"
291
+ }
292
+ ],
293
+ "filesModified": ["relative/path/to/file.ts"],
294
+ "confidence": 85,
295
+ "warnings": ["optional warnings"]
296
+ }
297
+ \`\`\`
298
+
299
+ ## RULES
300
+ 1. Output ONLY the JSON - no markdown, no explanation outside JSON
301
+ 2. Use unified diff format for content field matching the ACTUAL file content shown above
302
+ 3. Paths must be relative to project root
303
+ 4. Do NOT modify files outside the project
304
+ 5. Do NOT introduce new dependencies without explicit instruction
305
+ 6. Keep changes minimal and focused on the specific issue
306
+ 7. Confidence should reflect certainty that changes will fix the issue
307
+ 8. The diff content field must use actual line content from the files shown above
308
+
309
+ ## BEGIN`;
310
+ }
311
+ /**
312
+ * Generate strict Build Mode prompt for agent (sync version for compatibility)
313
+ */
314
+ function generateBuildModePrompt(fixPack, scanOutput, context) {
315
+ const config = exports.FIX_PACKS[fixPack];
316
+ return `# STRICT BUILD MODE - ${config.name}
317
+
318
+ ## TASK
319
+ ${config.description}
320
+
321
+ ## SCAN OUTPUT
322
+ \`\`\`
323
+ ${scanOutput.slice(0, 4000)}
324
+ \`\`\`
325
+
326
+ ## PROJECT CONTEXT
327
+ - Path: ${context.projectPath}
328
+ ${context.framework ? `- Framework: ${context.framework}` : ''}
329
+
330
+ ## REQUIRED OUTPUT FORMAT
331
+ You MUST respond with ONLY a valid JSON object matching this schema:
332
+
333
+ \`\`\`json
334
+ {
335
+ "success": boolean,
336
+ "explanation": "Brief explanation of changes",
337
+ "diffs": [
338
+ {
339
+ "file": "relative/path/to/file.ts",
340
+ "oldStart": 1,
341
+ "oldLines": 3,
342
+ "newStart": 1,
343
+ "newLines": 4,
344
+ "content": "@@ -1,3 +1,4 @@\\n context line\\n-old line\\n+new line\\n+added line\\n context"
345
+ }
346
+ ],
347
+ "filesModified": ["relative/path/to/file.ts"],
348
+ "confidence": 85,
349
+ "warnings": ["optional warnings"]
350
+ }
351
+ \`\`\`
352
+
353
+ ## RULES
354
+ 1. Output ONLY the JSON - no markdown, no explanation outside JSON
355
+ 2. Use unified diff format for content field
356
+ 3. Paths must be relative to project root
357
+ 4. Do NOT modify files outside the project
358
+ 5. Do NOT introduce new dependencies without explicit instruction
359
+ 6. Keep changes minimal and focused on the specific issue
360
+ 7. Confidence should reflect certainty that changes will fix the issue
361
+
362
+ ## BEGIN`;
363
+ }
364
+ /**
365
+ * Generate reprompt with failure context
366
+ */
367
+ function generateRepromptWithFailures(originalPrompt, _previousOutput, verification) {
368
+ const topBlockers = verification.blockers.slice(0, 3);
369
+ return `${originalPrompt}
370
+
371
+ ## PREVIOUS ATTEMPT FAILED
372
+
373
+ Your previous changes did not pass verification. Here are the top blockers:
374
+
375
+ ${topBlockers.map((b, i) => `${i + 1}. ${b}`).join('\n')}
376
+
377
+ ## VERIFICATION RESULTS
378
+ ${verification.checks.map(c => `- ${c.name}: ${c.passed ? '✓' : '✗'} ${c.message}`).join('\n')}
379
+
380
+ Please provide a corrected fix that addresses these specific issues.
381
+ Remember: Output ONLY valid JSON matching the required schema.`;
382
+ }
383
+ // ============================================================================
384
+ // TEMP WORKSPACE MANAGER
385
+ // ============================================================================
386
+ class TempWorkspaceManager {
387
+ baseDir;
388
+ workspaces = new Map();
389
+ constructor() {
390
+ this.baseDir = path.join(require('os').tmpdir(), 'guardrail-autofix');
391
+ }
392
+ /**
393
+ * Create isolated workspace using git worktree (preferred) or copy
394
+ */
395
+ async createWorkspace(projectPath) {
396
+ const id = crypto.randomBytes(8).toString('hex');
397
+ const workspacePath = path.join(this.baseDir, id);
398
+ await fs.promises.mkdir(workspacePath, { recursive: true });
399
+ // Try git worktree first
400
+ try {
401
+ const gitDir = path.join(projectPath, '.git');
402
+ if (fs.existsSync(gitDir)) {
403
+ (0, child_process_1.execSync)(`git worktree add "${workspacePath}" HEAD --detach`, {
404
+ cwd: projectPath,
405
+ stdio: 'pipe',
406
+ });
407
+ this.workspaces.set(id, workspacePath);
408
+ return workspacePath;
409
+ }
410
+ }
411
+ catch {
412
+ // Git worktree failed, fall back to copy
413
+ }
414
+ // Copy project (excluding node_modules, .git)
415
+ await this.copyProject(projectPath, workspacePath);
416
+ this.workspaces.set(id, workspacePath);
417
+ return workspacePath;
418
+ }
419
+ /**
420
+ * Apply diffs to workspace
421
+ */
422
+ async applyDiffs(workspacePath, diffs) {
423
+ let applied = 0;
424
+ const errors = [];
425
+ for (const diff of diffs) {
426
+ try {
427
+ const filePath = path.join(workspacePath, diff.file);
428
+ // Ensure directory exists
429
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
430
+ // Read existing content or create new
431
+ let content = '';
432
+ try {
433
+ content = await fs.promises.readFile(filePath, 'utf8');
434
+ }
435
+ catch {
436
+ // New file
437
+ }
438
+ // Apply unified diff
439
+ const newContent = this.applyUnifiedDiff(content, diff);
440
+ await fs.promises.writeFile(filePath, newContent);
441
+ applied++;
442
+ }
443
+ catch (e) {
444
+ errors.push(`Failed to apply diff to ${diff.file}: ${e.message}`);
445
+ }
446
+ }
447
+ return { applied, errors };
448
+ }
449
+ /**
450
+ * Cleanup workspace
451
+ */
452
+ async cleanup(workspacePath) {
453
+ const projectPath = this.findProjectForWorkspace(workspacePath);
454
+ // Try to remove git worktree first
455
+ if (projectPath) {
456
+ try {
457
+ (0, child_process_1.execSync)(`git worktree remove "${workspacePath}" --force`, {
458
+ cwd: projectPath,
459
+ stdio: 'pipe',
460
+ });
461
+ return;
462
+ }
463
+ catch {
464
+ // Fall through to rm
465
+ }
466
+ }
467
+ // Remove directory
468
+ try {
469
+ await fs.promises.rm(workspacePath, { recursive: true, force: true });
470
+ }
471
+ catch {
472
+ // Ignore
473
+ }
474
+ }
475
+ findProjectForWorkspace(workspacePath) {
476
+ for (const [, ws] of this.workspaces) {
477
+ if (ws === workspacePath) {
478
+ return ws;
479
+ }
480
+ }
481
+ return null;
482
+ }
483
+ async copyProject(src, dest) {
484
+ const entries = await fs.promises.readdir(src, { withFileTypes: true });
485
+ for (const entry of entries) {
486
+ const srcPath = path.join(src, entry.name);
487
+ const destPath = path.join(dest, entry.name);
488
+ // Skip node_modules, .git, dist, build
489
+ if (['node_modules', '.git', 'dist', 'build', '.next', '__pycache__'].includes(entry.name)) {
490
+ continue;
491
+ }
492
+ if (entry.isDirectory()) {
493
+ await fs.promises.mkdir(destPath, { recursive: true });
494
+ await this.copyProject(srcPath, destPath);
495
+ }
496
+ else {
497
+ await fs.promises.copyFile(srcPath, destPath);
498
+ }
499
+ }
500
+ }
501
+ applyUnifiedDiff(content, diff) {
502
+ // Parse unified diff format and apply
503
+ const lines = content.split('\n');
504
+ const diffLines = diff.content.split('\n');
505
+ // Handle simple replacement case (AI often generates simple diffs)
506
+ if (this.isSimpleReplacement(diff)) {
507
+ return this.applySimpleReplacement(content, diff);
508
+ }
509
+ // Find the hunk header and process
510
+ let resultLines = [];
511
+ let srcIdx = 0;
512
+ let diffIdx = 0;
513
+ // Copy lines before the change
514
+ while (srcIdx < diff.oldStart - 1 && srcIdx < lines.length) {
515
+ resultLines.push(lines[srcIdx] || '');
516
+ srcIdx++;
517
+ }
518
+ // Process diff lines
519
+ for (; diffIdx < diffLines.length; diffIdx++) {
520
+ const line = diffLines[diffIdx] || '';
521
+ // Skip hunk header
522
+ if (line.startsWith('@@'))
523
+ continue;
524
+ if (line.startsWith('-')) {
525
+ // Delete line - skip source line
526
+ srcIdx++;
527
+ }
528
+ else if (line.startsWith('+')) {
529
+ // Add line
530
+ resultLines.push(line.slice(1));
531
+ }
532
+ else if (line.startsWith(' ') || line === '') {
533
+ // Context line
534
+ if (srcIdx < lines.length) {
535
+ resultLines.push(lines[srcIdx] || '');
536
+ srcIdx++;
537
+ }
538
+ }
539
+ }
540
+ // Copy remaining lines
541
+ while (srcIdx < lines.length) {
542
+ resultLines.push(lines[srcIdx] || '');
543
+ srcIdx++;
544
+ }
545
+ return resultLines.join('\n');
546
+ }
547
+ /**
548
+ * Check if this is a simple line addition/replacement
549
+ */
550
+ isSimpleReplacement(diff) {
551
+ const lines = diff.content.split('\n').filter(l => !l.startsWith('@@'));
552
+ const addLines = lines.filter(l => l.startsWith('+'));
553
+ const delLines = lines.filter(l => l.startsWith('-'));
554
+ // Simple if it's just additions at the start
555
+ return addLines.length > 0 && delLines.length === 0 && diff.oldStart === 1;
556
+ }
557
+ /**
558
+ * Apply a simple replacement/insertion
559
+ */
560
+ applySimpleReplacement(content, diff) {
561
+ const lines = content.split('\n');
562
+ const diffLines = diff.content.split('\n').filter(l => !l.startsWith('@@'));
563
+ const newLines = [];
564
+ // Add new lines first
565
+ for (const line of diffLines) {
566
+ if (line.startsWith('+')) {
567
+ newLines.push(line.slice(1));
568
+ }
569
+ }
570
+ // Then add original content
571
+ newLines.push(...lines);
572
+ return newLines.join('\n');
573
+ }
574
+ }
575
+ exports.TempWorkspaceManager = TempWorkspaceManager;
576
+ // ============================================================================
577
+ // VERIFICATION PIPELINE
578
+ // ============================================================================
579
+ class VerificationPipeline {
580
+ /**
581
+ * Run verification checks on workspace
582
+ */
583
+ async verify(workspacePath, checks, onProgress) {
584
+ const startTime = Date.now();
585
+ const results = [];
586
+ const blockers = [];
587
+ for (const check of checks) {
588
+ const checkStart = Date.now();
589
+ onProgress?.(check, 'running');
590
+ try {
591
+ (0, child_process_1.execSync)(check, {
592
+ cwd: workspacePath,
593
+ stdio: 'pipe',
594
+ timeout: 120000, // 2 min timeout per check
595
+ });
596
+ results.push({
597
+ name: check,
598
+ passed: true,
599
+ message: 'Passed',
600
+ duration: Date.now() - checkStart,
601
+ });
602
+ onProgress?.(check, 'passed');
603
+ }
604
+ catch (e) {
605
+ const error = e;
606
+ const output = (error.stderr?.toString() || error.stdout?.toString() || error.message).slice(0, 500);
607
+ results.push({
608
+ name: check,
609
+ passed: false,
610
+ message: output,
611
+ duration: Date.now() - checkStart,
612
+ });
613
+ blockers.push(`${check}: ${output.split('\n')[0]}`);
614
+ onProgress?.(check, 'failed');
615
+ }
616
+ }
617
+ return {
618
+ passed: results.every(r => r.passed),
619
+ checks: results,
620
+ blockers,
621
+ duration: Date.now() - startTime,
622
+ };
623
+ }
624
+ /**
625
+ * Run additional security checks
626
+ */
627
+ async securityChecks(workspacePath) {
628
+ const issues = [];
629
+ // Check for secrets
630
+ try {
631
+ const secretPatterns = [
632
+ /AKIA[0-9A-Z]{16}/g, // AWS
633
+ /sk-[a-zA-Z0-9]{48}/g, // OpenAI
634
+ /ghp_[a-zA-Z0-9]{36}/g, // GitHub
635
+ /xoxb-[0-9]{11}-[0-9]{11}-[a-zA-Z0-9]{24}/g, // Slack
636
+ ];
637
+ const files = await this.findFiles(workspacePath, ['*.ts', '*.js', '*.json']);
638
+ for (const file of files.slice(0, 100)) {
639
+ const content = await fs.promises.readFile(file, 'utf8');
640
+ for (const pattern of secretPatterns) {
641
+ if (pattern.test(content)) {
642
+ issues.push(`Potential secret in ${path.relative(workspacePath, file)}`);
643
+ break;
644
+ }
645
+ }
646
+ }
647
+ }
648
+ catch {
649
+ // Ignore
650
+ }
651
+ return { passed: issues.length === 0, issues };
652
+ }
653
+ async findFiles(dir, patterns) {
654
+ const files = [];
655
+ const walk = async (d) => {
656
+ try {
657
+ const entries = await fs.promises.readdir(d, { withFileTypes: true });
658
+ for (const entry of entries) {
659
+ const fullPath = path.join(d, entry.name);
660
+ if (entry.isDirectory() && !['node_modules', '.git'].includes(entry.name)) {
661
+ await walk(fullPath);
662
+ }
663
+ else if (entry.isFile()) {
664
+ for (const pattern of patterns) {
665
+ const regex = new RegExp(pattern.replace('*', '.*'));
666
+ if (regex.test(entry.name)) {
667
+ files.push(fullPath);
668
+ break;
669
+ }
670
+ }
671
+ }
672
+ }
673
+ }
674
+ catch {
675
+ // Ignore
676
+ }
677
+ };
678
+ await walk(dir);
679
+ return files;
680
+ }
681
+ }
682
+ exports.VerificationPipeline = VerificationPipeline;
683
+ // ============================================================================
684
+ // MAIN AUTOFIX RUNNER
685
+ // ============================================================================
686
+ class VerifiedAutofixRunner {
687
+ workspaceManager;
688
+ verificationPipeline;
689
+ constructor() {
690
+ this.workspaceManager = new TempWorkspaceManager();
691
+ this.verificationPipeline = new VerificationPipeline();
692
+ }
693
+ /**
694
+ * Run verified autofix process
695
+ */
696
+ async run(options) {
697
+ const startTime = Date.now();
698
+ const config = exports.FIX_PACKS[options.fixPack];
699
+ const maxAttempts = options.maxAttempts || config.maxAttempts;
700
+ const result = {
701
+ success: false,
702
+ fixPack: options.fixPack,
703
+ attempts: 0,
704
+ maxAttempts,
705
+ duration: 0,
706
+ verification: null,
707
+ appliedDiffs: 0,
708
+ filesModified: [],
709
+ errors: [],
710
+ generatedDiffs: [],
711
+ aiExplanation: '',
712
+ metrics: {
713
+ promptTokens: 0,
714
+ completionTokens: 0,
715
+ repromptCount: 0,
716
+ verificationTime: 0,
717
+ },
718
+ };
719
+ let workspacePath = null;
720
+ try {
721
+ options.onProgress?.('scan', 'Running initial scan...');
722
+ // Step 1: Run initial scan
723
+ let scanOutput;
724
+ try {
725
+ scanOutput = (0, child_process_1.execSync)(config.scanCommand, {
726
+ cwd: options.projectPath,
727
+ encoding: 'utf8',
728
+ timeout: 60000,
729
+ });
730
+ }
731
+ catch (e) {
732
+ scanOutput = e.stdout || '';
733
+ }
734
+ // Step 2: Generate initial prompt with file context
735
+ options.onProgress?.('context', 'Reading affected files...');
736
+ const prompt = await generateBuildModePromptWithContext(options.fixPack, scanOutput, {
737
+ projectPath: options.projectPath,
738
+ });
739
+ let currentPrompt = prompt;
740
+ let lastOutput = null;
741
+ // Step 3: Attempt loop
742
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
743
+ result.attempts = attempt;
744
+ options.onProgress?.('agent', `Attempt ${attempt}/${maxAttempts}...`);
745
+ // Call agent (mock for now - would integrate with actual agent)
746
+ const agentResponse = await this.callAgent(currentPrompt);
747
+ // Validate output
748
+ const validation = validateStrictOutput(agentResponse);
749
+ if (!validation.valid) {
750
+ result.errors.push(`Attempt ${attempt}: Invalid output format - ${validation.errors.join(', ')}`);
751
+ continue;
752
+ }
753
+ lastOutput = agentResponse;
754
+ // Store AI response for display
755
+ result.generatedDiffs = lastOutput.diffs;
756
+ result.aiExplanation = lastOutput.explanation;
757
+ result.filesModified = lastOutput.filesModified;
758
+ if (!lastOutput.success || lastOutput.diffs.length === 0) {
759
+ result.errors.push(`Attempt ${attempt}: Agent reported no fixes available`);
760
+ continue;
761
+ }
762
+ // For dry-run, show diffs without full verification
763
+ if (options.dryRun) {
764
+ options.onProgress?.('preview', `Generated ${lastOutput.diffs.length} diff(s) for ${lastOutput.filesModified.length} file(s)`);
765
+ result.success = true;
766
+ result.appliedDiffs = lastOutput.diffs.length;
767
+ break;
768
+ }
769
+ // Step 4: Create temp workspace
770
+ options.onProgress?.('workspace', 'Creating temp workspace...');
771
+ workspacePath = await this.workspaceManager.createWorkspace(options.projectPath);
772
+ // Step 5: Apply diffs
773
+ options.onProgress?.('apply', 'Applying changes...');
774
+ const applyResult = await this.workspaceManager.applyDiffs(workspacePath, lastOutput.diffs);
775
+ result.appliedDiffs = applyResult.applied;
776
+ if (applyResult.errors.length > 0) {
777
+ result.errors.push(...applyResult.errors);
778
+ }
779
+ // Step 6: Run verification
780
+ options.onProgress?.('verify', 'Running verification...');
781
+ const verification = await this.verificationPipeline.verify(workspacePath, config.verifyCommands, (check, status) => options.onProgress?.('verify', `${check}: ${status}`));
782
+ result.verification = verification;
783
+ result.metrics.verificationTime = verification.duration;
784
+ if (verification.passed) {
785
+ // Step 7: Security check
786
+ const security = await this.verificationPipeline.securityChecks(workspacePath);
787
+ if (!security.passed) {
788
+ result.errors.push(...security.issues);
789
+ continue;
790
+ }
791
+ // Step 8: Apply to real workspace
792
+ options.onProgress?.('apply', 'Applying to project...');
793
+ await this.applyToProject(options.projectPath, lastOutput.diffs);
794
+ result.success = true;
795
+ break;
796
+ }
797
+ else {
798
+ // Generate reprompt with failure context
799
+ result.metrics.repromptCount++;
800
+ currentPrompt = generateRepromptWithFailures(prompt, lastOutput, verification);
801
+ // Cleanup workspace for next attempt
802
+ await this.workspaceManager.cleanup(workspacePath);
803
+ workspacePath = null;
804
+ }
805
+ }
806
+ }
807
+ finally {
808
+ // Cleanup
809
+ if (workspacePath) {
810
+ await this.workspaceManager.cleanup(workspacePath);
811
+ }
812
+ result.duration = Date.now() - startTime;
813
+ }
814
+ return result;
815
+ }
816
+ /**
817
+ * Call AI agent using OpenAI or Anthropic API
818
+ * Prefers OpenAI if OPENAI_API_KEY is set, otherwise falls back to Anthropic
819
+ */
820
+ async callAgent(prompt) {
821
+ const openaiKey = process.env['OPENAI_API_KEY'];
822
+ const anthropicKey = process.env['ANTHROPIC_API_KEY'];
823
+ if (!openaiKey && !anthropicKey) {
824
+ console.warn('No AI API key set - set OPENAI_API_KEY or ANTHROPIC_API_KEY');
825
+ return {
826
+ success: false,
827
+ explanation: 'No AI API key configured',
828
+ diffs: [],
829
+ filesModified: [],
830
+ confidence: 0,
831
+ warnings: ['Set OPENAI_API_KEY or ANTHROPIC_API_KEY to enable AI-powered autofix'],
832
+ };
833
+ }
834
+ try {
835
+ let text;
836
+ if (openaiKey) {
837
+ // Use OpenAI
838
+ console.log('Using OpenAI API...');
839
+ const client = new openai_1.default({ apiKey: openaiKey });
840
+ const response = await client.chat.completions.create({
841
+ model: process.env['OPENAI_MODEL'] || 'gpt-4o',
842
+ max_tokens: 8192,
843
+ temperature: 0.2,
844
+ messages: [
845
+ {
846
+ role: 'system',
847
+ content: 'You are a code fix assistant. Always respond with valid JSON matching the exact schema requested. Do not include any text outside the JSON.',
848
+ },
849
+ {
850
+ role: 'user',
851
+ content: prompt,
852
+ },
853
+ ],
854
+ });
855
+ text = response.choices[0]?.message?.content?.trim() || '';
856
+ }
857
+ else {
858
+ // Use Anthropic
859
+ console.log('Using Anthropic API...');
860
+ const client = new sdk_1.default({ apiKey: anthropicKey });
861
+ const response = await client.messages.create({
862
+ model: process.env['ANTHROPIC_MODEL'] || 'claude-sonnet-4-20250514',
863
+ max_tokens: 8192,
864
+ temperature: 0.2,
865
+ messages: [
866
+ {
867
+ role: 'user',
868
+ content: prompt,
869
+ },
870
+ ],
871
+ });
872
+ const content = response.content[0];
873
+ if (!content || content.type !== 'text') {
874
+ throw new Error('Unexpected response type from Claude');
875
+ }
876
+ text = content.text.trim();
877
+ }
878
+ // Parse JSON from response (may be wrapped in markdown code blocks)
879
+ let jsonStr = text;
880
+ const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
881
+ if (jsonMatch && jsonMatch[1]) {
882
+ jsonStr = jsonMatch[1].trim();
883
+ }
884
+ try {
885
+ const parsed = JSON.parse(jsonStr);
886
+ return parsed;
887
+ }
888
+ catch (parseError) {
889
+ // If JSON parsing fails, return structured error
890
+ return {
891
+ success: false,
892
+ explanation: 'Failed to parse agent response as JSON',
893
+ diffs: [],
894
+ filesModified: [],
895
+ confidence: 0,
896
+ warnings: [`Parse error: ${parseError.message}`, `Raw response: ${text.slice(0, 500)}`],
897
+ };
898
+ }
899
+ }
900
+ catch (error) {
901
+ const errorMessage = error instanceof Error ? error.message : String(error);
902
+ return {
903
+ success: false,
904
+ explanation: `API call failed: ${errorMessage}`,
905
+ diffs: [],
906
+ filesModified: [],
907
+ confidence: 0,
908
+ warnings: [errorMessage],
909
+ };
910
+ }
911
+ }
912
+ /**
913
+ * Apply diffs to actual project
914
+ */
915
+ async applyToProject(projectPath, diffs) {
916
+ for (const diff of diffs) {
917
+ const filePath = path.join(projectPath, diff.file);
918
+ // Backup original
919
+ const backupPath = `${filePath}.guardrail-backup`;
920
+ try {
921
+ await fs.promises.copyFile(filePath, backupPath);
922
+ }
923
+ catch {
924
+ // New file, no backup needed
925
+ }
926
+ // Apply diff
927
+ const manager = new TempWorkspaceManager();
928
+ await manager.applyDiffs(projectPath, [diff]);
929
+ }
930
+ }
931
+ }
932
+ exports.VerifiedAutofixRunner = VerifiedAutofixRunner;
933
+ // ============================================================================
934
+ // COST ESTIMATION
935
+ // ============================================================================
936
+ const MODEL_COSTS = {
937
+ 'gpt-4o': { input: 0.005, output: 0.015 },
938
+ 'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
939
+ 'gpt-4-turbo': { input: 0.01, output: 0.03 },
940
+ 'claude-sonnet-4-20250514': { input: 0.003, output: 0.015 },
941
+ 'claude-3-haiku-20240307': { input: 0.00025, output: 0.00125 },
942
+ };
943
+ function estimateCost(promptLength, model) {
944
+ const selectedModel = model || process.env['OPENAI_MODEL'] || process.env['ANTHROPIC_MODEL'] || 'gpt-4o';
945
+ const defaultCosts = { input: 0.005, output: 0.015 };
946
+ const costs = MODEL_COSTS[selectedModel] ?? defaultCosts;
947
+ // Rough estimate: 4 chars per token, expect 2x output
948
+ const inputTokens = Math.ceil(promptLength / 4);
949
+ const outputTokens = Math.ceil(inputTokens * 0.5);
950
+ const inputCost = (inputTokens / 1000) * costs.input;
951
+ const outputCost = (outputTokens / 1000) * costs.output;
952
+ return {
953
+ model: selectedModel,
954
+ estimatedTokens: inputTokens + outputTokens,
955
+ estimatedCost: inputCost + outputCost,
956
+ currency: 'USD',
957
+ };
958
+ }
959
+ // ============================================================================
960
+ // BACKUP & RESTORE
961
+ // ============================================================================
962
+ async function listBackups(projectPath) {
963
+ const backups = [];
964
+ async function scan(dir) {
965
+ try {
966
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
967
+ for (const entry of entries) {
968
+ const fullPath = path.join(dir, entry.name);
969
+ if (entry.isDirectory() && !entry.name.includes('node_modules')) {
970
+ await scan(fullPath);
971
+ }
972
+ else if (entry.name.endsWith('.guardrail-backup')) {
973
+ backups.push(fullPath.replace(projectPath + path.sep, ''));
974
+ }
975
+ }
976
+ }
977
+ catch {
978
+ // Skip inaccessible directories
979
+ }
980
+ }
981
+ await scan(projectPath);
982
+ return backups;
983
+ }
984
+ async function restoreBackups(projectPath) {
985
+ const backups = await listBackups(projectPath);
986
+ const restored = [];
987
+ const errors = [];
988
+ for (const backup of backups) {
989
+ const backupPath = path.join(projectPath, backup);
990
+ const originalPath = backupPath.replace('.guardrail-backup', '');
991
+ try {
992
+ await fs.promises.copyFile(backupPath, originalPath);
993
+ await fs.promises.unlink(backupPath);
994
+ restored.push(originalPath.replace(projectPath + path.sep, ''));
995
+ }
996
+ catch (e) {
997
+ errors.push(`Failed to restore ${backup}: ${e.message}`);
998
+ }
999
+ }
1000
+ return { restored, errors };
1001
+ }
1002
+ async function cleanBackups(projectPath) {
1003
+ const backups = await listBackups(projectPath);
1004
+ let cleaned = 0;
1005
+ for (const backup of backups) {
1006
+ try {
1007
+ await fs.promises.unlink(path.join(projectPath, backup));
1008
+ cleaned++;
1009
+ }
1010
+ catch {
1011
+ // Skip errors
1012
+ }
1013
+ }
1014
+ return cleaned;
1015
+ }
1016
+ // ============================================================================
1017
+ // EXPORTS
1018
+ // ============================================================================
1019
+ exports.verifiedAutofix = new VerifiedAutofixRunner();
1020
+ const runVerifiedAutofix = (options) => exports.verifiedAutofix.run(options);
1021
+ exports.runVerifiedAutofix = runVerifiedAutofix;