@delegance/claude-autopilot 2.5.0 → 5.0.0-alpha.2
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 +63 -0
- package/README.md +169 -106
- package/bin/_launcher.js +77 -0
- package/bin/claude-autopilot.js +3 -0
- package/bin/guardrail.js +3 -0
- package/package.json +23 -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 +2 -2
- 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 +173 -111
- package/src/cli/hook.ts +72 -27
- package/src/cli/ignore-helper.ts +116 -0
- package/src/cli/index.ts +355 -31
- 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 +109 -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 +10 -10
- package/src/core/config/preset-resolver.ts +6 -6
- package/src/core/config/schema.ts +103 -2
- 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,14 +1,14 @@
|
|
|
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';
|
|
@@ -16,6 +16,17 @@ import { runSetup } from './setup.ts';
|
|
|
16
16
|
import { runDoctor } from './preflight.ts';
|
|
17
17
|
import { runCi } from './ci.ts';
|
|
18
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';
|
|
19
30
|
|
|
20
31
|
const args = process.argv.slice(2);
|
|
21
32
|
|
|
@@ -29,8 +40,113 @@ if (args[0] === '--version' || args[0] === '-v') {
|
|
|
29
40
|
process.exit(0);
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
// Help flag — route to help handler explicitly before subcommand defaulting.
|
|
44
|
+
// Without this, `--help` falls through the "args[0].startsWith('--')" check below
|
|
45
|
+
// and defaults to `run`, which is surprising and a v4 regression we preserve no longer.
|
|
46
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
47
|
+
args.unshift('help');
|
|
48
|
+
args.splice(1, 1); // remove the original --help/-h token
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Verb grouping (new in alpha.2): `claude-autopilot review <verb>` and
|
|
52
|
+
// `claude-autopilot advanced <verb>` are dispatcher prefixes that route to the
|
|
53
|
+
// same flat handlers. Legacy flat invocation (`claude-autopilot run`) is unchanged
|
|
54
|
+
// — the grouped form is purely additive.
|
|
55
|
+
//
|
|
56
|
+
// Scope gates enforce that only the documented verb sets work under each prefix,
|
|
57
|
+
// so `claude-autopilot review doctor` is rejected with a clear error instead of
|
|
58
|
+
// silently routing to the doctor handler (which would confuse the mental model).
|
|
59
|
+
const REVIEW_VERBS = new Set(['run', 'scan', 'ci', 'fix', 'baseline', 'explain', 'watch', 'report']);
|
|
60
|
+
// `detector` is a library used by setup/run, not a CLI subcommand — leave it out.
|
|
61
|
+
const ADVANCED_VERBS = new Set(['lsp', 'mcp', 'worker', 'autoregress', 'test-gen', 'hook', 'ignore']);
|
|
62
|
+
if (args[0] === 'review') {
|
|
63
|
+
const sub = args[1];
|
|
64
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
65
|
+
console.log(`
|
|
66
|
+
Usage: claude-autopilot review <verb> [options]
|
|
67
|
+
|
|
68
|
+
Review-phase verbs:
|
|
69
|
+
run Review git-changed files (default)
|
|
70
|
+
scan Review any path — no git required
|
|
71
|
+
ci Opinionated CI entrypoint (post comments + SARIF)
|
|
72
|
+
fix Auto-fix cached findings using the configured LLM
|
|
73
|
+
baseline Manage the committed findings baseline
|
|
74
|
+
explain Deep-dive explanation + remediation for a specific finding
|
|
75
|
+
watch Watch for file changes and re-run on each save
|
|
76
|
+
report Render cached findings as a markdown report
|
|
77
|
+
|
|
78
|
+
These are aliases for the flat subcommands — \`claude-autopilot run\` and
|
|
79
|
+
\`claude-autopilot review run\` are equivalent.
|
|
80
|
+
`);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
if (!REVIEW_VERBS.has(sub)) {
|
|
84
|
+
console.error(`\x1b[31m[claude-autopilot] "${sub}" is not a review-phase verb.\x1b[0m`);
|
|
85
|
+
console.error(`\x1b[2m Valid: ${[...REVIEW_VERBS].join(', ')}\x1b[0m`);
|
|
86
|
+
console.error(`\x1b[2m Did you mean: claude-autopilot ${sub} ...?\x1b[0m`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
args.shift(); // drop 'review', leave the flat subcommand at args[0]
|
|
90
|
+
}
|
|
91
|
+
if (args[0] === 'advanced') {
|
|
92
|
+
const sub = args[1];
|
|
93
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
94
|
+
console.log(`
|
|
95
|
+
Usage: claude-autopilot advanced <verb> [options]
|
|
96
|
+
|
|
97
|
+
Advanced / niche verbs (hidden from top-level --help to keep it readable):
|
|
98
|
+
lsp Language server — publishes findings as LSP diagnostics
|
|
99
|
+
mcp MCP server for Claude / ChatGPT integration
|
|
100
|
+
worker Persistent review daemon (start|stop|status)
|
|
101
|
+
autoregress Snapshot regression tests
|
|
102
|
+
test-gen Generate test cases for uncovered exports
|
|
103
|
+
hook Install / remove the pre-push git hook
|
|
104
|
+
ignore Edit the findings ignore list
|
|
105
|
+
|
|
106
|
+
These are aliases for the flat subcommands; they still work without the 'advanced' prefix.
|
|
107
|
+
`);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
if (!ADVANCED_VERBS.has(sub)) {
|
|
111
|
+
console.error(`\x1b[31m[claude-autopilot] "${sub}" is not an advanced verb.\x1b[0m`);
|
|
112
|
+
console.error(`\x1b[2m Valid: ${[...ADVANCED_VERBS].join(', ')}\x1b[0m`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
args.shift(); // drop 'advanced'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
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;
|
|
119
|
+
const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce', 'ask', 'focus', 'fail-on', 'note', 'reason', 'expires', 'profile', 'severity', 'prompt', 'context-file'];
|
|
120
|
+
|
|
121
|
+
// Bare invocation — no subcommand, no flags → show welcome guide
|
|
122
|
+
if (args.length === 0) {
|
|
123
|
+
const hasKey = !!(process.env.ANTHROPIC_API_KEY || process.env.GEMINI_API_KEY ||
|
|
124
|
+
process.env.GOOGLE_API_KEY || process.env.OPENAI_API_KEY || process.env.GROQ_API_KEY);
|
|
125
|
+
const keyLine = hasKey
|
|
126
|
+
? '\x1b[32m✓\x1b[0m LLM API key detected'
|
|
127
|
+
: '\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)';
|
|
128
|
+
console.log(`
|
|
129
|
+
\x1b[1m@delegance/guardrail\x1b[0m — LLM-powered code review for your PR diffs
|
|
130
|
+
|
|
131
|
+
${keyLine}
|
|
132
|
+
|
|
133
|
+
\x1b[1mQuick start:\x1b[0m
|
|
134
|
+
|
|
135
|
+
\x1b[36mnpx guardrail run --base main\x1b[0m Review files changed vs main
|
|
136
|
+
\x1b[36mnpx guardrail scan src/auth/\x1b[0m Scan any path (no git required)
|
|
137
|
+
\x1b[36mnpx guardrail scan --ask "SQL injection?" src/db/\x1b[0m
|
|
138
|
+
\x1b[36mnpx guardrail fix\x1b[0m Auto-fix cached findings
|
|
139
|
+
\x1b[36mnpx guardrail pr-desc\x1b[0m Generate PR description from current diff
|
|
140
|
+
|
|
141
|
+
\x1b[1mSetup:\x1b[0m
|
|
142
|
+
|
|
143
|
+
\x1b[36mnpx guardrail setup\x1b[0m Auto-detect stack, write config, install hook
|
|
144
|
+
\x1b[36mnpx guardrail doctor\x1b[0m Check prerequisites
|
|
145
|
+
|
|
146
|
+
Run \x1b[36mnpx guardrail --help\x1b[0m for full command reference.
|
|
147
|
+
`);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
34
150
|
|
|
35
151
|
// Detect first non-flag arg as subcommand, default to 'run'
|
|
36
152
|
const subcommand = (args[0] && !args[0].startsWith('--')) ? args[0] : 'run';
|
|
@@ -41,7 +157,7 @@ function flag(name: string): string | undefined {
|
|
|
41
157
|
if (idx < 0) return undefined;
|
|
42
158
|
const val = args[idx + 1];
|
|
43
159
|
if (val === undefined || val.startsWith('--')) {
|
|
44
|
-
console.error(`\x1b[31m[
|
|
160
|
+
console.error(`\x1b[31m[guardrail] --${name} requires a value\x1b[0m`);
|
|
45
161
|
process.exit(1);
|
|
46
162
|
}
|
|
47
163
|
return val;
|
|
@@ -53,18 +169,37 @@ function boolFlag(name: string): boolean {
|
|
|
53
169
|
|
|
54
170
|
function printUsage(): void {
|
|
55
171
|
console.log(`
|
|
56
|
-
Usage:
|
|
172
|
+
Usage: guardrail <command> [options]
|
|
57
173
|
|
|
58
174
|
Commands:
|
|
59
|
-
run
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
175
|
+
run Review git-changed files (default)
|
|
176
|
+
scan Review any path — no git required
|
|
177
|
+
report Render cached findings as a markdown report
|
|
178
|
+
explain Deep-dive explanation + remediation for a specific finding
|
|
179
|
+
ignore Interactively add findings to .guardrail-ignore
|
|
180
|
+
watch Watch for file changes and re-run on each save
|
|
181
|
+
pr Review a specific PR by number (auto-detects if on PR branch)
|
|
182
|
+
fix Auto-fix cached findings using the configured LLM
|
|
183
|
+
costs Show per-run cost summary
|
|
184
|
+
ci Opinionated CI entrypoint (post comments + SARIF)
|
|
185
|
+
init Scaffold guardrail.config.yaml from a preset
|
|
186
|
+
setup Auto-detect stack, write config, install pre-push hook
|
|
187
|
+
doctor Check prerequisites (alias: preflight)
|
|
188
|
+
preflight Check prerequisites (alias: doctor)
|
|
189
|
+
hook Install / remove the pre-push git hook
|
|
190
|
+
baseline Manage the committed findings baseline (create|update|show|delete)
|
|
191
|
+
triage Mark individual findings as accepted/dismissed
|
|
192
|
+
pr-desc Generate a PR title / summary / test plan from the current diff
|
|
193
|
+
council Multi-model review — dispatch the diff to N models and synthesize consensus
|
|
194
|
+
mcp MCP server for Claude / ChatGPT integration
|
|
195
|
+
autoregress Snapshot regression tests (run|diff|update|generate)
|
|
196
|
+
lsp Language server — publishes findings as LSP diagnostics (stdin/stdout)
|
|
197
|
+
worker Persistent review daemon for multi-terminal parallel usage (start|stop|status)
|
|
198
|
+
test-gen Detect uncovered exports and generate test cases using the LLM
|
|
64
199
|
|
|
65
200
|
Options (run):
|
|
66
201
|
--base <ref> Git base ref for diff (default: HEAD~1)
|
|
67
|
-
--config <path> Path to config file (default: ./
|
|
202
|
+
--config <path> Path to config file (default: ./guardrail.config.yaml)
|
|
68
203
|
--files <a,b,c> Explicit comma-separated file list (skips git detection)
|
|
69
204
|
--dry-run Show what would run without executing
|
|
70
205
|
--diff Send git diff hunks instead of full files (~70% fewer tokens)
|
|
@@ -74,8 +209,19 @@ Options (run):
|
|
|
74
209
|
--format <text|sarif> Output format (default: text)
|
|
75
210
|
--output <path> Output file path (required with --format sarif)
|
|
76
211
|
|
|
77
|
-
|
|
78
|
-
|
|
212
|
+
Options (scan):
|
|
213
|
+
<path> [path...] Files or directories to scan (or --all for entire codebase)
|
|
214
|
+
--all Scan entire codebase
|
|
215
|
+
--ask <question> Targeted question to inject into the LLM review prompt
|
|
216
|
+
--focus <type> security | logic | performance (default: all)
|
|
217
|
+
--dry-run List files that would be scanned without running
|
|
218
|
+
--config <path> Path to config file
|
|
219
|
+
|
|
220
|
+
Options (pr):
|
|
221
|
+
<number> PR number to review (optional if on a PR branch)
|
|
222
|
+
--no-post-comments Skip posting/updating PR summary comment
|
|
223
|
+
--no-inline-comments Skip posting per-line inline annotations
|
|
224
|
+
--config <path> Path to config file
|
|
79
225
|
|
|
80
226
|
Options (fix):
|
|
81
227
|
--severity <critical|warning|all> Which findings to fix (default: critical)
|
|
@@ -83,7 +229,7 @@ Options (fix):
|
|
|
83
229
|
--config <path> Path to config file
|
|
84
230
|
|
|
85
231
|
Options (watch):
|
|
86
|
-
--config <path> Path to config file (default: ./
|
|
232
|
+
--config <path> Path to config file (default: ./guardrail.config.yaml)
|
|
87
233
|
--debounce <ms> Debounce delay in ms (default: 300)
|
|
88
234
|
|
|
89
235
|
Options (autoregress):
|
|
@@ -95,8 +241,32 @@ Options (autoregress):
|
|
|
95
241
|
}
|
|
96
242
|
|
|
97
243
|
switch (subcommand) {
|
|
244
|
+
case 'scan': {
|
|
245
|
+
const config = flag('config');
|
|
246
|
+
const ask = flag('ask');
|
|
247
|
+
const focusArg = flag('focus');
|
|
248
|
+
if (focusArg && !['security', 'logic', 'performance', 'brand', 'all'].includes(focusArg)) {
|
|
249
|
+
console.error(`\x1b[31m[guardrail] --focus must be "security", "logic", "performance", or "all"\x1b[0m`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
const dryRun = boolFlag('dry-run');
|
|
253
|
+
const all = boolFlag('all');
|
|
254
|
+
// Remaining non-flag args after 'scan' are paths
|
|
255
|
+
const targets = args.slice(1).filter(a => !a.startsWith('--') && a !== ask && a !== focusArg && a !== config);
|
|
256
|
+
const code = await runScan({
|
|
257
|
+
configPath: config,
|
|
258
|
+
targets: targets.length > 0 ? targets : undefined,
|
|
259
|
+
all,
|
|
260
|
+
ask,
|
|
261
|
+
focus: focusArg as 'security' | 'logic' | 'performance' | 'brand' | 'all' | undefined,
|
|
262
|
+
dryRun,
|
|
263
|
+
});
|
|
264
|
+
process.exit(code);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
98
268
|
case 'init': {
|
|
99
|
-
console.log('\x1b[33m[init]
|
|
269
|
+
console.log('\x1b[33m[init] guardrail init is deprecated — use: npx guardrail setup\x1b[0m\n');
|
|
100
270
|
const force = args.includes('--force');
|
|
101
271
|
await runSetup({ force });
|
|
102
272
|
break;
|
|
@@ -120,7 +290,7 @@ switch (subcommand) {
|
|
|
120
290
|
const debounceArg = flag('debounce');
|
|
121
291
|
const debounceMs = debounceArg ? parseInt(debounceArg, 10) : undefined;
|
|
122
292
|
if (debounceArg && (isNaN(debounceMs!) || debounceMs! < 0)) {
|
|
123
|
-
console.error(`\x1b[31m[
|
|
293
|
+
console.error(`\x1b[31m[guardrail] --debounce must be a non-negative integer\x1b[0m`);
|
|
124
294
|
process.exit(1);
|
|
125
295
|
}
|
|
126
296
|
await runWatch({ configPath: config, debounceMs });
|
|
@@ -134,19 +304,27 @@ switch (subcommand) {
|
|
|
134
304
|
const dryRun = boolFlag('dry-run');
|
|
135
305
|
const diff = boolFlag('diff');
|
|
136
306
|
const delta = boolFlag('delta');
|
|
307
|
+
const staticOnly = args.includes('--static-only');
|
|
137
308
|
const inlineComments = boolFlag('inline-comments');
|
|
138
309
|
const postComments = boolFlag('post-comments');
|
|
139
310
|
const formatArg = flag('format');
|
|
140
311
|
const outputPath = flag('output');
|
|
141
312
|
|
|
142
|
-
if (formatArg && formatArg !== 'text' && formatArg !== 'sarif') {
|
|
143
|
-
console.error(`\x1b[31m[
|
|
313
|
+
if (formatArg && formatArg !== 'text' && formatArg !== 'sarif' && formatArg !== 'junit') {
|
|
314
|
+
console.error(`\x1b[31m[guardrail] --format must be "text", "sarif", or "junit"\x1b[0m`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
if ((formatArg === 'sarif' || formatArg === 'junit') && !outputPath) {
|
|
318
|
+
console.error(`\x1b[31m[guardrail] --format ${formatArg} requires --output <path>\x1b[0m`);
|
|
144
319
|
process.exit(1);
|
|
145
320
|
}
|
|
146
|
-
|
|
147
|
-
|
|
321
|
+
|
|
322
|
+
const failOnArg = flag('fail-on');
|
|
323
|
+
if (failOnArg && !['critical', 'warning', 'note', 'none'].includes(failOnArg)) {
|
|
324
|
+
console.error(`\x1b[31m[guardrail] --fail-on must be "critical", "warning", "note", or "none"\x1b[0m`);
|
|
148
325
|
process.exit(1);
|
|
149
326
|
}
|
|
327
|
+
const newOnly = boolFlag('new-only');
|
|
150
328
|
|
|
151
329
|
const code = await runCommand({
|
|
152
330
|
base,
|
|
@@ -155,10 +333,13 @@ switch (subcommand) {
|
|
|
155
333
|
dryRun,
|
|
156
334
|
diff,
|
|
157
335
|
delta,
|
|
336
|
+
newOnly,
|
|
337
|
+
failOn: failOnArg as 'critical' | 'warning' | 'note' | 'none' | undefined,
|
|
158
338
|
inlineComments,
|
|
159
339
|
postComments,
|
|
160
|
-
format: formatArg as 'text' | 'sarif' | undefined,
|
|
340
|
+
format: formatArg as 'text' | 'sarif' | 'junit' | undefined,
|
|
161
341
|
outputPath,
|
|
342
|
+
skipReview: staticOnly,
|
|
162
343
|
});
|
|
163
344
|
process.exit(code);
|
|
164
345
|
break;
|
|
@@ -171,6 +352,8 @@ switch (subcommand) {
|
|
|
171
352
|
const noPostComments = boolFlag('no-post-comments');
|
|
172
353
|
const noInlineComments = boolFlag('no-inline-comments');
|
|
173
354
|
const diff = boolFlag('diff');
|
|
355
|
+
const newOnly = boolFlag('new-only');
|
|
356
|
+
const failOnArg = flag('fail-on');
|
|
174
357
|
const code = await runCi({
|
|
175
358
|
configPath: config,
|
|
176
359
|
base,
|
|
@@ -178,6 +361,33 @@ switch (subcommand) {
|
|
|
178
361
|
postComments: noPostComments ? false : undefined,
|
|
179
362
|
inlineComments: noInlineComments ? false : undefined,
|
|
180
363
|
diff,
|
|
364
|
+
newOnly,
|
|
365
|
+
failOn: failOnArg as 'critical' | 'warning' | 'note' | 'none' | undefined,
|
|
366
|
+
});
|
|
367
|
+
process.exit(code);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
case 'baseline': {
|
|
372
|
+
const { runBaseline: rb } = await import('./baseline.ts');
|
|
373
|
+
const sub = args[1] ?? 'show';
|
|
374
|
+
const note = flag('note');
|
|
375
|
+
const config = flag('config');
|
|
376
|
+
const code = await rb(sub, { cwd: process.cwd(), note, baselinePath: config });
|
|
377
|
+
process.exit(code);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
case 'pr': {
|
|
382
|
+
const config = flag('config');
|
|
383
|
+
const noPostComments = boolFlag('no-post-comments');
|
|
384
|
+
const noInlineComments = boolFlag('no-inline-comments');
|
|
385
|
+
const prNumber = args.slice(1).find(a => !a.startsWith('--') && /^\d+$/.test(a));
|
|
386
|
+
const code = await runPr({
|
|
387
|
+
configPath: config,
|
|
388
|
+
prNumber,
|
|
389
|
+
noPostComments,
|
|
390
|
+
noInlineComments,
|
|
181
391
|
});
|
|
182
392
|
process.exit(code);
|
|
183
393
|
break;
|
|
@@ -187,7 +397,11 @@ switch (subcommand) {
|
|
|
187
397
|
const { runHook } = await import('./hook.ts');
|
|
188
398
|
const hookSub = args[1] ?? 'status';
|
|
189
399
|
const force = boolFlag('force');
|
|
190
|
-
const code = await runHook(hookSub, {
|
|
400
|
+
const code = await runHook(hookSub, {
|
|
401
|
+
force,
|
|
402
|
+
preCommitOnly: args.includes('--pre-commit-only'),
|
|
403
|
+
prePushOnly: args.includes('--pre-push-only'),
|
|
404
|
+
});
|
|
191
405
|
process.exit(code);
|
|
192
406
|
break;
|
|
193
407
|
}
|
|
@@ -203,19 +417,67 @@ switch (subcommand) {
|
|
|
203
417
|
const config = flag('config');
|
|
204
418
|
const severityArg = flag('severity');
|
|
205
419
|
if (severityArg && !['critical', 'warning', 'all'].includes(severityArg)) {
|
|
206
|
-
console.error(`\x1b[31m[
|
|
420
|
+
console.error(`\x1b[31m[guardrail] --severity must be "critical", "warning", or "all"\x1b[0m`);
|
|
207
421
|
process.exit(1);
|
|
208
422
|
}
|
|
209
423
|
const dryRun = boolFlag('dry-run');
|
|
424
|
+
const noVerify = boolFlag('no-verify');
|
|
210
425
|
const code = await runFix({
|
|
211
426
|
configPath: config,
|
|
212
427
|
severity: severityArg as 'critical' | 'warning' | 'all' | undefined,
|
|
213
428
|
dryRun,
|
|
429
|
+
noVerify,
|
|
430
|
+
});
|
|
431
|
+
process.exit(code);
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
case 'triage': {
|
|
436
|
+
const sub = args[1];
|
|
437
|
+
const rest = args.slice(2);
|
|
438
|
+
const code = await runTriage(sub, rest);
|
|
439
|
+
process.exit(code);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
case 'test-gen': {
|
|
444
|
+
const config = flag('config');
|
|
445
|
+
const base = flag('base');
|
|
446
|
+
const dryRun = boolFlag('dry-run');
|
|
447
|
+
const verify = boolFlag('verify');
|
|
448
|
+
const targets = args.slice(1).filter(a => !a.startsWith('--') && a !== config && a !== base);
|
|
449
|
+
const code = await runTestGen({
|
|
450
|
+
cwd: process.cwd(),
|
|
451
|
+
configPath: config,
|
|
452
|
+
targets: targets.length > 0 ? targets : undefined,
|
|
453
|
+
base,
|
|
454
|
+
dryRun,
|
|
455
|
+
verify,
|
|
214
456
|
});
|
|
215
457
|
process.exit(code);
|
|
216
458
|
break;
|
|
217
459
|
}
|
|
218
460
|
|
|
461
|
+
case 'pr-desc': {
|
|
462
|
+
const { runPrDesc } = await import('./pr-desc.ts');
|
|
463
|
+
const baseIdx = args.indexOf('--base');
|
|
464
|
+
const base = baseIdx !== -1 ? args[baseIdx + 1] : undefined;
|
|
465
|
+
const outputIdx = args.indexOf('--output');
|
|
466
|
+
const output = outputIdx !== -1 ? args[outputIdx + 1] : undefined;
|
|
467
|
+
await runPrDesc({
|
|
468
|
+
base,
|
|
469
|
+
post: args.includes('--post'),
|
|
470
|
+
yes: args.includes('--yes'),
|
|
471
|
+
output,
|
|
472
|
+
});
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
case 'lsp': {
|
|
477
|
+
await runLsp({ cwd: process.cwd() });
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
|
|
219
481
|
case 'costs': {
|
|
220
482
|
const { runCosts } = await import('./costs.ts');
|
|
221
483
|
const code = await runCosts();
|
|
@@ -223,14 +485,76 @@ switch (subcommand) {
|
|
|
223
485
|
break;
|
|
224
486
|
}
|
|
225
487
|
|
|
488
|
+
case 'report': {
|
|
489
|
+
const outputPath = flag('output');
|
|
490
|
+
const trend = boolFlag('trend');
|
|
491
|
+
const code = await runReport({ output: outputPath, trend });
|
|
492
|
+
process.exit(code);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
case 'explain': {
|
|
497
|
+
const config = flag('config');
|
|
498
|
+
// Target is the first non-flag arg after 'explain'
|
|
499
|
+
const target = args.slice(1).find(a => !a.startsWith('--'));
|
|
500
|
+
const code = await runExplain({ configPath: config, target });
|
|
501
|
+
process.exit(code);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
case 'ignore': {
|
|
506
|
+
const all = boolFlag('all');
|
|
507
|
+
const dryRun = boolFlag('dry-run');
|
|
508
|
+
const code = await runIgnore({ all, dryRun });
|
|
509
|
+
process.exit(code);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
|
|
226
513
|
case 'setup': {
|
|
227
514
|
const force = args.includes('--force');
|
|
228
|
-
|
|
515
|
+
const profileArg = flag('profile');
|
|
516
|
+
if (profileArg && !['security-strict', 'team', 'solo'].includes(profileArg)) {
|
|
517
|
+
console.error(`\x1b[31m[guardrail] --profile must be "security-strict", "team", or "solo"\x1b[0m`);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
await runSetup({ force, profile: profileArg as 'security-strict' | 'team' | 'solo' | undefined });
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
case 'worker': {
|
|
525
|
+
const sub = args[1];
|
|
526
|
+
const config = flag('config');
|
|
527
|
+
const code = await runWorker(sub, { cwd: process.cwd(), configPath: config });
|
|
528
|
+
process.exit(code);
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
case 'council': {
|
|
533
|
+
const config = flag('config');
|
|
534
|
+
const prompt = flag('prompt');
|
|
535
|
+
const contextFile = flag('context-file');
|
|
536
|
+
const dryRun = boolFlag('dry-run');
|
|
537
|
+
const noSynthesize = boolFlag('no-synthesize');
|
|
538
|
+
const code = await runCouncilCmd({
|
|
539
|
+
prompt,
|
|
540
|
+
contextFile,
|
|
541
|
+
configPath: config,
|
|
542
|
+
dryRun,
|
|
543
|
+
noSynthesize,
|
|
544
|
+
});
|
|
545
|
+
process.exit(code);
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
case 'mcp': {
|
|
550
|
+
const { runMcp } = await import('./mcp.ts');
|
|
551
|
+
const configPath = flag('config');
|
|
552
|
+
await runMcp({ cwd: process.cwd(), configPath });
|
|
229
553
|
break;
|
|
230
554
|
}
|
|
231
555
|
|
|
232
556
|
default:
|
|
233
|
-
console.error(`\x1b[31m[
|
|
557
|
+
console.error(`\x1b[31m[guardrail] Unknown subcommand: "${subcommand}"\x1b[0m`);
|
|
234
558
|
printUsage();
|
|
235
559
|
process.exit(1);
|
|
236
560
|
}
|
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
|
|