@delegance/claude-autopilot 5.2.1 → 5.5.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +49 -17
  3. package/dist/src/adapters/council/claude.js +2 -1
  4. package/dist/src/adapters/council/openai.js +2 -1
  5. package/dist/src/adapters/deploy/generic.d.ts +39 -0
  6. package/dist/src/adapters/deploy/generic.js +98 -0
  7. package/dist/src/adapters/deploy/index.d.ts +13 -0
  8. package/dist/src/adapters/deploy/index.js +45 -0
  9. package/dist/src/adapters/deploy/types.d.ts +157 -0
  10. package/dist/src/adapters/deploy/types.js +15 -0
  11. package/dist/src/adapters/deploy/vercel.d.ts +127 -0
  12. package/dist/src/adapters/deploy/vercel.js +446 -0
  13. package/dist/src/adapters/review-engine/claude.js +2 -1
  14. package/dist/src/adapters/review-engine/codex.js +2 -1
  15. package/dist/src/adapters/review-engine/gemini.js +2 -1
  16. package/dist/src/adapters/review-engine/openai-compatible.js +2 -1
  17. package/dist/src/adapters/sdk-loader.d.ts +15 -0
  18. package/dist/src/adapters/sdk-loader.js +77 -0
  19. package/dist/src/cli/costs.js +4 -2
  20. package/dist/src/cli/deploy.d.ts +71 -0
  21. package/dist/src/cli/deploy.js +514 -0
  22. package/dist/src/cli/index.js +91 -3
  23. package/dist/src/cli/pr.js +8 -2
  24. package/dist/src/cli/preflight.js +76 -1
  25. package/dist/src/core/config/schema.d.ts +34 -0
  26. package/dist/src/core/config/schema.js +18 -0
  27. package/dist/src/core/config/types.d.ts +6 -0
  28. package/dist/src/core/errors.d.ts +1 -1
  29. package/dist/src/core/errors.js +1 -0
  30. package/dist/src/core/migrate/detector-rules.js +6 -0
  31. package/dist/src/core/migrate/schema-validator.js +7 -0
  32. package/dist/src/core/persist/cost-log.js +8 -0
  33. package/package.json +8 -5
  34. package/scripts/autoregress.ts +2 -1
  35. package/skills/migrate/SKILL.md +193 -47
@@ -30,6 +30,7 @@ import { runMigrateV4 } from "./migrate-v4.js";
30
30
  import { runMigrateDoctor } from "./migrate-doctor.js";
31
31
  import { initMigrate, NoMigrationToolDetectedError } from "./init-migrate.js";
32
32
  import { dispatch as runMigrateDispatch } from "../core/migrate/dispatcher.js";
33
+ import { runDeploy, runDeployRollback, runDeployStatus } from "./deploy.js";
33
34
  import { findPackageRoot } from "./_pkg-root.js";
34
35
  import { GuardrailError } from "../core/errors.js";
35
36
  // Format unhandled errors as a one-line user-facing message instead of dumping a
@@ -159,8 +160,8 @@ These are aliases for the flat subcommands; they still work without the 'advance
159
160
  }
160
161
  args.shift(); // drop 'advanced'
161
162
  }
162
- 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', 'migrate-v4', 'migrate', 'migrate-doctor', 'brainstorm', 'help', '--help', '-h'];
163
- const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce', 'ask', 'focus', 'fail-on', 'note', 'reason', 'expires', 'profile', 'severity', 'prompt', 'context-file', 'path'];
163
+ 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', 'migrate-v4', 'migrate', 'migrate-doctor', 'deploy', 'brainstorm', 'help', '--help', '-h'];
164
+ const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce', 'ask', 'focus', 'fail-on', 'note', 'reason', 'expires', 'profile', 'severity', 'prompt', 'context-file', 'path', 'adapter', 'ref', 'sha'];
164
165
  // Bare invocation — no subcommand, no flags → show welcome guide
165
166
  if (args.length === 0) {
166
167
  const hasKey = !!(process.env.ANTHROPIC_API_KEY || process.env.GEMINI_API_KEY ||
@@ -257,6 +258,7 @@ Commands:
257
258
  init Scaffold guardrail.config.yaml + auto-detect migrate stack (writes .autopilot/stack.md)
258
259
  migrate Run database migrations via the stack-aware dispatcher
259
260
  migrate doctor Validate .autopilot/stack.md and skill manifests (alias: migrate-doctor)
261
+ deploy Deploy via configured adapter (vercel | generic) — also: rollback, status
260
262
  setup Auto-detect stack, write config, install pre-push hook
261
263
  doctor Check prerequisites (alias: preflight)
262
264
  preflight Check prerequisites (alias: doctor)
@@ -319,6 +321,21 @@ Options (migrate):
319
321
 
320
322
  Options (migrate doctor / migrate-doctor):
321
323
  --fix Apply auto-fixable mutations (legacy stack.md, skills/migrate/, schema_version)
324
+
325
+ Options (deploy):
326
+ --adapter <vercel|generic> Override deploy.adapter from config
327
+ --config <path> Path to config file
328
+ --ref <ref> Git ref (branch / tag) to deploy
329
+ --sha <commit> Specific commit SHA to deploy
330
+ --watch Stream build logs to stderr in real time (Vercel only)
331
+ --to <deploy-id> Target deploy ID for 'deploy rollback'
332
+ --pr <n> Post upserting deploy summary comment on the PR
333
+
334
+ Subcommands (deploy):
335
+ deploy Deploy via configured adapter
336
+ deploy rollback Roll back to previous prod deploy
337
+ deploy rollback --to <id> Roll back to a specific deploy
338
+ deploy status Show current prod + last 5 builds
322
339
  `);
323
340
  }
324
341
  switch (subcommand) {
@@ -638,7 +655,23 @@ switch (subcommand) {
638
655
  break;
639
656
  }
640
657
  case 'mcp': {
641
- const { runMcp } = await import("./mcp.js");
658
+ let runMcp;
659
+ try {
660
+ ({ runMcp } = await import("./mcp.js"));
661
+ }
662
+ catch (err) {
663
+ const code = err.code;
664
+ const msg = err.message ?? String(err);
665
+ // The mcp module imports @modelcontextprotocol/sdk at the top — if the
666
+ // package was installed with --omit=optional the dynamic import surfaces
667
+ // ERR_MODULE_NOT_FOUND naming the SDK. Translate to a friendly hint.
668
+ if ((code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') && /modelcontextprotocol/.test(msg)) {
669
+ console.error('\x1b[31m[claude-autopilot] mcp subcommand requires @modelcontextprotocol/sdk\x1b[0m');
670
+ console.error(' install: npm install @modelcontextprotocol/sdk');
671
+ process.exit(1);
672
+ }
673
+ throw err;
674
+ }
642
675
  const configPath = flag('config');
643
676
  await runMcp({ cwd: process.cwd(), configPath });
644
677
  break;
@@ -705,6 +738,61 @@ switch (subcommand) {
705
738
  await runMigrateDoctorCLI();
706
739
  break;
707
740
  }
741
+ case 'deploy': {
742
+ const config = flag('config');
743
+ const adapterArg = flag('adapter');
744
+ if (adapterArg && !['vercel', 'generic'].includes(adapterArg)) {
745
+ console.error(`\x1b[31m[claude-autopilot] --adapter must be "vercel" or "generic"\x1b[0m`);
746
+ process.exit(1);
747
+ }
748
+ // Phase 3 — `deploy rollback` and `deploy status` subverbs. The first
749
+ // non-flag positional after `deploy` selects the verb. The historic
750
+ // `claude-autopilot deploy` (no subverb) keeps calling runDeploy.
751
+ const subverb = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
752
+ if (subverb === 'rollback') {
753
+ const to = flag('to');
754
+ const code = await runDeployRollback({
755
+ configPath: config,
756
+ adapterOverride: adapterArg,
757
+ to,
758
+ });
759
+ process.exit(code);
760
+ }
761
+ if (subverb === 'status') {
762
+ const code = await runDeployStatus({
763
+ configPath: config,
764
+ adapterOverride: adapterArg,
765
+ });
766
+ process.exit(code);
767
+ }
768
+ if (subverb !== undefined) {
769
+ console.error(`\x1b[31m[claude-autopilot] unknown deploy subverb "${subverb}" — valid: rollback, status\x1b[0m`);
770
+ process.exit(1);
771
+ }
772
+ const ref = flag('ref');
773
+ const commitSha = flag('sha');
774
+ const watch = boolFlag('watch');
775
+ const prRaw = flag('pr');
776
+ let prNum;
777
+ if (prRaw !== undefined) {
778
+ const n = parseInt(prRaw, 10);
779
+ if (Number.isNaN(n) || n <= 0) {
780
+ console.error(`\x1b[31m[claude-autopilot] --pr must be a positive integer, got "${prRaw}"\x1b[0m`);
781
+ process.exit(1);
782
+ }
783
+ prNum = n;
784
+ }
785
+ const code = await runDeploy({
786
+ configPath: config,
787
+ adapterOverride: adapterArg,
788
+ ref,
789
+ commitSha,
790
+ watch,
791
+ pr: prNum,
792
+ });
793
+ process.exit(code);
794
+ break;
795
+ }
708
796
  case 'brainstorm': {
709
797
  // `brainstorm` is the front of the pipeline and is implemented as a Claude
710
798
  // Code skill (superpowers:brainstorming → autopilot), not a standalone CLI.
@@ -25,9 +25,15 @@ function gitFetch(remote, ref, cwd) {
25
25
  export async function runPr(options = {}) {
26
26
  const cwd = options.cwd ?? process.cwd();
27
27
  const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
28
+ // 5.2.2 — pr previously hard-failed when guardrail.config.yaml was missing
29
+ // ("not found at <path>"). The first-run UX surprised users running
30
+ // `claude-autopilot pr <n>` on a fresh repo: setup hadn't been invoked, and
31
+ // the error didn't say to invoke it. Now matches `run`'s behavior — defer
32
+ // config-loading to the underlying runCommand, which auto-detects stack +
33
+ // testCommand when the file is missing.
28
34
  if (!fs.existsSync(configPath)) {
29
- console.error(fmt('red', `[pr] guardrail.config.yaml not found at ${configPath}`));
30
- return 1;
35
+ console.log(fmt('dim', `[pr] guardrail.config.yaml not found auto-detecting from stack signals.`));
36
+ console.log(fmt('dim', ` Run \`claude-autopilot setup\` first to commit a config.`));
31
37
  }
32
38
  // Resolve PR number
33
39
  let prNumber = options.prNumber;
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
5
5
  import { runSafe } from "../core/shell.js";
6
6
  import { detectLLMKey, loadEnvFile, LLM_KEY_NAMES } from "../core/detect/llm-key.js";
7
7
  import { findPackageRoot } from "./_pkg-root.js";
8
+ import { isSdkInstalled } from "../adapters/sdk-loader.js";
8
9
  const PASS = '\x1b[32m✓\x1b[0m';
9
10
  const FAIL = '\x1b[31m✗\x1b[0m';
10
11
  const WARN = '\x1b[33m!\x1b[0m';
@@ -29,6 +30,39 @@ function skillRoots() {
29
30
  roots.push(path.join(home, '.claude', 'plugins'));
30
31
  return roots.filter(p => fs.existsSync(p));
31
32
  }
33
+ /**
34
+ * Reads `deploy.adapter` from guardrail.config.yaml without pulling in the
35
+ * full config loader (which validates against a JSON schema and would noise
36
+ * up the doctor output). Returns `null` if the config or field is absent.
37
+ */
38
+ function readDeployAdapter(configPath) {
39
+ if (!fs.existsSync(configPath))
40
+ return null;
41
+ try {
42
+ const text = fs.readFileSync(configPath, 'utf8');
43
+ // Match `deploy:` block then look for `adapter:` two lines down. Cheap
44
+ // line-based parse — avoids js-yaml dependency in the doctor path.
45
+ const lines = text.split(/\r?\n/);
46
+ let inDeploy = false;
47
+ for (const line of lines) {
48
+ if (/^deploy\s*:\s*$/.test(line)) {
49
+ inDeploy = true;
50
+ continue;
51
+ }
52
+ if (inDeploy && /^\S/.test(line))
53
+ inDeploy = false; // dedented out of block
54
+ if (inDeploy) {
55
+ const m = line.match(/^\s+adapter\s*:\s*['"]?([a-zA-Z0-9_-]+)['"]?\s*$/);
56
+ if (m)
57
+ return m[1] ?? null;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
32
66
  export function findMissingSuperpowersSkills() {
33
67
  // Traverse each root once, collect all discovered skill names, then diff against
34
68
  // the required set. Previous implementation did N × roots separate recursive walks.
@@ -173,7 +207,48 @@ export async function runDoctor() {
173
207
  ? 'git user.name / user.email not set — commits will fail.'
174
208
  : undefined,
175
209
  });
176
- // 9. Superpowers plugin required for pipeline phases, optional for review-only use
210
+ // 9. LLM SDK install state surfaces which providers are usable. After
211
+ // the v5.5 lazy-load refactor, three SDKs moved to optionalDependencies;
212
+ // `--omit=optional` users may have removed them. This check shows users
213
+ // which providers will work without `npm install <sdk>`.
214
+ const sdkChecks = [
215
+ { pkg: '@anthropic-ai/sdk', provider: 'claude' },
216
+ { pkg: 'openai', provider: 'openai/codex' },
217
+ { pkg: '@google/generative-ai', provider: 'gemini' },
218
+ ];
219
+ const installed = [];
220
+ const missing = [];
221
+ for (const { pkg } of sdkChecks) {
222
+ if (await isSdkInstalled(pkg))
223
+ installed.push(pkg);
224
+ else
225
+ missing.push(pkg);
226
+ }
227
+ checks.push({
228
+ name: `LLM SDKs installed (${installed.length}/${sdkChecks.length})`,
229
+ result: installed.length === 0 ? 'fail' : missing.length > 0 ? 'warn' : 'pass',
230
+ message: installed.length === 0
231
+ ? `No LLM SDKs found. Install at least one: ${sdkChecks.map(s => s.pkg).join(', ')}`
232
+ : missing.length > 0
233
+ ? `Missing (run npm install <pkg> to enable): ${missing.join(', ')}`
234
+ : undefined,
235
+ });
236
+ // 10. Vercel deploy adapter auth (Phase 6 of v5.4 spec). Detects
237
+ // `deploy.adapter: vercel` in guardrail.config.yaml and verifies the
238
+ // auth token is set. Warn-not-fail because users without deploy
239
+ // configured don't care.
240
+ const deployAdapter = readDeployAdapter(configYaml);
241
+ if (deployAdapter === 'vercel') {
242
+ const token = process.env.VERCEL_TOKEN ?? envVars['VERCEL_TOKEN'];
243
+ checks.push({
244
+ name: 'VERCEL_TOKEN (deploy adapter: vercel)',
245
+ result: token ? 'pass' : 'warn',
246
+ message: token
247
+ ? undefined
248
+ : 'deploy.adapter is "vercel" but VERCEL_TOKEN is not set. Generate one at https://vercel.com/account/tokens',
249
+ });
250
+ }
251
+ // 11. Superpowers plugin — required for pipeline phases, optional for review-only use
177
252
  const missingSkills = findMissingSuperpowersSkills();
178
253
  const allSkillsFound = missingSkills.length === 0;
179
254
  checks.push({
@@ -206,6 +206,40 @@ export declare const GUARDRAIL_CONFIG_SCHEMA: {
206
206
  };
207
207
  readonly additionalProperties: false;
208
208
  };
209
+ readonly deploy: {
210
+ readonly type: "object";
211
+ readonly required: readonly ["adapter"];
212
+ readonly additionalProperties: false;
213
+ readonly properties: {
214
+ readonly adapter: {
215
+ readonly enum: readonly ["vercel", "generic"];
216
+ };
217
+ readonly project: {
218
+ readonly type: "string";
219
+ };
220
+ readonly team: {
221
+ readonly type: "string";
222
+ };
223
+ readonly target: {
224
+ readonly enum: readonly ["production", "preview"];
225
+ };
226
+ readonly deployCommand: {
227
+ readonly type: "string";
228
+ };
229
+ readonly watchBuildLogs: {
230
+ readonly type: "boolean";
231
+ };
232
+ readonly rollbackOn: {
233
+ readonly type: "array";
234
+ readonly items: {
235
+ readonly enum: readonly ["healthCheckFailure", "smokeTestFailure"];
236
+ };
237
+ };
238
+ readonly healthCheckUrl: {
239
+ readonly type: "string";
240
+ };
241
+ };
242
+ };
209
243
  readonly 'schema-alignment': {
210
244
  readonly type: "object";
211
245
  readonly properties: {
@@ -112,6 +112,24 @@ export const GUARDRAIL_CONFIG_SCHEMA = {
112
112
  },
113
113
  additionalProperties: false,
114
114
  },
115
+ deploy: {
116
+ type: 'object',
117
+ required: ['adapter'],
118
+ additionalProperties: false,
119
+ properties: {
120
+ adapter: { enum: ['vercel', 'generic'] },
121
+ project: { type: 'string' },
122
+ team: { type: 'string' },
123
+ target: { enum: ['production', 'preview'] },
124
+ deployCommand: { type: 'string' },
125
+ watchBuildLogs: { type: 'boolean' },
126
+ rollbackOn: {
127
+ type: 'array',
128
+ items: { enum: ['healthCheckFailure', 'smokeTestFailure'] },
129
+ },
130
+ healthCheckUrl: { type: 'string' },
131
+ },
132
+ },
115
133
  'schema-alignment': {
116
134
  type: 'object',
117
135
  properties: {
@@ -1,4 +1,5 @@
1
1
  import type { SchemaAlignmentConfig } from '../schema-alignment/types.ts';
2
+ import type { DeployConfig } from '../../adapters/deploy/types.ts';
2
3
  export interface AdapterReference {
3
4
  adapter: string;
4
5
  options?: Record<string, unknown>;
@@ -92,6 +93,11 @@ export interface GuardrailConfig {
92
93
  };
93
94
  };
94
95
  'schema-alignment'?: SchemaAlignmentConfig;
96
+ /**
97
+ * Deploy phase configuration. Optional — when absent, the deploy phase is a
98
+ * no-op. See `src/adapters/deploy/types.ts` for the full DeployConfig shape.
99
+ */
100
+ deploy?: DeployConfig;
95
101
  cache?: Record<string, unknown>;
96
102
  persistence?: Record<string, unknown>;
97
103
  concurrency?: Record<string, unknown>;
@@ -1,4 +1,4 @@
1
- export type ErrorCode = 'auth' | 'rate_limit' | 'transient_network' | 'invalid_config' | 'adapter_bug' | 'user_input' | 'budget_exceeded' | 'concurrency_lock' | 'superseded';
1
+ export type ErrorCode = 'auth' | 'rate_limit' | 'transient_network' | 'invalid_config' | 'adapter_bug' | 'user_input' | 'budget_exceeded' | 'concurrency_lock' | 'superseded' | 'no_previous_deploy';
2
2
  export interface GuardrailErrorOptions {
3
3
  code: ErrorCode;
4
4
  retryable?: boolean;
@@ -3,6 +3,7 @@ const DEFAULT_RETRYABLE = {
3
3
  auth: false, rate_limit: true, transient_network: true, invalid_config: false,
4
4
  adapter_bug: false, user_input: false, budget_exceeded: false,
5
5
  concurrency_lock: false, superseded: false,
6
+ no_previous_deploy: false,
6
7
  };
7
8
  export class GuardrailError extends Error {
8
9
  code;
@@ -38,6 +38,7 @@ export const DETECTION_RULES = [
38
38
  requireAll: ['prisma/schema.prisma'],
39
39
  excludeIf: ['prisma/migrations'],
40
40
  defaultSkill: 'migrate@1',
41
+ defaultCommand: { exec: 'prisma', args: ['db', 'push'] },
41
42
  promptOnSelect: true,
42
43
  },
43
44
  {
@@ -58,6 +59,7 @@ export const DETECTION_RULES = [
58
59
  requireAny: ['drizzle.config.ts', 'drizzle.config.js'],
59
60
  excludeIf: ['drizzle/migrations'],
60
61
  defaultSkill: 'migrate@1',
62
+ defaultCommand: { exec: 'drizzle-kit', args: ['push'] },
61
63
  promptOnSelect: true,
62
64
  },
63
65
  {
@@ -76,6 +78,9 @@ export const DETECTION_RULES = [
76
78
  confidence: 'high',
77
79
  requireAll: ['go.mod', 'migrate'],
78
80
  defaultSkill: 'migrate@1',
81
+ // Conventional invocation; users with non-standard layouts will edit
82
+ // the generated stack.md (e.g. different -path or DSN flag).
83
+ defaultCommand: { exec: 'migrate', args: ['-database', '$DATABASE_URL', '-path', 'migrations', 'up'] },
79
84
  promptOnSelect: false,
80
85
  },
81
86
  {
@@ -132,6 +137,7 @@ export const DETECTION_RULES = [
132
137
  requireAll: [],
133
138
  requireAny: ['ormconfig.json', 'ormconfig.ts', 'ormconfig.js', 'data-source.ts'],
134
139
  defaultSkill: 'migrate@1',
140
+ defaultCommand: { exec: 'typeorm', args: ['migration:run'] },
135
141
  promptOnSelect: true,
136
142
  },
137
143
  {
@@ -12,6 +12,7 @@
12
12
  import * as fs from 'node:fs';
13
13
  import * as path from 'node:path';
14
14
  import Ajv from 'ajv';
15
+ import addFormats from 'ajv-formats';
15
16
  import * as yaml from 'js-yaml';
16
17
  import { requirePackageRoot } from "../../cli/_pkg-root.js";
17
18
  // Resolve presets/ relative to the canonical package root so this works under
@@ -28,6 +29,12 @@ function loadStableIds() {
28
29
  }
29
30
  function buildValidator() {
30
31
  const ajv = new Ajv({ strict: false, allErrors: true });
32
+ // Register standard formats (date-time, uri, email, etc.) so the schema's
33
+ // `format: "date-time"` constraint is actually validated rather than warned-
34
+ // about-and-ignored on every CLI invocation. Pre-5.2.3 every command printed:
35
+ // "unknown format \"date-time\" ignored in schema at path #/.../detected_at"
36
+ // — twice, since the validator is built lazily in two paths.
37
+ addFormats(ajv);
31
38
  const stableIds = loadStableIds();
32
39
  // Custom keyword: validates that the value is one of the registered stable IDs.
33
40
  ajv.addKeyword({
@@ -3,6 +3,14 @@ import * as path from 'node:path';
3
3
  const CACHE_DIR = '.guardrail-cache';
4
4
  const LOG_FILE = 'costs.jsonl';
5
5
  export function appendCostLog(cwd, entry) {
6
+ // Skip no-op entries that only pollute the report — runs that didn't
7
+ // actually invoke an LLM (dry-runs, no-findings paths, "no code files at
8
+ // path" early returns). Without this filter, randai's costs.jsonl picked up
9
+ // 6 zero-token zero-duration entries from setup-flow scans, drowning the
10
+ // 4 real review entries in `claude-autopilot costs` output.
11
+ if (entry.inputTokens === 0 && entry.outputTokens === 0 && entry.costUSD === 0) {
12
+ return;
13
+ }
6
14
  // Cost log is observability, not a contract. A failed write (read-only FS,
7
15
  // full disk, permission error) must NEVER block the caller — every callsite
8
16
  // calls this *after* its primary output is emitted, and a throw here would
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "5.2.1",
3
+ "version": "5.5.2",
4
4
  "type": "module",
5
5
  "description": "Autonomous development pipeline for Claude Code: brainstorm → spec → plan → implement → migrate → validate → PR → review → merge. Multi-model, local-first, every phase a skill you can intervene in.",
6
6
  "keywords": [
@@ -58,18 +58,21 @@
58
58
  "autoregress": "tsx scripts/autoregress.ts"
59
59
  },
60
60
  "dependencies": {
61
- "@anthropic-ai/sdk": "^0.91.1",
62
- "@google/generative-ai": "^0.24.1",
63
- "@modelcontextprotocol/sdk": "^1.29.0",
64
61
  "ajv": "^8",
62
+ "ajv-formats": "^3.0.1",
65
63
  "dotenv": ">=16",
66
64
  "js-yaml": "^4",
67
65
  "minimatch": ">=9",
68
- "openai": ">=4",
69
66
  "proper-lockfile": "^4.1.2",
70
67
  "shell-quote": "^1.8.3",
71
68
  "tsx": ">=4"
72
69
  },
70
+ "optionalDependencies": {
71
+ "@anthropic-ai/sdk": "^0.91.1",
72
+ "@google/generative-ai": "^0.24.1",
73
+ "@modelcontextprotocol/sdk": "^1.29.0",
74
+ "openai": ">=4"
75
+ },
73
76
  "devDependencies": {
74
77
  "@types/js-yaml": "^4",
75
78
  "@types/node": "^25",
@@ -6,7 +6,7 @@ import * as os from 'node:os';
6
6
  import { spawnSync } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { selectSnapshots } from './snapshots/impact-selector.ts';
9
- import OpenAI from 'openai';
9
+ import { loadOpenAI } from '../src/adapters/sdk-loader.ts';
10
10
  import { buildImportMap } from './snapshots/import-scanner.ts';
11
11
 
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -230,6 +230,7 @@ async function cmdGenerate(args: string[]): Promise<number> {
230
230
 
231
231
  console.log(`[autoregress generate] generating snapshots for ${srcFiles.length} file(s)`);
232
232
 
233
+ const OpenAI = await loadOpenAI();
233
234
  const client = new OpenAI({ apiKey });
234
235
  let sourceCommit = 'unknown';
235
236
  try {