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.
- package/dist/__tests__/autopilot-enterprise.test.d.ts +7 -0
- package/dist/__tests__/autopilot-enterprise.test.d.ts.map +1 -0
- package/dist/__tests__/autopilot-enterprise.test.js +334 -0
- package/dist/autopilot/autopilot-runner.d.ts +9 -0
- package/dist/autopilot/autopilot-runner.d.ts.map +1 -1
- package/dist/autopilot/autopilot-runner.js +182 -1
- package/dist/autopilot/types.d.ts +18 -2
- package/dist/autopilot/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/smells/index.d.ts +59 -0
- package/dist/smells/index.d.ts.map +1 -0
- package/dist/smells/index.js +251 -0
- package/package.json +19 -2
- package/src/__tests__/autopilot.test.ts +0 -196
- package/src/__tests__/tier-config.test.ts +0 -289
- package/src/__tests__/utils/hash-inline.test.ts +0 -76
- package/src/__tests__/utils/hash.test.ts +0 -119
- package/src/__tests__/utils/simple.test.ts +0 -10
- package/src/__tests__/utils/utils-simple.test.ts +0 -5
- package/src/__tests__/utils/utils.test.ts +0 -203
- package/src/autopilot/autopilot-runner.ts +0 -503
- package/src/autopilot/index.ts +0 -6
- package/src/autopilot/types.ts +0 -119
- package/src/cache/index.ts +0 -7
- package/src/cache/redis-cache.d.ts +0 -155
- package/src/cache/redis-cache.d.ts.map +0 -1
- package/src/cache/redis-cache.ts +0 -517
- package/src/ci/github-actions.ts +0 -335
- package/src/ci/index.ts +0 -12
- package/src/ci/pre-commit.ts +0 -338
- package/src/db/usage-schema.prisma +0 -114
- package/src/entitlements.ts +0 -570
- package/src/env.d.ts +0 -68
- package/src/env.d.ts.map +0 -1
- package/src/env.ts +0 -247
- package/src/fix-packs/__tests__/generate-fix-packs.test.ts +0 -317
- package/src/fix-packs/generate-fix-packs.ts +0 -577
- package/src/fix-packs/index.ts +0 -8
- package/src/fix-packs/types.ts +0 -206
- package/src/index.d.ts +0 -7
- package/src/index.d.ts.map +0 -1
- package/src/index.ts +0 -12
- package/src/metrics/prometheus.d.ts +0 -104
- package/src/metrics/prometheus.d.ts.map +0 -1
- package/src/metrics/prometheus.ts +0 -446
- package/src/quota-ledger.ts +0 -548
- package/src/rbac/__tests__/permissions.test.ts +0 -446
- package/src/rbac/index.ts +0 -46
- package/src/rbac/permissions.ts +0 -301
- package/src/rbac/types.ts +0 -298
- package/src/tier-config.json +0 -157
- package/src/tier-config.ts +0 -815
- package/src/types.d.ts +0 -365
- package/src/types.d.ts.map +0 -1
- package/src/types.ts +0 -441
- package/src/utils.d.ts +0 -36
- package/src/utils.d.ts.map +0 -1
- package/src/utils.ts +0 -140
- package/src/verified-autofix/__tests__/format-validator.test.ts +0 -335
- package/src/verified-autofix/__tests__/pipeline.test.ts +0 -419
- package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +0 -241
- package/src/verified-autofix/__tests__/workspace.test.ts +0 -373
- package/src/verified-autofix/format-validator.ts +0 -517
- package/src/verified-autofix/index.ts +0 -63
- package/src/verified-autofix/pipeline.ts +0 -403
- package/src/verified-autofix/repo-fingerprint.ts +0 -459
- package/src/verified-autofix/workspace.ts +0 -531
- package/src/verified-autofix.ts +0 -1187
- package/src/visualization/dependency-graph.d.ts +0 -85
- package/src/visualization/dependency-graph.d.ts.map +0 -1
- package/src/visualization/dependency-graph.ts +0 -495
- package/src/visualization/index.ts +0 -5
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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;
|