@delegance/claude-autopilot 5.2.2 โ†’ 6.2.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 (130) hide show
  1. package/CHANGELOG.md +1027 -1
  2. package/README.md +104 -17
  3. package/dist/src/adapters/council/claude.js +2 -1
  4. package/dist/src/adapters/council/openai.js +14 -7
  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/generic.d.ts +39 -0
  10. package/dist/src/adapters/deploy/generic.js +98 -0
  11. package/dist/src/adapters/deploy/index.d.ts +15 -0
  12. package/dist/src/adapters/deploy/index.js +78 -0
  13. package/dist/src/adapters/deploy/render.d.ts +181 -0
  14. package/dist/src/adapters/deploy/render.js +550 -0
  15. package/dist/src/adapters/deploy/types.d.ts +221 -0
  16. package/dist/src/adapters/deploy/types.js +15 -0
  17. package/dist/src/adapters/deploy/vercel.d.ts +143 -0
  18. package/dist/src/adapters/deploy/vercel.js +426 -0
  19. package/dist/src/adapters/pricing.d.ts +36 -0
  20. package/dist/src/adapters/pricing.js +40 -0
  21. package/dist/src/adapters/review-engine/claude.js +2 -1
  22. package/dist/src/adapters/review-engine/codex.js +12 -8
  23. package/dist/src/adapters/review-engine/gemini.js +2 -1
  24. package/dist/src/adapters/review-engine/openai-compatible.js +2 -1
  25. package/dist/src/adapters/sdk-loader.d.ts +15 -0
  26. package/dist/src/adapters/sdk-loader.js +77 -0
  27. package/dist/src/cli/autopilot.d.ts +71 -0
  28. package/dist/src/cli/autopilot.js +735 -0
  29. package/dist/src/cli/brainstorm.d.ts +23 -0
  30. package/dist/src/cli/brainstorm.js +131 -0
  31. package/dist/src/cli/costs.d.ts +15 -1
  32. package/dist/src/cli/costs.js +99 -10
  33. package/dist/src/cli/deploy.d.ts +71 -0
  34. package/dist/src/cli/deploy.js +539 -0
  35. package/dist/src/cli/fix.d.ts +18 -0
  36. package/dist/src/cli/fix.js +105 -11
  37. package/dist/src/cli/help-text.d.ts +52 -0
  38. package/dist/src/cli/help-text.js +400 -0
  39. package/dist/src/cli/implement.d.ts +91 -0
  40. package/dist/src/cli/implement.js +196 -0
  41. package/dist/src/cli/index.js +784 -222
  42. package/dist/src/cli/json-envelope.d.ts +187 -0
  43. package/dist/src/cli/json-envelope.js +270 -0
  44. package/dist/src/cli/json-mode.d.ts +33 -0
  45. package/dist/src/cli/json-mode.js +201 -0
  46. package/dist/src/cli/migrate.d.ts +111 -0
  47. package/dist/src/cli/migrate.js +305 -0
  48. package/dist/src/cli/plan.d.ts +81 -0
  49. package/dist/src/cli/plan.js +149 -0
  50. package/dist/src/cli/pr.d.ts +106 -0
  51. package/dist/src/cli/pr.js +191 -19
  52. package/dist/src/cli/preflight.js +102 -1
  53. package/dist/src/cli/review.d.ts +27 -0
  54. package/dist/src/cli/review.js +126 -0
  55. package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
  56. package/dist/src/cli/runs-watch-renderer.js +275 -0
  57. package/dist/src/cli/runs-watch.d.ts +41 -0
  58. package/dist/src/cli/runs-watch.js +395 -0
  59. package/dist/src/cli/runs.d.ts +122 -0
  60. package/dist/src/cli/runs.js +902 -0
  61. package/dist/src/cli/scan.d.ts +93 -0
  62. package/dist/src/cli/scan.js +166 -40
  63. package/dist/src/cli/spec.d.ts +66 -0
  64. package/dist/src/cli/spec.js +132 -0
  65. package/dist/src/cli/validate.d.ts +29 -0
  66. package/dist/src/cli/validate.js +131 -0
  67. package/dist/src/core/config/schema.d.ts +43 -0
  68. package/dist/src/core/config/schema.js +25 -0
  69. package/dist/src/core/config/types.d.ts +17 -0
  70. package/dist/src/core/council/runner.d.ts +10 -1
  71. package/dist/src/core/council/runner.js +25 -3
  72. package/dist/src/core/council/types.d.ts +7 -0
  73. package/dist/src/core/errors.d.ts +1 -1
  74. package/dist/src/core/errors.js +12 -0
  75. package/dist/src/core/logging/redaction.d.ts +13 -0
  76. package/dist/src/core/logging/redaction.js +20 -0
  77. package/dist/src/core/migrate/detector-rules.js +6 -0
  78. package/dist/src/core/migrate/schema-validator.js +22 -1
  79. package/dist/src/core/phases/static-rules.d.ts +5 -1
  80. package/dist/src/core/phases/static-rules.js +2 -5
  81. package/dist/src/core/run-state/budget.d.ts +88 -0
  82. package/dist/src/core/run-state/budget.js +141 -0
  83. package/dist/src/core/run-state/cli-internal.d.ts +21 -0
  84. package/dist/src/core/run-state/cli-internal.js +174 -0
  85. package/dist/src/core/run-state/events.d.ts +59 -0
  86. package/dist/src/core/run-state/events.js +504 -0
  87. package/dist/src/core/run-state/lock.d.ts +61 -0
  88. package/dist/src/core/run-state/lock.js +206 -0
  89. package/dist/src/core/run-state/phase-context.d.ts +60 -0
  90. package/dist/src/core/run-state/phase-context.js +108 -0
  91. package/dist/src/core/run-state/phase-registry.d.ts +137 -0
  92. package/dist/src/core/run-state/phase-registry.js +162 -0
  93. package/dist/src/core/run-state/phase-runner.d.ts +80 -0
  94. package/dist/src/core/run-state/phase-runner.js +447 -0
  95. package/dist/src/core/run-state/provider-readback.d.ts +130 -0
  96. package/dist/src/core/run-state/provider-readback.js +426 -0
  97. package/dist/src/core/run-state/replay-decision.d.ts +69 -0
  98. package/dist/src/core/run-state/replay-decision.js +144 -0
  99. package/dist/src/core/run-state/resolve-engine.d.ts +100 -0
  100. package/dist/src/core/run-state/resolve-engine.js +190 -0
  101. package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
  102. package/dist/src/core/run-state/resume-preflight.js +116 -0
  103. package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +73 -0
  104. package/dist/src/core/run-state/run-phase-with-lifecycle.js +186 -0
  105. package/dist/src/core/run-state/runs.d.ts +57 -0
  106. package/dist/src/core/run-state/runs.js +288 -0
  107. package/dist/src/core/run-state/snapshot.d.ts +14 -0
  108. package/dist/src/core/run-state/snapshot.js +114 -0
  109. package/dist/src/core/run-state/state.d.ts +40 -0
  110. package/dist/src/core/run-state/state.js +164 -0
  111. package/dist/src/core/run-state/types.d.ts +278 -0
  112. package/dist/src/core/run-state/types.js +13 -0
  113. package/dist/src/core/run-state/ulid.d.ts +11 -0
  114. package/dist/src/core/run-state/ulid.js +95 -0
  115. package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
  116. package/dist/src/core/schema-alignment/extractor/index.js +2 -2
  117. package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
  118. package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
  119. package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
  120. package/dist/src/core/schema-alignment/git-history.js +53 -0
  121. package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
  122. package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
  123. package/package.json +9 -5
  124. package/scripts/autoregress.ts +3 -2
  125. package/skills/claude-autopilot.md +1 -1
  126. package/skills/make-interfaces-feel-better/SKILL.md +104 -0
  127. package/skills/migrate/SKILL.md +193 -47
  128. package/skills/simplify-ui/SKILL.md +103 -0
  129. package/skills/ui/SKILL.md +117 -0
  130. package/skills/ui-ux-pro-max/SKILL.md +90 -0
@@ -0,0 +1,539 @@
1
+ // src/cli/deploy.ts
2
+ //
3
+ // `claude-autopilot deploy` โ€” Phase 1 of the v5.4 Vercel adapter spec.
4
+ //
5
+ // Loads guardrail.config.yaml, picks a deploy adapter (config or `--adapter`
6
+ // override), runs the deploy, prints a one-line status, returns an exit code.
7
+ //
8
+ // Phase 1 wires only the deploy verb. `deploy status` and `deploy rollback`
9
+ // are scaffolded in the spec for Phase 5 (CLI subcommands wrapping
10
+ // adapter.status/rollback) and not implemented here.
11
+ import * as fs from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import { GuardrailError } from "../core/errors.js";
15
+ import { loadConfig } from "../core/config/loader.js";
16
+ import { runSafe } from "../core/shell.js";
17
+ import { createDeployAdapter } from "../adapters/deploy/index.js";
18
+ function hasListDeployments(a) {
19
+ return typeof a.listDeployments === 'function';
20
+ }
21
+ /**
22
+ * Returns process exit code.
23
+ * 0 โ€” deploy passed
24
+ * 1 โ€” deploy failed (build error, auth error, missing config)
25
+ * 2 โ€” still in progress at poll timeout (caller may retry via deploy status)
26
+ */
27
+ /**
28
+ * Shared config-loading + adapter-merge logic used by all `deploy` runners
29
+ * (`runDeploy`, `runDeployRollback`, `runDeployStatus`). Returns either the
30
+ * merged `DeployConfig` or an exit code that the caller should propagate.
31
+ *
32
+ * Error-vs-success split mirrors the original inline behavior in `runDeploy`:
33
+ * explicit `--config <missing>` is loud; default-path missing is silent and
34
+ * falls through to the "no adapter configured" check (Bugbot HIGH on PR #59).
35
+ */
36
+ async function loadDeployConfigAsync(opts) {
37
+ const cwd = opts.cwd ?? process.cwd();
38
+ const explicitConfig = opts.configPath !== undefined;
39
+ const configPath = opts.configPath ?? path.join(cwd, 'guardrail.config.yaml');
40
+ let configBlock;
41
+ if (fs.existsSync(configPath)) {
42
+ try {
43
+ const config = await loadConfig(configPath);
44
+ configBlock = config.deploy;
45
+ }
46
+ catch (err) {
47
+ console.error(formatErr('failed to load config', err));
48
+ return { errorCode: 1 };
49
+ }
50
+ }
51
+ else if (explicitConfig) {
52
+ console.error(`\x1b[31m[deploy] config file not found: ${configPath}\x1b[0m`);
53
+ return { errorCode: 1 };
54
+ }
55
+ const adapter = opts.adapterOverride ?? configBlock?.adapter;
56
+ if (!adapter) {
57
+ console.error('\x1b[31m[deploy] no deploy adapter configured\x1b[0m\n' +
58
+ ' hint: set `deploy.adapter` in guardrail.config.yaml, or pass --adapter <vercel|fly|render|generic>');
59
+ return { errorCode: 1 };
60
+ }
61
+ const merged = {
62
+ ...(configBlock ?? { adapter }),
63
+ adapter,
64
+ };
65
+ return { merged };
66
+ }
67
+ export async function runDeploy(opts) {
68
+ const loaded = await loadDeployConfigAsync(opts);
69
+ if ('errorCode' in loaded)
70
+ return loaded.errorCode;
71
+ const { merged } = loaded;
72
+ const adapter = merged.adapter;
73
+ let result;
74
+ let healthOutcome = { status: 'skipped' };
75
+ let streamController;
76
+ let streamPromise;
77
+ let deployAdapterRef;
78
+ try {
79
+ const factory = opts.adapterFactory ?? createDeployAdapter;
80
+ const deployAdapter = factory(merged);
81
+ deployAdapterRef = deployAdapter;
82
+ // --watch: opt into log streaming. We start the stream from inside an
83
+ // onDeployStart callback so it begins as soon as the platform returns
84
+ // an ID, in parallel with the (still-running) deploy.
85
+ let onDeployStart;
86
+ if (opts.watch) {
87
+ if (typeof deployAdapter.streamLogs === 'function') {
88
+ // Phase 3 of v5.6 โ€” when an adapter advertises `streamMode: 'polling'`
89
+ // (currently only Render), surface a one-line stderr notice BEFORE
90
+ // iteration starts so users understand why their log lines arrive
91
+ // in batches with short gaps. Adapters with `streamMode: 'websocket'`
92
+ // (Vercel SSE, Fly WS) or `'none'`/undefined get no notice โ€” their
93
+ // streaming behavior matches user expectations. Spec: ยง "Capability
94
+ // metadata".
95
+ if (deployAdapter.capabilities?.streamMode === 'polling') {
96
+ process.stderr.write(`[deploy] note: ${deployAdapter.name} uses 2s log polling โ€” lines may arrive in batches and could include short gaps. See docs/deploy/adapters.md#log-streaming for details.\n`);
97
+ }
98
+ streamController = new AbortController();
99
+ const streamFn = deployAdapter.streamLogs.bind(deployAdapter);
100
+ const ctrlSignal = streamController.signal;
101
+ const adapterName = deployAdapter.name;
102
+ onDeployStart = (deployId) => {
103
+ streamPromise = (async () => {
104
+ try {
105
+ for await (const line of streamFn({ deployId, signal: ctrlSignal })) {
106
+ process.stderr.write(`[deploy:${adapterName}] ${line.text}\n`);
107
+ }
108
+ }
109
+ catch (err) {
110
+ if (!(err instanceof Error && err.name === 'AbortError')) {
111
+ console.error(`\x1b[2m[deploy] log stream ended: ${err?.message ?? String(err)}\x1b[0m`);
112
+ }
113
+ }
114
+ })();
115
+ };
116
+ }
117
+ else {
118
+ console.error(`\x1b[33m[deploy] --watch ignored โ€” adapter "${deployAdapter.name}" does not support log streaming\x1b[0m`);
119
+ }
120
+ }
121
+ result = await deployAdapter.deploy({
122
+ ref: opts.ref,
123
+ commitSha: opts.commitSha,
124
+ onDeployStart,
125
+ });
126
+ // Stop the stream now that the deploy is settled. Wait briefly so any
127
+ // in-flight log lines flush before we report.
128
+ streamController?.abort();
129
+ if (streamPromise) {
130
+ try {
131
+ await streamPromise;
132
+ }
133
+ catch { /* already logged */ }
134
+ }
135
+ // Phase 4 โ€” post-deploy health check. Skipped when deploy itself failed
136
+ // OR when no explicit `healthCheckUrl` is configured. We deliberately do
137
+ // NOT fall back to `result.deployUrl`: silently probing the deploy URL
138
+ // would change behavior for everyone upgrading to Phase 4 (their deploys
139
+ // would suddenly fail if the URL is preview-only or rate-limited). Health
140
+ // checks are opt-in via config. The spec explicitly leaves room for a
141
+ // future `healthCheckUrl: auto` mode that interpolates from `deployUrl`.
142
+ if (result.status === 'pass') {
143
+ const healthUrl = merged.healthCheckUrl;
144
+ if (healthUrl) {
145
+ healthOutcome = await runHealthCheck({
146
+ url: healthUrl,
147
+ fetchImpl: opts.fetchImpl ?? globalThis.fetch,
148
+ sleepImpl: opts.sleepImpl ?? defaultSleep,
149
+ });
150
+ if (healthOutcome.status === 'fail') {
151
+ const triggers = merged.rollbackOn ?? [];
152
+ const wantRollback = triggers.includes('healthCheckFailure');
153
+ if (wantRollback) {
154
+ if (typeof deployAdapter.rollback === 'function') {
155
+ // BOUND: exactly one auto-rollback per deploy attempt (spec ยง
156
+ // "Health-check policy" โ†’ "After rollback completes (success
157
+ // or failure), the adapter returns; no second rollback
158
+ // attempt"). The single `rollback({})` call below is the only
159
+ // place this path is invoked; we do NOT loop. Result status
160
+ // becomes one of the two new terminal values:
161
+ // - `fail_rolled_back` โ€” rollback returned `pass`
162
+ // - `fail_rollback_failed` โ€” rollback returned non-pass OR threw
163
+ try {
164
+ const rb = await deployAdapter.rollback({});
165
+ if (rb.status === 'pass') {
166
+ result = {
167
+ ...result,
168
+ status: 'fail_rolled_back',
169
+ rolledBackTo: rb.rolledBackTo ?? rb.deployId,
170
+ output: `Deploy passed; health check failed (${healthOutcome.lastError}); auto-rolled back to ${rb.rolledBackTo ?? rb.deployId ?? '<unknown>'}.`,
171
+ };
172
+ printAutoRollback(deployAdapter.name, healthOutcome, rb);
173
+ }
174
+ else {
175
+ result = {
176
+ ...result,
177
+ status: 'fail_rollback_failed',
178
+ output: `Deploy passed; health check failed; auto-rollback ALSO failed: ${rb.output ?? '<no output>'}`,
179
+ };
180
+ printAutoRollbackFailed(rb.output ?? 'rollback returned non-pass');
181
+ }
182
+ }
183
+ catch (err) {
184
+ const msg = err?.message ?? String(err);
185
+ result = {
186
+ ...result,
187
+ status: 'fail_rollback_failed',
188
+ output: `Deploy passed; health check failed; auto-rollback ERRORED: ${msg}`,
189
+ };
190
+ printAutoRollbackFailed(msg);
191
+ }
192
+ }
193
+ else {
194
+ console.error(`\x1b[33m[deploy] rollbackOn=[healthCheckFailure] configured but adapter "${deployAdapter.name}" does not support rollback\x1b[0m`);
195
+ result = {
196
+ ...result,
197
+ status: 'fail',
198
+ output: `Deploy passed but health check failed: ${healthOutcome.lastError} at ${healthOutcome.url} (adapter does not support rollback)`,
199
+ };
200
+ }
201
+ }
202
+ else {
203
+ result = {
204
+ ...result,
205
+ status: 'fail',
206
+ output: `Deploy passed but health check failed: ${healthOutcome.lastError} at ${healthOutcome.url}`,
207
+ };
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+ catch (err) {
214
+ streamController?.abort();
215
+ if (streamPromise) {
216
+ try {
217
+ await streamPromise;
218
+ }
219
+ catch { /* already logged */ }
220
+ }
221
+ console.error(formatErr(`deploy via ${adapter} failed`, err));
222
+ return 1;
223
+ }
224
+ printResult(adapter, result);
225
+ if (opts.pr !== undefined) {
226
+ try {
227
+ postDeployPrComment({
228
+ pr: opts.pr,
229
+ cwd: opts.cwd ?? process.cwd(),
230
+ adapterName: deployAdapterRef?.name ?? adapter,
231
+ result,
232
+ healthOutcome,
233
+ ghImpl: opts.ghImpl ?? defaultGhImpl,
234
+ });
235
+ }
236
+ catch (err) {
237
+ console.error(`\x1b[33m[deploy] failed to post PR comment: ${err?.message ?? String(err)}\x1b[0m`);
238
+ }
239
+ }
240
+ if (result.status === 'pass')
241
+ return 0;
242
+ if (result.status === 'in-progress')
243
+ return 2;
244
+ return 1;
245
+ }
246
+ function printResult(adapter, r) {
247
+ const color = r.status === 'pass' ? '\x1b[32m' : r.status === 'in-progress' ? '\x1b[33m' : '\x1b[31m';
248
+ const seconds = (r.durationMs / 1000).toFixed(1);
249
+ const parts = [`status=${r.status}`, `adapter=${adapter}`];
250
+ if (r.deployId)
251
+ parts.push(`deployId=${r.deployId}`);
252
+ if (r.deployUrl)
253
+ parts.push(`url=${r.deployUrl}`);
254
+ parts.push(`duration=${seconds}s`);
255
+ console.log(`${color}[deploy] ${parts.join(' ')}\x1b[0m`);
256
+ if (r.buildLogsUrl)
257
+ console.log(`\x1b[2m logs: ${r.buildLogsUrl}\x1b[0m`);
258
+ if (r.output && r.status !== 'pass') {
259
+ console.log(`\x1b[2m${r.output}\x1b[0m`);
260
+ }
261
+ }
262
+ function formatErr(prefix, err) {
263
+ if (err instanceof GuardrailError) {
264
+ const provider = err.provider ? ` [${err.provider}]` : '';
265
+ const code = `[${err.code}]`;
266
+ const hint = err.code === 'auth' ? '\n hint: check VERCEL_TOKEN at https://vercel.com/account/tokens' : '';
267
+ return `\x1b[31m[deploy] ${prefix}${provider} ${code} ${err.message}\x1b[0m${hint}`;
268
+ }
269
+ return `\x1b[31m[deploy] ${prefix}: ${err?.message ?? String(err)}\x1b[0m`;
270
+ }
271
+ /**
272
+ * Handle `claude-autopilot deploy rollback [--to <id>]`.
273
+ *
274
+ * Returns process exit code (0 on success, 1 on failure). Failure modes:
275
+ * - Adapter doesn't implement rollback (e.g. generic adapter).
276
+ * - No previous prod deploy exists when `--to` is omitted.
277
+ * - Auth / network / API error (surfaced via formatErr).
278
+ */
279
+ export async function runDeployRollback(opts) {
280
+ const loaded = await loadDeployConfigAsync(opts);
281
+ if ('errorCode' in loaded)
282
+ return loaded.errorCode;
283
+ const { merged } = loaded;
284
+ const adapter = merged.adapter;
285
+ let result;
286
+ try {
287
+ const factory = opts.adapterFactory ?? createDeployAdapter;
288
+ const deployAdapter = factory(merged);
289
+ if (typeof deployAdapter.rollback !== 'function') {
290
+ console.error(`\x1b[31m[deploy] adapter "${deployAdapter.name}" does not support rollback\x1b[0m`);
291
+ return 1;
292
+ }
293
+ result = await deployAdapter.rollback({ to: opts.to });
294
+ }
295
+ catch (err) {
296
+ console.error(formatErr(`rollback via ${adapter} failed`, err));
297
+ return 1;
298
+ }
299
+ printRollbackResult(adapter, result);
300
+ return result.status === 'pass' ? 0 : 1;
301
+ }
302
+ function printRollbackResult(adapter, r) {
303
+ const color = r.status === 'pass' ? '\x1b[32m' : '\x1b[31m';
304
+ const seconds = (r.durationMs / 1000).toFixed(1);
305
+ const parts = [`status=${r.status}`, `adapter=${adapter}`];
306
+ if (r.rolledBackTo)
307
+ parts.push(`rolledBackTo=${r.rolledBackTo}`);
308
+ if (r.deployUrl)
309
+ parts.push(`url=${r.deployUrl}`);
310
+ parts.push(`duration=${seconds}s`);
311
+ console.log(`${color}[deploy] rollback ${parts.join(' ')}\x1b[0m`);
312
+ if (r.buildLogsUrl)
313
+ console.log(`\x1b[2m logs: ${r.buildLogsUrl}\x1b[0m`);
314
+ if (r.output && r.status !== 'pass') {
315
+ console.log(`\x1b[2m${r.output}\x1b[0m`);
316
+ }
317
+ }
318
+ /**
319
+ * Handle `claude-autopilot deploy status`. Lists the current production
320
+ * deploy plus the last 5 builds (newest-first), pulling from the adapter's
321
+ * `listDeployments` capability. Adapters that don't expose this method
322
+ * (e.g. the generic shell adapter) get a clear error and exit 1.
323
+ */
324
+ export async function runDeployStatus(opts) {
325
+ const loaded = await loadDeployConfigAsync(opts);
326
+ if ('errorCode' in loaded)
327
+ return loaded.errorCode;
328
+ const { merged } = loaded;
329
+ const adapter = merged.adapter;
330
+ try {
331
+ const factory = opts.adapterFactory ?? createDeployAdapter;
332
+ const deployAdapter = factory(merged);
333
+ if (!hasListDeployments(deployAdapter)) {
334
+ console.error(`\x1b[31m[deploy] adapter "${deployAdapter.name}" does not support status listing\x1b[0m`);
335
+ return 1;
336
+ }
337
+ const items = await deployAdapter.listDeployments(5);
338
+ const sorted = [...items].sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
339
+ printStatus(adapter, sorted);
340
+ return 0;
341
+ }
342
+ catch (err) {
343
+ console.error(formatErr(`status via ${adapter} failed`, err));
344
+ return 1;
345
+ }
346
+ }
347
+ function printStatus(adapter, items) {
348
+ console.log(`\x1b[1m[deploy] status โ€” adapter=${adapter}\x1b[0m`);
349
+ if (items.length === 0) {
350
+ console.log('\x1b[2m (no deployments found)\x1b[0m');
351
+ return;
352
+ }
353
+ const current = items[0];
354
+ const rest = items.slice(1);
355
+ console.log(` current: ${current.id}` +
356
+ (current.state ? ` state=${current.state}` : '') +
357
+ (current.url ? ` url=https://${current.url}` : '') +
358
+ (typeof current.createdAt === 'number' ? ` age=${formatAge(current.createdAt)}` : ''));
359
+ if (rest.length > 0) {
360
+ console.log(' recent builds:');
361
+ for (const d of rest) {
362
+ console.log(` ${d.id}` +
363
+ (d.state ? ` state=${d.state}` : '') +
364
+ (typeof d.createdAt === 'number' ? ` age=${formatAge(d.createdAt)}` : '') +
365
+ (d.url ? ` url=https://${d.url}` : ''));
366
+ }
367
+ }
368
+ }
369
+ function formatAge(createdAtMs) {
370
+ const deltaMs = Date.now() - createdAtMs;
371
+ if (deltaMs < 0)
372
+ return '0s';
373
+ const seconds = Math.floor(deltaMs / 1000);
374
+ if (seconds < 60)
375
+ return `${seconds}s`;
376
+ const minutes = Math.floor(seconds / 60);
377
+ if (minutes < 60)
378
+ return `${minutes}m`;
379
+ const hours = Math.floor(minutes / 60);
380
+ if (hours < 24)
381
+ return `${hours}h`;
382
+ const days = Math.floor(hours / 24);
383
+ return `${days}d`;
384
+ }
385
+ /** Per v5.6 spec ยง "Health-check policy" โ€” cap retries at 5ร— with 6s backoff. */
386
+ const HEALTH_CHECK_MAX_ATTEMPTS = 5;
387
+ const HEALTH_CHECK_BACKOFF_MS = 6000;
388
+ /**
389
+ * Probe a URL up to {@link HEALTH_CHECK_MAX_ATTEMPTS} times with
390
+ * {@link HEALTH_CHECK_BACKOFF_MS} backoff between attempts. 2xx โ†’ pass.
391
+ * Per-attempt timeout is 10s. Network errors are treated as failures and
392
+ * retried.
393
+ *
394
+ * Total wall-clock budget: ~30s (5 attempts ร— 6s backoff between, minus
395
+ * the trailing skip โ€” matches the spec's "max ~30s window").
396
+ */
397
+ async function runHealthCheck(opts) {
398
+ const { url, fetchImpl, sleepImpl } = opts;
399
+ let lastError = '';
400
+ for (let attempt = 1; attempt <= HEALTH_CHECK_MAX_ATTEMPTS; attempt += 1) {
401
+ const ctrl = new AbortController();
402
+ const timer = setTimeout(() => ctrl.abort(), 10_000);
403
+ try {
404
+ const res = await fetchImpl(url, { signal: ctrl.signal });
405
+ clearTimeout(timer);
406
+ if (res.status >= 200 && res.status < 300) {
407
+ return { status: 'pass', url };
408
+ }
409
+ lastError = `HTTP ${res.status}`;
410
+ }
411
+ catch (err) {
412
+ clearTimeout(timer);
413
+ lastError = err?.message ?? String(err);
414
+ }
415
+ if (attempt < HEALTH_CHECK_MAX_ATTEMPTS)
416
+ await sleepImpl(HEALTH_CHECK_BACKOFF_MS);
417
+ }
418
+ return { status: 'fail', url, lastError };
419
+ }
420
+ function defaultSleep(ms) {
421
+ return new Promise((r) => setTimeout(r, ms));
422
+ }
423
+ /**
424
+ * Print the distinct yellow auto-rollback marker. Called only after the
425
+ * adapter's `rollback({})` returned `pass` โ€” i.e. the previous prod deploy
426
+ * has been promoted and is now serving traffic.
427
+ */
428
+ function printAutoRollback(adapter, hc, rb) {
429
+ const yellow = '\x1b[33m';
430
+ const dim = '\x1b[2m';
431
+ const reset = '\x1b[0m';
432
+ const target = rb.rolledBackTo ?? rb.deployId ?? '<unknown>';
433
+ console.log(`${yellow}๐Ÿ”„ [deploy] auto-rolled-back-to=${target} via=${adapter} health-check-url=${hc.url}${reset}`);
434
+ console.log(`${dim} reason: health check failed ${HEALTH_CHECK_MAX_ATTEMPTS}x against ${hc.url} (${hc.lastError})${reset}`);
435
+ if (rb.deployUrl) {
436
+ console.log(`${dim} current: ${rb.deployUrl}${reset}`);
437
+ }
438
+ }
439
+ /**
440
+ * Print the auto-rollback failure marker. Called when the rollback attempt
441
+ * itself errors or returns non-pass โ€” the original (failing) deploy is
442
+ * still in place and the operator must intervene.
443
+ */
444
+ function printAutoRollbackFailed(reason) {
445
+ const yellow = '\x1b[33m';
446
+ const dim = '\x1b[2m';
447
+ const reset = '\x1b[0m';
448
+ console.log(`${yellow}๐Ÿ”„ [deploy] auto-rollback FAILED โ€” original deploy left in place${reset}`);
449
+ console.log(`${dim} reason: ${reason}${reset}`);
450
+ }
451
+ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
452
+ // Phase 4 โ€” `--pr <n>` deploy summary comment.
453
+ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
454
+ const DEPLOY_COMMENT_MARKER = '<!-- claude-autopilot-deploy -->';
455
+ /** Build the markdown body for the deploy summary comment. Pure, side-effect-free. */
456
+ function buildDeployCommentBody(input) {
457
+ const { adapterName, result, healthOutcome } = input;
458
+ const lines = [DEPLOY_COMMENT_MARKER];
459
+ if (result.rolledBackTo) {
460
+ lines.push('## โŒ Deploy auto-rolled back', '');
461
+ lines.push('| Step | Status | URL / ID |');
462
+ lines.push('|---|:---:|---|');
463
+ lines.push(`| New deploy \`${result.deployId ?? 'unknown'}\` | โœ… built | ${result.deployUrl ?? 'โ€”'} |`);
464
+ if (healthOutcome.status === 'fail') {
465
+ lines.push(`| Health check | โŒ failed | ${healthOutcome.url} |`);
466
+ }
467
+ lines.push(`| Auto-rollback to \`${result.rolledBackTo}\` | โœ… promoted | (current production) |`);
468
+ }
469
+ else if (result.status === 'pass') {
470
+ lines.push('## โœ… Deploy succeeded', '');
471
+ lines.push('| Field | Value |');
472
+ lines.push('|---|---|');
473
+ lines.push(`| Deploy ID | \`${result.deployId ?? 'unknown'}\` |`);
474
+ if (result.deployUrl)
475
+ lines.push(`| URL | ${result.deployUrl} |`);
476
+ if (healthOutcome.status === 'pass') {
477
+ lines.push(`| Health check | โœ… ${healthOutcome.url} |`);
478
+ }
479
+ }
480
+ else {
481
+ lines.push('## โŒ Deploy failed', '');
482
+ lines.push('| Field | Value |');
483
+ lines.push('|---|---|');
484
+ if (result.deployId)
485
+ lines.push(`| Deploy ID | \`${result.deployId}\` |`);
486
+ if (result.deployUrl)
487
+ lines.push(`| URL | ${result.deployUrl} |`);
488
+ if (result.output)
489
+ lines.push(`| Reason | ${result.output.replace(/\n/g, ' ')} |`);
490
+ }
491
+ lines.push('', `*adapter=${adapterName} ยท duration=${(result.durationMs / 1000).toFixed(1)}s*`);
492
+ return lines.join('\n');
493
+ }
494
+ /**
495
+ * Upsert the deploy summary comment on a PR. Looks up an existing comment
496
+ * anchored on `DEPLOY_COMMENT_MARKER` and PATCHes it; otherwise creates
497
+ * a new one. The marker is distinct from `<!-- guardrail-review -->` so
498
+ * deploy and review comments coexist.
499
+ */
500
+ function postDeployPrComment(input) {
501
+ const { pr, cwd, ghImpl } = input;
502
+ const body = buildDeployCommentBody(input);
503
+ const lookup = ghImpl([
504
+ 'api',
505
+ `repos/{owner}/{repo}/issues/${pr}/comments`,
506
+ '--jq',
507
+ `[.[] | select(.body | startswith("${DEPLOY_COMMENT_MARKER}")) | .id] | first`,
508
+ ], { cwd });
509
+ const existingId = lookup.trim();
510
+ if (existingId && /^\d+$/.test(existingId)) {
511
+ ghImpl([
512
+ 'api',
513
+ `repos/{owner}/{repo}/issues/comments/${existingId}`,
514
+ '--method',
515
+ 'PATCH',
516
+ '--field',
517
+ 'body=@-',
518
+ ], { cwd, body });
519
+ }
520
+ else {
521
+ ghImpl(['pr', 'comment', String(pr), '--body-file', '-'], { cwd, body });
522
+ }
523
+ }
524
+ /**
525
+ * Default `gh` runner โ€” wraps `core/shell.runSafe` and passes `body` (when
526
+ * present) via stdin. We translate the placeholder argv tokens `@-` and `-`
527
+ * into `--body-file <tmp>` style is unnecessary because `runSafe` already
528
+ * supports `input: string` which `gh` consumes when given `--body-file -`
529
+ * or `--field body=@-`.
530
+ */
531
+ function defaultGhImpl(args, opts) {
532
+ const result = runSafe('gh', args, { cwd: opts?.cwd, input: opts?.body });
533
+ return result ?? '';
534
+ }
535
+ // `os` is used by future temp-file fallbacks; kept imported so adding one
536
+ // later doesn't perturb the import block. Reference it once to avoid
537
+ // "unused import" warnings under strict linters.
538
+ void os;
539
+ //# sourceMappingURL=deploy.js.map
@@ -1,3 +1,4 @@
1
+ import type { ReviewEngine } from '../adapters/review-engine/types.ts';
1
2
  export interface FixCommandOptions {
2
3
  cwd?: string;
3
4
  configPath?: string;
@@ -5,6 +6,23 @@ export interface FixCommandOptions {
5
6
  dryRun?: boolean;
6
7
  yes?: boolean;
7
8
  noVerify?: boolean;
9
+ /**
10
+ * v6.0.2 โ€” engine knob inputs. Same shape and precedence as scan / costs
11
+ * (CLI > env > config > built-in default off in v6.0.x). The CLI
12
+ * dispatcher wires `cliEngine` from `--engine` / `--no-engine`;
13
+ * `envEngine` from `process.env.CLAUDE_AUTOPILOT_ENGINE`. An absent CLI
14
+ * flag + absent env value falls through to the loaded config and then to
15
+ * the built-in default.
16
+ */
17
+ cliEngine?: boolean;
18
+ envEngine?: string;
19
+ /**
20
+ * Test-only seam โ€” injects a pre-built ReviewEngine so tests can exercise
21
+ * the engine-wrap path without hitting `loadAdapter()` (and therefore
22
+ * without needing an LLM API key in the environment). Mirrors the seam
23
+ * in `scan.ts`. Production callers MUST NOT pass this.
24
+ */
25
+ __testReviewEngine?: ReviewEngine;
8
26
  }
9
27
  export declare function runFix(options?: FixCommandOptions): Promise<number>;
10
28
  //# sourceMappingURL=fix.d.ts.map