@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/hook.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export const GUARDRAIL_MARKER = '# guardrail-managed';
|
|
5
|
+
|
|
6
|
+
const PRE_COMMIT_TEMPLATE = `#!/bin/sh
|
|
7
|
+
${GUARDRAIL_MARKER}
|
|
8
|
+
# guardrail pre-commit hook — runs static rules only (<1s, no LLM)
|
|
9
|
+
# --files accepts comma-separated paths; quoting prevents shell word-splitting
|
|
10
|
+
STAGED=$(git diff --cached --name-only --diff-filter=ACM | tr '\\n' ',')
|
|
11
|
+
STAGED=\${STAGED%,}
|
|
12
|
+
if [ -z "$STAGED" ]; then exit 0; fi
|
|
13
|
+
npx guardrail run --static-only --files "$STAGED"
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const PRE_PUSH_TEMPLATE = `#!/bin/sh
|
|
17
|
+
${GUARDRAIL_MARKER}
|
|
18
|
+
# guardrail pre-push hook — full LLM review against upstream
|
|
19
|
+
BASE=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "HEAD~1")
|
|
20
|
+
npx guardrail run --base "$BASE"
|
|
7
21
|
`;
|
|
8
22
|
|
|
9
23
|
function findGitDir(cwd: string): string | null {
|
|
@@ -26,9 +40,39 @@ function findGitDir(cwd: string): string | null {
|
|
|
26
40
|
return null;
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
function isGuardrailHook(hookPath: string): boolean {
|
|
44
|
+
try {
|
|
45
|
+
return fs.readFileSync(hookPath, 'utf8').includes(GUARDRAIL_MARKER);
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeHook(hookPath: string, content: string, force: boolean): boolean {
|
|
52
|
+
if (fs.existsSync(hookPath) && !force && !isGuardrailHook(hookPath)) {
|
|
53
|
+
console.error(`[hook] hook already exists at ${hookPath} (not guardrail-managed)`);
|
|
54
|
+
console.error(' Use --force to overwrite.');
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
fs.mkdirSync(path.dirname(hookPath), { recursive: true });
|
|
58
|
+
fs.writeFileSync(hookPath, content, 'utf8');
|
|
59
|
+
fs.chmodSync(hookPath, 0o755);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function removeHook(hookPath: string): void {
|
|
64
|
+
if (!fs.existsSync(hookPath)) return;
|
|
65
|
+
if (isGuardrailHook(hookPath)) {
|
|
66
|
+
fs.rmSync(hookPath);
|
|
67
|
+
console.log(`[hook] removed ${hookPath}`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`[hook] skipping ${hookPath} — not guardrail-managed`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
29
73
|
export async function runHook(
|
|
30
74
|
sub: string,
|
|
31
|
-
options: { cwd?: string; force?: boolean; silent?: boolean } = {},
|
|
75
|
+
options: { cwd?: string; force?: boolean; silent?: boolean; preCommitOnly?: boolean; prePushOnly?: boolean } = {},
|
|
32
76
|
): Promise<number> {
|
|
33
77
|
const cwd = options.cwd ?? process.cwd();
|
|
34
78
|
const gitDir = findGitDir(cwd);
|
|
@@ -38,42 +82,43 @@ export async function runHook(
|
|
|
38
82
|
return 1;
|
|
39
83
|
}
|
|
40
84
|
|
|
41
|
-
const
|
|
85
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
86
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
87
|
+
const prePushPath = path.join(hooksDir, 'pre-push');
|
|
88
|
+
const force = options.force ?? false;
|
|
42
89
|
|
|
43
90
|
switch (sub) {
|
|
44
91
|
case 'install': {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
92
|
+
const installPreCommit = !options.prePushOnly;
|
|
93
|
+
const installPrePush = !options.preCommitOnly;
|
|
94
|
+
let ok = true;
|
|
95
|
+
if (installPreCommit) {
|
|
96
|
+
const written = writeHook(preCommitPath, PRE_COMMIT_TEMPLATE, force);
|
|
97
|
+
if (written) console.log(`[hook] installed pre-commit hook at ${preCommitPath}`);
|
|
98
|
+
else ok = false;
|
|
49
99
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
100
|
+
if (installPrePush) {
|
|
101
|
+
const written = writeHook(prePushPath, PRE_PUSH_TEMPLATE, force);
|
|
102
|
+
if (written) console.log(`[hook] installed pre-push hook at ${prePushPath}`);
|
|
103
|
+
else ok = false;
|
|
104
|
+
}
|
|
105
|
+
return ok ? 0 : 1;
|
|
55
106
|
}
|
|
56
107
|
case 'uninstall': {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
61
|
-
fs.rmSync(hookPath);
|
|
62
|
-
console.log(`[hook] removed ${hookPath}`);
|
|
108
|
+
removeHook(preCommitPath);
|
|
109
|
+
removeHook(prePushPath);
|
|
63
110
|
return 0;
|
|
64
111
|
}
|
|
65
112
|
case 'status': {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
console.log('[hook] not installed');
|
|
71
|
-
}
|
|
113
|
+
const pcInstalled = fs.existsSync(preCommitPath) && isGuardrailHook(preCommitPath);
|
|
114
|
+
const ppInstalled = fs.existsSync(prePushPath) && isGuardrailHook(prePushPath);
|
|
115
|
+
console.log(`[hook] pre-commit: ${pcInstalled ? 'installed (guardrail-managed)' : 'not installed'}`);
|
|
116
|
+
console.log(`[hook] pre-push: ${ppInstalled ? 'installed (guardrail-managed)' : 'not installed'}`);
|
|
72
117
|
return 0;
|
|
73
118
|
}
|
|
74
119
|
default:
|
|
75
120
|
console.error(`[hook] unknown subcommand: ${sub}`);
|
|
76
|
-
console.error('Usage:
|
|
121
|
+
console.error('Usage: guardrail hook <install|uninstall|status> [--force] [--pre-commit-only] [--pre-push-only]');
|
|
77
122
|
return 1;
|
|
78
123
|
}
|
|
79
124
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as readline from 'node:readline';
|
|
4
|
+
import { loadCachedFindings } from '../core/persist/findings-cache.ts';
|
|
5
|
+
import type { Finding } from '../core/findings/types.ts';
|
|
6
|
+
|
|
7
|
+
const C = {
|
|
8
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
9
|
+
green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m',
|
|
10
|
+
};
|
|
11
|
+
const fmt = (c: keyof typeof C, t: string) => `${C[c]}${t}${C.reset}`;
|
|
12
|
+
|
|
13
|
+
const IGNORE_FILE = '.guardrail-ignore';
|
|
14
|
+
|
|
15
|
+
function readIgnoreFile(cwd: string): string {
|
|
16
|
+
const p = path.join(cwd, IGNORE_FILE);
|
|
17
|
+
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function appendIgnoreRule(cwd: string, rule: string): void {
|
|
21
|
+
const p = path.join(cwd, IGNORE_FILE);
|
|
22
|
+
const existing = readIgnoreFile(cwd);
|
|
23
|
+
const separator = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
24
|
+
fs.appendFileSync(p, `${separator}${rule}\n`, 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isAlreadyIgnored(existing: string, rule: string): boolean {
|
|
28
|
+
return existing.split('\n').some(l => l.trim() === rule);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildRule(finding: Finding, scope: 'path' | 'rule+path' | 'rule'): string {
|
|
32
|
+
if (scope === 'path') {
|
|
33
|
+
const dir = path.dirname(finding.file);
|
|
34
|
+
return dir === '.' ? finding.file : `${dir}/**`;
|
|
35
|
+
}
|
|
36
|
+
if (scope === 'rule') return `${finding.id} **`;
|
|
37
|
+
// rule+path: most specific
|
|
38
|
+
const dir = path.dirname(finding.file);
|
|
39
|
+
const glob = dir === '.' ? finding.file : `${dir}/**`;
|
|
40
|
+
return `${finding.id} ${glob}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function prompt(question: string): Promise<string> {
|
|
44
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
45
|
+
return new Promise(resolve => {
|
|
46
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface IgnoreCommandOptions {
|
|
51
|
+
cwd?: string;
|
|
52
|
+
all?: boolean; // suppress all findings without prompting
|
|
53
|
+
dryRun?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function runIgnore(options: IgnoreCommandOptions = {}): Promise<number> {
|
|
57
|
+
const cwd = options.cwd ?? process.cwd();
|
|
58
|
+
const findings = loadCachedFindings(cwd);
|
|
59
|
+
|
|
60
|
+
if (findings.length === 0) {
|
|
61
|
+
console.log(fmt('yellow', '[ignore] No cached findings — run `guardrail run` or `guardrail scan` first.'));
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const existing = readIgnoreFile(cwd);
|
|
66
|
+
let added = 0;
|
|
67
|
+
|
|
68
|
+
console.log(`\n${fmt('bold', '[guardrail ignore]')} ${findings.length} finding${findings.length !== 1 ? 's' : ''} to review\n`);
|
|
69
|
+
|
|
70
|
+
for (const [i, f] of findings.entries()) {
|
|
71
|
+
const sev = f.severity === 'critical' ? fmt('red', 'CRITICAL')
|
|
72
|
+
: f.severity === 'warning' ? fmt('yellow', 'WARNING ')
|
|
73
|
+
: fmt('dim', 'NOTE ');
|
|
74
|
+
const loc = f.file !== '<unspecified>' ? `${f.file}${f.line ? `:${f.line}` : ''}` : '<pipeline>';
|
|
75
|
+
|
|
76
|
+
console.log(`\n${fmt('bold', `${i + 1}/${findings.length}`)} [${sev}] ${f.message}`);
|
|
77
|
+
console.log(fmt('dim', ` ${loc} (rule: ${f.id})`));
|
|
78
|
+
|
|
79
|
+
let scope: string;
|
|
80
|
+
if (options.all) {
|
|
81
|
+
scope = 'r'; // rule+path
|
|
82
|
+
} else {
|
|
83
|
+
console.log(fmt('dim', ' [s] skip [p] suppress path [r] suppress rule+path [R] suppress rule everywhere [q] quit'));
|
|
84
|
+
scope = await prompt(' > ');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (scope === 'q') break;
|
|
88
|
+
if (scope === 's' || scope === '') continue;
|
|
89
|
+
|
|
90
|
+
const ruleScope = scope === 'p' ? 'path' : scope === 'R' ? 'rule' : 'rule+path';
|
|
91
|
+
const rule = buildRule(f, ruleScope);
|
|
92
|
+
|
|
93
|
+
if (isAlreadyIgnored(existing, rule)) {
|
|
94
|
+
console.log(fmt('dim', ` (already in ${IGNORE_FILE}: ${rule})`));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.dryRun) {
|
|
99
|
+
console.log(fmt('dim', ` (dry run) would add: ${rule}`));
|
|
100
|
+
} else {
|
|
101
|
+
appendIgnoreRule(cwd, rule);
|
|
102
|
+
console.log(fmt('green', ` + added: ${rule}`));
|
|
103
|
+
}
|
|
104
|
+
added++;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('');
|
|
108
|
+
if (options.dryRun) {
|
|
109
|
+
console.log(fmt('yellow', `[ignore] Dry run — ${added} rule${added !== 1 ? 's' : ''} would be added to ${IGNORE_FILE}\n`));
|
|
110
|
+
} else if (added > 0) {
|
|
111
|
+
console.log(fmt('green', `[ignore] ${added} rule${added !== 1 ? 's' : ''} added to ${IGNORE_FILE}\n`));
|
|
112
|
+
} else {
|
|
113
|
+
console.log(fmt('dim', `[ignore] No rules added\n`));
|
|
114
|
+
}
|
|
115
|
+
return 0;
|
|
116
|
+
}
|