@delegance/claude-autopilot 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.0] — 2026-04-21
4
+
5
+ ### Added
6
+ - `autopilot setup` — zero-prompt setup: auto-detects project type (Go, Rails, FastAPI, T3, Next.js+Supabase), infers test command, writes config, installs git hook in one command
7
+ - `autopilot setup --force` — overwrite existing config
8
+
3
9
  ## [1.0.2] — 2026-04-21
4
10
 
5
11
  ### Fixed
package/README.md CHANGED
@@ -13,19 +13,15 @@ Requires Node 22+. Also requires `gh` CLI authenticated and `claude` CLI install
13
13
  ## Quick Start
14
14
 
15
15
  ```bash
16
- # Scaffold config
17
- npx autopilot init
16
+ # One command — auto-detects project type, writes config, installs hook
17
+ npx autopilot setup
18
18
 
19
- # Run on changed files
19
+ # Then run your first pipeline
20
20
  npx autopilot run
21
-
22
- # Watch mode (re-runs on every file save)
23
- npx autopilot watch
24
-
25
- # Install pre-push hook
26
- npx autopilot hook install
27
21
  ```
28
22
 
23
+ Requires Node 22+, `gh` CLI authenticated, `claude` CLI (Claude Code).
24
+
29
25
  ## Commands
30
26
 
31
27
  ### `autopilot run`
@@ -77,6 +73,17 @@ npx autopilot autoregress generate --files src/foo.ts,src/bar.ts
77
73
 
78
74
  Requires `OPENAI_API_KEY` for `generate` mode.
79
75
 
76
+ ### `autopilot setup`
77
+
78
+ Zero-prompt setup: auto-detects project type, writes config, installs git hook in one command.
79
+
80
+ ```bash
81
+ npx autopilot setup # Auto-detect project, write config, install hook
82
+ npx autopilot setup --force # Overwrite existing autopilot.config.yaml
83
+ ```
84
+
85
+ Auto-detection supports: Go, Rails, FastAPI, T3, Next.js+Supabase.
86
+
80
87
  ### `autopilot init`
81
88
 
82
89
  Scaffolds `autopilot.config.yaml` from a preset.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code automation pipeline: spec \u2192 plan \u2192 implement \u2192 validate \u2192 PR",
6
6
  "keywords": [
@@ -0,0 +1,72 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ export interface DetectionResult {
5
+ preset: string;
6
+ testCommand: string;
7
+ confidence: 'high' | 'low';
8
+ evidence: string;
9
+ }
10
+
11
+ function readJson(filePath: string): Record<string, unknown> | null {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ function fileContains(filePath: string, needle: string): boolean {
20
+ try {
21
+ return fs.readFileSync(filePath, 'utf8').includes(needle);
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ function nodeTestCommand(cwd: string): string {
28
+ const pkg = readJson(path.join(cwd, 'package.json'));
29
+ const scripts = pkg?.['scripts'] as Record<string, string> | undefined;
30
+ return scripts?.['test'] ?? 'npm test';
31
+ }
32
+
33
+ export function detectProject(cwd: string): DetectionResult {
34
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) {
35
+ return { preset: 'go', testCommand: 'go test ./...', confidence: 'high', evidence: 'found go.mod' };
36
+ }
37
+
38
+ const gemfile = path.join(cwd, 'Gemfile');
39
+ if (fs.existsSync(gemfile) && fileContains(gemfile, 'rails')) {
40
+ return { preset: 'rails-postgres', testCommand: 'bundle exec rails test', confidence: 'high', evidence: "found Gemfile with 'rails'" };
41
+ }
42
+
43
+ const reqTxt = path.join(cwd, 'requirements.txt');
44
+ const pyproject = path.join(cwd, 'pyproject.toml');
45
+ if ((fs.existsSync(reqTxt) && fileContains(reqTxt, 'fastapi')) ||
46
+ (fs.existsSync(pyproject) && fileContains(pyproject, 'fastapi'))) {
47
+ return { preset: 'python-fastapi', testCommand: 'pytest', confidence: 'high', evidence: 'found fastapi in requirements' };
48
+ }
49
+
50
+ const pkgPath = path.join(cwd, 'package.json');
51
+ if (fs.existsSync(pkgPath)) {
52
+ const pkg = readJson(pkgPath);
53
+ const deps = {
54
+ ...(pkg?.['dependencies'] as Record<string, string> ?? {}),
55
+ ...(pkg?.['devDependencies'] as Record<string, string> ?? {}),
56
+ };
57
+ const testCmd = nodeTestCommand(cwd);
58
+
59
+ if ('@trpc/server' in deps) {
60
+ return { preset: 't3', testCommand: testCmd, confidence: 'high', evidence: 'found @trpc/server in package.json' };
61
+ }
62
+ if ('next' in deps && '@supabase/supabase-js' in deps) {
63
+ return { preset: 'nextjs-supabase', testCommand: testCmd, confidence: 'high', evidence: 'found next + @supabase/supabase-js in package.json' };
64
+ }
65
+ if ('next' in deps) {
66
+ return { preset: 'nextjs-supabase', testCommand: testCmd, confidence: 'low', evidence: 'found next in package.json (no supabase detected)' };
67
+ }
68
+ return { preset: 'nextjs-supabase', testCommand: testCmd, confidence: 'low', evidence: 'found package.json (no strong framework signals)' };
69
+ }
70
+
71
+ return { preset: 'nextjs-supabase', testCommand: 'npm test', confidence: 'low', evidence: 'no project signals found — using default preset' };
72
+ }
package/src/cli/index.ts CHANGED
@@ -13,10 +13,11 @@
13
13
  import { runInit } from './init.ts';
14
14
  import { runCommand } from './run.ts';
15
15
  import { runWatch } from './watch.ts';
16
+ import { runSetup } from './setup.ts';
16
17
 
17
18
  const args = process.argv.slice(2);
18
19
 
19
- const SUBCOMMANDS = ['init', 'run', 'watch', 'hook', 'autoregress', 'preflight', 'help', '--help', '-h'] as const;
20
+ const SUBCOMMANDS = ['init', 'run', 'watch', 'hook', 'autoregress', 'preflight', 'setup', 'help', '--help', '-h'] as const;
20
21
  const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce'];
21
22
 
22
23
  // Detect first non-flag arg as subcommand, default to 'run'
@@ -141,6 +142,12 @@ switch (subcommand) {
141
142
  break;
142
143
  }
143
144
 
145
+ case 'setup': {
146
+ const force = args.includes('--force');
147
+ await runSetup({ force });
148
+ break;
149
+ }
150
+
144
151
  default:
145
152
  console.error(`\x1b[31m[autopilot] Unknown subcommand: "${subcommand}"\x1b[0m`);
146
153
  printUsage();
@@ -0,0 +1,81 @@
1
+ import * as fs from 'node:fs';
2
+ import * as fsAsync from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { detectProject } from './detector.ts';
6
+ import { runHook } from './hook.ts';
7
+
8
+ const PASS = '\x1b[32m✓\x1b[0m';
9
+ const WARN = '\x1b[33m!\x1b[0m';
10
+
11
+ const PRESET_LABELS: Record<string, string> = {
12
+ 'nextjs-supabase': 'Next.js + Supabase',
13
+ 't3': 'T3 Stack (Next.js + tRPC + Prisma)',
14
+ 'rails-postgres': 'Ruby on Rails + PostgreSQL',
15
+ 'python-fastapi': 'Python FastAPI',
16
+ 'go': 'Go + PostgreSQL',
17
+ };
18
+
19
+ export interface SetupOptions {
20
+ cwd?: string;
21
+ force?: boolean;
22
+ skipHook?: boolean;
23
+ }
24
+
25
+ function presetSearchPaths(name: string, cwd: string): string[] {
26
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
27
+ return [
28
+ path.join(pkgRoot, 'presets', name, 'autopilot.config.yaml'),
29
+ path.join(cwd, 'node_modules', '@delegance', 'claude-autopilot', 'presets', name, 'autopilot.config.yaml'),
30
+ ];
31
+ }
32
+
33
+ function findPresetConfig(name: string, cwd: string): string | null {
34
+ for (const p of presetSearchPaths(name, cwd)) {
35
+ if (fs.existsSync(p)) return p;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ export async function runSetup(options: SetupOptions = {}): Promise<void> {
41
+ const cwd = options.cwd ?? process.cwd();
42
+ const dest = path.join(cwd, 'autopilot.config.yaml');
43
+
44
+ if (fs.existsSync(dest) && !options.force) {
45
+ throw new Error('autopilot.config.yaml already exists — use --force to overwrite');
46
+ }
47
+
48
+ console.log('\n[setup] Detecting project type...');
49
+
50
+ const detection = detectProject(cwd);
51
+ const label = PRESET_LABELS[detection.preset] ?? detection.preset;
52
+
53
+ if (detection.confidence === 'high') {
54
+ console.log(` ${PASS} ${label} (${detection.evidence})`);
55
+ } else {
56
+ console.log(` ${WARN} ${label} — no strong signals found, defaulted to ${detection.preset}`);
57
+ console.log(` \x1b[2mEdit autopilot.config.yaml to switch presets if needed\x1b[0m`);
58
+ }
59
+ console.log(` ${PASS} Test command: ${detection.testCommand}`);
60
+
61
+ const presetConfigPath = findPresetConfig(detection.preset, cwd);
62
+ if (!presetConfigPath) {
63
+ throw new Error(`Preset config not found for: ${detection.preset}. Looked in:\n ${presetSearchPaths(detection.preset, cwd).join('\n ')}`);
64
+ }
65
+
66
+ let presetContent = await fsAsync.readFile(presetConfigPath, 'utf8');
67
+ presetContent = presetContent.trimEnd() + `\ntestCommand: "${detection.testCommand}"\n`;
68
+ await fsAsync.writeFile(dest, presetContent, 'utf8');
69
+ console.log(` ${PASS} Created autopilot.config.yaml`);
70
+
71
+ if (!options.skipHook) {
72
+ const hookCode = await runHook('install', { cwd });
73
+ if (hookCode === 0) {
74
+ console.log(` ${PASS} Installed pre-push git hook`);
75
+ } else {
76
+ console.log(` ${WARN} Hook install failed (not fatal — run: npx autopilot hook install)`);
77
+ }
78
+ }
79
+
80
+ console.log('\n[setup] Done. Run: npx autopilot run\n');
81
+ }