@delegance/claude-autopilot 2.4.0 → 5.0.0-alpha.1
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/CHANGELOG.md +50 -0
- package/README.md +164 -106
- package/bin/_launcher.js +77 -0
- package/bin/claude-autopilot.js +3 -0
- package/bin/guardrail.js +3 -0
- package/package.json +15 -9
- package/presets/generic/guardrail.config.yaml +35 -0
- package/presets/generic/stack.md +40 -0
- package/presets/nextjs-supabase/{autopilot.config.yaml → guardrail.config.yaml} +7 -0
- package/scripts/autoregress.ts +27 -11
- package/skills/autopilot/SKILL.md +170 -0
- package/skills/claude-autopilot.md +80 -0
- package/skills/guardrail.md +39 -0
- package/skills/migrate/SKILL.md +83 -0
- package/src/adapters/council/claude.ts +41 -0
- package/src/adapters/council/openai.ts +40 -0
- package/src/adapters/council/types.ts +7 -0
- package/src/adapters/loader.ts +7 -7
- package/src/adapters/review-engine/auto.ts +2 -2
- package/src/adapters/review-engine/claude.ts +9 -11
- package/src/adapters/review-engine/codex.ts +9 -11
- package/src/adapters/review-engine/gemini.ts +9 -11
- package/src/adapters/review-engine/openai-compatible.ts +10 -12
- package/src/adapters/review-engine/parse-output.ts +32 -6
- package/src/adapters/review-engine/prompt-builder.ts +19 -0
- package/src/adapters/review-engine/types.ts +1 -1
- package/src/adapters/vcs-host/commit-status.ts +39 -0
- package/src/adapters/vcs-host/github.ts +2 -2
- package/src/cli/baseline.ts +125 -0
- package/src/cli/ci.ts +11 -8
- package/src/cli/costs.ts +80 -0
- package/src/cli/council.ts +96 -0
- package/src/cli/detector.ts +21 -5
- package/src/cli/explain.ts +197 -0
- package/src/cli/fix.ts +249 -0
- package/src/cli/hook.ts +72 -27
- package/src/cli/ignore-helper.ts +116 -0
- package/src/cli/index.ts +302 -28
- package/src/cli/init.ts +12 -12
- package/src/cli/lsp.ts +200 -0
- package/src/cli/mcp.ts +206 -0
- package/src/cli/pr-comment.ts +5 -5
- package/src/cli/pr-desc.ts +168 -0
- package/src/cli/pr-review-comments.ts +3 -3
- package/src/cli/pr.ts +76 -0
- package/src/cli/preflight.ts +15 -32
- package/src/cli/report.ts +186 -0
- package/src/cli/run.ts +140 -36
- package/src/cli/scan.ts +233 -0
- package/src/cli/setup.ts +121 -15
- package/src/cli/test-gen.ts +125 -0
- package/src/cli/triage.ts +137 -0
- package/src/cli/watch.ts +52 -31
- package/src/cli/worker.ts +109 -0
- package/src/core/cache/review-cache.ts +2 -2
- package/src/core/chunking/index.ts +2 -2
- package/src/core/config/loader.ts +24 -12
- package/src/core/config/preset-resolver.ts +6 -6
- package/src/core/config/schema.ts +121 -3
- package/src/core/config/types.ts +57 -2
- package/src/core/council/config.ts +71 -0
- package/src/core/council/context.ts +17 -0
- package/src/core/council/runner.ts +83 -0
- package/src/core/council/types.ts +45 -0
- package/src/core/detect/llm-key.ts +89 -0
- package/src/core/detect/workspaces.ts +103 -0
- package/src/core/errors.ts +4 -4
- package/src/core/fix/generator.ts +149 -0
- package/src/core/ignore/index.ts +4 -4
- package/src/core/mcp/concurrency.ts +16 -0
- package/src/core/mcp/handlers/fix-finding.ts +126 -0
- package/src/core/mcp/handlers/get-capabilities.ts +62 -0
- package/src/core/mcp/handlers/get-findings.ts +36 -0
- package/src/core/mcp/handlers/review-diff.ts +65 -0
- package/src/core/mcp/handlers/scan-files.ts +65 -0
- package/src/core/mcp/handlers/validate-fix.ts +41 -0
- package/src/core/mcp/run-store.ts +85 -0
- package/src/core/mcp/workspace.ts +35 -0
- package/src/core/persist/baseline.ts +112 -0
- package/src/core/persist/cost-log.ts +1 -1
- package/src/core/persist/findings-cache.ts +1 -1
- package/src/core/persist/triage.ts +112 -0
- package/src/core/phases/static-rules.ts +18 -5
- package/src/core/pipeline/review-phase.ts +65 -26
- package/src/core/pipeline/run.ts +42 -10
- package/src/core/runtime/lock.ts +2 -2
- package/src/core/runtime/state.ts +2 -2
- package/src/core/schema-alignment/detector.ts +59 -0
- package/src/core/schema-alignment/extractor/index.ts +24 -0
- package/src/core/schema-alignment/extractor/prisma.ts +21 -0
- package/src/core/schema-alignment/extractor/sql.ts +99 -0
- package/src/core/schema-alignment/llm-check.ts +91 -0
- package/src/core/schema-alignment/scanner.ts +107 -0
- package/src/core/schema-alignment/types.ts +43 -0
- package/src/core/shell.ts +3 -3
- package/src/core/static-rules/registry.ts +17 -8
- package/src/core/static-rules/rules/brand-tokens.ts +145 -0
- package/src/core/static-rules/rules/hardcoded-secrets.ts +27 -1
- package/src/core/static-rules/rules/insecure-redirect.ts +67 -0
- package/src/core/static-rules/rules/missing-auth.ts +70 -0
- package/src/core/static-rules/rules/schema-alignment.ts +132 -0
- package/src/core/static-rules/rules/sql-injection.ts +71 -0
- package/src/core/static-rules/rules/ssrf.ts +63 -0
- package/src/core/static-rules/tailwind-extractor.ts +38 -0
- package/src/core/test-gen/coverage-analyzer.ts +93 -0
- package/src/core/test-gen/framework-detector.ts +21 -0
- package/src/core/test-gen/test-writer.ts +33 -0
- package/src/core/ui/design-context-loader.ts +87 -0
- package/src/core/worker/client.ts +46 -0
- package/src/core/worker/lockfile.ts +38 -0
- package/src/core/worker/server.ts +81 -0
- package/src/formatters/junit.ts +52 -0
- package/src/formatters/sarif.ts +2 -2
- package/src/index.ts +1 -2
- package/tests/snapshots/baselines/src-formatters-sarif.json +4 -4
- package/tests/snapshots/index.json +3 -3
- package/tests/snapshots/src-formatters-sarif.snap.ts +1 -1
- package/tests/snapshots/src-snapshots-impact-selector.snap.ts +3 -3
- package/tests/snapshots/src-snapshots-import-scanner.snap.ts +3 -3
- package/tests/snapshots/src-snapshots-serializer.snap.ts +2 -2
- package/bin/autopilot.js +0 -20
- package/skills/autopilot.md +0 -157
- /package/presets/go/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
- /package/presets/python-fastapi/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
- /package/presets/rails-postgres/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
- /package/presets/t3/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
- /package/{src → scripts}/snapshots/impact-selector.ts +0 -0
- /package/{src → scripts}/snapshots/import-scanner.ts +0 -0
- /package/{src → scripts}/snapshots/serializer.ts +0 -0
package/src/cli/index.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* guardrail CLI — entry point
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* guardrail run review git-changed files
|
|
7
|
+
* guardrail scan src/auth/ review any path (no git required)
|
|
8
|
+
* guardrail scan --ask "..." ask a targeted question about code
|
|
9
|
+
* guardrail ci opinionated CI entrypoint
|
|
10
|
+
* guardrail watch re-run on every file save
|
|
11
|
+
* guardrail doctor check prerequisites
|
|
12
12
|
*/
|
|
13
13
|
import { runCommand } from './run.ts';
|
|
14
14
|
import { runWatch } from './watch.ts';
|
|
15
15
|
import { runSetup } from './setup.ts';
|
|
16
16
|
import { runDoctor } from './preflight.ts';
|
|
17
17
|
import { runCi } from './ci.ts';
|
|
18
|
+
import { runFix } from './fix.ts';
|
|
19
|
+
import { runScan } from './scan.ts';
|
|
20
|
+
import { runReport } from './report.ts';
|
|
21
|
+
import { runExplain } from './explain.ts';
|
|
22
|
+
import { runIgnore } from './ignore-helper.ts';
|
|
23
|
+
import { runPr } from './pr.ts';
|
|
24
|
+
import { runBaseline } from './baseline.ts';
|
|
25
|
+
import { runTriage } from './triage.ts';
|
|
26
|
+
import { runLsp } from './lsp.ts';
|
|
27
|
+
import { runWorker } from './worker.ts';
|
|
28
|
+
import { runTestGen } from './test-gen.ts';
|
|
29
|
+
import { runCouncilCmd } from './council.ts';
|
|
18
30
|
|
|
19
31
|
const args = process.argv.slice(2);
|
|
20
32
|
|
|
@@ -28,8 +40,38 @@ if (args[0] === '--version' || args[0] === '-v') {
|
|
|
28
40
|
process.exit(0);
|
|
29
41
|
}
|
|
30
42
|
|
|
31
|
-
const SUBCOMMANDS = ['init', 'run', 'ci', 'watch', 'hook', 'autoregress', 'doctor', 'preflight', 'setup', 'help', '--help', '-h'] as const;
|
|
32
|
-
const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce'];
|
|
43
|
+
const SUBCOMMANDS = ['init', 'run', 'scan', 'report', 'explain', 'ignore', 'ci', 'pr', 'fix', 'costs', 'watch', 'hook', 'autoregress', 'baseline', 'triage', 'lsp', 'worker', 'mcp', 'test-gen', 'pr-desc', 'doctor', 'preflight', 'setup', 'council', 'help', '--help', '-h'] as const;
|
|
44
|
+
const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce', 'ask', 'focus', 'fail-on', 'note', 'reason', 'expires', 'profile', 'severity', 'prompt', 'context-file'];
|
|
45
|
+
|
|
46
|
+
// Bare invocation — no subcommand, no flags → show welcome guide
|
|
47
|
+
if (args.length === 0) {
|
|
48
|
+
const hasKey = !!(process.env.ANTHROPIC_API_KEY || process.env.GEMINI_API_KEY ||
|
|
49
|
+
process.env.GOOGLE_API_KEY || process.env.OPENAI_API_KEY || process.env.GROQ_API_KEY);
|
|
50
|
+
const keyLine = hasKey
|
|
51
|
+
? '\x1b[32m✓\x1b[0m LLM API key detected'
|
|
52
|
+
: '\x1b[33m!\x1b[0m No LLM API key found — set one of:\n ANTHROPIC_API_KEY https://console.anthropic.com/\n OPENAI_API_KEY https://platform.openai.com/api-keys\n GEMINI_API_KEY https://aistudio.google.com/app/apikey\n GROQ_API_KEY https://console.groq.com/keys (fast free tier)';
|
|
53
|
+
console.log(`
|
|
54
|
+
\x1b[1m@delegance/guardrail\x1b[0m — LLM-powered code review for your PR diffs
|
|
55
|
+
|
|
56
|
+
${keyLine}
|
|
57
|
+
|
|
58
|
+
\x1b[1mQuick start:\x1b[0m
|
|
59
|
+
|
|
60
|
+
\x1b[36mnpx guardrail run --base main\x1b[0m Review files changed vs main
|
|
61
|
+
\x1b[36mnpx guardrail scan src/auth/\x1b[0m Scan any path (no git required)
|
|
62
|
+
\x1b[36mnpx guardrail scan --ask "SQL injection?" src/db/\x1b[0m
|
|
63
|
+
\x1b[36mnpx guardrail fix\x1b[0m Auto-fix cached findings
|
|
64
|
+
\x1b[36mnpx guardrail pr-desc\x1b[0m Generate PR description from current diff
|
|
65
|
+
|
|
66
|
+
\x1b[1mSetup:\x1b[0m
|
|
67
|
+
|
|
68
|
+
\x1b[36mnpx guardrail setup\x1b[0m Auto-detect stack, write config, install hook
|
|
69
|
+
\x1b[36mnpx guardrail doctor\x1b[0m Check prerequisites
|
|
70
|
+
|
|
71
|
+
Run \x1b[36mnpx guardrail --help\x1b[0m for full command reference.
|
|
72
|
+
`);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
33
75
|
|
|
34
76
|
// Detect first non-flag arg as subcommand, default to 'run'
|
|
35
77
|
const subcommand = (args[0] && !args[0].startsWith('--')) ? args[0] : 'run';
|
|
@@ -40,7 +82,7 @@ function flag(name: string): string | undefined {
|
|
|
40
82
|
if (idx < 0) return undefined;
|
|
41
83
|
const val = args[idx + 1];
|
|
42
84
|
if (val === undefined || val.startsWith('--')) {
|
|
43
|
-
console.error(`\x1b[31m[
|
|
85
|
+
console.error(`\x1b[31m[guardrail] --${name} requires a value\x1b[0m`);
|
|
44
86
|
process.exit(1);
|
|
45
87
|
}
|
|
46
88
|
return val;
|
|
@@ -52,18 +94,29 @@ function boolFlag(name: string): boolean {
|
|
|
52
94
|
|
|
53
95
|
function printUsage(): void {
|
|
54
96
|
console.log(`
|
|
55
|
-
Usage:
|
|
97
|
+
Usage: guardrail <command> [options]
|
|
56
98
|
|
|
57
99
|
Commands:
|
|
58
|
-
run
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
100
|
+
run Review git-changed files (default)
|
|
101
|
+
scan Review any path — no git required
|
|
102
|
+
report Render cached findings as a markdown report
|
|
103
|
+
explain Deep-dive explanation + remediation for a specific finding
|
|
104
|
+
ignore Interactively add findings to .guardrail-ignore
|
|
105
|
+
watch Watch for file changes and re-run on each save
|
|
106
|
+
pr Review a specific PR by number (auto-detects if on PR branch)
|
|
107
|
+
fix Auto-fix cached findings using the configured LLM
|
|
108
|
+
costs Show per-run cost summary
|
|
109
|
+
ci Opinionated CI entrypoint (post comments + SARIF)
|
|
110
|
+
init Scaffold guardrail.config.yaml from a preset
|
|
111
|
+
doctor Check prerequisites (alias: preflight)
|
|
112
|
+
autoregress Snapshot regression tests (run|diff|update|generate)
|
|
113
|
+
lsp Language server — publishes findings as LSP diagnostics (stdin/stdout)
|
|
114
|
+
worker Persistent review daemon for multi-terminal parallel usage (start|stop|status)
|
|
115
|
+
test-gen Detect uncovered exports and generate test cases using the LLM
|
|
63
116
|
|
|
64
117
|
Options (run):
|
|
65
118
|
--base <ref> Git base ref for diff (default: HEAD~1)
|
|
66
|
-
--config <path> Path to config file (default: ./
|
|
119
|
+
--config <path> Path to config file (default: ./guardrail.config.yaml)
|
|
67
120
|
--files <a,b,c> Explicit comma-separated file list (skips git detection)
|
|
68
121
|
--dry-run Show what would run without executing
|
|
69
122
|
--diff Send git diff hunks instead of full files (~70% fewer tokens)
|
|
@@ -73,8 +126,27 @@ Options (run):
|
|
|
73
126
|
--format <text|sarif> Output format (default: text)
|
|
74
127
|
--output <path> Output file path (required with --format sarif)
|
|
75
128
|
|
|
129
|
+
Options (scan):
|
|
130
|
+
<path> [path...] Files or directories to scan (or --all for entire codebase)
|
|
131
|
+
--all Scan entire codebase
|
|
132
|
+
--ask <question> Targeted question to inject into the LLM review prompt
|
|
133
|
+
--focus <type> security | logic | performance (default: all)
|
|
134
|
+
--dry-run List files that would be scanned without running
|
|
135
|
+
--config <path> Path to config file
|
|
136
|
+
|
|
137
|
+
Options (pr):
|
|
138
|
+
<number> PR number to review (optional if on a PR branch)
|
|
139
|
+
--no-post-comments Skip posting/updating PR summary comment
|
|
140
|
+
--no-inline-comments Skip posting per-line inline annotations
|
|
141
|
+
--config <path> Path to config file
|
|
142
|
+
|
|
143
|
+
Options (fix):
|
|
144
|
+
--severity <critical|warning|all> Which findings to fix (default: critical)
|
|
145
|
+
--dry-run Preview fixes without writing files
|
|
146
|
+
--config <path> Path to config file
|
|
147
|
+
|
|
76
148
|
Options (watch):
|
|
77
|
-
--config <path> Path to config file (default: ./
|
|
149
|
+
--config <path> Path to config file (default: ./guardrail.config.yaml)
|
|
78
150
|
--debounce <ms> Debounce delay in ms (default: 300)
|
|
79
151
|
|
|
80
152
|
Options (autoregress):
|
|
@@ -86,8 +158,32 @@ Options (autoregress):
|
|
|
86
158
|
}
|
|
87
159
|
|
|
88
160
|
switch (subcommand) {
|
|
161
|
+
case 'scan': {
|
|
162
|
+
const config = flag('config');
|
|
163
|
+
const ask = flag('ask');
|
|
164
|
+
const focusArg = flag('focus');
|
|
165
|
+
if (focusArg && !['security', 'logic', 'performance', 'brand', 'all'].includes(focusArg)) {
|
|
166
|
+
console.error(`\x1b[31m[guardrail] --focus must be "security", "logic", "performance", or "all"\x1b[0m`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
const dryRun = boolFlag('dry-run');
|
|
170
|
+
const all = boolFlag('all');
|
|
171
|
+
// Remaining non-flag args after 'scan' are paths
|
|
172
|
+
const targets = args.slice(1).filter(a => !a.startsWith('--') && a !== ask && a !== focusArg && a !== config);
|
|
173
|
+
const code = await runScan({
|
|
174
|
+
configPath: config,
|
|
175
|
+
targets: targets.length > 0 ? targets : undefined,
|
|
176
|
+
all,
|
|
177
|
+
ask,
|
|
178
|
+
focus: focusArg as 'security' | 'logic' | 'performance' | 'brand' | 'all' | undefined,
|
|
179
|
+
dryRun,
|
|
180
|
+
});
|
|
181
|
+
process.exit(code);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
|
|
89
185
|
case 'init': {
|
|
90
|
-
console.log('\x1b[33m[init]
|
|
186
|
+
console.log('\x1b[33m[init] guardrail init is deprecated — use: npx guardrail setup\x1b[0m\n');
|
|
91
187
|
const force = args.includes('--force');
|
|
92
188
|
await runSetup({ force });
|
|
93
189
|
break;
|
|
@@ -111,7 +207,7 @@ switch (subcommand) {
|
|
|
111
207
|
const debounceArg = flag('debounce');
|
|
112
208
|
const debounceMs = debounceArg ? parseInt(debounceArg, 10) : undefined;
|
|
113
209
|
if (debounceArg && (isNaN(debounceMs!) || debounceMs! < 0)) {
|
|
114
|
-
console.error(`\x1b[31m[
|
|
210
|
+
console.error(`\x1b[31m[guardrail] --debounce must be a non-negative integer\x1b[0m`);
|
|
115
211
|
process.exit(1);
|
|
116
212
|
}
|
|
117
213
|
await runWatch({ configPath: config, debounceMs });
|
|
@@ -125,20 +221,28 @@ switch (subcommand) {
|
|
|
125
221
|
const dryRun = boolFlag('dry-run');
|
|
126
222
|
const diff = boolFlag('diff');
|
|
127
223
|
const delta = boolFlag('delta');
|
|
224
|
+
const staticOnly = args.includes('--static-only');
|
|
128
225
|
const inlineComments = boolFlag('inline-comments');
|
|
129
226
|
const postComments = boolFlag('post-comments');
|
|
130
227
|
const formatArg = flag('format');
|
|
131
228
|
const outputPath = flag('output');
|
|
132
229
|
|
|
133
|
-
if (formatArg && formatArg !== 'text' && formatArg !== 'sarif') {
|
|
134
|
-
console.error(`\x1b[31m[
|
|
230
|
+
if (formatArg && formatArg !== 'text' && formatArg !== 'sarif' && formatArg !== 'junit') {
|
|
231
|
+
console.error(`\x1b[31m[guardrail] --format must be "text", "sarif", or "junit"\x1b[0m`);
|
|
135
232
|
process.exit(1);
|
|
136
233
|
}
|
|
137
|
-
if (formatArg === 'sarif' && !outputPath) {
|
|
138
|
-
console.error(`\x1b[31m[
|
|
234
|
+
if ((formatArg === 'sarif' || formatArg === 'junit') && !outputPath) {
|
|
235
|
+
console.error(`\x1b[31m[guardrail] --format ${formatArg} requires --output <path>\x1b[0m`);
|
|
139
236
|
process.exit(1);
|
|
140
237
|
}
|
|
141
238
|
|
|
239
|
+
const failOnArg = flag('fail-on');
|
|
240
|
+
if (failOnArg && !['critical', 'warning', 'note', 'none'].includes(failOnArg)) {
|
|
241
|
+
console.error(`\x1b[31m[guardrail] --fail-on must be "critical", "warning", "note", or "none"\x1b[0m`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
const newOnly = boolFlag('new-only');
|
|
245
|
+
|
|
142
246
|
const code = await runCommand({
|
|
143
247
|
base,
|
|
144
248
|
configPath: config,
|
|
@@ -146,10 +250,13 @@ switch (subcommand) {
|
|
|
146
250
|
dryRun,
|
|
147
251
|
diff,
|
|
148
252
|
delta,
|
|
253
|
+
newOnly,
|
|
254
|
+
failOn: failOnArg as 'critical' | 'warning' | 'note' | 'none' | undefined,
|
|
149
255
|
inlineComments,
|
|
150
256
|
postComments,
|
|
151
|
-
format: formatArg as 'text' | 'sarif' | undefined,
|
|
257
|
+
format: formatArg as 'text' | 'sarif' | 'junit' | undefined,
|
|
152
258
|
outputPath,
|
|
259
|
+
skipReview: staticOnly,
|
|
153
260
|
});
|
|
154
261
|
process.exit(code);
|
|
155
262
|
break;
|
|
@@ -162,6 +269,8 @@ switch (subcommand) {
|
|
|
162
269
|
const noPostComments = boolFlag('no-post-comments');
|
|
163
270
|
const noInlineComments = boolFlag('no-inline-comments');
|
|
164
271
|
const diff = boolFlag('diff');
|
|
272
|
+
const newOnly = boolFlag('new-only');
|
|
273
|
+
const failOnArg = flag('fail-on');
|
|
165
274
|
const code = await runCi({
|
|
166
275
|
configPath: config,
|
|
167
276
|
base,
|
|
@@ -169,6 +278,33 @@ switch (subcommand) {
|
|
|
169
278
|
postComments: noPostComments ? false : undefined,
|
|
170
279
|
inlineComments: noInlineComments ? false : undefined,
|
|
171
280
|
diff,
|
|
281
|
+
newOnly,
|
|
282
|
+
failOn: failOnArg as 'critical' | 'warning' | 'note' | 'none' | undefined,
|
|
283
|
+
});
|
|
284
|
+
process.exit(code);
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case 'baseline': {
|
|
289
|
+
const { runBaseline: rb } = await import('./baseline.ts');
|
|
290
|
+
const sub = args[1] ?? 'show';
|
|
291
|
+
const note = flag('note');
|
|
292
|
+
const config = flag('config');
|
|
293
|
+
const code = await rb(sub, { cwd: process.cwd(), note, baselinePath: config });
|
|
294
|
+
process.exit(code);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case 'pr': {
|
|
299
|
+
const config = flag('config');
|
|
300
|
+
const noPostComments = boolFlag('no-post-comments');
|
|
301
|
+
const noInlineComments = boolFlag('no-inline-comments');
|
|
302
|
+
const prNumber = args.slice(1).find(a => !a.startsWith('--') && /^\d+$/.test(a));
|
|
303
|
+
const code = await runPr({
|
|
304
|
+
configPath: config,
|
|
305
|
+
prNumber,
|
|
306
|
+
noPostComments,
|
|
307
|
+
noInlineComments,
|
|
172
308
|
});
|
|
173
309
|
process.exit(code);
|
|
174
310
|
break;
|
|
@@ -178,7 +314,11 @@ switch (subcommand) {
|
|
|
178
314
|
const { runHook } = await import('./hook.ts');
|
|
179
315
|
const hookSub = args[1] ?? 'status';
|
|
180
316
|
const force = boolFlag('force');
|
|
181
|
-
const code = await runHook(hookSub, {
|
|
317
|
+
const code = await runHook(hookSub, {
|
|
318
|
+
force,
|
|
319
|
+
preCommitOnly: args.includes('--pre-commit-only'),
|
|
320
|
+
prePushOnly: args.includes('--pre-push-only'),
|
|
321
|
+
});
|
|
182
322
|
process.exit(code);
|
|
183
323
|
break;
|
|
184
324
|
}
|
|
@@ -190,14 +330,148 @@ switch (subcommand) {
|
|
|
190
330
|
break;
|
|
191
331
|
}
|
|
192
332
|
|
|
333
|
+
case 'fix': {
|
|
334
|
+
const config = flag('config');
|
|
335
|
+
const severityArg = flag('severity');
|
|
336
|
+
if (severityArg && !['critical', 'warning', 'all'].includes(severityArg)) {
|
|
337
|
+
console.error(`\x1b[31m[guardrail] --severity must be "critical", "warning", or "all"\x1b[0m`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
const dryRun = boolFlag('dry-run');
|
|
341
|
+
const noVerify = boolFlag('no-verify');
|
|
342
|
+
const code = await runFix({
|
|
343
|
+
configPath: config,
|
|
344
|
+
severity: severityArg as 'critical' | 'warning' | 'all' | undefined,
|
|
345
|
+
dryRun,
|
|
346
|
+
noVerify,
|
|
347
|
+
});
|
|
348
|
+
process.exit(code);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case 'triage': {
|
|
353
|
+
const sub = args[1];
|
|
354
|
+
const rest = args.slice(2);
|
|
355
|
+
const code = await runTriage(sub, rest);
|
|
356
|
+
process.exit(code);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
case 'test-gen': {
|
|
361
|
+
const config = flag('config');
|
|
362
|
+
const base = flag('base');
|
|
363
|
+
const dryRun = boolFlag('dry-run');
|
|
364
|
+
const verify = boolFlag('verify');
|
|
365
|
+
const targets = args.slice(1).filter(a => !a.startsWith('--') && a !== config && a !== base);
|
|
366
|
+
const code = await runTestGen({
|
|
367
|
+
cwd: process.cwd(),
|
|
368
|
+
configPath: config,
|
|
369
|
+
targets: targets.length > 0 ? targets : undefined,
|
|
370
|
+
base,
|
|
371
|
+
dryRun,
|
|
372
|
+
verify,
|
|
373
|
+
});
|
|
374
|
+
process.exit(code);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
case 'pr-desc': {
|
|
379
|
+
const { runPrDesc } = await import('./pr-desc.ts');
|
|
380
|
+
const baseIdx = args.indexOf('--base');
|
|
381
|
+
const base = baseIdx !== -1 ? args[baseIdx + 1] : undefined;
|
|
382
|
+
const outputIdx = args.indexOf('--output');
|
|
383
|
+
const output = outputIdx !== -1 ? args[outputIdx + 1] : undefined;
|
|
384
|
+
await runPrDesc({
|
|
385
|
+
base,
|
|
386
|
+
post: args.includes('--post'),
|
|
387
|
+
yes: args.includes('--yes'),
|
|
388
|
+
output,
|
|
389
|
+
});
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
case 'lsp': {
|
|
394
|
+
await runLsp({ cwd: process.cwd() });
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
case 'costs': {
|
|
399
|
+
const { runCosts } = await import('./costs.ts');
|
|
400
|
+
const code = await runCosts();
|
|
401
|
+
process.exit(code);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
case 'report': {
|
|
406
|
+
const outputPath = flag('output');
|
|
407
|
+
const trend = boolFlag('trend');
|
|
408
|
+
const code = await runReport({ output: outputPath, trend });
|
|
409
|
+
process.exit(code);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
case 'explain': {
|
|
414
|
+
const config = flag('config');
|
|
415
|
+
// Target is the first non-flag arg after 'explain'
|
|
416
|
+
const target = args.slice(1).find(a => !a.startsWith('--'));
|
|
417
|
+
const code = await runExplain({ configPath: config, target });
|
|
418
|
+
process.exit(code);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
case 'ignore': {
|
|
423
|
+
const all = boolFlag('all');
|
|
424
|
+
const dryRun = boolFlag('dry-run');
|
|
425
|
+
const code = await runIgnore({ all, dryRun });
|
|
426
|
+
process.exit(code);
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
|
|
193
430
|
case 'setup': {
|
|
194
431
|
const force = args.includes('--force');
|
|
195
|
-
|
|
432
|
+
const profileArg = flag('profile');
|
|
433
|
+
if (profileArg && !['security-strict', 'team', 'solo'].includes(profileArg)) {
|
|
434
|
+
console.error(`\x1b[31m[guardrail] --profile must be "security-strict", "team", or "solo"\x1b[0m`);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
await runSetup({ force, profile: profileArg as 'security-strict' | 'team' | 'solo' | undefined });
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
case 'worker': {
|
|
442
|
+
const sub = args[1];
|
|
443
|
+
const config = flag('config');
|
|
444
|
+
const code = await runWorker(sub, { cwd: process.cwd(), configPath: config });
|
|
445
|
+
process.exit(code);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
case 'council': {
|
|
450
|
+
const config = flag('config');
|
|
451
|
+
const prompt = flag('prompt');
|
|
452
|
+
const contextFile = flag('context-file');
|
|
453
|
+
const dryRun = boolFlag('dry-run');
|
|
454
|
+
const noSynthesize = boolFlag('no-synthesize');
|
|
455
|
+
const code = await runCouncilCmd({
|
|
456
|
+
prompt,
|
|
457
|
+
contextFile,
|
|
458
|
+
configPath: config,
|
|
459
|
+
dryRun,
|
|
460
|
+
noSynthesize,
|
|
461
|
+
});
|
|
462
|
+
process.exit(code);
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
case 'mcp': {
|
|
467
|
+
const { runMcp } = await import('./mcp.ts');
|
|
468
|
+
const configPath = flag('config');
|
|
469
|
+
await runMcp({ cwd: process.cwd(), configPath });
|
|
196
470
|
break;
|
|
197
471
|
}
|
|
198
472
|
|
|
199
473
|
default:
|
|
200
|
-
console.error(`\x1b[31m[
|
|
474
|
+
console.error(`\x1b[31m[guardrail] Unknown subcommand: "${subcommand}"\x1b[0m`);
|
|
201
475
|
printUsage();
|
|
202
476
|
process.exit(1);
|
|
203
477
|
}
|
package/src/cli/init.ts
CHANGED
|
@@ -17,14 +17,14 @@ const PRESET_DESCRIPTIONS: Record<string, string> = {
|
|
|
17
17
|
const PRESET_NAMES = Object.keys(PRESET_DESCRIPTIONS);
|
|
18
18
|
|
|
19
19
|
export async function runInit(cwd: string = process.cwd()): Promise<void> {
|
|
20
|
-
const dest = path.join(cwd, '
|
|
20
|
+
const dest = path.join(cwd, 'guardrail.config.yaml');
|
|
21
21
|
|
|
22
22
|
if (fs.existsSync(dest)) {
|
|
23
|
-
console.error(`\x1b[33m[init]
|
|
23
|
+
console.error(`\x1b[33m[init] guardrail.config.yaml already exists — remove it first to re-init\x1b[0m`);
|
|
24
24
|
process.exit(1);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
console.log('\n\x1b[1m[
|
|
27
|
+
console.log('\n\x1b[1m[guardrail init] Choose a preset:\x1b[0m\n');
|
|
28
28
|
PRESET_NAMES.forEach((name, i) => {
|
|
29
29
|
console.log(` ${i + 1}. ${name.padEnd(22)} ${PRESET_DESCRIPTIONS[name]}`);
|
|
30
30
|
});
|
|
@@ -63,27 +63,27 @@ export async function runInit(cwd: string = process.cwd()): Promise<void> {
|
|
|
63
63
|
const presetContent = await fsAsync.readFile(presetConfigPath, 'utf8');
|
|
64
64
|
await fsAsync.writeFile(dest, presetContent, 'utf8');
|
|
65
65
|
|
|
66
|
-
console.log(`\n\x1b[32m✓\x1b[0m Created
|
|
66
|
+
console.log(`\n\x1b[32m✓\x1b[0m Created guardrail.config.yaml from preset \x1b[1m${presetName}\x1b[0m`);
|
|
67
67
|
console.log('\nNext steps:');
|
|
68
|
-
console.log(' 1. Review
|
|
68
|
+
console.log(' 1. Review guardrail.config.yaml and adjust testCommand / protectedPaths');
|
|
69
69
|
console.log(' 2. Set OPENAI_API_KEY in your environment (for Codex review)');
|
|
70
70
|
console.log(' 3. Run your first pipeline to verify the setup:');
|
|
71
|
-
console.log(' npx
|
|
71
|
+
console.log(' npx guardrail run');
|
|
72
72
|
console.log(' 4. Generate snapshot baselines after your first feature lands:');
|
|
73
|
-
console.log(' npx
|
|
73
|
+
console.log(' npx guardrail autoregress generate');
|
|
74
74
|
console.log(' 5. Install the pre-push git hook (enforces snapshots on push):');
|
|
75
|
-
console.log(' npx
|
|
75
|
+
console.log(' npx guardrail hook install');
|
|
76
76
|
console.log(' 6. (Optional) Add CI with GitHub Actions:');
|
|
77
|
-
console.log(' uses: axledbetter/
|
|
77
|
+
console.log(' uses: axledbetter/guardrail/.github/actions/ci@main\n');
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function presetSearchPaths(name: string): string[] {
|
|
81
81
|
// fileURLToPath handles encoded chars and Windows drive letters safely
|
|
82
82
|
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
83
83
|
return [
|
|
84
|
-
path.join(pkgRoot, 'presets', name, '
|
|
85
|
-
path.join(process.cwd(), 'presets', name, '
|
|
86
|
-
path.join(process.cwd(), 'node_modules', '@delegance', 'claude-autopilot', 'presets', name, '
|
|
84
|
+
path.join(pkgRoot, 'presets', name, 'guardrail.config.yaml'),
|
|
85
|
+
path.join(process.cwd(), 'presets', name, 'guardrail.config.yaml'),
|
|
86
|
+
path.join(process.cwd(), 'node_modules', '@delegance', 'claude-autopilot', 'presets', name, 'guardrail.config.yaml'),
|
|
87
87
|
];
|
|
88
88
|
}
|
|
89
89
|
|