@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +164 -106
  3. package/bin/_launcher.js +77 -0
  4. package/bin/claude-autopilot.js +3 -0
  5. package/bin/guardrail.js +3 -0
  6. package/package.json +15 -9
  7. package/presets/generic/guardrail.config.yaml +35 -0
  8. package/presets/generic/stack.md +40 -0
  9. package/presets/nextjs-supabase/{autopilot.config.yaml → guardrail.config.yaml} +7 -0
  10. package/scripts/autoregress.ts +27 -11
  11. package/skills/autopilot/SKILL.md +170 -0
  12. package/skills/claude-autopilot.md +80 -0
  13. package/skills/guardrail.md +39 -0
  14. package/skills/migrate/SKILL.md +83 -0
  15. package/src/adapters/council/claude.ts +41 -0
  16. package/src/adapters/council/openai.ts +40 -0
  17. package/src/adapters/council/types.ts +7 -0
  18. package/src/adapters/loader.ts +7 -7
  19. package/src/adapters/review-engine/auto.ts +2 -2
  20. package/src/adapters/review-engine/claude.ts +9 -11
  21. package/src/adapters/review-engine/codex.ts +9 -11
  22. package/src/adapters/review-engine/gemini.ts +9 -11
  23. package/src/adapters/review-engine/openai-compatible.ts +10 -12
  24. package/src/adapters/review-engine/parse-output.ts +32 -6
  25. package/src/adapters/review-engine/prompt-builder.ts +19 -0
  26. package/src/adapters/review-engine/types.ts +1 -1
  27. package/src/adapters/vcs-host/commit-status.ts +39 -0
  28. package/src/adapters/vcs-host/github.ts +2 -2
  29. package/src/cli/baseline.ts +125 -0
  30. package/src/cli/ci.ts +11 -8
  31. package/src/cli/costs.ts +80 -0
  32. package/src/cli/council.ts +96 -0
  33. package/src/cli/detector.ts +21 -5
  34. package/src/cli/explain.ts +197 -0
  35. package/src/cli/fix.ts +249 -0
  36. package/src/cli/hook.ts +72 -27
  37. package/src/cli/ignore-helper.ts +116 -0
  38. package/src/cli/index.ts +302 -28
  39. package/src/cli/init.ts +12 -12
  40. package/src/cli/lsp.ts +200 -0
  41. package/src/cli/mcp.ts +206 -0
  42. package/src/cli/pr-comment.ts +5 -5
  43. package/src/cli/pr-desc.ts +168 -0
  44. package/src/cli/pr-review-comments.ts +3 -3
  45. package/src/cli/pr.ts +76 -0
  46. package/src/cli/preflight.ts +15 -32
  47. package/src/cli/report.ts +186 -0
  48. package/src/cli/run.ts +140 -36
  49. package/src/cli/scan.ts +233 -0
  50. package/src/cli/setup.ts +121 -15
  51. package/src/cli/test-gen.ts +125 -0
  52. package/src/cli/triage.ts +137 -0
  53. package/src/cli/watch.ts +52 -31
  54. package/src/cli/worker.ts +109 -0
  55. package/src/core/cache/review-cache.ts +2 -2
  56. package/src/core/chunking/index.ts +2 -2
  57. package/src/core/config/loader.ts +24 -12
  58. package/src/core/config/preset-resolver.ts +6 -6
  59. package/src/core/config/schema.ts +121 -3
  60. package/src/core/config/types.ts +57 -2
  61. package/src/core/council/config.ts +71 -0
  62. package/src/core/council/context.ts +17 -0
  63. package/src/core/council/runner.ts +83 -0
  64. package/src/core/council/types.ts +45 -0
  65. package/src/core/detect/llm-key.ts +89 -0
  66. package/src/core/detect/workspaces.ts +103 -0
  67. package/src/core/errors.ts +4 -4
  68. package/src/core/fix/generator.ts +149 -0
  69. package/src/core/ignore/index.ts +4 -4
  70. package/src/core/mcp/concurrency.ts +16 -0
  71. package/src/core/mcp/handlers/fix-finding.ts +126 -0
  72. package/src/core/mcp/handlers/get-capabilities.ts +62 -0
  73. package/src/core/mcp/handlers/get-findings.ts +36 -0
  74. package/src/core/mcp/handlers/review-diff.ts +65 -0
  75. package/src/core/mcp/handlers/scan-files.ts +65 -0
  76. package/src/core/mcp/handlers/validate-fix.ts +41 -0
  77. package/src/core/mcp/run-store.ts +85 -0
  78. package/src/core/mcp/workspace.ts +35 -0
  79. package/src/core/persist/baseline.ts +112 -0
  80. package/src/core/persist/cost-log.ts +1 -1
  81. package/src/core/persist/findings-cache.ts +1 -1
  82. package/src/core/persist/triage.ts +112 -0
  83. package/src/core/phases/static-rules.ts +18 -5
  84. package/src/core/pipeline/review-phase.ts +65 -26
  85. package/src/core/pipeline/run.ts +42 -10
  86. package/src/core/runtime/lock.ts +2 -2
  87. package/src/core/runtime/state.ts +2 -2
  88. package/src/core/schema-alignment/detector.ts +59 -0
  89. package/src/core/schema-alignment/extractor/index.ts +24 -0
  90. package/src/core/schema-alignment/extractor/prisma.ts +21 -0
  91. package/src/core/schema-alignment/extractor/sql.ts +99 -0
  92. package/src/core/schema-alignment/llm-check.ts +91 -0
  93. package/src/core/schema-alignment/scanner.ts +107 -0
  94. package/src/core/schema-alignment/types.ts +43 -0
  95. package/src/core/shell.ts +3 -3
  96. package/src/core/static-rules/registry.ts +17 -8
  97. package/src/core/static-rules/rules/brand-tokens.ts +145 -0
  98. package/src/core/static-rules/rules/hardcoded-secrets.ts +27 -1
  99. package/src/core/static-rules/rules/insecure-redirect.ts +67 -0
  100. package/src/core/static-rules/rules/missing-auth.ts +70 -0
  101. package/src/core/static-rules/rules/schema-alignment.ts +132 -0
  102. package/src/core/static-rules/rules/sql-injection.ts +71 -0
  103. package/src/core/static-rules/rules/ssrf.ts +63 -0
  104. package/src/core/static-rules/tailwind-extractor.ts +38 -0
  105. package/src/core/test-gen/coverage-analyzer.ts +93 -0
  106. package/src/core/test-gen/framework-detector.ts +21 -0
  107. package/src/core/test-gen/test-writer.ts +33 -0
  108. package/src/core/ui/design-context-loader.ts +87 -0
  109. package/src/core/worker/client.ts +46 -0
  110. package/src/core/worker/lockfile.ts +38 -0
  111. package/src/core/worker/server.ts +81 -0
  112. package/src/formatters/junit.ts +52 -0
  113. package/src/formatters/sarif.ts +2 -2
  114. package/src/index.ts +1 -2
  115. package/tests/snapshots/baselines/src-formatters-sarif.json +4 -4
  116. package/tests/snapshots/index.json +3 -3
  117. package/tests/snapshots/src-formatters-sarif.snap.ts +1 -1
  118. package/tests/snapshots/src-snapshots-impact-selector.snap.ts +3 -3
  119. package/tests/snapshots/src-snapshots-import-scanner.snap.ts +3 -3
  120. package/tests/snapshots/src-snapshots-serializer.snap.ts +2 -2
  121. package/bin/autopilot.js +0 -20
  122. package/skills/autopilot.md +0 -157
  123. /package/presets/go/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
  124. /package/presets/python-fastapi/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
  125. /package/presets/rails-postgres/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
  126. /package/presets/t3/{autopilot.config.yaml → guardrail.config.yaml} +0 -0
  127. /package/{src → scripts}/snapshots/impact-selector.ts +0 -0
  128. /package/{src → scripts}/snapshots/import-scanner.ts +0 -0
  129. /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 HOOK_CONTENT = `#!/bin/sh
5
- # autopilot pre-push hook — runs impact-selected snapshots before push
6
- npx tsx scripts/autoregress.ts run
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 hookPath = path.join(gitDir, 'hooks', 'pre-push');
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
- if (fs.existsSync(hookPath) && !options.force) {
46
- console.error(`[hook] pre-push hook already exists at ${hookPath}`);
47
- console.error(' Use --force to overwrite.');
48
- return 1;
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
- fs.mkdirSync(path.dirname(hookPath), { recursive: true });
51
- fs.writeFileSync(hookPath, HOOK_CONTENT, 'utf8');
52
- fs.chmodSync(hookPath, 0o755);
53
- console.log(`[hook] installed pre-push hook at ${hookPath}`);
54
- return 0;
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
- if (!fs.existsSync(hookPath)) {
58
- console.log('[hook] no pre-push hook installed');
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
- if (fs.existsSync(hookPath)) {
67
- console.log(`[hook] installed at ${hookPath}`);
68
- console.log(fs.readFileSync(hookPath, 'utf8'));
69
- } else {
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: autopilot hook <install|uninstall|status> [--force]');
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
+ }