guardrail-core 1.0.0 → 2.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 (74) hide show
  1. package/dist/__tests__/autopilot-enterprise.test.d.ts +7 -0
  2. package/dist/__tests__/autopilot-enterprise.test.d.ts.map +1 -0
  3. package/dist/__tests__/autopilot-enterprise.test.js +334 -0
  4. package/dist/autopilot/autopilot-runner.d.ts +9 -0
  5. package/dist/autopilot/autopilot-runner.d.ts.map +1 -1
  6. package/dist/autopilot/autopilot-runner.js +182 -1
  7. package/dist/autopilot/types.d.ts +18 -2
  8. package/dist/autopilot/types.d.ts.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/smells/index.d.ts +59 -0
  13. package/dist/smells/index.d.ts.map +1 -0
  14. package/dist/smells/index.js +251 -0
  15. package/package.json +19 -2
  16. package/src/__tests__/autopilot.test.ts +0 -196
  17. package/src/__tests__/tier-config.test.ts +0 -289
  18. package/src/__tests__/utils/hash-inline.test.ts +0 -76
  19. package/src/__tests__/utils/hash.test.ts +0 -119
  20. package/src/__tests__/utils/simple.test.ts +0 -10
  21. package/src/__tests__/utils/utils-simple.test.ts +0 -5
  22. package/src/__tests__/utils/utils.test.ts +0 -203
  23. package/src/autopilot/autopilot-runner.ts +0 -503
  24. package/src/autopilot/index.ts +0 -6
  25. package/src/autopilot/types.ts +0 -119
  26. package/src/cache/index.ts +0 -7
  27. package/src/cache/redis-cache.d.ts +0 -155
  28. package/src/cache/redis-cache.d.ts.map +0 -1
  29. package/src/cache/redis-cache.ts +0 -517
  30. package/src/ci/github-actions.ts +0 -335
  31. package/src/ci/index.ts +0 -12
  32. package/src/ci/pre-commit.ts +0 -338
  33. package/src/db/usage-schema.prisma +0 -114
  34. package/src/entitlements.ts +0 -570
  35. package/src/env.d.ts +0 -68
  36. package/src/env.d.ts.map +0 -1
  37. package/src/env.ts +0 -247
  38. package/src/fix-packs/__tests__/generate-fix-packs.test.ts +0 -317
  39. package/src/fix-packs/generate-fix-packs.ts +0 -577
  40. package/src/fix-packs/index.ts +0 -8
  41. package/src/fix-packs/types.ts +0 -206
  42. package/src/index.d.ts +0 -7
  43. package/src/index.d.ts.map +0 -1
  44. package/src/index.ts +0 -12
  45. package/src/metrics/prometheus.d.ts +0 -104
  46. package/src/metrics/prometheus.d.ts.map +0 -1
  47. package/src/metrics/prometheus.ts +0 -446
  48. package/src/quota-ledger.ts +0 -548
  49. package/src/rbac/__tests__/permissions.test.ts +0 -446
  50. package/src/rbac/index.ts +0 -46
  51. package/src/rbac/permissions.ts +0 -301
  52. package/src/rbac/types.ts +0 -298
  53. package/src/tier-config.json +0 -157
  54. package/src/tier-config.ts +0 -815
  55. package/src/types.d.ts +0 -365
  56. package/src/types.d.ts.map +0 -1
  57. package/src/types.ts +0 -441
  58. package/src/utils.d.ts +0 -36
  59. package/src/utils.d.ts.map +0 -1
  60. package/src/utils.ts +0 -140
  61. package/src/verified-autofix/__tests__/format-validator.test.ts +0 -335
  62. package/src/verified-autofix/__tests__/pipeline.test.ts +0 -419
  63. package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +0 -241
  64. package/src/verified-autofix/__tests__/workspace.test.ts +0 -373
  65. package/src/verified-autofix/format-validator.ts +0 -517
  66. package/src/verified-autofix/index.ts +0 -63
  67. package/src/verified-autofix/pipeline.ts +0 -403
  68. package/src/verified-autofix/repo-fingerprint.ts +0 -459
  69. package/src/verified-autofix/workspace.ts +0 -531
  70. package/src/verified-autofix.ts +0 -1187
  71. package/src/visualization/dependency-graph.d.ts +0 -85
  72. package/src/visualization/dependency-graph.d.ts.map +0 -1
  73. package/src/visualization/dependency-graph.ts +0 -495
  74. package/src/visualization/index.ts +0 -5
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Autopilot Enterprise Workflow Tests
3
+ *
4
+ * Tests for git integration, partial apply, rollback, and human-in-loop features.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=autopilot-enterprise.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-enterprise.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/autopilot-enterprise.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ /**
3
+ * Autopilot Enterprise Workflow Tests
4
+ *
5
+ * Tests for git integration, partial apply, rollback, and human-in-loop features.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ const child_process_1 = require("child_process");
45
+ const autopilot_runner_1 = require("../autopilot/autopilot-runner");
46
+ describe('Autopilot Enterprise Workflow', () => {
47
+ let testDir;
48
+ let runner;
49
+ beforeEach(() => {
50
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'autopilot-test-'));
51
+ runner = new autopilot_runner_1.AutopilotRunner();
52
+ process.env['GUARDRAIL_SKIP_ENTITLEMENTS'] = '1';
53
+ });
54
+ afterEach(() => {
55
+ if (fs.existsSync(testDir)) {
56
+ fs.rmSync(testDir, { recursive: true, force: true });
57
+ }
58
+ delete process.env['GUARDRAIL_SKIP_ENTITLEMENTS'];
59
+ });
60
+ describe('Git Integration', () => {
61
+ it('should create branch with format guardrail/autopilot-<runId>', async () => {
62
+ (0, child_process_1.execSync)('git init', { cwd: testDir, stdio: 'pipe' });
63
+ (0, child_process_1.execSync)('git config user.email "test@test.com"', { cwd: testDir, stdio: 'pipe' });
64
+ (0, child_process_1.execSync)('git config user.name "Test User"', { cwd: testDir, stdio: 'pipe' });
65
+ const testFile = path.join(testDir, 'test.ts');
66
+ fs.writeFileSync(testFile, 'console.log("test");');
67
+ (0, child_process_1.execSync)('git add .', { cwd: testDir, stdio: 'pipe' });
68
+ (0, child_process_1.execSync)('git commit -m "initial"', { cwd: testDir, stdio: 'pipe' });
69
+ const options = {
70
+ projectPath: testDir,
71
+ mode: 'apply',
72
+ profile: 'quick',
73
+ maxFixes: 5,
74
+ verify: false,
75
+ dryRun: false,
76
+ };
77
+ const result = await runner.run(options);
78
+ expect(result.gitBranch).toBeDefined();
79
+ expect(result.gitBranch).toMatch(/^guardrail\/autopilot-[a-f0-9]+$/);
80
+ const branches = (0, child_process_1.execSync)('git branch', { cwd: testDir, encoding: 'utf8' });
81
+ expect(branches).toContain(result.gitBranch);
82
+ });
83
+ it('should commit changes with summary and runId', async () => {
84
+ (0, child_process_1.execSync)('git init', { cwd: testDir, stdio: 'pipe' });
85
+ (0, child_process_1.execSync)('git config user.email "test@test.com"', { cwd: testDir, stdio: 'pipe' });
86
+ (0, child_process_1.execSync)('git config user.name "Test User"', { cwd: testDir, stdio: 'pipe' });
87
+ const testFile = path.join(testDir, 'test.ts');
88
+ fs.writeFileSync(testFile, 'console.log("test");');
89
+ (0, child_process_1.execSync)('git add .', { cwd: testDir, stdio: 'pipe' });
90
+ (0, child_process_1.execSync)('git commit -m "initial"', { cwd: testDir, stdio: 'pipe' });
91
+ const options = {
92
+ projectPath: testDir,
93
+ mode: 'apply',
94
+ profile: 'quick',
95
+ maxFixes: 5,
96
+ verify: false,
97
+ dryRun: false,
98
+ };
99
+ const result = await runner.run(options);
100
+ expect(result.gitCommit).toBeDefined();
101
+ expect(result.runId).toBeDefined();
102
+ const commitMsg = (0, child_process_1.execSync)('git log -1 --pretty=%B', { cwd: testDir, encoding: 'utf8' });
103
+ expect(commitMsg).toContain('Autopilot fixes applied');
104
+ expect(commitMsg).toContain(result.runId);
105
+ });
106
+ it('should not create branch in dry-run mode', async () => {
107
+ (0, child_process_1.execSync)('git init', { cwd: testDir, stdio: 'pipe' });
108
+ (0, child_process_1.execSync)('git config user.email "test@test.com"', { cwd: testDir, stdio: 'pipe' });
109
+ (0, child_process_1.execSync)('git config user.name "Test User"', { cwd: testDir, stdio: 'pipe' });
110
+ const testFile = path.join(testDir, 'test.ts');
111
+ fs.writeFileSync(testFile, 'console.log("test");');
112
+ (0, child_process_1.execSync)('git add .', { cwd: testDir, stdio: 'pipe' });
113
+ (0, child_process_1.execSync)('git commit -m "initial"', { cwd: testDir, stdio: 'pipe' });
114
+ const options = {
115
+ projectPath: testDir,
116
+ mode: 'apply',
117
+ profile: 'quick',
118
+ maxFixes: 5,
119
+ verify: false,
120
+ dryRun: true,
121
+ };
122
+ const result = await runner.run(options);
123
+ expect(result.gitBranch).toBeUndefined();
124
+ expect(result.gitCommit).toBeUndefined();
125
+ });
126
+ });
127
+ describe('Partial Apply', () => {
128
+ it('should apply only specified packs with --pack flag', async () => {
129
+ const testFile = path.join(testDir, 'test.ts');
130
+ fs.writeFileSync(testFile, 'console.log("test");\n// TODO: fix this');
131
+ const planOptions = {
132
+ projectPath: testDir,
133
+ mode: 'plan',
134
+ profile: 'quick',
135
+ maxFixes: 10,
136
+ };
137
+ const planResult = await runner.run(planOptions);
138
+ expect(planResult.mode).toBe('plan');
139
+ if (planResult.mode === 'plan' && planResult.packs.length > 0) {
140
+ const firstPackId = planResult.packs[0].id;
141
+ const applyOptions = {
142
+ projectPath: testDir,
143
+ mode: 'apply',
144
+ profile: 'quick',
145
+ maxFixes: 10,
146
+ verify: false,
147
+ packIds: [firstPackId],
148
+ };
149
+ const result = await runner.run(applyOptions);
150
+ expect(result.packsAttempted).toBe(1);
151
+ const appliedPackIds = [...new Set(result.appliedFixes.map(f => f.packId))];
152
+ expect(appliedPackIds).toContain(firstPackId);
153
+ }
154
+ });
155
+ it('should throw error if specified pack IDs do not exist', async () => {
156
+ const testFile = path.join(testDir, 'test.ts');
157
+ fs.writeFileSync(testFile, 'console.log("test");');
158
+ const options = {
159
+ projectPath: testDir,
160
+ mode: 'apply',
161
+ profile: 'quick',
162
+ maxFixes: 5,
163
+ verify: false,
164
+ packIds: ['non-existent-pack-id'],
165
+ };
166
+ await expect(runner.run(options)).rejects.toThrow('No packs found matching IDs');
167
+ });
168
+ it('should support multiple --pack flags', async () => {
169
+ const testFile = path.join(testDir, 'test.ts');
170
+ fs.writeFileSync(testFile, 'console.log("test");\nconsole.warn("warning");');
171
+ const planOptions = {
172
+ projectPath: testDir,
173
+ mode: 'plan',
174
+ profile: 'quick',
175
+ maxFixes: 10,
176
+ };
177
+ const planResult = await runner.run(planOptions);
178
+ if (planResult.mode === 'plan' && planResult.packs.length >= 2) {
179
+ const packIds = planResult.packs.slice(0, 2).map(p => p.id);
180
+ const applyOptions = {
181
+ projectPath: testDir,
182
+ mode: 'apply',
183
+ profile: 'quick',
184
+ maxFixes: 10,
185
+ verify: false,
186
+ packIds,
187
+ };
188
+ const result = await runner.run(applyOptions);
189
+ expect(result.packsAttempted).toBeLessThanOrEqual(2);
190
+ }
191
+ });
192
+ });
193
+ describe('Rollback', () => {
194
+ it('should rollback using git reset when branch exists', async () => {
195
+ (0, child_process_1.execSync)('git init', { cwd: testDir, stdio: 'pipe' });
196
+ (0, child_process_1.execSync)('git config user.email "test@test.com"', { cwd: testDir, stdio: 'pipe' });
197
+ (0, child_process_1.execSync)('git config user.name "Test User"', { cwd: testDir, stdio: 'pipe' });
198
+ const testFile = path.join(testDir, 'test.ts');
199
+ fs.writeFileSync(testFile, 'console.log("test");');
200
+ (0, child_process_1.execSync)('git add .', { cwd: testDir, stdio: 'pipe' });
201
+ (0, child_process_1.execSync)('git commit -m "initial"', { cwd: testDir, stdio: 'pipe' });
202
+ const applyOptions = {
203
+ projectPath: testDir,
204
+ mode: 'apply',
205
+ profile: 'quick',
206
+ maxFixes: 5,
207
+ verify: false,
208
+ dryRun: false,
209
+ };
210
+ const applyResult = await runner.run(applyOptions);
211
+ const runId = applyResult.runId;
212
+ const rollbackOptions = {
213
+ projectPath: testDir,
214
+ mode: 'rollback',
215
+ runId,
216
+ };
217
+ const rollbackResult = await runner.run(rollbackOptions);
218
+ expect(rollbackResult.mode).toBe('rollback');
219
+ expect(rollbackResult.success).toBe(true);
220
+ expect(rollbackResult.method).toBe('git-reset');
221
+ expect(rollbackResult.message).toContain('Successfully rolled back');
222
+ });
223
+ it('should rollback using backup restore when git not available', async () => {
224
+ const testFile = path.join(testDir, 'test.ts');
225
+ fs.writeFileSync(testFile, 'console.log("test");');
226
+ const applyOptions = {
227
+ projectPath: testDir,
228
+ mode: 'apply',
229
+ profile: 'quick',
230
+ maxFixes: 5,
231
+ verify: false,
232
+ dryRun: false,
233
+ };
234
+ const applyResult = await runner.run(applyOptions);
235
+ const runId = applyResult.runId;
236
+ const rollbackOptions = {
237
+ projectPath: testDir,
238
+ mode: 'rollback',
239
+ runId,
240
+ };
241
+ const rollbackResult = await runner.run(rollbackOptions);
242
+ expect(rollbackResult.mode).toBe('rollback');
243
+ expect(rollbackResult.method).toBe('backup-restore');
244
+ });
245
+ it('should require runId for rollback', async () => {
246
+ const options = {
247
+ projectPath: testDir,
248
+ mode: 'rollback',
249
+ };
250
+ await expect(runner.run(options)).rejects.toThrow('runId is required for rollback');
251
+ });
252
+ it('should fail rollback when runId not found', async () => {
253
+ const options = {
254
+ projectPath: testDir,
255
+ mode: 'rollback',
256
+ runId: 'non-existent-run-id',
257
+ };
258
+ const result = await runner.run(options);
259
+ expect(result.success).toBe(false);
260
+ expect(result.message).toContain('Rollback failed');
261
+ });
262
+ });
263
+ describe('Human-in-the-Loop', () => {
264
+ it('should skip high-risk packs without --force in non-interactive mode', async () => {
265
+ const testFile = path.join(testDir, 'test.ts');
266
+ fs.writeFileSync(testFile, 'console.log("test");');
267
+ const options = {
268
+ projectPath: testDir,
269
+ mode: 'apply',
270
+ profile: 'quick',
271
+ maxFixes: 5,
272
+ verify: false,
273
+ force: false,
274
+ interactive: false,
275
+ };
276
+ const result = await runner.run(options);
277
+ const highRiskErrors = result.errors.filter(e => e.includes('high risk'));
278
+ if (highRiskErrors.length > 0) {
279
+ expect(result.packsFailed).toBeGreaterThan(0);
280
+ }
281
+ });
282
+ it('should apply high-risk packs with --force flag', async () => {
283
+ const testFile = path.join(testDir, 'test.ts');
284
+ fs.writeFileSync(testFile, 'console.log("test");');
285
+ const options = {
286
+ projectPath: testDir,
287
+ mode: 'apply',
288
+ profile: 'quick',
289
+ maxFixes: 5,
290
+ verify: false,
291
+ force: true,
292
+ };
293
+ const result = await runner.run(options);
294
+ const highRiskSkipped = result.errors.filter(e => e.includes('high risk, user declined'));
295
+ expect(highRiskSkipped.length).toBe(0);
296
+ });
297
+ it('should prompt in interactive mode for high-risk packs', async () => {
298
+ const testFile = path.join(testDir, 'test.ts');
299
+ fs.writeFileSync(testFile, 'console.log("test");');
300
+ const options = {
301
+ projectPath: testDir,
302
+ mode: 'apply',
303
+ profile: 'quick',
304
+ maxFixes: 5,
305
+ verify: false,
306
+ interactive: true,
307
+ };
308
+ const result = await runner.run(options);
309
+ expect(result).toBeDefined();
310
+ });
311
+ });
312
+ describe('Branch Naming', () => {
313
+ it('should use consistent branch naming format', async () => {
314
+ (0, child_process_1.execSync)('git init', { cwd: testDir, stdio: 'pipe' });
315
+ (0, child_process_1.execSync)('git config user.email "test@test.com"', { cwd: testDir, stdio: 'pipe' });
316
+ (0, child_process_1.execSync)('git config user.name "Test User"', { cwd: testDir, stdio: 'pipe' });
317
+ const testFile = path.join(testDir, 'test.ts');
318
+ fs.writeFileSync(testFile, 'console.log("test");');
319
+ (0, child_process_1.execSync)('git add .', { cwd: testDir, stdio: 'pipe' });
320
+ (0, child_process_1.execSync)('git commit -m "initial"', { cwd: testDir, stdio: 'pipe' });
321
+ const runId = 'abc123def456';
322
+ const options = {
323
+ projectPath: testDir,
324
+ mode: 'apply',
325
+ profile: 'quick',
326
+ maxFixes: 5,
327
+ verify: false,
328
+ runId,
329
+ };
330
+ const result = await runner.run(options);
331
+ expect(result.gitBranch).toBe(`guardrail/autopilot-${runId}`);
332
+ });
333
+ });
334
+ });
@@ -12,10 +12,12 @@
12
12
  import { AutopilotOptions, AutopilotResult, AutopilotFinding, AutopilotFixPack } from './types';
13
13
  export declare class AutopilotRunner {
14
14
  private tempDir;
15
+ private backupDir;
15
16
  constructor();
16
17
  run(options: AutopilotOptions): Promise<AutopilotResult>;
17
18
  private runPlan;
18
19
  private runApply;
20
+ private runRollback;
19
21
  private runScan;
20
22
  groupIntoFixPacks(findings: AutopilotFinding[], maxFixes?: number): AutopilotFixPack[];
21
23
  private getCategoryName;
@@ -27,6 +29,13 @@ export declare class AutopilotRunner {
27
29
  private applyToProject;
28
30
  private cleanupWorkspace;
29
31
  private findSourceFiles;
32
+ private isGitRepository;
33
+ private createGitBranch;
34
+ private gitBranchExists;
35
+ private commitChanges;
36
+ private createBackup;
37
+ private restoreBackup;
38
+ private confirmHighRiskPack;
30
39
  }
31
40
  export declare const autopilotRunner: AutopilotRunner;
32
41
  export declare const runAutopilot: (options: AutopilotOptions) => Promise<AutopilotResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"autopilot-runner.d.ts","sourceRoot":"","sources":["../../src/autopilot/autopilot-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EACL,gBAAgB,EAChB,eAAe,EAGf,gBAAgB,EAChB,gBAAgB,EAMjB,MAAM,SAAS,CAAC;AAKjB,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAS;;IAMlB,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;YAiBhD,OAAO;YAoCP,QAAQ;YAyHR,OAAO;IA2ErB,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAkCtF,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,sBAAsB;YAahB,mBAAmB;YAyBnB,WAAW;YAkBX,YAAY;YA4BZ,eAAe;YAiCf,cAAc;YAgBd,gBAAgB;IAY9B,OAAO,CAAC,eAAe;CAiBxB;AAED,eAAO,MAAM,eAAe,iBAAwB,CAAC;AACrD,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,6BAAiC,CAAC"}
1
+ {"version":3,"file":"autopilot-runner.d.ts","sourceRoot":"","sources":["../../src/autopilot/autopilot-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EACL,gBAAgB,EAChB,eAAe,EAIf,gBAAgB,EAChB,gBAAgB,EAMjB,MAAM,SAAS,CAAC;AAKjB,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;;IAOpB,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;YAmBhD,OAAO;YAoCP,QAAQ;YAkKR,WAAW;YA2DX,OAAO;IA2ErB,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAkCtF,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,sBAAsB;YAahB,mBAAmB;YAyBnB,WAAW;YAkBX,YAAY;YA4BZ,eAAe;YAiCf,cAAc;YAgBd,gBAAgB;IAY9B,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,eAAe;YAST,eAAe;IAU7B,OAAO,CAAC,eAAe;YAST,aAAa;YA0Bb,YAAY;YAeZ,aAAa;YAmBb,mBAAmB;CAOlC;AAED,eAAO,MAAM,eAAe,iBAAwB,CAAC;AACrD,eAAO,MAAM,YAAY,GAAI,SAAS,gBAAgB,6BAAiC,CAAC"}
@@ -55,8 +55,10 @@ const entitlements_1 = require("../entitlements");
55
55
  const entitlements = new entitlements_1.EntitlementsManager();
56
56
  class AutopilotRunner {
57
57
  tempDir;
58
+ backupDir;
58
59
  constructor() {
59
60
  this.tempDir = path.join(os.tmpdir(), 'guardrail-autopilot');
61
+ this.backupDir = path.join(os.tmpdir(), 'guardrail-autopilot-backups');
60
62
  }
61
63
  async run(options) {
62
64
  if (process.env['GUARDRAIL_SKIP_ENTITLEMENTS'] !== '1') {
@@ -69,6 +71,9 @@ class AutopilotRunner {
69
71
  if (options.mode === 'plan') {
70
72
  return this.runPlan(options);
71
73
  }
74
+ else if (options.mode === 'rollback') {
75
+ return this.runRollback(options);
76
+ }
72
77
  else {
73
78
  return this.runApply(options);
74
79
  }
@@ -104,16 +109,25 @@ class AutopilotRunner {
104
109
  }
105
110
  async runApply(options) {
106
111
  const startTime = new Date();
112
+ const runId = options.runId || crypto.randomBytes(8).toString('hex');
107
113
  const appliedFixes = [];
108
114
  const errors = [];
109
115
  let verification = null;
116
+ let gitBranch;
117
+ let gitCommit;
110
118
  options.onProgress?.('scan', 'Running initial scan...');
111
119
  const initialScan = await this.runScan(options.projectPath, options.profile || 'ship');
112
120
  if (process.env['GUARDRAIL_SKIP_ENTITLEMENTS'] !== '1') {
113
121
  await entitlements.trackUsage('scans', 1);
114
122
  }
115
123
  options.onProgress?.('group', 'Grouping findings into fix packs...');
116
- const packs = this.groupIntoFixPacks(initialScan.findings, options.maxFixes);
124
+ let packs = this.groupIntoFixPacks(initialScan.findings, options.maxFixes);
125
+ if (options.packIds && options.packIds.length > 0) {
126
+ packs = packs.filter(p => options.packIds.includes(p.id));
127
+ if (packs.length === 0) {
128
+ throw new Error(`No packs found matching IDs: ${options.packIds.join(', ')}`);
129
+ }
130
+ }
117
131
  if (packs.length === 0) {
118
132
  return {
119
133
  mode: 'apply',
@@ -133,6 +147,12 @@ class AutopilotRunner {
133
147
  errors: ['No fixable issues found'],
134
148
  };
135
149
  }
150
+ const isGitRepo = this.isGitRepository(options.projectPath);
151
+ if (isGitRepo && !options.dryRun) {
152
+ options.onProgress?.('git', 'Creating git branch...');
153
+ gitBranch = await this.createGitBranch(options.projectPath, runId);
154
+ await this.createBackup(options.projectPath, runId);
155
+ }
136
156
  let workspacePath = null;
137
157
  let packsSucceeded = 0;
138
158
  let packsFailed = 0;
@@ -140,6 +160,14 @@ class AutopilotRunner {
140
160
  options.onProgress?.('workspace', 'Creating temp workspace...');
141
161
  workspacePath = await this.createTempWorkspace(options.projectPath, options.branchStrategy);
142
162
  for (const pack of packs) {
163
+ if (pack.estimatedRisk === 'high' && !options.force) {
164
+ const shouldApply = await this.confirmHighRiskPack(pack, options.interactive);
165
+ if (!shouldApply) {
166
+ errors.push(`Pack ${pack.id} skipped: high risk, user declined`);
167
+ packsFailed++;
168
+ continue;
169
+ }
170
+ }
143
171
  options.onProgress?.('fix', `Applying ${pack.name}...`);
144
172
  try {
145
173
  const fixes = await this.applyFixPack(workspacePath, pack, options);
@@ -163,6 +191,10 @@ class AutopilotRunner {
163
191
  if (verification.passed && !options.dryRun) {
164
192
  options.onProgress?.('apply', 'Applying changes to project...');
165
193
  await this.applyToProject(options.projectPath, workspacePath);
194
+ if (isGitRepo && gitBranch) {
195
+ options.onProgress?.('git', 'Committing changes...');
196
+ gitCommit = await this.commitChanges(options.projectPath, runId, appliedFixes);
197
+ }
166
198
  if (process.env['GUARDRAIL_SKIP_ENTITLEMENTS'] !== '1') {
167
199
  await entitlements.trackUsage('fixRuns', appliedFixes.filter(f => f.success).length);
168
200
  }
@@ -171,6 +203,10 @@ class AutopilotRunner {
171
203
  else if (!options.dryRun) {
172
204
  options.onProgress?.('apply', 'Applying changes (unverified)...');
173
205
  await this.applyToProject(options.projectPath, workspacePath);
206
+ if (isGitRepo && gitBranch) {
207
+ options.onProgress?.('git', 'Committing changes...');
208
+ gitCommit = await this.commitChanges(options.projectPath, runId, appliedFixes);
209
+ }
174
210
  if (process.env['GUARDRAIL_SKIP_ENTITLEMENTS'] !== '1') {
175
211
  await entitlements.trackUsage('fixRuns', appliedFixes.filter(f => f.success).length);
176
212
  }
@@ -209,6 +245,67 @@ class AutopilotRunner {
209
245
  remainingFindings,
210
246
  newScanVerdict,
211
247
  errors,
248
+ runId,
249
+ gitBranch,
250
+ gitCommit,
251
+ };
252
+ }
253
+ async runRollback(options) {
254
+ if (!options.runId) {
255
+ throw new Error('runId is required for rollback');
256
+ }
257
+ const isGitRepo = this.isGitRepository(options.projectPath);
258
+ const backupPath = path.join(this.backupDir, options.runId);
259
+ const hasBackup = fs.existsSync(backupPath);
260
+ let method;
261
+ let success = false;
262
+ let message = '';
263
+ try {
264
+ if (isGitRepo) {
265
+ const branchName = `guardrail/autopilot-${options.runId}`;
266
+ const branchExists = this.gitBranchExists(options.projectPath, branchName);
267
+ if (branchExists) {
268
+ options.onProgress?.('rollback', 'Rolling back via git reset...');
269
+ (0, child_process_1.execSync)('git checkout -', { cwd: options.projectPath, stdio: 'pipe' });
270
+ (0, child_process_1.execSync)(`git branch -D ${branchName}`, { cwd: options.projectPath, stdio: 'pipe' });
271
+ method = 'git-reset';
272
+ success = true;
273
+ message = `Successfully rolled back git branch ${branchName}`;
274
+ }
275
+ else if (hasBackup) {
276
+ options.onProgress?.('rollback', 'Rolling back via backup restore...');
277
+ await this.restoreBackup(options.projectPath, options.runId);
278
+ method = 'backup-restore';
279
+ success = true;
280
+ message = `Successfully restored from backup`;
281
+ }
282
+ else {
283
+ throw new Error(`No git branch or backup found for runId: ${options.runId}`);
284
+ }
285
+ }
286
+ else if (hasBackup) {
287
+ options.onProgress?.('rollback', 'Rolling back via backup restore...');
288
+ await this.restoreBackup(options.projectPath, options.runId);
289
+ method = 'backup-restore';
290
+ success = true;
291
+ message = `Successfully restored from backup`;
292
+ }
293
+ else {
294
+ throw new Error(`No backup found for runId: ${options.runId}`);
295
+ }
296
+ }
297
+ catch (e) {
298
+ method = isGitRepo ? 'git-reset' : 'backup-restore';
299
+ message = `Rollback failed: ${e.message}`;
300
+ }
301
+ return {
302
+ mode: 'rollback',
303
+ projectPath: options.projectPath,
304
+ runId: options.runId,
305
+ timestamp: new Date().toISOString(),
306
+ success,
307
+ method,
308
+ message,
212
309
  };
213
310
  }
214
311
  async runScan(projectPath, _profile) {
@@ -472,6 +569,90 @@ class AutopilotRunner {
472
569
  }
473
570
  return files;
474
571
  }
572
+ isGitRepository(projectPath) {
573
+ try {
574
+ const gitDir = path.join(projectPath, '.git');
575
+ return fs.existsSync(gitDir);
576
+ }
577
+ catch {
578
+ return false;
579
+ }
580
+ }
581
+ async createGitBranch(projectPath, runId) {
582
+ const branchName = `guardrail/autopilot-${runId}`;
583
+ try {
584
+ (0, child_process_1.execSync)(`git checkout -b ${branchName}`, { cwd: projectPath, stdio: 'pipe' });
585
+ return branchName;
586
+ }
587
+ catch (e) {
588
+ throw new Error(`Failed to create git branch: ${e.message}`);
589
+ }
590
+ }
591
+ gitBranchExists(projectPath, branchName) {
592
+ try {
593
+ const branches = (0, child_process_1.execSync)('git branch --list', { cwd: projectPath, encoding: 'utf8' });
594
+ return branches.includes(branchName);
595
+ }
596
+ catch {
597
+ return false;
598
+ }
599
+ }
600
+ async commitChanges(projectPath, runId, fixes) {
601
+ try {
602
+ const successCount = fixes.filter(f => f.success).length;
603
+ const packIds = [...new Set(fixes.map(f => f.packId))];
604
+ const summary = `Autopilot fixes applied (runId: ${runId})`;
605
+ const details = [
606
+ ``,
607
+ `Applied ${successCount} fixes across ${packIds.length} pack(s)`,
608
+ ``,
609
+ `Packs:`,
610
+ ...packIds.map(id => `- ${id}`),
611
+ ``,
612
+ `Generated by Guardrail Autopilot`,
613
+ ].join('\n');
614
+ (0, child_process_1.execSync)('git add -A', { cwd: projectPath, stdio: 'pipe' });
615
+ (0, child_process_1.execSync)(`git commit -m "${summary}" -m "${details}"`, { cwd: projectPath, stdio: 'pipe' });
616
+ const commit = (0, child_process_1.execSync)('git rev-parse HEAD', { cwd: projectPath, encoding: 'utf8' }).trim();
617
+ return commit;
618
+ }
619
+ catch (e) {
620
+ throw new Error(`Failed to commit changes: ${e.message}`);
621
+ }
622
+ }
623
+ async createBackup(projectPath, runId) {
624
+ const backupPath = path.join(this.backupDir, runId);
625
+ await fs.promises.mkdir(backupPath, { recursive: true });
626
+ const files = this.findSourceFiles(projectPath);
627
+ for (const file of files) {
628
+ const relPath = path.relative(projectPath, file);
629
+ const backupFile = path.join(backupPath, relPath);
630
+ const backupFileDir = path.dirname(backupFile);
631
+ await fs.promises.mkdir(backupFileDir, { recursive: true });
632
+ await fs.promises.copyFile(file, backupFile);
633
+ }
634
+ }
635
+ async restoreBackup(projectPath, runId) {
636
+ const backupPath = path.join(this.backupDir, runId);
637
+ if (!fs.existsSync(backupPath)) {
638
+ throw new Error(`Backup not found for runId: ${runId}`);
639
+ }
640
+ const files = this.findSourceFiles(backupPath);
641
+ for (const file of files) {
642
+ const relPath = path.relative(backupPath, file);
643
+ const projectFile = path.join(projectPath, relPath);
644
+ const projectFileDir = path.dirname(projectFile);
645
+ await fs.promises.mkdir(projectFileDir, { recursive: true });
646
+ await fs.promises.copyFile(file, projectFile);
647
+ }
648
+ await fs.promises.rm(backupPath, { recursive: true, force: true });
649
+ }
650
+ async confirmHighRiskPack(_pack, interactive) {
651
+ if (!interactive) {
652
+ return false;
653
+ }
654
+ return true;
655
+ }
475
656
  }
476
657
  exports.AutopilotRunner = AutopilotRunner;
477
658
  exports.autopilotRunner = new AutopilotRunner();
@@ -4,7 +4,7 @@
4
4
  * Type definitions for the Autopilot batch remediation system.
5
5
  * PRO/COMPLIANCE+ feature.
6
6
  */
7
- export type AutopilotMode = 'plan' | 'apply';
7
+ export type AutopilotMode = 'plan' | 'apply' | 'rollback';
8
8
  export type AutopilotFixPackCategory = 'security' | 'quality' | 'type-errors' | 'build-blockers' | 'test-failures' | 'placeholders' | 'route-integrity';
9
9
  export interface AutopilotFinding {
10
10
  id: string;
@@ -35,6 +35,10 @@ export interface AutopilotOptions {
35
35
  dryRun?: boolean;
36
36
  json?: boolean;
37
37
  onProgress?: (stage: string, message: string) => void;
38
+ packIds?: string[];
39
+ runId?: string;
40
+ force?: boolean;
41
+ interactive?: boolean;
38
42
  }
39
43
  export interface AutopilotVerificationResult {
40
44
  passed: boolean;
@@ -90,8 +94,20 @@ export interface AutopilotApplyResult {
90
94
  remainingFindings: number;
91
95
  newScanVerdict: 'pass' | 'fail' | 'skipped';
92
96
  errors: string[];
97
+ runId?: string;
98
+ gitBranch?: string;
99
+ gitCommit?: string;
93
100
  }
94
- export type AutopilotResult = AutopilotPlanResult | AutopilotApplyResult;
101
+ export interface AutopilotRollbackResult {
102
+ mode: 'rollback';
103
+ projectPath: string;
104
+ runId: string;
105
+ timestamp: string;
106
+ success: boolean;
107
+ method: 'git-reset' | 'backup-restore';
108
+ message: string;
109
+ }
110
+ export type AutopilotResult = AutopilotPlanResult | AutopilotApplyResult | AutopilotRollbackResult;
95
111
  export interface AutopilotScanResult {
96
112
  findings: AutopilotFinding[];
97
113
  score: number;