@delegance/claude-autopilot 5.5.2 → 7.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 (150) hide show
  1. package/CHANGELOG.md +1776 -6
  2. package/README.md +65 -1
  3. package/bin/_launcher.js +38 -23
  4. package/dist/src/adapters/council/openai.js +12 -6
  5. package/dist/src/adapters/deploy/_http.d.ts +43 -0
  6. package/dist/src/adapters/deploy/_http.js +99 -0
  7. package/dist/src/adapters/deploy/fly.d.ts +206 -0
  8. package/dist/src/adapters/deploy/fly.js +696 -0
  9. package/dist/src/adapters/deploy/index.d.ts +2 -0
  10. package/dist/src/adapters/deploy/index.js +33 -0
  11. package/dist/src/adapters/deploy/render.d.ts +181 -0
  12. package/dist/src/adapters/deploy/render.js +550 -0
  13. package/dist/src/adapters/deploy/types.d.ts +67 -3
  14. package/dist/src/adapters/deploy/vercel.d.ts +17 -1
  15. package/dist/src/adapters/deploy/vercel.js +29 -49
  16. package/dist/src/adapters/pricing.d.ts +36 -0
  17. package/dist/src/adapters/pricing.js +40 -0
  18. package/dist/src/adapters/review-engine/codex.js +10 -7
  19. package/dist/src/cli/autopilot.d.ts +75 -0
  20. package/dist/src/cli/autopilot.js +750 -0
  21. package/dist/src/cli/brainstorm.d.ts +23 -0
  22. package/dist/src/cli/brainstorm.js +131 -0
  23. package/dist/src/cli/costs.d.ts +15 -1
  24. package/dist/src/cli/costs.js +99 -10
  25. package/dist/src/cli/dashboard/index.d.ts +5 -0
  26. package/dist/src/cli/dashboard/index.js +49 -0
  27. package/dist/src/cli/dashboard/login.d.ts +22 -0
  28. package/dist/src/cli/dashboard/login.js +260 -0
  29. package/dist/src/cli/dashboard/logout.d.ts +12 -0
  30. package/dist/src/cli/dashboard/logout.js +45 -0
  31. package/dist/src/cli/dashboard/status.d.ts +30 -0
  32. package/dist/src/cli/dashboard/status.js +65 -0
  33. package/dist/src/cli/dashboard/upload.d.ts +16 -0
  34. package/dist/src/cli/dashboard/upload.js +48 -0
  35. package/dist/src/cli/deploy.d.ts +3 -3
  36. package/dist/src/cli/deploy.js +34 -9
  37. package/dist/src/cli/engine-flag-deprecation.d.ts +14 -0
  38. package/dist/src/cli/engine-flag-deprecation.js +20 -0
  39. package/dist/src/cli/fix.d.ts +18 -0
  40. package/dist/src/cli/fix.js +105 -11
  41. package/dist/src/cli/help-text.d.ts +52 -0
  42. package/dist/src/cli/help-text.js +416 -0
  43. package/dist/src/cli/implement.d.ts +91 -0
  44. package/dist/src/cli/implement.js +196 -0
  45. package/dist/src/cli/index.d.ts +2 -1
  46. package/dist/src/cli/index.js +774 -245
  47. package/dist/src/cli/json-envelope.d.ts +187 -0
  48. package/dist/src/cli/json-envelope.js +270 -0
  49. package/dist/src/cli/json-mode.d.ts +33 -0
  50. package/dist/src/cli/json-mode.js +201 -0
  51. package/dist/src/cli/migrate.d.ts +111 -0
  52. package/dist/src/cli/migrate.js +305 -0
  53. package/dist/src/cli/plan.d.ts +81 -0
  54. package/dist/src/cli/plan.js +149 -0
  55. package/dist/src/cli/pr.d.ts +106 -0
  56. package/dist/src/cli/pr.js +191 -19
  57. package/dist/src/cli/preflight.js +26 -0
  58. package/dist/src/cli/review.d.ts +27 -0
  59. package/dist/src/cli/review.js +126 -0
  60. package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
  61. package/dist/src/cli/runs-watch-renderer.js +275 -0
  62. package/dist/src/cli/runs-watch.d.ts +41 -0
  63. package/dist/src/cli/runs-watch.js +395 -0
  64. package/dist/src/cli/runs.d.ts +122 -0
  65. package/dist/src/cli/runs.js +902 -0
  66. package/dist/src/cli/scaffold.d.ts +39 -0
  67. package/dist/src/cli/scaffold.js +287 -0
  68. package/dist/src/cli/scan.d.ts +93 -0
  69. package/dist/src/cli/scan.js +166 -40
  70. package/dist/src/cli/setup.d.ts +30 -0
  71. package/dist/src/cli/setup.js +137 -0
  72. package/dist/src/cli/spec.d.ts +66 -0
  73. package/dist/src/cli/spec.js +132 -0
  74. package/dist/src/cli/validate.d.ts +29 -0
  75. package/dist/src/cli/validate.js +131 -0
  76. package/dist/src/core/config/schema.d.ts +9 -0
  77. package/dist/src/core/config/schema.js +7 -0
  78. package/dist/src/core/config/types.d.ts +11 -0
  79. package/dist/src/core/council/runner.d.ts +10 -1
  80. package/dist/src/core/council/runner.js +25 -3
  81. package/dist/src/core/council/types.d.ts +7 -0
  82. package/dist/src/core/errors.d.ts +1 -1
  83. package/dist/src/core/errors.js +11 -0
  84. package/dist/src/core/logging/redaction.d.ts +13 -0
  85. package/dist/src/core/logging/redaction.js +20 -0
  86. package/dist/src/core/migrate/schema-validator.js +15 -1
  87. package/dist/src/core/phases/static-rules.d.ts +5 -1
  88. package/dist/src/core/phases/static-rules.js +2 -5
  89. package/dist/src/core/run-state/budget.d.ts +88 -0
  90. package/dist/src/core/run-state/budget.js +141 -0
  91. package/dist/src/core/run-state/cli-internal.d.ts +21 -0
  92. package/dist/src/core/run-state/cli-internal.js +174 -0
  93. package/dist/src/core/run-state/events.d.ts +59 -0
  94. package/dist/src/core/run-state/events.js +512 -0
  95. package/dist/src/core/run-state/lock.d.ts +61 -0
  96. package/dist/src/core/run-state/lock.js +206 -0
  97. package/dist/src/core/run-state/phase-context.d.ts +60 -0
  98. package/dist/src/core/run-state/phase-context.js +108 -0
  99. package/dist/src/core/run-state/phase-registry.d.ts +137 -0
  100. package/dist/src/core/run-state/phase-registry.js +162 -0
  101. package/dist/src/core/run-state/phase-runner.d.ts +80 -0
  102. package/dist/src/core/run-state/phase-runner.js +447 -0
  103. package/dist/src/core/run-state/provider-readback.d.ts +130 -0
  104. package/dist/src/core/run-state/provider-readback.js +426 -0
  105. package/dist/src/core/run-state/replay-decision.d.ts +69 -0
  106. package/dist/src/core/run-state/replay-decision.js +144 -0
  107. package/dist/src/core/run-state/resolve-engine.d.ts +45 -0
  108. package/dist/src/core/run-state/resolve-engine.js +74 -0
  109. package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
  110. package/dist/src/core/run-state/resume-preflight.js +116 -0
  111. package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +69 -0
  112. package/dist/src/core/run-state/run-phase-with-lifecycle.js +193 -0
  113. package/dist/src/core/run-state/runs.d.ts +57 -0
  114. package/dist/src/core/run-state/runs.js +288 -0
  115. package/dist/src/core/run-state/snapshot.d.ts +14 -0
  116. package/dist/src/core/run-state/snapshot.js +114 -0
  117. package/dist/src/core/run-state/state.d.ts +40 -0
  118. package/dist/src/core/run-state/state.js +164 -0
  119. package/dist/src/core/run-state/types.d.ts +284 -0
  120. package/dist/src/core/run-state/types.js +19 -0
  121. package/dist/src/core/run-state/ulid.d.ts +11 -0
  122. package/dist/src/core/run-state/ulid.js +95 -0
  123. package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
  124. package/dist/src/core/schema-alignment/extractor/index.js +2 -2
  125. package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
  126. package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
  127. package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
  128. package/dist/src/core/schema-alignment/git-history.js +53 -0
  129. package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
  130. package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
  131. package/dist/src/dashboard/auto-upload.d.ts +26 -0
  132. package/dist/src/dashboard/auto-upload.js +107 -0
  133. package/dist/src/dashboard/config.d.ts +22 -0
  134. package/dist/src/dashboard/config.js +109 -0
  135. package/dist/src/dashboard/upload/canonical.d.ts +3 -0
  136. package/dist/src/dashboard/upload/canonical.js +16 -0
  137. package/dist/src/dashboard/upload/chain.d.ts +9 -0
  138. package/dist/src/dashboard/upload/chain.js +27 -0
  139. package/dist/src/dashboard/upload/snapshot.d.ts +23 -0
  140. package/dist/src/dashboard/upload/snapshot.js +66 -0
  141. package/dist/src/dashboard/upload/uploader.d.ts +54 -0
  142. package/dist/src/dashboard/upload/uploader.js +330 -0
  143. package/package.json +19 -3
  144. package/scripts/autoregress.ts +1 -1
  145. package/scripts/test-runner.mjs +4 -0
  146. package/skills/claude-autopilot.md +1 -1
  147. package/skills/make-interfaces-feel-better/SKILL.md +104 -0
  148. package/skills/simplify-ui/SKILL.md +103 -0
  149. package/skills/ui/SKILL.md +117 -0
  150. package/skills/ui-ux-pro-max/SKILL.md +90 -0
@@ -0,0 +1,201 @@
1
+ // src/cli/json-mode.ts
2
+ //
3
+ // v6 Phase 5 — strict --json channel discipline.
4
+ //
5
+ // When --json is set, the spec mandates:
6
+ // - stdout: exactly one JSON envelope per command invocation.
7
+ // - stderr: only NDJSON event lines. No human-readable warnings, no color.
8
+ // - All warnings / prompts / human diagnostics route to typed events.
9
+ // - Interactive prompts hard-fail with exit:78.
10
+ //
11
+ // Many existing CLI handlers call `console.log` / `console.error` /
12
+ // `console.warn` / `process.stdout.write` / `process.stderr.write` directly
13
+ // for human output. Migrating each one to thread a json flag and switch
14
+ // behavior would be a multi-thousand-line patch. Instead, we install a
15
+ // channel-discipline shim BEFORE the handler runs that captures every
16
+ // non-NDJSON-shaped write and reroutes it:
17
+ //
18
+ // - stdout writes that aren't valid JSON → captured into messages[]
19
+ // (will be attached to the
20
+ // final envelope by the
21
+ // dispatcher).
22
+ // - stdout writes that ARE valid JSON → captured raw (the dispatcher
23
+ // will pick the LAST one as
24
+ // the envelope, on the
25
+ // assumption that handlers
26
+ // that already emit a JSON
27
+ // envelope want it to win).
28
+ // - stderr writes that are NDJSON → passed through as-is.
29
+ // - stderr writes that aren't NDJSON → wrapped in a synthetic
30
+ // run.warning event and
31
+ // re-emitted as NDJSON.
32
+ //
33
+ // Console wrappers route through the same shim:
34
+ // console.log -> stdout shim
35
+ // console.error / console.warn -> stderr shim (with level metadata)
36
+ //
37
+ // All ANSI color codes are stripped on the way out.
38
+ //
39
+ // The shim is restorable — the dispatcher calls `restore()` after the
40
+ // handler completes so test runs (which spawn many handlers in-process) get
41
+ // a clean slate.
42
+ import { __getChannelTestSink, stripAnsi, syntheticRunWarning } from "./json-envelope.js";
43
+ /** Try to JSON.parse a chunk. Returns the parsed value on success, undefined
44
+ * on failure. We strip a trailing newline before parsing so handlers writing
45
+ * one envelope per line are accepted. */
46
+ function tryParse(chunk) {
47
+ const trimmed = chunk.trim();
48
+ if (trimmed.length === 0)
49
+ return undefined;
50
+ try {
51
+ return JSON.parse(trimmed);
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ /** Coerce an unknown buffer/string into a string. Mirrors Node's stream
58
+ * semantics where writes accept either. */
59
+ function toText(chunk) {
60
+ if (typeof chunk === 'string')
61
+ return chunk;
62
+ if (chunk instanceof Uint8Array)
63
+ return Buffer.from(chunk).toString('utf8');
64
+ return String(chunk);
65
+ }
66
+ /** Install the channel-discipline shim. Returns a handle the caller uses to
67
+ * collect captured output and to restore the original state.
68
+ *
69
+ * Safe to call when --json is OFF — the function is a no-op in that case
70
+ * (returns a handle whose restore() does nothing and whose buffers stay
71
+ * empty). Callers should still respect the active flag and skip the
72
+ * envelope emission in text mode. */
73
+ export function installJsonModeChannelDiscipline(opts = { active: true }) {
74
+ const capturedMessages = [];
75
+ const capturedJsonStdout = [];
76
+ if (!opts.active) {
77
+ return {
78
+ restore: () => { },
79
+ capturedMessages,
80
+ capturedJsonStdout,
81
+ };
82
+ }
83
+ const sink = __getChannelTestSink();
84
+ const origStdoutWrite = process.stdout.write.bind(process.stdout);
85
+ const origStderrWrite = process.stderr.write.bind(process.stderr);
86
+ // When a test sink is installed, the shim still wraps process streams so
87
+ // user code goes through capture, but pass-through writes (NDJSON we want
88
+ // to surface as-is) go to the sink so tests can read them deterministically
89
+ // without interleaving with the test runner's own TAP output on the real
90
+ // streams.
91
+ const writeStderr = (line) => {
92
+ if (sink)
93
+ sink.stderr(line);
94
+ else
95
+ origStderrWrite(line);
96
+ };
97
+ const origConsoleLog = console.log;
98
+ const origConsoleError = console.error;
99
+ const origConsoleWarn = console.warn;
100
+ const origConsoleInfo = console.info;
101
+ const origConsoleDebug = console.debug;
102
+ /** Splits a chunk on newlines, tries to JSON.parse each non-empty line.
103
+ * Lines that parse as JSON are recorded as JSON envelopes; the rest are
104
+ * recorded as captured text messages with the given level. */
105
+ function captureStdout(chunk, level) {
106
+ const cleaned = stripAnsi(chunk);
107
+ // Stdout chunks may contain multiple JSON envelopes back-to-back; split
108
+ // on newlines and parse each line. We treat the WHOLE chunk as a single
109
+ // text message if no line parses (preserves multi-line formatted text).
110
+ const lines = cleaned.split('\n');
111
+ let anyParsed = false;
112
+ for (const line of lines) {
113
+ const parsed = tryParse(line);
114
+ if (parsed !== undefined) {
115
+ capturedJsonStdout.push(parsed);
116
+ anyParsed = true;
117
+ }
118
+ }
119
+ if (!anyParsed && cleaned.trim().length > 0) {
120
+ capturedMessages.push({ level, text: cleaned.replace(/\n+$/, '') });
121
+ }
122
+ }
123
+ /** Splits a chunk on newlines for stderr. NDJSON lines pass through to the
124
+ * real stderr; non-NDJSON lines are wrapped in synthetic run.warning
125
+ * events and re-emitted. */
126
+ function captureStderr(chunk, level) {
127
+ const cleaned = stripAnsi(chunk);
128
+ const lines = cleaned.split('\n');
129
+ for (const line of lines) {
130
+ if (line.length === 0)
131
+ continue;
132
+ const parsed = tryParse(line);
133
+ if (parsed !== undefined) {
134
+ // Pass through as-is (with trailing newline) — already NDJSON.
135
+ writeStderr(line + '\n');
136
+ }
137
+ else {
138
+ const ev = syntheticRunWarning(line, { level });
139
+ writeStderr(JSON.stringify(ev) + '\n');
140
+ }
141
+ }
142
+ }
143
+ // Wrap process.stdout.write. Note: returning true keeps the stream
144
+ // signature truthy; we drop the original write entirely under JSON mode
145
+ // because every byte is captured and re-routed via the envelope.
146
+ process.stdout.write = ((chunk, ...rest) => {
147
+ captureStdout(toText(chunk), 'log');
148
+ // Honor the optional callback signature so caller code that passes one
149
+ // (rare in our handlers but possible) doesn't hang.
150
+ const cb = rest.find(r => typeof r === 'function');
151
+ if (cb)
152
+ cb();
153
+ return true;
154
+ });
155
+ process.stderr.write = ((chunk, ...rest) => {
156
+ captureStderr(toText(chunk), 'warn');
157
+ const cb = rest.find(r => typeof r === 'function');
158
+ if (cb)
159
+ cb();
160
+ return true;
161
+ });
162
+ console.log = (...args) => {
163
+ captureStdout(args.map(toText).join(' ') + '\n', 'log');
164
+ };
165
+ console.info = (...args) => {
166
+ captureStdout(args.map(toText).join(' ') + '\n', 'info');
167
+ };
168
+ console.debug = (...args) => {
169
+ captureStdout(args.map(toText).join(' ') + '\n', 'debug');
170
+ };
171
+ console.warn = (...args) => {
172
+ captureStderr(args.map(toText).join(' ') + '\n', 'warn');
173
+ };
174
+ console.error = (...args) => {
175
+ captureStderr(args.map(toText).join(' ') + '\n', 'error');
176
+ };
177
+ let restored = false;
178
+ function restore() {
179
+ if (restored)
180
+ return;
181
+ restored = true;
182
+ process.stdout.write = origStdoutWrite;
183
+ process.stderr.write = origStderrWrite;
184
+ console.log = origConsoleLog;
185
+ console.error = origConsoleError;
186
+ console.warn = origConsoleWarn;
187
+ console.info = origConsoleInfo;
188
+ console.debug = origConsoleDebug;
189
+ }
190
+ return { restore, capturedMessages, capturedJsonStdout };
191
+ }
192
+ /** Build a ChannelOptions value from the parsed --json flag plus
193
+ * process.stdin TTY-ness. The caller (dispatcher) usually wants the
194
+ * computed nonInteractive flag for prompt-or-hard-fail decisions. */
195
+ export function computeChannelOptions(json) {
196
+ return {
197
+ json,
198
+ nonInteractive: json || !process.stdin.isTTY,
199
+ };
200
+ }
201
+ //# sourceMappingURL=json-mode.js.map
@@ -0,0 +1,111 @@
1
+ import type { GuardrailConfig } from '../core/config/types.ts';
2
+ import { type RunPhase } from '../core/run-state/phase-runner.ts';
3
+ import type { ResultArtifact } from '../core/migrate/types.ts';
4
+ export interface MigrateCommandOptions {
5
+ cwd?: string;
6
+ configPath?: string;
7
+ /** Target environment from `.autopilot/stack.md`. Defaults to `dev`. */
8
+ env?: string;
9
+ /** When true, the dispatcher passes `dryRun: true` through the envelope so
10
+ * the skill executes a no-side-effect plan rather than applying. */
11
+ dryRun?: boolean;
12
+ /** `--yes` — required to apply prod migrations in CI per policy. */
13
+ yesFlag?: boolean;
14
+ /** `--non-interactive` / `--json` / not-a-TTY equivalent. */
15
+ nonInteractive?: boolean;
16
+ /**
17
+ * v6.0.8 — engine knob inputs. Same precedence as scan / costs / fix /
18
+ * plan / review / validate (CLI > env > config > built-in default off).
19
+ */
20
+ cliEngine?: boolean;
21
+ envEngine?: string;
22
+ /**
23
+ * Test-only seam — replaces the real dispatcher with a fake so smoke
24
+ * tests can exercise the engine-wrap path without spawning a child
25
+ * process or hitting a real database. Production callers MUST NOT
26
+ * pass this; the CLI dispatcher in `src/cli/index.ts` does not expose
27
+ * a flag that sets it. Underscore-prefixed for grep-ability.
28
+ */
29
+ __testDispatch?: (input: MigrateInput) => Promise<ResultArtifact>;
30
+ }
31
+ /**
32
+ * Phase input — captured as a struct so the engine path's phase body matches
33
+ * the engine-off path's call signature.
34
+ *
35
+ * Exported so the v6.2.1 orchestrator's phase registry can carry the typed
36
+ * I/O shape on its `PhaseRegistration<MigrateInput, MigrateOutput>` slot.
37
+ */
38
+ export interface MigrateInput {
39
+ cwd: string;
40
+ env: string;
41
+ dryRun: boolean;
42
+ yesFlag: boolean;
43
+ nonInteractive: boolean;
44
+ /** Runtime version string (from package.json) — required by the
45
+ * dispatcher's manifest handshake. Resolved in the outer scope so the
46
+ * phase body stays a pure await on `dispatch()`. */
47
+ runtimeVersion: string;
48
+ /** v6.2.1 — dispatcher seam plumbed into the phase body so the wrap can
49
+ * emit the pre-effect breadcrumb BEFORE invoking the dispatcher. The
50
+ * outer scope assembles either the real `runMigrateDispatch` or the
51
+ * test-only `__testDispatch`; the phase body just calls `dispatchFn`. */
52
+ dispatchFn: (input: MigrateInput) => Promise<ResultArtifact>;
53
+ }
54
+ /**
55
+ * Phase output — JSON-serializable summary suitable for persistence as
56
+ * `result` on phases/migrate.json. A future skip-already-applied (Phase 6)
57
+ * could reconstruct the dispatch outcome without re-running by reading the
58
+ * persisted externalRefs + this result.
59
+ *
60
+ * Exported alongside `MigrateInput` for the registry's typed I/O slot.
61
+ */
62
+ export interface MigrateOutput {
63
+ /** Status from the result artifact (applied | skipped | error | ...). */
64
+ status: ResultArtifact['status'];
65
+ /** Reason code from the result artifact (migration-applied,
66
+ * migration-disabled, env-not-configured, etc.). */
67
+ reasonCode: string;
68
+ /** List of migrations applied this run (empty on `skipped` / `error`). */
69
+ appliedMigrations: string[];
70
+ /** Operator-facing next-action hints surfaced by the skill. */
71
+ nextActions: string[];
72
+ /** Echoed env so the render layer / skip-already-applied has it. */
73
+ env: string;
74
+ }
75
+ /** v6.2.1 — early-exit / build-result discriminants (parity with the v6.2.0
76
+ * builders in scan / spec / plan / implement). `migrate` has no early-exit
77
+ * branches today but the discriminant is included for shape parity. */
78
+ export interface BuildMigratePhaseEarlyExit {
79
+ kind: 'early-exit';
80
+ exitCode: number;
81
+ }
82
+ export interface BuildMigratePhaseResult {
83
+ kind: 'phase';
84
+ phase: RunPhase<MigrateInput, MigrateOutput>;
85
+ input: MigrateInput;
86
+ config: GuardrailConfig;
87
+ renderResult: (output: MigrateOutput) => number;
88
+ }
89
+ /**
90
+ * v6.2.1 — extract the `RunPhase<MigrateInput, MigrateOutput>` construction
91
+ * out of `runMigrate(options)` so the new top-level `autopilot` orchestrator
92
+ * can drive `runPhase` itself with a shared `phaseIdx` against the same run
93
+ * dir. Mirrors the v6.2.0 builder pattern in scan / spec / plan / implement.
94
+ *
95
+ * This builder ALSO closes the v6.2.1 idempotency contract gap on `migrate`:
96
+ * the phase body now emits a `migration-batch` externalRef BEFORE invoking
97
+ * the dispatcher. See the long rationale on `executeMigratePhase` below.
98
+ *
99
+ * The `lastResultArtifact` ref is the seam through which `runMigrate` gets
100
+ * the full `ResultArtifact` for its --json envelope (the JSON-serializable
101
+ * `MigrateOutput` is a compact subset; the full artifact has nonce,
102
+ * contractVersion, sideEffectsPerformed, etc.). Builder callers that don't
103
+ * need the artifact can ignore it.
104
+ */
105
+ export declare function buildMigratePhase(options: MigrateCommandOptions): Promise<BuildMigratePhaseResult | BuildMigratePhaseEarlyExit>;
106
+ export declare function runMigrate(options?: MigrateCommandOptions): Promise<{
107
+ exitCode: number;
108
+ /** Surfaced for the CLI dispatcher's `--json` payload callback. */
109
+ result: ResultArtifact | null;
110
+ }>;
111
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1,305 @@
1
+ // src/cli/migrate.ts
2
+ //
3
+ // v6.0.8 — engine-wrap shell for the `migrate` pipeline phase. Runs the
4
+ // stack-aware migrate dispatcher (`src/core/migrate/dispatcher.ts`) inside
5
+ // a `RunPhase<MigrateInput, MigrateOutput>` so v6 pipeline runs check-
6
+ // point a `migrate` phase entry alongside `plan`, `review`, and
7
+ // `validate`.
8
+ //
9
+ // migrate is the FIRST side-effecting phase to land under
10
+ // `runPhaseWithLifecycle` (followed by `implement` and `pr` in v6.0.7 +
11
+ // v6.0.9 — see the rebase contract in PR #102 / #103). The wrap declares:
12
+ //
13
+ // idempotent: false (per spec table at docs/specs/v6-run-state-engine.md)
14
+ // hasSideEffects: true (applies migrations against a database)
15
+ // externalRefs: migration-version (one per applied migration / per env)
16
+ //
17
+ // Why `idempotent: false` even though the underlying skill is ledger-
18
+ // guarded:
19
+ // The Delegance migrate skill (and all conforming `migrate@1` skills)
20
+ // tracks applied migrations in a ledger table — re-running the verb
21
+ // against a database that already has a given migration applied is a
22
+ // no-op (the dispatcher returns `status: 'skipped'`, `reasonCode:
23
+ // migration-disabled` or skill-specific equivalent). So at the
24
+ // *outcome* layer, replay is safe.
25
+ //
26
+ // At the *engine semantics* layer, however, `idempotent: true` means
27
+ // "re-running the phase against the same input produces equivalent
28
+ // output." A dispatch invocation that previously applied N migrations
29
+ // on attempt 1 and applies 0 on attempt 2 (everything already in the
30
+ // ledger) DOES produce different output (different `appliedMigrations`
31
+ // list, different `status`). The spec table's `idempotent: false` is
32
+ // the right declaration.
33
+ //
34
+ // The practical consequence: when a prior `phase.success` exists for
35
+ // `migrate` and the engine is asked to retry, it consults the
36
+ // persisted `externalRefs` (`migration-version` entries) to decide
37
+ // whether to skip-already-applied or retry. Phase 6 will wire the
38
+ // read-back to live `migration_state` queries; until then, retries on
39
+ // side-effecting phases require `--force-replay`. Documented in
40
+ // docs/v6/migration-guide.md "Idempotency + replay rules".
41
+ //
42
+ // Why `hasSideEffects: true`:
43
+ // Migrations mutate database schema / seed data. The dispatcher writes
44
+ // audit log entries, schema cache refreshes, types regeneration. The
45
+ // engine's "no replay without read-back" gate is exactly what we want.
46
+ //
47
+ // `migration-version` externalRefs:
48
+ // For every migration name in `result.appliedMigrations`, we emit a
49
+ // `phase.externalRef` event with `kind: 'migration-version'` and `id`
50
+ // shaped as `<env>:<migration_name>`. The `<env>:` prefix scopes the
51
+ // ref by target environment (dev / qa / prod) so multi-env pipelines
52
+ // can read back per-env state. Phase 6's read-back rule will compare
53
+ // the persisted set to the live ledger to decide skip-already-applied
54
+ // vs retry vs needs-human.
55
+ //
56
+ // Engine-off path (default through v6.0.x): byte-for-byte identical to
57
+ // the pre-v6.0.8 inline dispatch case in `src/cli/index.ts`. The
58
+ // `runEngineOff` callback supplied to `runPhaseWithLifecycle` invokes
59
+ // the same dispatch + render shape that the legacy code path used. CI /
60
+ // scripts that don't pass `--engine` are unaffected.
61
+ import * as path from 'node:path';
62
+ import * as fs from 'node:fs';
63
+ import * as crypto from 'node:crypto';
64
+ import { loadConfig } from "../core/config/loader.js";
65
+ import { runPhaseWithLifecycle } from "../core/run-state/run-phase-with-lifecycle.js";
66
+ import { dispatch as runMigrateDispatch } from "../core/migrate/dispatcher.js";
67
+ import { findPackageRoot } from "./_pkg-root.js";
68
+ const C = {
69
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
70
+ green: '\x1b[32m', red: '\x1b[31m', cyan: '\x1b[36m',
71
+ };
72
+ const fmt = (c, t) => `${C[c]}${t}${C.reset}`;
73
+ /**
74
+ * v6.2.1 — extract the `RunPhase<MigrateInput, MigrateOutput>` construction
75
+ * out of `runMigrate(options)` so the new top-level `autopilot` orchestrator
76
+ * can drive `runPhase` itself with a shared `phaseIdx` against the same run
77
+ * dir. Mirrors the v6.2.0 builder pattern in scan / spec / plan / implement.
78
+ *
79
+ * This builder ALSO closes the v6.2.1 idempotency contract gap on `migrate`:
80
+ * the phase body now emits a `migration-batch` externalRef BEFORE invoking
81
+ * the dispatcher. See the long rationale on `executeMigratePhase` below.
82
+ *
83
+ * The `lastResultArtifact` ref is the seam through which `runMigrate` gets
84
+ * the full `ResultArtifact` for its --json envelope (the JSON-serializable
85
+ * `MigrateOutput` is a compact subset; the full artifact has nonce,
86
+ * contractVersion, sideEffectsPerformed, etc.). Builder callers that don't
87
+ * need the artifact can ignore it.
88
+ */
89
+ export async function buildMigratePhase(options) {
90
+ const cwd = options.cwd ?? process.cwd();
91
+ const configPath = options.configPath ?? path.join(cwd, 'guardrail.config.yaml');
92
+ let config = { configVersion: 1 };
93
+ if (fs.existsSync(configPath)) {
94
+ const loaded = await loadConfig(configPath);
95
+ if (loaded)
96
+ config = loaded;
97
+ }
98
+ const envName = options.env ?? 'dev';
99
+ const dryRun = options.dryRun ?? false;
100
+ const yesFlag = options.yesFlag ?? false;
101
+ const nonInteractive = options.nonInteractive ?? !process.stdin.isTTY;
102
+ // Read package version for the runtime handshake. The CLI dispatcher used
103
+ // to do this inline; we keep the same lookup shape so the engine-off path
104
+ // is byte-for-byte identical to v6.0.7.
105
+ const root = findPackageRoot(import.meta.url);
106
+ let runtimeVersion = 'unknown';
107
+ if (root) {
108
+ try {
109
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
110
+ runtimeVersion = pkg.version;
111
+ }
112
+ catch {
113
+ /* fall through with 'unknown' — handshake will fail closed */
114
+ }
115
+ }
116
+ const dispatchFn = options.__testDispatch
117
+ ?? (async (input) => {
118
+ return runMigrateDispatch({
119
+ repoRoot: input.cwd,
120
+ env: input.env,
121
+ yesFlag: input.yesFlag,
122
+ nonInteractive: input.nonInteractive,
123
+ currentRuntimeVersion: input.runtimeVersion,
124
+ dryRun: input.dryRun,
125
+ });
126
+ });
127
+ const migrateInput = {
128
+ cwd,
129
+ env: envName,
130
+ dryRun,
131
+ yesFlag,
132
+ nonInteractive,
133
+ runtimeVersion,
134
+ dispatchFn,
135
+ };
136
+ const phase = {
137
+ name: 'migrate',
138
+ // See top-of-file rationale. The spec table at line 162 of
139
+ // docs/specs/v6-run-state-engine.md declares idempotent: false
140
+ // because dispatch output varies by ledger state — the v6.0.8
141
+ // wrap matches that declaration. The underlying skill IS
142
+ // ledger-guarded against double-apply; that's a property of the
143
+ // skill, not of the phase contract. With `hasSideEffects: true`
144
+ // and persisted `migration-batch` (pre-effect) + `migration-version`
145
+ // (post-effect) externalRefs, Phase 6's resume gate reads back the
146
+ // live migration_state to decide skip-already-applied vs retry vs
147
+ // needs-human.
148
+ idempotent: false,
149
+ hasSideEffects: true,
150
+ run: async (input, ctx) => executeMigratePhase(input, ctx),
151
+ };
152
+ return {
153
+ kind: 'phase',
154
+ phase,
155
+ input: migrateInput,
156
+ config,
157
+ renderResult: (output) => renderMigrateOutput(output),
158
+ };
159
+ }
160
+ export async function runMigrate(options = {}) {
161
+ const built = await buildMigratePhase(options);
162
+ if (built.kind === 'early-exit') {
163
+ return { exitCode: built.exitCode, result: null };
164
+ }
165
+ const { phase, input: migrateInput, config, renderResult } = built;
166
+ // Outer ref so the render path + the wrapper's --json envelope can
167
+ // surface the full ResultArtifact. Updated by `executeMigratePhase` on
168
+ // every dispatch (engine-on and engine-off paths share the same body).
169
+ let resultArtifact = null;
170
+ const captureInput = {
171
+ ...migrateInput,
172
+ dispatchFn: async (inp) => {
173
+ const artifact = await migrateInput.dispatchFn(inp);
174
+ resultArtifact = artifact;
175
+ return artifact;
176
+ },
177
+ };
178
+ // v6.0.6+ — lifecycle wiring lives in `runPhaseWithLifecycle`. The
179
+ // helper owns engine resolution, createRun, run.complete, state.json
180
+ // refresh, and lock release. The caller just supplies the phase, the
181
+ // input, the loaded config, and an engine-off escape hatch.
182
+ let output;
183
+ try {
184
+ const lifecycleResult = await runPhaseWithLifecycle({
185
+ cwd: migrateInput.cwd,
186
+ phase,
187
+ input: captureInput,
188
+ config,
189
+ cliEngine: options.cliEngine,
190
+ envEngine: options.envEngine,
191
+ // Engine-off escape hatch — re-uses the same dispatchFn. We do
192
+ // NOT thread a real ctx through here because the engine-off path
193
+ // has no event ledger to write into; externalRefs only matter on
194
+ // the engine path. The artifact still lands on `resultArtifact`
195
+ // for the --json payload callback in the CLI dispatcher.
196
+ runEngineOff: () => executeMigratePhase(captureInput, null),
197
+ });
198
+ output = lifecycleResult.output;
199
+ }
200
+ catch {
201
+ // Helper already printed `[migrate] engine: phase failed — <msg>`
202
+ // + the inspect hint, emitted run.complete failed, refreshed
203
+ // state.json, released the lock. Surface the legacy non-zero exit.
204
+ return { exitCode: 1, result: resultArtifact };
205
+ }
206
+ return {
207
+ exitCode: renderResult(output),
208
+ result: resultArtifact,
209
+ };
210
+ }
211
+ // ---------------------------------------------------------------------------
212
+ // Phase body — emit the pre-effect `migration-batch` breadcrumb, dispatch,
213
+ // then emit one post-effect `migration-version` ref per applied migration.
214
+ //
215
+ // v6.2.1 idempotency contract (per docs/specs/v6.2.1-side-effect-idempotency.md):
216
+ // 1. PRE-effect breadcrumb: a `migration-batch` ref BEFORE `dispatchFn` so
217
+ // a partial crash leaves a resume target. The orchestrator's resume
218
+ // preflight reads this back to distinguish "we started this batch" from
219
+ // "we never started any batch."
220
+ // 2. POST-effect reconciliation: one `migration-version` ref per applied
221
+ // migration AFTER dispatch returns successfully. These are authoritative
222
+ // for the readback's skip-already-applied decision.
223
+ //
224
+ // On the deterministic-id question: the spec prescribes
225
+ // `${env}:${sha256Hex(env + ':' + plannedMigrations.sort().join(','))}`
226
+ // which would let two runs of the same batch share an id (better resume
227
+ // semantics). That requires extracting `planMigrations(input)` from the
228
+ // dispatcher to surface the planned set without applying. The current
229
+ // dispatcher doesn't expose that pre-dispatch — every supported migrate skill
230
+ // (Delegance Supabase, Rails, Alembic, …) discovers its planned set inside
231
+ // the skill subprocess after the manifest handshake + envelope build, so a
232
+ // pre-dispatch list would require an across-the-board skill protocol change
233
+ // (a new "plan" verb that runs without side effects). That's out of scope
234
+ // for v6.2.1 — the spec explicitly approves the fallback breadcrumb form
235
+ // `${env}:pre-dispatch:${Date.now()}` ("less idempotent but still
236
+ // recoverable"). The post-effect `migration-version` refs ARE deterministic
237
+ // (`${env}:${migration}`) and remain authoritative for the readback's
238
+ // skip-already-applied decision; the batch ref's role is purely "did we
239
+ // start this work?" — non-deterministic ids don't break that contract.
240
+ //
241
+ // Cross-run dedup of the batch ref (e.g. recognizing two pre-dispatch
242
+ // breadcrumbs as the same operation) is gated on the deterministic id form
243
+ // and explicitly listed under v6.2.1 "out of scope."
244
+ // ---------------------------------------------------------------------------
245
+ async function executeMigratePhase(input, ctx) {
246
+ // PRE-effect breadcrumb. Recorded BEFORE the dispatcher so that even a
247
+ // partial crash leaves a resume target. Engine-off path has no ctx; the
248
+ // breadcrumb is then a no-op (same precedent as pr.ts and every other
249
+ // wrapped verb's engine-off path).
250
+ if (ctx) {
251
+ // Fallback id form per the v6.2.1 spec — see the long rationale comment
252
+ // above. `crypto` is imported even though only the timestamp variant is
253
+ // used today; it's reserved for the deterministic-id upgrade once a
254
+ // `planMigrations()` extraction lands.
255
+ void crypto;
256
+ ctx.emitExternalRef({
257
+ kind: 'migration-batch',
258
+ id: `${input.env}:pre-dispatch:${Date.now()}`,
259
+ });
260
+ }
261
+ const artifact = await input.dispatchFn(input);
262
+ // POST-effect reconciliation refs — one per applied migration. The id is
263
+ // shaped `<env>:<migration_name>` so multi-env pipelines (dev → qa → prod)
264
+ // can disambiguate the same migration across targets. Phase 6's read-back
265
+ // rule compares this set to the live ledger.
266
+ if (ctx) {
267
+ for (const migration of artifact.appliedMigrations) {
268
+ ctx.emitExternalRef({
269
+ kind: 'migration-version',
270
+ id: `${input.env}:${migration}`,
271
+ });
272
+ }
273
+ }
274
+ return {
275
+ status: artifact.status,
276
+ reasonCode: artifact.reasonCode,
277
+ appliedMigrations: artifact.appliedMigrations,
278
+ nextActions: artifact.nextActions,
279
+ env: input.env,
280
+ };
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // Render — translate MigrateOutput back to the legacy stdout banner + exit
284
+ // code. Lives outside the wrapped phase because it's pure presentation; doing
285
+ // rendering inside the phase body would couple the engine path's idempotency
286
+ // to console output.
287
+ // ---------------------------------------------------------------------------
288
+ function renderMigrateOutput(output) {
289
+ const ok = output.status === 'applied' || output.status === 'skipped';
290
+ const color = ok ? C.green : C.red;
291
+ console.log(`${color}[migrate] status=${output.status} reason=${output.reasonCode}${C.reset}`);
292
+ if (output.appliedMigrations.length > 0) {
293
+ console.log(` applied: ${output.appliedMigrations.join(', ')}`);
294
+ }
295
+ if (output.nextActions.length > 0) {
296
+ console.log(` next: ${output.nextActions.join('; ')}`);
297
+ }
298
+ // Suppress unused-helper TS warning when fmt isn't called above (the dim /
299
+ // bold / cyan helpers are reserved for future render paths — keeping the
300
+ // import + fmt() reference parallel with other wrapped verbs makes
301
+ // bin-mods easier to read).
302
+ void fmt;
303
+ return ok ? 0 : 1;
304
+ }
305
+ //# sourceMappingURL=migrate.js.map