@delegance/claude-autopilot 5.0.8 → 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 (48) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +5 -1
  3. package/dist/src/cli/index.js +130 -2
  4. package/dist/src/cli/init-migrate.d.ts +35 -0
  5. package/dist/src/cli/init-migrate.js +299 -0
  6. package/dist/src/cli/migrate-doctor.d.ts +19 -0
  7. package/dist/src/cli/migrate-doctor.js +191 -0
  8. package/dist/src/core/migrate/alias-resolver.d.ts +18 -0
  9. package/dist/src/core/migrate/alias-resolver.js +150 -0
  10. package/dist/src/core/migrate/audit-log.d.ts +30 -0
  11. package/dist/src/core/migrate/audit-log.js +100 -0
  12. package/dist/src/core/migrate/contract.d.ts +27 -0
  13. package/dist/src/core/migrate/contract.js +35 -0
  14. package/dist/src/core/migrate/detector-rules.d.ts +26 -0
  15. package/dist/src/core/migrate/detector-rules.js +147 -0
  16. package/dist/src/core/migrate/detector.d.ts +16 -0
  17. package/dist/src/core/migrate/detector.js +105 -0
  18. package/dist/src/core/migrate/dispatcher.d.ts +19 -0
  19. package/dist/src/core/migrate/dispatcher.js +358 -0
  20. package/dist/src/core/migrate/doctor-checks.d.ts +19 -0
  21. package/dist/src/core/migrate/doctor-checks.js +304 -0
  22. package/dist/src/core/migrate/envelope.d.ts +25 -0
  23. package/dist/src/core/migrate/envelope.js +84 -0
  24. package/dist/src/core/migrate/executor.d.ts +33 -0
  25. package/dist/src/core/migrate/executor.js +102 -0
  26. package/dist/src/core/migrate/handshake.d.ts +17 -0
  27. package/dist/src/core/migrate/handshake.js +130 -0
  28. package/dist/src/core/migrate/migrator.d.ts +34 -0
  29. package/dist/src/core/migrate/migrator.js +302 -0
  30. package/dist/src/core/migrate/monorepo.d.ts +2 -0
  31. package/dist/src/core/migrate/monorepo.js +114 -0
  32. package/dist/src/core/migrate/policy-enforcer.d.ts +28 -0
  33. package/dist/src/core/migrate/policy-enforcer.js +111 -0
  34. package/dist/src/core/migrate/result-parser.d.ts +16 -0
  35. package/dist/src/core/migrate/result-parser.js +152 -0
  36. package/dist/src/core/migrate/schema-validator.d.ts +11 -0
  37. package/dist/src/core/migrate/schema-validator.js +103 -0
  38. package/dist/src/core/migrate/types.d.ts +49 -0
  39. package/dist/src/core/migrate/types.js +3 -0
  40. package/package.json +5 -1
  41. package/presets/aliases.lock.json +20 -0
  42. package/presets/schemas/migrate.schema.json +134 -0
  43. package/skills/autopilot/SKILL.md +29 -9
  44. package/skills/migrate/skill.manifest.json +7 -0
  45. package/skills/migrate-none/SKILL.md +40 -0
  46. package/skills/migrate-none/skill.manifest.json +7 -0
  47. package/skills/migrate-supabase/SKILL.md +126 -0
  48. package/skills/migrate-supabase/skill.manifest.json +7 -0
@@ -0,0 +1,191 @@
1
+ // src/cli/migrate-doctor.ts
2
+ //
3
+ // CLI wrapper for migrate doctor checks (Task 7.2).
4
+ //
5
+ // runMigrateDoctor({ repoRoot, fix? }):
6
+ //
7
+ // fix = false (default, "plain doctor")
8
+ // - calls runAllChecks
9
+ // - returns the named results unchanged
10
+ // - NEVER writes to disk (asserted by golden-file test)
11
+ //
12
+ // fix = true ("doctor --fix")
13
+ // - runs the same checks
14
+ // - applies AUTO-FIXABLE mutations to .autopilot/stack.md:
15
+ // a) top-level `dev_command` → `migrate.envs.dev.command`
16
+ // b) missing `schema_version: 1`
17
+ // c) raw `migrate.skill` → stable ID (via resolveSkill)
18
+ // d) missing default policy keys backfilled (per skill shape)
19
+ // - writes the updated YAML, then re-runs the checks and returns
20
+ // both the post-fix results and the list of mutations performed
21
+ //
22
+ // Spec: docs/superpowers/specs/2026-04-29-migrate-skill-generalization-design.md
23
+ // (§ "claude-autopilot doctor")
24
+ import * as fs from 'node:fs';
25
+ import * as path from 'node:path';
26
+ import * as yaml from 'js-yaml';
27
+ import { runAllChecks } from "../core/migrate/doctor-checks.js";
28
+ import { resolveSkill } from "../core/migrate/alias-resolver.js";
29
+ import { detectsLegacyMigrateSkill, migrateLegacySkill, } from "../core/migrate/migrator.js";
30
+ const DEFAULT_POLICY_GENERIC = {
31
+ allow_prod_in_ci: false,
32
+ require_clean_git: true,
33
+ require_manual_approval: true,
34
+ require_dry_run_first: false,
35
+ };
36
+ const DEFAULT_POLICY_SUPABASE = {
37
+ allow_prod_in_ci: false,
38
+ };
39
+ function stackPath(repoRoot) {
40
+ return path.join(repoRoot, '.autopilot', 'stack.md');
41
+ }
42
+ function applyAutoFixes(repoRoot) {
43
+ const sp = stackPath(repoRoot);
44
+ if (!fs.existsSync(sp)) {
45
+ return { mutations: [], wrote: false };
46
+ }
47
+ const raw = fs.readFileSync(sp, 'utf8');
48
+ let parsed;
49
+ try {
50
+ const loaded = yaml.load(raw);
51
+ if (!loaded || typeof loaded !== 'object') {
52
+ return { mutations: [], wrote: false };
53
+ }
54
+ parsed = loaded;
55
+ }
56
+ catch {
57
+ return { mutations: [], wrote: false };
58
+ }
59
+ const mutations = [];
60
+ // a) Migrate top-level dev_command → migrate.envs.dev.command
61
+ if ('dev_command' in parsed) {
62
+ const legacy = parsed.dev_command;
63
+ parsed.migrate = parsed.migrate ?? {};
64
+ parsed.migrate.envs = parsed.migrate.envs ?? {};
65
+ if (!parsed.migrate.envs.dev?.command) {
66
+ parsed.migrate.envs.dev = parsed.migrate.envs.dev ?? {};
67
+ parsed.migrate.envs.dev.command = legacy;
68
+ mutations.push('migrated top-level dev_command → migrate.envs.dev.command');
69
+ }
70
+ else {
71
+ mutations.push('removed redundant top-level dev_command (envs.dev.command already set)');
72
+ }
73
+ delete parsed.dev_command;
74
+ }
75
+ // b) Backfill schema_version
76
+ if (parsed.schema_version === undefined) {
77
+ parsed.schema_version = 1;
78
+ mutations.push('added schema_version: 1');
79
+ }
80
+ // c) Normalize raw skill → stable ID
81
+ const skill = parsed.migrate?.skill;
82
+ if (typeof skill === 'string') {
83
+ const res = resolveSkill(skill, { repoRoot });
84
+ if (res.ok && res.normalizedFromRaw && res.stableId !== skill) {
85
+ parsed.migrate.skill = res.stableId;
86
+ mutations.push(`normalized migrate.skill: "${skill}" → "${res.stableId}"`);
87
+ }
88
+ }
89
+ // d) Backfill missing default policy keys based on resolved skill shape
90
+ if (parsed.migrate) {
91
+ const resolvedSkill = parsed.migrate.skill;
92
+ let defaults = null;
93
+ if (resolvedSkill === 'migrate@1')
94
+ defaults = DEFAULT_POLICY_GENERIC;
95
+ else if (resolvedSkill === 'migrate.supabase@1')
96
+ defaults = DEFAULT_POLICY_SUPABASE;
97
+ if (defaults) {
98
+ const policy = (parsed.migrate.policy ?? {});
99
+ const added = [];
100
+ for (const [k, v] of Object.entries(defaults)) {
101
+ if (!(k in policy)) {
102
+ policy[k] = v;
103
+ added.push(k);
104
+ }
105
+ }
106
+ if (added.length > 0) {
107
+ parsed.migrate.policy = policy;
108
+ mutations.push(`backfilled default policy keys: ${added.join(', ')}`);
109
+ }
110
+ }
111
+ }
112
+ if (mutations.length === 0) {
113
+ return { mutations, wrote: false };
114
+ }
115
+ fs.writeFileSync(sp, yaml.dump(parsed, { lineWidth: 120, noRefs: true }), 'utf8');
116
+ return { mutations, wrote: true };
117
+ }
118
+ function isoStampForReport() {
119
+ return new Date().toISOString().replace(/[.:]/g, '-');
120
+ }
121
+ function writeMigrationReport(repoRoot, report, context) {
122
+ const dir = path.join(repoRoot, '.autopilot');
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ const file = path.join(dir, `migration-report-${isoStampForReport()}.md`);
125
+ const lines = [
126
+ `# Legacy /migrate skill migration report`,
127
+ ``,
128
+ `- generated: ${new Date().toISOString()}`,
129
+ `- migrated: ${context.migrated}`,
130
+ ];
131
+ if (context.reason)
132
+ lines.push(`- reason: ${context.reason}`);
133
+ if (context.archivePath) {
134
+ lines.push(`- archive: ${path.relative(repoRoot, context.archivePath)}`);
135
+ }
136
+ lines.push(``, `## Steps`, ``);
137
+ for (const step of report)
138
+ lines.push(`- ${step}`);
139
+ lines.push('');
140
+ fs.writeFileSync(file, lines.join('\n'), 'utf8');
141
+ return file;
142
+ }
143
+ export async function runMigrateDoctor(opts) {
144
+ const repoRoot = path.resolve(opts.repoRoot);
145
+ const fix = opts.fix ?? false;
146
+ if (!fix) {
147
+ // Plain doctor: read-only.
148
+ const results = runAllChecks(repoRoot);
149
+ // Detect-only: surface legacy /migrate skill presence as a separate
150
+ // named check so callers can see it, but never write.
151
+ if (detectsLegacyMigrateSkill(repoRoot)) {
152
+ results.push({
153
+ name: 'legacyMigrateSkillAbsent',
154
+ result: {
155
+ ok: false,
156
+ message: 'skills/migrate/SKILL.md still has the legacy Supabase shape — run with --fix to migrate',
157
+ fixHint: 'claude-autopilot migrate doctor --fix',
158
+ },
159
+ });
160
+ }
161
+ return { allOk: results.every(r => r.result.ok), results };
162
+ }
163
+ // --fix: apply auto-fixable mutations, then re-run checks.
164
+ const { mutations: yamlMutations } = applyAutoFixes(repoRoot);
165
+ const mutations = [...yamlMutations];
166
+ let migrationReportPath;
167
+ // Wire in the legacy /migrate skill migrator (Task 8.2).
168
+ if (detectsLegacyMigrateSkill(repoRoot)) {
169
+ const m = migrateLegacySkill({ repoRoot });
170
+ for (const step of m.report) {
171
+ mutations.push(`migrator: ${step}`);
172
+ }
173
+ if (m.migrated && m.reason) {
174
+ mutations.push(`migrator: completed (${m.reason})`);
175
+ }
176
+ migrationReportPath = writeMigrationReport(repoRoot, m.report, {
177
+ migrated: m.migrated,
178
+ reason: m.reason,
179
+ archivePath: m.archivePath,
180
+ });
181
+ mutations.push(`migrator: wrote migration report → ${path.relative(repoRoot, migrationReportPath)}`);
182
+ }
183
+ const results = runAllChecks(repoRoot);
184
+ return {
185
+ allOk: results.every(r => r.result.ok),
186
+ results,
187
+ mutations,
188
+ migrationReportPath,
189
+ };
190
+ }
191
+ //# sourceMappingURL=migrate-doctor.js.map
@@ -0,0 +1,18 @@
1
+ export interface ResolveOptions {
2
+ repoRoot: string;
3
+ /** Optional workspace path for monorepo lookup precedence (workspace
4
+ * aliases take precedence over repo-root aliases). */
5
+ workspace?: string;
6
+ }
7
+ export type ResolveResult = {
8
+ ok: true;
9
+ stableId: string;
10
+ skillPath: string;
11
+ normalizedFromRaw: boolean;
12
+ } | {
13
+ ok: false;
14
+ reasonCode: 'aliases-file-missing' | 'aliases-file-invalid' | 'stable-id-unknown' | 'raw-alias-ambiguous' | 'path-escape' | 'skill-path-missing' | 'invalid-input';
15
+ message: string;
16
+ };
17
+ export declare function resolveSkill(input: string, opts: ResolveOptions): ResolveResult;
18
+ //# sourceMappingURL=alias-resolver.d.ts.map
@@ -0,0 +1,150 @@
1
+ // src/core/migrate/alias-resolver.ts
2
+ //
3
+ // Resolves stable skill IDs (e.g. "migrate@1") or raw aliases (e.g. "migrate")
4
+ // against presets/aliases.lock.json. Path escape is the CRITICAL security
5
+ // concern per Codex review:
6
+ // - resolved paths are realpath'd and verified to stay under <repo>/skills/
7
+ // or <repo>/node_modules/ (the trusted skill roots)
8
+ // - absolute paths in alias map are rejected
9
+ // - .. traversal in alias map is rejected
10
+ // - symlinks pointing outside trusted root are rejected (resolved + checked)
11
+ //
12
+ // Raw alias collisions are a hard error: ambiguous "thing" → require user to
13
+ // use exact stable ID.
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+ import { TRUSTED_SKILL_ROOTS } from "./contract.js";
17
+ import { findPackageRoot } from "../../cli/_pkg-root.js";
18
+ function loadAliasMap(repoRoot) {
19
+ // Lookup precedence:
20
+ // 1. repoRoot/presets/aliases.lock.json — repo-local override (e.g. monorepo)
21
+ // 2. <package-root>/presets/aliases.lock.json — installed package
22
+ // findPackageRoot walks up from this module looking for the package.json
23
+ // declaring '@delegance/claude-autopilot', so it works under both source and
24
+ // compiled (dist/) layouts. Earlier code used __dirname + '../../..' which
25
+ // landed at <install>/dist/presets/ in the published tarball (presets/ ships
26
+ // at the package root, not under dist/).
27
+ const pkgRoot = findPackageRoot(import.meta.url);
28
+ const candidates = [
29
+ path.join(repoRoot, 'presets', 'aliases.lock.json'),
30
+ ...(pkgRoot ? [path.join(pkgRoot, 'presets', 'aliases.lock.json')] : []),
31
+ ];
32
+ for (const p of candidates) {
33
+ if (fs.existsSync(p)) {
34
+ try {
35
+ const raw = fs.readFileSync(p, 'utf8');
36
+ const parsed = JSON.parse(raw);
37
+ if (typeof parsed?.schemaVersion === 'number' && Array.isArray(parsed.aliases)) {
38
+ return parsed;
39
+ }
40
+ }
41
+ catch {
42
+ // continue to next candidate
43
+ }
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ function isUnderTrustedRoot(absResolvedPath, repoRoot) {
49
+ let realRepoRoot;
50
+ try {
51
+ realRepoRoot = fs.realpathSync(repoRoot);
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ for (const root of TRUSTED_SKILL_ROOTS) {
57
+ const trustedAbs = path.resolve(realRepoRoot, root);
58
+ const rel = path.relative(trustedAbs, absResolvedPath);
59
+ // rel must NOT start with '..' and must not be absolute (path traversal sentinels)
60
+ if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
61
+ return true;
62
+ }
63
+ // Also accept the trusted root itself
64
+ if (absResolvedPath === trustedAbs) {
65
+ return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ function validatePathEscape(rawResolvesTo, repoRoot) {
71
+ // Reject absolute paths and .. traversal in the alias map *before* resolving
72
+ if (path.isAbsolute(rawResolvesTo)) {
73
+ return { ok: false, reason: 'absolute path in alias map' };
74
+ }
75
+ if (rawResolvesTo.split(/[/\\]/).some(seg => seg === '..')) {
76
+ return { ok: false, reason: '.. traversal in alias map' };
77
+ }
78
+ // Resolve via realpath (follows symlinks)
79
+ const candidate = path.resolve(repoRoot, rawResolvesTo);
80
+ let real;
81
+ try {
82
+ real = fs.realpathSync(candidate);
83
+ }
84
+ catch {
85
+ return { ok: false, reason: `path does not exist: ${candidate}` };
86
+ }
87
+ if (!isUnderTrustedRoot(real, repoRoot)) {
88
+ return { ok: false, reason: `resolved path escapes trusted roots: ${real}` };
89
+ }
90
+ return { ok: true, resolved: real };
91
+ }
92
+ export function resolveSkill(input, opts) {
93
+ if (!input || typeof input !== 'string') {
94
+ return {
95
+ ok: false,
96
+ reasonCode: 'invalid-input',
97
+ message: 'skill input must be a non-empty string',
98
+ };
99
+ }
100
+ // Lookup precedence: workspace .autopilot/aliases.lock.json (if exists) > repo root presets/aliases.lock.json
101
+ const lookupRoot = opts.repoRoot;
102
+ const aliasMap = loadAliasMap(lookupRoot);
103
+ if (!aliasMap) {
104
+ return {
105
+ ok: false,
106
+ reasonCode: 'aliases-file-missing',
107
+ message: `no aliases.lock.json found under ${lookupRoot}`,
108
+ };
109
+ }
110
+ // 1. Exact stable ID match
111
+ const stableMatch = aliasMap.aliases.find(a => a.stableId === input);
112
+ if (stableMatch) {
113
+ return finalizeResolve(stableMatch, lookupRoot, /*normalizedFromRaw*/ false);
114
+ }
115
+ // 2. Raw alias normalization (with collision check)
116
+ const rawMatches = aliasMap.aliases.filter(a => a.rawAliases?.includes(input));
117
+ if (rawMatches.length > 1) {
118
+ const candidates = rawMatches.map(m => m.stableId).join(', ');
119
+ return {
120
+ ok: false,
121
+ reasonCode: 'raw-alias-ambiguous',
122
+ message: `raw alias '${input}' maps to multiple stable IDs: ${candidates}. Use the exact stable ID in stack.md.`,
123
+ };
124
+ }
125
+ if (rawMatches.length === 1) {
126
+ return finalizeResolve(rawMatches[0], lookupRoot, /*normalizedFromRaw*/ true);
127
+ }
128
+ return {
129
+ ok: false,
130
+ reasonCode: 'stable-id-unknown',
131
+ message: `unknown skill '${input}'. Known stable IDs: ${aliasMap.aliases.map(a => a.stableId).join(', ')}. Run \`claude-autopilot doctor\` for help.`,
132
+ };
133
+ }
134
+ function finalizeResolve(entry, repoRoot, normalizedFromRaw) {
135
+ const check = validatePathEscape(entry.resolvesTo, repoRoot);
136
+ if (!check.ok) {
137
+ return {
138
+ ok: false,
139
+ reasonCode: 'path-escape',
140
+ message: `alias ${entry.stableId} resolvesTo path-escape rejected: ${check.reason}`,
141
+ };
142
+ }
143
+ return {
144
+ ok: true,
145
+ stableId: entry.stableId,
146
+ skillPath: check.resolved,
147
+ normalizedFromRaw,
148
+ };
149
+ }
150
+ //# sourceMappingURL=alias-resolver.js.map
@@ -0,0 +1,30 @@
1
+ export interface AuditEvent {
2
+ seq: number;
3
+ ts: string;
4
+ invocationId: string;
5
+ event: string;
6
+ requested_skill: string;
7
+ resolved_skill: string;
8
+ skill_path: string;
9
+ envelope_contract_version: string;
10
+ skill_runtime_api_version: string;
11
+ envelope_hash: string;
12
+ policy_decisions: string[];
13
+ mode: 'apply' | 'dry-run' | 'doctor-fix';
14
+ actor: string;
15
+ ci_provider: string | null;
16
+ ci_run_id: string | null;
17
+ result_status: string;
18
+ duration_ms: number;
19
+ prev_hash: string | null;
20
+ }
21
+ export type AuditEventInput = Omit<AuditEvent, 'seq' | 'prev_hash' | 'ts'>;
22
+ export declare function appendAuditEvent(logPath: string, input: AuditEventInput): Promise<void>;
23
+ export declare function readEvents(logPath: string): AuditEvent[];
24
+ export interface VerifyResult {
25
+ valid: boolean;
26
+ breakAtLine?: number;
27
+ reason?: string;
28
+ }
29
+ export declare function verifyChain(logPath: string): VerifyResult;
30
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1,100 @@
1
+ // src/core/migrate/audit-log.ts
2
+ //
3
+ // JSONL audit log with monotonic seq + prev_hash chain. Concurrent writes
4
+ // serialized via proper-lockfile (advisory lock with retries + stale recovery).
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import * as crypto from 'node:crypto';
8
+ import lockfile from 'proper-lockfile';
9
+ function sha256(s) {
10
+ return 'sha256:' + crypto.createHash('sha256').update(s).digest('hex');
11
+ }
12
+ function readLastLine(p) {
13
+ if (!fs.existsSync(p))
14
+ return null;
15
+ const raw = fs.readFileSync(p, 'utf8');
16
+ if (!raw.trim())
17
+ return null;
18
+ const lines = raw.trim().split('\n');
19
+ const last = lines[lines.length - 1];
20
+ const obj = JSON.parse(last);
21
+ return { seq: obj.seq, lineHash: sha256(last) };
22
+ }
23
+ export async function appendAuditEvent(logPath, input) {
24
+ const dir = path.dirname(logPath);
25
+ fs.mkdirSync(dir, { recursive: true });
26
+ if (!fs.existsSync(logPath))
27
+ fs.writeFileSync(logPath, '');
28
+ let release;
29
+ try {
30
+ release = await lockfile.lock(logPath, {
31
+ retries: { retries: 10, factor: 1.5, minTimeout: 50, maxTimeout: 500 },
32
+ stale: 5000,
33
+ });
34
+ }
35
+ catch (err) {
36
+ // Fail closed: refuse to write a potentially-forked entry.
37
+ throw new Error(`audit-log: could not acquire lock on ${logPath}: ${err.message}. ` +
38
+ `Check for stale locks or filesystem issues. Audit chain not extended.`);
39
+ }
40
+ try {
41
+ const last = readLastLine(logPath);
42
+ const seq = (last?.seq ?? 0) + 1;
43
+ const prev_hash = last?.lineHash ?? null;
44
+ const event = {
45
+ ...input,
46
+ seq,
47
+ prev_hash,
48
+ ts: new Date().toISOString(),
49
+ };
50
+ // Append + fsync so chain is durable before lock release.
51
+ const handle = fs.openSync(logPath, 'a');
52
+ try {
53
+ fs.writeSync(handle, JSON.stringify(event) + '\n');
54
+ fs.fsyncSync(handle);
55
+ }
56
+ finally {
57
+ fs.closeSync(handle);
58
+ }
59
+ }
60
+ finally {
61
+ await release();
62
+ }
63
+ }
64
+ export function readEvents(logPath) {
65
+ if (!fs.existsSync(logPath))
66
+ return [];
67
+ const raw = fs.readFileSync(logPath, 'utf8').trim();
68
+ if (!raw)
69
+ return [];
70
+ return raw.split('\n').map(line => JSON.parse(line));
71
+ }
72
+ export function verifyChain(logPath) {
73
+ const lines = (fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf8') : '').trim();
74
+ if (!lines)
75
+ return { valid: true };
76
+ const arr = lines.split('\n');
77
+ let expectedSeq = 1;
78
+ let expectedPrevHash = null;
79
+ for (let i = 0; i < arr.length; i++) {
80
+ const lineNo = i + 1;
81
+ const line = arr[i];
82
+ let obj;
83
+ try {
84
+ obj = JSON.parse(line);
85
+ }
86
+ catch {
87
+ return { valid: false, breakAtLine: lineNo, reason: 'invalid-json' };
88
+ }
89
+ if (obj.seq !== expectedSeq) {
90
+ return { valid: false, breakAtLine: lineNo, reason: `seq mismatch: expected ${expectedSeq}, got ${obj.seq}` };
91
+ }
92
+ if (obj.prev_hash !== expectedPrevHash) {
93
+ return { valid: false, breakAtLine: lineNo, reason: 'prev_hash mismatch' };
94
+ }
95
+ expectedSeq += 1;
96
+ expectedPrevHash = sha256(line);
97
+ }
98
+ return { valid: true };
99
+ }
100
+ //# sourceMappingURL=audit-log.js.map
@@ -0,0 +1,27 @@
1
+ /** Wire format version for the envelope + result artifact. Skills must
2
+ * declare a compatible skill_runtime_api_version. */
3
+ export declare const ENVELOPE_CONTRACT_VERSION: "1.0";
4
+ /** Hard cap on result artifact size. Larger output rejected with
5
+ * reasonCode: 'result-too-large'. */
6
+ export declare const RESULT_ARTIFACT_MAX_BYTES = 1048576;
7
+ /** Stdout fallback marker prefix; nonce-bound. Format:
8
+ * @@AUTOPILOT_RESULT_BEGIN:<nonce>@@\n{...}\n@@AUTOPILOT_RESULT_END:<nonce>@@
9
+ * Disabled by default; opt-in via skill manifest stdoutFallback: true. */
10
+ export declare const STDOUT_MARKER_BEGIN_PREFIX = "@@AUTOPILOT_RESULT_BEGIN:";
11
+ export declare const STDOUT_MARKER_END_PREFIX = "@@AUTOPILOT_RESULT_END:";
12
+ export declare const STDOUT_MARKER_SUFFIX = "@@";
13
+ /** Reserved sideEffectsPerformed enum (v1). Skills cannot invent values;
14
+ * new entries land via package release. */
15
+ export declare const RESERVED_SIDE_EFFECTS: readonly ["types-regenerated", "migration-ledger-updated", "schema-cache-refreshed", "seed-data-applied", "snapshot-written", "no-side-effects"];
16
+ /** Shell metacharacters forbidden in CommandSpec args[] entries. The
17
+ * structured argv contract executes via spawn(shell:false), so these
18
+ * characters provide no benefit and are rejected at schema validation. */
19
+ export declare const SHELL_METACHARS: RegExp;
20
+ /** Trusted root prefixes for skill resolution. resolved skill paths must
21
+ * start with one of these (after realpath canonicalization) — prevents
22
+ * alias map entries from escaping the repo or installed package dir. */
23
+ export declare const TRUSTED_SKILL_ROOTS: readonly ["skills/", "node_modules/"];
24
+ /** Default temp directory permissions for per-invocation result artifact
25
+ * storage. 0700 = rwx for owner only. */
26
+ export declare const RESULT_TEMPDIR_MODE = 448;
27
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1,35 @@
1
+ // src/core/migrate/contract.ts
2
+ /** Wire format version for the envelope + result artifact. Skills must
3
+ * declare a compatible skill_runtime_api_version. */
4
+ export const ENVELOPE_CONTRACT_VERSION = '1.0';
5
+ /** Hard cap on result artifact size. Larger output rejected with
6
+ * reasonCode: 'result-too-large'. */
7
+ export const RESULT_ARTIFACT_MAX_BYTES = 1_048_576;
8
+ /** Stdout fallback marker prefix; nonce-bound. Format:
9
+ * @@AUTOPILOT_RESULT_BEGIN:<nonce>@@\n{...}\n@@AUTOPILOT_RESULT_END:<nonce>@@
10
+ * Disabled by default; opt-in via skill manifest stdoutFallback: true. */
11
+ export const STDOUT_MARKER_BEGIN_PREFIX = '@@AUTOPILOT_RESULT_BEGIN:';
12
+ export const STDOUT_MARKER_END_PREFIX = '@@AUTOPILOT_RESULT_END:';
13
+ export const STDOUT_MARKER_SUFFIX = '@@';
14
+ /** Reserved sideEffectsPerformed enum (v1). Skills cannot invent values;
15
+ * new entries land via package release. */
16
+ export const RESERVED_SIDE_EFFECTS = [
17
+ 'types-regenerated',
18
+ 'migration-ledger-updated',
19
+ 'schema-cache-refreshed',
20
+ 'seed-data-applied',
21
+ 'snapshot-written',
22
+ 'no-side-effects',
23
+ ];
24
+ /** Shell metacharacters forbidden in CommandSpec args[] entries. The
25
+ * structured argv contract executes via spawn(shell:false), so these
26
+ * characters provide no benefit and are rejected at schema validation. */
27
+ export const SHELL_METACHARS = /[|;&><`$()]/;
28
+ /** Trusted root prefixes for skill resolution. resolved skill paths must
29
+ * start with one of these (after realpath canonicalization) — prevents
30
+ * alias map entries from escaping the repo or installed package dir. */
31
+ export const TRUSTED_SKILL_ROOTS = ['skills/', 'node_modules/'];
32
+ /** Default temp directory permissions for per-invocation result artifact
33
+ * storage. 0700 = rwx for owner only. */
34
+ export const RESULT_TEMPDIR_MODE = 0o700;
35
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1,26 @@
1
+ import type { CommandSpec } from './types.ts';
2
+ export type Confidence = 'high' | 'medium' | 'low';
3
+ export interface DetectionRule {
4
+ name: string;
5
+ stack: string;
6
+ confidence: Confidence;
7
+ /** All entries must exist (relative to project_root) for a match. */
8
+ requireAll: string[];
9
+ /** At least one entry must exist (relative to project_root). Optional. */
10
+ requireAny?: string[];
11
+ /** Patterns to glob for; at least one match required. Optional. */
12
+ requireGlob?: string[];
13
+ /** Path that, if present, disqualifies the rule (e.g. supabase-bare excluded if data/deltas/ present). */
14
+ excludeIf?: string[];
15
+ /** A file's content must contain this regex (e.g. Gemfile contains rails). */
16
+ contentMatches?: {
17
+ file: string;
18
+ pattern: RegExp;
19
+ };
20
+ defaultSkill: string;
21
+ defaultCommand?: CommandSpec;
22
+ /** When confidence is low/medium, prompt user before auto-selecting. */
23
+ promptOnSelect: boolean;
24
+ }
25
+ export declare const DETECTION_RULES: DetectionRule[];
26
+ //# sourceMappingURL=detector-rules.d.ts.map