@delegance/claude-autopilot 5.0.7 → 5.2.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +5 -1
  3. package/dist/src/adapters/review-engine/parse-output.js +21 -5
  4. package/dist/src/cli/fix.js +21 -3
  5. package/dist/src/cli/index.js +130 -2
  6. package/dist/src/cli/init-migrate.d.ts +35 -0
  7. package/dist/src/cli/init-migrate.js +299 -0
  8. package/dist/src/cli/migrate-doctor.d.ts +19 -0
  9. package/dist/src/cli/migrate-doctor.js +191 -0
  10. package/dist/src/core/migrate/alias-resolver.d.ts +18 -0
  11. package/dist/src/core/migrate/alias-resolver.js +150 -0
  12. package/dist/src/core/migrate/audit-log.d.ts +30 -0
  13. package/dist/src/core/migrate/audit-log.js +100 -0
  14. package/dist/src/core/migrate/contract.d.ts +27 -0
  15. package/dist/src/core/migrate/contract.js +35 -0
  16. package/dist/src/core/migrate/detector-rules.d.ts +26 -0
  17. package/dist/src/core/migrate/detector-rules.js +147 -0
  18. package/dist/src/core/migrate/detector.d.ts +16 -0
  19. package/dist/src/core/migrate/detector.js +105 -0
  20. package/dist/src/core/migrate/dispatcher.d.ts +19 -0
  21. package/dist/src/core/migrate/dispatcher.js +358 -0
  22. package/dist/src/core/migrate/doctor-checks.d.ts +19 -0
  23. package/dist/src/core/migrate/doctor-checks.js +304 -0
  24. package/dist/src/core/migrate/envelope.d.ts +25 -0
  25. package/dist/src/core/migrate/envelope.js +84 -0
  26. package/dist/src/core/migrate/executor.d.ts +33 -0
  27. package/dist/src/core/migrate/executor.js +102 -0
  28. package/dist/src/core/migrate/handshake.d.ts +17 -0
  29. package/dist/src/core/migrate/handshake.js +130 -0
  30. package/dist/src/core/migrate/migrator.d.ts +34 -0
  31. package/dist/src/core/migrate/migrator.js +302 -0
  32. package/dist/src/core/migrate/monorepo.d.ts +2 -0
  33. package/dist/src/core/migrate/monorepo.js +114 -0
  34. package/dist/src/core/migrate/policy-enforcer.d.ts +28 -0
  35. package/dist/src/core/migrate/policy-enforcer.js +111 -0
  36. package/dist/src/core/migrate/result-parser.d.ts +16 -0
  37. package/dist/src/core/migrate/result-parser.js +152 -0
  38. package/dist/src/core/migrate/schema-validator.d.ts +11 -0
  39. package/dist/src/core/migrate/schema-validator.js +103 -0
  40. package/dist/src/core/migrate/types.d.ts +49 -0
  41. package/dist/src/core/migrate/types.js +3 -0
  42. package/package.json +5 -1
  43. package/presets/aliases.lock.json +20 -0
  44. package/presets/schemas/migrate.schema.json +134 -0
  45. package/skills/autopilot/SKILL.md +29 -9
  46. package/skills/migrate/skill.manifest.json +7 -0
  47. package/skills/migrate-none/SKILL.md +40 -0
  48. package/skills/migrate-none/skill.manifest.json +7 -0
  49. package/skills/migrate-supabase/SKILL.md +126 -0
  50. package/skills/migrate-supabase/skill.manifest.json +7 -0
@@ -0,0 +1,111 @@
1
+ // src/core/migrate/policy-enforcer.ts
2
+ //
3
+ // Enforces migrate.policy.* fields plus the dispatcher-level CI prod
4
+ // safety floor (4 flags + recognized CI provider). Skills cannot relax
5
+ // these checks. Failures return reasonCode + checklist message.
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import { execFileSync } from 'node:child_process';
9
+ import { detectCI } from "./envelope.js";
10
+ export function enforcePolicy(ctx) {
11
+ const decisions = [];
12
+ const isProdLike = ctx.env !== 'dev';
13
+ // 1. CI prod gate (only relevant for non-dev in CI)
14
+ if (isProdLike && ctx.ci) {
15
+ if (!ctx.policy.allow_prod_in_ci) {
16
+ decisions.push(`allow_prod_in_ci=false`);
17
+ return {
18
+ ok: false,
19
+ reasonCode: 'prod-blocked-by-policy',
20
+ message: `migrate.policy.allow_prod_in_ci is false. To run --env ${ctx.env} in CI:\n 1) Set migrate.policy.allow_prod_in_ci: true in stack.md\n 2) Pass --yes flag\n 3) Set AUTOPILOT_CI_POLICY=allow-prod\n 4) Set AUTOPILOT_TARGET_ENV=${ctx.env}`,
21
+ decisions,
22
+ };
23
+ }
24
+ if (!ctx.yesFlag) {
25
+ decisions.push(`yes-flag=missing`);
26
+ return { ok: false, reasonCode: 'yes-flag-missing', message: '--yes flag required for non-dev env in CI', decisions };
27
+ }
28
+ if (process.env.AUTOPILOT_CI_POLICY !== 'allow-prod') {
29
+ decisions.push(`AUTOPILOT_CI_POLICY=missing`);
30
+ return { ok: false, reasonCode: 'ci-policy-missing', message: 'AUTOPILOT_CI_POLICY=allow-prod env var required for non-dev env in CI', decisions };
31
+ }
32
+ const targetEnv = process.env.AUTOPILOT_TARGET_ENV;
33
+ if (targetEnv !== ctx.env) {
34
+ decisions.push(`AUTOPILOT_TARGET_ENV=${targetEnv ?? 'unset'} expected ${ctx.env}`);
35
+ return { ok: false, reasonCode: 'target-env-mismatch', message: `AUTOPILOT_TARGET_ENV must equal --env (${ctx.env})`, decisions };
36
+ }
37
+ const ciInfo = detectCI();
38
+ if (!ciInfo.provider) {
39
+ decisions.push(`ci-provider=unrecognized`);
40
+ return {
41
+ ok: false,
42
+ reasonCode: 'no-recognized-ci-provider',
43
+ message: 'No recognized CI provider env detected. Set AUTOPILOT_CI_PROVIDER=<name> for self-hosted CI.',
44
+ decisions,
45
+ };
46
+ }
47
+ decisions.push(`ci-provider=${ciInfo.provider}${ciInfo.overridden ? '(override)' : ''}`);
48
+ }
49
+ // 2. require_clean_git
50
+ if (ctx.policy.require_clean_git) {
51
+ let dirty = false;
52
+ try {
53
+ const out = execFileSync('git', ['status', '--porcelain'], {
54
+ cwd: ctx.repoRoot,
55
+ encoding: 'utf8',
56
+ stdio: ['ignore', 'pipe', 'ignore'],
57
+ });
58
+ dirty = out.trim().length > 0;
59
+ }
60
+ catch {
61
+ // not a git repo or other failure → treat as dirty (fail closed)
62
+ dirty = true;
63
+ }
64
+ if (dirty) {
65
+ decisions.push(`require_clean_git=failed`);
66
+ return {
67
+ ok: false,
68
+ reasonCode: 'unclean-git',
69
+ message: 'Working tree has uncommitted changes. Commit, stash, or reset before running migrate.',
70
+ decisions,
71
+ };
72
+ }
73
+ decisions.push(`require_clean_git=passed`);
74
+ }
75
+ // 3. require_manual_approval (only relevant for non-dev, non-CI interactive)
76
+ if (isProdLike && !ctx.ci && ctx.policy.require_manual_approval) {
77
+ if (ctx.yesFlag) {
78
+ decisions.push(`manual_approval=skipped(--yes)`);
79
+ }
80
+ else if (ctx.nonInteractive) {
81
+ decisions.push(`manual_approval=blocked(non-interactive)`);
82
+ return {
83
+ ok: false,
84
+ reasonCode: 'manual-approval-required',
85
+ message: `Non-dev env (${ctx.env}) requires interactive approval. Pass --yes to skip.`,
86
+ decisions,
87
+ };
88
+ }
89
+ else {
90
+ // Interactive prompt would go here; for now we treat as approved if interactive (the dispatcher
91
+ // is responsible for prompting before calling enforcePolicy in interactive mode).
92
+ decisions.push(`manual_approval=interactive`);
93
+ }
94
+ }
95
+ // 4. require_dry_run_first
96
+ if (ctx.policy.require_dry_run_first && isProdLike) {
97
+ const dryRunPath = path.join(ctx.repoRoot, '.autopilot', 'dry-runs', `${ctx.gitHead}-${ctx.env}.json`);
98
+ if (!fs.existsSync(dryRunPath)) {
99
+ decisions.push(`require_dry_run_first=failed`);
100
+ return {
101
+ ok: false,
102
+ reasonCode: 'no-prior-dry-run',
103
+ message: `require_dry_run_first=true and no dry-run artifact at ${dryRunPath}. Run with --dry-run first.`,
104
+ decisions,
105
+ };
106
+ }
107
+ decisions.push(`require_dry_run_first=passed`);
108
+ }
109
+ return { ok: true, decisions };
110
+ }
111
+ //# sourceMappingURL=policy-enforcer.js.map
@@ -0,0 +1,16 @@
1
+ import type { ResultArtifact } from './types.ts';
2
+ export interface ExpectedIdentity {
3
+ invocationId: string;
4
+ nonce: string;
5
+ }
6
+ interface ParseOpts {
7
+ filePath: string;
8
+ stdout: string;
9
+ expected: ExpectedIdentity;
10
+ allowStdoutFallback: boolean;
11
+ }
12
+ export declare function parseResultFromFile(filePath: string, expected: ExpectedIdentity): ResultArtifact;
13
+ export declare function parseResultFromStdout(stdout: string, expected: ExpectedIdentity): ResultArtifact;
14
+ export declare function parseResult(opts: ParseOpts): ResultArtifact;
15
+ export {};
16
+ //# sourceMappingURL=result-parser.d.ts.map
@@ -0,0 +1,152 @@
1
+ // src/core/migrate/result-parser.ts
2
+ //
3
+ // Parses ResultArtifact from a skill subprocess. File transport is
4
+ // primary and mandatory by default; stdout fallback is opt-in (per
5
+ // skill manifest) and nonce-bound to defend against subprocess output
6
+ // spoofing.
7
+ //
8
+ // Invariants:
9
+ // - All errors return a synthetic ResultArtifact with status='error',
10
+ // so the dispatcher always has a typed object to act on.
11
+ // - Required fields enforced; unknown fields ignored (forward compat
12
+ // for minor envelope-contract version increments).
13
+ // - Major contract version mismatch is a hard error.
14
+ import * as fs from 'node:fs';
15
+ import { ENVELOPE_CONTRACT_VERSION, RESULT_ARTIFACT_MAX_BYTES, RESERVED_SIDE_EFFECTS, STDOUT_MARKER_BEGIN_PREFIX, STDOUT_MARKER_END_PREFIX, STDOUT_MARKER_SUFFIX, } from "./contract.js";
16
+ const VALID_STATUSES = [
17
+ 'applied', 'skipped', 'validation-failed', 'needs-human', 'error',
18
+ ];
19
+ const VALID_SIDE_EFFECTS = new Set(RESERVED_SIDE_EFFECTS);
20
+ function syntheticError(reasonCode, _message, expected) {
21
+ return {
22
+ contractVersion: ENVELOPE_CONTRACT_VERSION,
23
+ skillId: 'unknown',
24
+ invocationId: expected.invocationId,
25
+ nonce: expected.nonce,
26
+ status: 'error',
27
+ reasonCode,
28
+ appliedMigrations: [],
29
+ destructiveDetected: false,
30
+ sideEffectsPerformed: ['no-side-effects'],
31
+ nextActions: [],
32
+ };
33
+ }
34
+ function isValidArtifact(o) {
35
+ if (!o || typeof o !== 'object')
36
+ return false;
37
+ const a = o;
38
+ if (typeof a.contractVersion !== 'string')
39
+ return false;
40
+ if (typeof a.skillId !== 'string')
41
+ return false;
42
+ if (typeof a.invocationId !== 'string')
43
+ return false;
44
+ if (typeof a.nonce !== 'string')
45
+ return false;
46
+ if (typeof a.reasonCode !== 'string')
47
+ return false;
48
+ if (typeof a.destructiveDetected !== 'boolean')
49
+ return false;
50
+ if (!Array.isArray(a.appliedMigrations) || !a.appliedMigrations.every(x => typeof x === 'string'))
51
+ return false;
52
+ if (!Array.isArray(a.nextActions) || !a.nextActions.every(x => typeof x === 'string'))
53
+ return false;
54
+ if (!Array.isArray(a.sideEffectsPerformed))
55
+ return false;
56
+ if (!a.sideEffectsPerformed.every(x => typeof x === 'string' && VALID_SIDE_EFFECTS.has(x)))
57
+ return false;
58
+ if (!VALID_STATUSES.includes(a.status))
59
+ return false;
60
+ return true;
61
+ }
62
+ function checkContractVersion(o) {
63
+ const major = (s) => s.split('.')[0];
64
+ if (major(o.contractVersion) !== major(ENVELOPE_CONTRACT_VERSION)) {
65
+ return { ok: false, reason: 'unsupported-contract-version' };
66
+ }
67
+ return { ok: true };
68
+ }
69
+ function checkIdentity(o, expected) {
70
+ if (o.invocationId !== expected.invocationId)
71
+ return { ok: false, reason: 'invocationId-mismatch' };
72
+ if (o.nonce !== expected.nonce)
73
+ return { ok: false, reason: 'nonce-mismatch' };
74
+ return { ok: true };
75
+ }
76
+ function parseAndValidate(raw, expected) {
77
+ if (Buffer.byteLength(raw, 'utf8') > RESULT_ARTIFACT_MAX_BYTES) {
78
+ return syntheticError('result-too-large', 'artifact exceeds 1 MB', expected);
79
+ }
80
+ let parsed;
81
+ try {
82
+ parsed = JSON.parse(raw);
83
+ }
84
+ catch {
85
+ return syntheticError('invalid-result-artifact', 'JSON parse failed', expected);
86
+ }
87
+ if (!isValidArtifact(parsed)) {
88
+ return syntheticError('invalid-result-artifact', 'required fields missing or wrong type', expected);
89
+ }
90
+ const cv = checkContractVersion(parsed);
91
+ if (!cv.ok)
92
+ return syntheticError(cv.reason, 'contract version mismatch', expected);
93
+ const id = checkIdentity(parsed, expected);
94
+ if (!id.ok)
95
+ return syntheticError(id.reason, 'identity mismatch', expected);
96
+ return parsed;
97
+ }
98
+ export function parseResultFromFile(filePath, expected) {
99
+ // Verify file ownership + type before reading. Use lstatSync (not statSync)
100
+ // so we don't follow symlinks — the dispatcher pre-creates a regular file
101
+ // with O_EXCL, so anything else here means tampering or a misbehaving skill.
102
+ let stat;
103
+ try {
104
+ stat = fs.lstatSync(filePath);
105
+ }
106
+ catch {
107
+ return syntheticError('result-file-missing', `cannot stat ${filePath}`, expected);
108
+ }
109
+ if (!stat.isFile()) {
110
+ return syntheticError('result-file-not-regular', `result path is not a regular file: ${filePath}`, expected);
111
+ }
112
+ if (process.platform !== 'win32' && stat.uid !== process.getuid?.()) {
113
+ return syntheticError('result-file-wrong-owner', `result file uid ${stat.uid} does not match process uid`, expected);
114
+ }
115
+ let raw;
116
+ try {
117
+ raw = fs.readFileSync(filePath, 'utf8');
118
+ }
119
+ catch {
120
+ return syntheticError('result-file-missing', `cannot read ${filePath}`, expected);
121
+ }
122
+ return parseAndValidate(raw, expected);
123
+ }
124
+ export function parseResultFromStdout(stdout, expected) {
125
+ const beginMarker = `${STDOUT_MARKER_BEGIN_PREFIX}${expected.nonce}${STDOUT_MARKER_SUFFIX}`;
126
+ const endMarker = `${STDOUT_MARKER_END_PREFIX}${expected.nonce}${STDOUT_MARKER_SUFFIX}`;
127
+ const beginIdx = stdout.indexOf(beginMarker);
128
+ if (beginIdx === -1) {
129
+ return syntheticError('result-not-found-in-stdout', 'BEGIN marker missing or nonce mismatch', expected);
130
+ }
131
+ const afterBegin = beginIdx + beginMarker.length;
132
+ const endIdx = stdout.indexOf(endMarker, afterBegin);
133
+ if (endIdx === -1) {
134
+ return syntheticError('result-truncated', 'END marker missing', expected);
135
+ }
136
+ const payload = stdout.slice(afterBegin, endIdx).trim();
137
+ return parseAndValidate(payload, expected);
138
+ }
139
+ export function parseResult(opts) {
140
+ // File transport is mandatory by default. Try it first.
141
+ const fromFile = parseResultFromFile(opts.filePath, opts.expected);
142
+ if (fromFile.status !== 'error' || fromFile.reasonCode !== 'result-file-missing') {
143
+ return fromFile;
144
+ }
145
+ // File missing and stdout fallback enabled → try stdout
146
+ if (opts.allowStdoutFallback) {
147
+ return parseResultFromStdout(opts.stdout, opts.expected);
148
+ }
149
+ // Otherwise file-missing is the answer
150
+ return fromFile;
151
+ }
152
+ //# sourceMappingURL=result-parser.js.map
@@ -0,0 +1,11 @@
1
+ export interface ValidationError {
2
+ message: string;
3
+ path?: string;
4
+ keyword?: string;
5
+ }
6
+ export interface ValidationResult {
7
+ valid: boolean;
8
+ errors: ValidationError[];
9
+ }
10
+ export declare function validateStackMd(yamlSource: string): ValidationResult;
11
+ //# sourceMappingURL=schema-validator.d.ts.map
@@ -0,0 +1,103 @@
1
+ // src/core/migrate/schema-validator.ts
2
+ //
3
+ // Wraps the migrate.schema.json with:
4
+ // - custom AJV keyword `stableSkillId` for live alias-map membership check
5
+ // - cross-field check: rejects when a non-dev env's command structurally
6
+ // equals envs.dev.command (prevents `prisma migrate dev` against prod)
7
+ // - YAML parsing layer (returns parse errors as validation errors)
8
+ //
9
+ // Exposes validateStackMd(yaml: string) → { valid, errors[] }
10
+ //
11
+ // Compiled validator is module-scoped so it's built once per process.
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import Ajv from 'ajv';
15
+ import * as yaml from 'js-yaml';
16
+ import { requirePackageRoot } from "../../cli/_pkg-root.js";
17
+ // Resolve presets/ relative to the canonical package root so this works under
18
+ // both the source layout (src/core/migrate/*.ts) and the compiled/published
19
+ // layout (dist/src/core/migrate/*.js where presets/ ships at the package root,
20
+ // not under dist/). The previous __dirname + '../../..' resolution landed at
21
+ // <install>/dist/presets/... in the published tarball — file does not exist.
22
+ const PKG_ROOT = requirePackageRoot(import.meta.url);
23
+ const SCHEMA_PATH = path.resolve(PKG_ROOT, 'presets', 'schemas', 'migrate.schema.json');
24
+ const ALIASES_PATH = path.resolve(PKG_ROOT, 'presets', 'aliases.lock.json');
25
+ function loadStableIds() {
26
+ const data = JSON.parse(fs.readFileSync(ALIASES_PATH, 'utf8'));
27
+ return new Set(data.aliases.map(a => a.stableId));
28
+ }
29
+ function buildValidator() {
30
+ const ajv = new Ajv({ strict: false, allErrors: true });
31
+ const stableIds = loadStableIds();
32
+ // Custom keyword: validates that the value is one of the registered stable IDs.
33
+ ajv.addKeyword({
34
+ keyword: 'stableSkillId',
35
+ type: 'string',
36
+ error: {
37
+ message: (ctx) => `skillId-not-in-registry: '${ctx.data}' is not in aliases.lock.json`,
38
+ },
39
+ validate: (_schema, data) => {
40
+ return typeof data === 'string' && stableIds.has(data);
41
+ },
42
+ });
43
+ const schema = JSON.parse(fs.readFileSync(SCHEMA_PATH, 'utf8'));
44
+ // Inject the custom keyword on migrate.skill (without modifying the on-disk schema).
45
+ if (schema?.properties?.migrate?.properties?.skill) {
46
+ schema.properties.migrate.properties.skill.stableSkillId = true;
47
+ }
48
+ return ajv.compile(schema);
49
+ }
50
+ const validate = buildValidator();
51
+ function commandsEqual(a, b) {
52
+ return JSON.stringify(a) === JSON.stringify(b);
53
+ }
54
+ function checkDevCommandReuse(parsed) {
55
+ const errors = [];
56
+ const root = parsed;
57
+ const envs = root?.migrate?.envs;
58
+ if (!envs || typeof envs !== 'object')
59
+ return errors;
60
+ const devCmd = envs.dev?.command;
61
+ if (!devCmd)
62
+ return errors;
63
+ for (const [name, spec] of Object.entries(envs)) {
64
+ if (name === 'dev')
65
+ continue;
66
+ if (spec?.command && commandsEqual(spec.command, devCmd)) {
67
+ errors.push({
68
+ message: `dev-command-reused-for-non-dev: envs.${name}.command equals envs.dev.command — running a dev migration against ${name} is destructive. Set an explicit command for ${name}.`,
69
+ path: `migrate.envs.${name}.command`,
70
+ });
71
+ }
72
+ }
73
+ return errors;
74
+ }
75
+ function ajvErrorsToValidationErrors(errors) {
76
+ return errors.map(e => ({
77
+ message: e.message ?? `validation failed at ${e.instancePath}`,
78
+ path: e.instancePath || undefined,
79
+ keyword: e.keyword,
80
+ }));
81
+ }
82
+ export function validateStackMd(yamlSource) {
83
+ let parsed;
84
+ try {
85
+ parsed = yaml.load(yamlSource);
86
+ }
87
+ catch (err) {
88
+ return {
89
+ valid: false,
90
+ errors: [{
91
+ message: `yaml-parse-failed: ${err.message}`,
92
+ }],
93
+ };
94
+ }
95
+ const ok = validate(parsed);
96
+ const schemaErrors = ok ? [] : ajvErrorsToValidationErrors(validate.errors ?? []);
97
+ const crossFieldErrors = ok ? checkDevCommandReuse(parsed) : [];
98
+ return {
99
+ valid: ok && crossFieldErrors.length === 0,
100
+ errors: [...schemaErrors, ...crossFieldErrors],
101
+ };
102
+ }
103
+ //# sourceMappingURL=schema-validator.js.map
@@ -0,0 +1,49 @@
1
+ export interface InvocationEnvelope {
2
+ contractVersion: '1.0';
3
+ invocationId: string;
4
+ /** 32-byte hex nonce, bound to subprocess identity for result-artifact authenticity */
5
+ nonce: string;
6
+ trigger: 'cli' | 'ci';
7
+ attempt: number;
8
+ repoRoot: string;
9
+ cwd: string;
10
+ changedFiles: string[];
11
+ env: string;
12
+ dryRun: boolean;
13
+ ci: boolean;
14
+ gitBase: string;
15
+ gitHead: string;
16
+ projectId?: string;
17
+ }
18
+ export type ResultStatus = 'applied' | 'skipped' | 'validation-failed' | 'needs-human' | 'error';
19
+ export type SideEffect = 'types-regenerated' | 'migration-ledger-updated' | 'schema-cache-refreshed' | 'seed-data-applied' | 'snapshot-written' | 'no-side-effects';
20
+ export interface ResultArtifact {
21
+ contractVersion: '1.0';
22
+ skillId: string;
23
+ invocationId: string;
24
+ /** Echoes the envelope nonce — mismatched value rejected by parser */
25
+ nonce: string;
26
+ status: ResultStatus;
27
+ reasonCode: string;
28
+ appliedMigrations: string[];
29
+ destructiveDetected: boolean;
30
+ sideEffectsPerformed: SideEffect[];
31
+ nextActions: string[];
32
+ }
33
+ export interface SkillManifest {
34
+ skillId: string;
35
+ skill_runtime_api_version: string;
36
+ min_runtime: string;
37
+ max_runtime: string;
38
+ stdoutFallback?: boolean;
39
+ }
40
+ export interface CommandSpec {
41
+ exec: string;
42
+ args: string[];
43
+ }
44
+ export interface AliasEntry {
45
+ stableId: string;
46
+ resolvesTo: string;
47
+ rawAliases?: string[];
48
+ }
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,3 @@
1
+ // src/core/migrate/types.ts
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "5.0.7",
3
+ "version": "5.2.0",
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": [
@@ -66,11 +66,15 @@
66
66
  "js-yaml": "^4",
67
67
  "minimatch": ">=9",
68
68
  "openai": ">=4",
69
+ "proper-lockfile": "^4.1.2",
70
+ "shell-quote": "^1.8.3",
69
71
  "tsx": ">=4"
70
72
  },
71
73
  "devDependencies": {
72
74
  "@types/js-yaml": "^4",
73
75
  "@types/node": "^25",
76
+ "@types/proper-lockfile": "^4.1.4",
77
+ "@types/shell-quote": "^1.7.5",
74
78
  "typescript": "^6"
75
79
  },
76
80
  "peerDependencies": {
@@ -0,0 +1,20 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "aliases": [
4
+ {
5
+ "stableId": "migrate@1",
6
+ "resolvesTo": "skills/migrate/",
7
+ "rawAliases": ["migrate"]
8
+ },
9
+ {
10
+ "stableId": "migrate.supabase@1",
11
+ "resolvesTo": "skills/migrate-supabase/",
12
+ "rawAliases": ["migrate-supabase"]
13
+ },
14
+ {
15
+ "stableId": "none@1",
16
+ "resolvesTo": "skills/migrate-none/",
17
+ "rawAliases": ["none", "skip"]
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,134 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/axledbetter/claude-autopilot/presets/schemas/migrate.schema.json",
4
+ "title": "Stack.md migrate block",
5
+ "type": "object",
6
+ "required": ["schema_version", "migrate"],
7
+ "properties": {
8
+ "schema_version": { "const": 1 },
9
+ "migrate": {
10
+ "type": "object",
11
+ "required": ["skill"],
12
+ "properties": {
13
+ "skill": {
14
+ "type": "string",
15
+ "description": "Stable skill ID (e.g. 'migrate@1', 'migrate.supabase@1', 'none@1')",
16
+ "pattern": "^[a-z][a-z0-9.-]*@[0-9]+$"
17
+ },
18
+ "envs": {
19
+ "type": "object",
20
+ "patternProperties": {
21
+ "^[a-z][a-z0-9-]*$": { "$ref": "#/$defs/envSpec" }
22
+ },
23
+ "additionalProperties": false
24
+ },
25
+ "post": {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "object",
29
+ "required": ["command"],
30
+ "properties": { "command": { "$ref": "#/$defs/commandSpec" } }
31
+ }
32
+ },
33
+ "policy": { "$ref": "#/$defs/policySpec" },
34
+ "supabase": { "$ref": "#/$defs/supabaseSpec" },
35
+ "detected_at": { "type": "string", "format": "date-time" },
36
+ "project_root": { "type": "string" },
37
+ "dev_command": {
38
+ "description": "Deprecated alias for envs.dev.command — auto-migrated by doctor --fix",
39
+ "$ref": "#/$defs/legacyCommandValue"
40
+ }
41
+ },
42
+ "allOf": [
43
+ {
44
+ "if": { "properties": { "skill": { "const": "migrate.supabase@1" } } },
45
+ "then": {
46
+ "required": ["supabase"],
47
+ "properties": {
48
+ "supabase": {
49
+ "required": ["deltas_dir", "types_out", "envs_file"]
50
+ }
51
+ }
52
+ }
53
+ },
54
+ {
55
+ "if": { "properties": { "skill": { "const": "migrate@1" } } },
56
+ "then": {
57
+ "required": ["envs"],
58
+ "properties": {
59
+ "envs": {
60
+ "required": ["dev"]
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ },
68
+ "$defs": {
69
+ "commandSpec": {
70
+ "oneOf": [
71
+ {
72
+ "type": "object",
73
+ "required": ["exec", "args"],
74
+ "properties": {
75
+ "exec": { "type": "string", "minLength": 1 },
76
+ "args": {
77
+ "type": "array",
78
+ "items": {
79
+ "type": "string",
80
+ "not": { "pattern": "[|;&><`$()]" }
81
+ }
82
+ }
83
+ },
84
+ "additionalProperties": false
85
+ },
86
+ {
87
+ "$ref": "#/$defs/legacyCommandValue"
88
+ }
89
+ ]
90
+ },
91
+ "legacyCommandValue": {
92
+ "type": "string",
93
+ "description": "Deprecated string form. Auto-migrated to structured argv by doctor --fix.",
94
+ "minLength": 1
95
+ },
96
+ "envSpec": {
97
+ "type": "object",
98
+ "required": ["command"],
99
+ "properties": {
100
+ "command": { "$ref": "#/$defs/commandSpec" },
101
+ "env_file": {
102
+ "type": "string",
103
+ "description": "Path to env file, must be relative to project_root, no '..'",
104
+ "not": {
105
+ "anyOf": [
106
+ { "pattern": "^/" },
107
+ { "pattern": "(^|/)\\.\\.(/|$)" }
108
+ ]
109
+ }
110
+ }
111
+ },
112
+ "additionalProperties": false
113
+ },
114
+ "policySpec": {
115
+ "type": "object",
116
+ "properties": {
117
+ "allow_prod_in_ci": { "type": "boolean", "default": false },
118
+ "require_clean_git": { "type": "boolean", "default": true },
119
+ "require_manual_approval": { "type": "boolean", "default": true },
120
+ "require_dry_run_first": { "type": "boolean", "default": false }
121
+ },
122
+ "additionalProperties": false
123
+ },
124
+ "supabaseSpec": {
125
+ "type": "object",
126
+ "properties": {
127
+ "deltas_dir": { "type": "string" },
128
+ "types_out": { "type": "string" },
129
+ "envs_file": { "type": "string" }
130
+ },
131
+ "additionalProperties": false
132
+ }
133
+ }
134
+ }