@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,330 @@
1
+ // CLI uploader — snapshot, chunk, retry, finalize.
2
+ //
3
+ // Flow:
4
+ // 1. Empty events.ndjson check → skip upload (Phase 2.2 returns 422 on
5
+ // expectedChunkCount=0; never call it).
6
+ // 2. Snapshot events.ndjson + state.json to <runDir>/.upload-snapshot/.
7
+ // 3. Bootstrap session: GET dashboard upload-session for resume; if 404
8
+ // mint fresh via POST /api/upload-session (Phase 2.2 endpoint, accepts
9
+ // Bearer clp_<key> via resolveCaller).
10
+ // 4. PUT each chunk with x-chunk-prev-hash; retry transient 5xx.
11
+ // 5. POST /api/runs/:runId/finalize with chainRoot + state.
12
+ // 6. On success, delete the snapshot dir.
13
+ import { promises as fs } from 'node:fs';
14
+ import * as path from 'node:path';
15
+ import { hashChunk, ZERO_HASH } from "./chain.js";
16
+ import { sha256OfCanonical } from "./canonical.js";
17
+ import { snapshotRun, deleteSnapshot, SnapshotMismatchError } from "./snapshot.js";
18
+ const CHUNK_BYTES = 1024 * 1024; // 1 MiB matches server MAX_CHUNK_BYTES
19
+ const DEFAULT_RETRY_DELAYS_MS = [1000, 4000, 16000, 64000];
20
+ function resolveRetryDelays() {
21
+ // Test seam — let CI/tests override the exponential backoff schedule
22
+ // so transient-failure assertions don't add minutes to the suite.
23
+ const override = process.env.CLAUDE_AUTOPILOT_UPLOAD_RETRY_MS;
24
+ if (!override)
25
+ return DEFAULT_RETRY_DELAYS_MS;
26
+ return override
27
+ .split(',')
28
+ .map((s) => Number.parseInt(s.trim(), 10))
29
+ .filter((n) => Number.isFinite(n) && n >= 0);
30
+ }
31
+ export class UploadError extends Error {
32
+ status;
33
+ constructor(message, status = null) {
34
+ super(message);
35
+ this.status = status;
36
+ }
37
+ }
38
+ /**
39
+ * Phase 3 — thrown when /api/upload-session returns 402 with a structured
40
+ * `limit_reached` payload. Auto-upload entry point detects this subclass
41
+ * and prints a friendly message without retrying or overriding the run's
42
+ * exit code.
43
+ */
44
+ export class UploadLimitError extends UploadError {
45
+ payload;
46
+ constructor(message, payload) {
47
+ super(message, 402);
48
+ this.payload = payload;
49
+ }
50
+ }
51
+ function resolveBaseUrl(opts) {
52
+ return (opts.baseUrl ??
53
+ process.env.AUTOPILOT_DASHBOARD_BASE_URL ??
54
+ 'https://autopilot.dev');
55
+ }
56
+ function checkAborted(signal) {
57
+ if (signal?.aborted) {
58
+ const reason = signal.reason;
59
+ const err = reason instanceof Error
60
+ ? reason
61
+ : new Error('upload aborted');
62
+ throw err;
63
+ }
64
+ }
65
+ async function delay(ms, signal) {
66
+ return new Promise((resolve, reject) => {
67
+ const t = setTimeout(resolve, ms);
68
+ if (signal) {
69
+ const onAbort = () => {
70
+ clearTimeout(t);
71
+ reject(new Error('aborted'));
72
+ };
73
+ if (signal.aborted)
74
+ onAbort();
75
+ else
76
+ signal.addEventListener('abort', onAbort, { once: true });
77
+ }
78
+ });
79
+ }
80
+ async function readChunks(filePath) {
81
+ const handle = await fs.open(filePath, 'r');
82
+ try {
83
+ const stat = await handle.stat();
84
+ const total = stat.size;
85
+ const out = [];
86
+ let position = 0;
87
+ while (position < total) {
88
+ const remaining = total - position;
89
+ const size = remaining < CHUNK_BYTES ? remaining : CHUNK_BYTES;
90
+ const buf = Buffer.alloc(size);
91
+ const { bytesRead } = await handle.read(buf, 0, size, position);
92
+ if (bytesRead !== size) {
93
+ throw new UploadError(`short read at offset ${position}: ${bytesRead}/${size}`);
94
+ }
95
+ out.push(buf);
96
+ position += size;
97
+ }
98
+ return out;
99
+ }
100
+ finally {
101
+ await handle.close();
102
+ }
103
+ }
104
+ async function fetchWithRetry(url, init, fetchImpl, signal, is5xxRetryable) {
105
+ let lastErr = null;
106
+ const delays = resolveRetryDelays();
107
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
108
+ checkAborted(signal);
109
+ try {
110
+ const res = await fetchImpl(url, init);
111
+ if (res.status >= 500 && res.status < 600 && is5xxRetryable && attempt < delays.length) {
112
+ const wait = delays[attempt];
113
+ await delay(wait, signal);
114
+ continue;
115
+ }
116
+ return res;
117
+ }
118
+ catch (err) {
119
+ if (signal?.aborted)
120
+ throw err;
121
+ lastErr = err;
122
+ if (attempt < delays.length) {
123
+ const wait = delays[attempt];
124
+ await delay(wait, signal);
125
+ continue;
126
+ }
127
+ throw err;
128
+ }
129
+ }
130
+ throw lastErr instanceof Error ? lastErr : new UploadError('exhausted retries');
131
+ }
132
+ async function bootstrapSession(baseUrl, apiKey, runId, expectedChunkCount, expectedBytes, fetchImpl, signal) {
133
+ // Resume path first.
134
+ const resumeUrl = `${baseUrl}/api/dashboard/runs/${encodeURIComponent(runId)}/upload-session`;
135
+ const resumeRes = await fetchWithRetry(resumeUrl, {
136
+ method: 'GET',
137
+ headers: { authorization: `Bearer ${apiKey}` },
138
+ signal,
139
+ }, fetchImpl, signal, true);
140
+ if (resumeRes.status === 200) {
141
+ const data = await resumeRes.json();
142
+ return { session: data, resumed: true };
143
+ }
144
+ if (resumeRes.status !== 404) {
145
+ const text = await resumeRes.text().catch(() => '');
146
+ throw new UploadError(`resume bootstrap failed: ${resumeRes.status} ${text}`, resumeRes.status);
147
+ }
148
+ // Mint fresh via Phase 2.2 endpoint.
149
+ const mintUrl = `${baseUrl}/api/upload-session`;
150
+ const mintRes = await fetchWithRetry(mintUrl, {
151
+ method: 'POST',
152
+ headers: {
153
+ authorization: `Bearer ${apiKey}`,
154
+ 'content-type': 'application/json',
155
+ },
156
+ body: JSON.stringify({ runId, expectedChunkCount, expectedBytes }),
157
+ signal,
158
+ }, fetchImpl, signal, true);
159
+ // Phase 3 — structured 402 means we hit a runs/storage cap. Surface as a
160
+ // typed error so the auto-upload caller can print a friendly message
161
+ // without retrying or overriding the run's exit code.
162
+ if (mintRes.status === 402) {
163
+ let parsed = {};
164
+ try {
165
+ parsed = await mintRes.json();
166
+ }
167
+ catch {
168
+ // fall through — message below still useful
169
+ }
170
+ const limit = parsed.limit ?? 'unknown';
171
+ const current = parsed.current ?? 0;
172
+ const max = parsed.max ?? 0;
173
+ const upgradeUrl = parsed.upgrade_url ?? '';
174
+ throw new UploadLimitError(`upload rejected — ${limit} cap reached (${current}/${max}). Upgrade at ${upgradeUrl}`, { limit, current, max, upgrade_url: upgradeUrl });
175
+ }
176
+ if (mintRes.status !== 201) {
177
+ const text = await mintRes.text().catch(() => '');
178
+ throw new UploadError(`mint failed: ${mintRes.status} ${text}`, mintRes.status);
179
+ }
180
+ const data = await mintRes.json();
181
+ return { session: { ...data, session: { ...data.session, nextExpectedSeq: 0 } }, resumed: false };
182
+ }
183
+ export async function uploadRun(runId, runDir, opts) {
184
+ const fetchImpl = opts.fetchImpl ?? fetch;
185
+ const baseUrl = resolveBaseUrl(opts);
186
+ const signal = opts.signal;
187
+ try {
188
+ // (1) Empty events check — skip cleanly so server's 422 isn't tripped.
189
+ const eventsPath = path.join(runDir, 'events.ndjson');
190
+ let eventsStat;
191
+ try {
192
+ eventsStat = await fs.stat(eventsPath);
193
+ }
194
+ catch {
195
+ return { ok: true, skipped: true };
196
+ }
197
+ if (eventsStat.size === 0) {
198
+ return { ok: true, skipped: true };
199
+ }
200
+ // (2) Snapshot.
201
+ checkAborted(signal);
202
+ const snap = await snapshotRun(runDir);
203
+ opts.onProgress?.({ kind: 'snapshot', bytes: snap.eventsBytes });
204
+ const chunks = await readChunks(snap.events);
205
+ const expectedChunkCount = chunks.length;
206
+ // (3) Bootstrap. Phase 3 — pass expectedBytes for storage cap preflight.
207
+ checkAborted(signal);
208
+ const { session, resumed } = await bootstrapSession(baseUrl, opts.apiKey, runId, expectedChunkCount, snap.eventsBytes, fetchImpl, signal);
209
+ const startSeq = session.session.nextExpectedSeq ?? 0;
210
+ opts.onProgress?.({ kind: 'session', resumed, nextExpectedSeq: startSeq });
211
+ // (4) Stream chunks. Walk the chain forward from seq 0 even when
212
+ // resuming so prev-hash for seq=startSeq is correct.
213
+ let prev = ZERO_HASH;
214
+ for (let i = 0; i < startSeq; i++) {
215
+ const chunk = chunks[i];
216
+ if (!chunk)
217
+ throw new UploadError(`missing chunk at seq ${i} during prefix replay`);
218
+ prev = hashChunk(prev, chunk);
219
+ }
220
+ let token = session.uploadToken;
221
+ let chainRoot = prev;
222
+ let reauthAttempts = 0; // bugbot HIGH — bound the 401 re-bootstrap retry
223
+ const MAX_REAUTH_ATTEMPTS = 1;
224
+ for (let seq = startSeq; seq < chunks.length; seq++) {
225
+ checkAborted(signal);
226
+ const chunk = chunks[seq];
227
+ if (!chunk)
228
+ throw new UploadError(`missing chunk at seq ${seq}`);
229
+ const thisHash = hashChunk(prev, chunk);
230
+ const url = `${baseUrl}/api/runs/${encodeURIComponent(runId)}/events/${seq}`;
231
+ const init = {
232
+ method: 'PUT',
233
+ headers: {
234
+ authorization: `Bearer ${token}`,
235
+ 'content-type': 'application/octet-stream',
236
+ 'x-chunk-prev-hash': prev,
237
+ },
238
+ body: chunk,
239
+ signal,
240
+ };
241
+ const res = await fetchWithRetry(url, init, fetchImpl, signal, true);
242
+ if (res.status === 200 || res.status === 201) {
243
+ prev = thisHash;
244
+ chainRoot = thisHash;
245
+ opts.onProgress?.({ kind: 'chunk-uploaded', seq, total: chunks.length });
246
+ continue;
247
+ }
248
+ if (res.status === 401) {
249
+ // bugbot HIGH — bound retries. Token might be expired, OR the API
250
+ // key is revoked (bootstrap succeeds but minted tokens are still
251
+ // 401). Without a counter, the loop spins forever.
252
+ if (reauthAttempts >= MAX_REAUTH_ATTEMPTS) {
253
+ const text = await res.text().catch(() => '');
254
+ throw new UploadError(`chunk ${seq} unauthorized after ${reauthAttempts} re-bootstrap attempt(s); check API key validity. ${text}`, res.status);
255
+ }
256
+ reauthAttempts++;
257
+ const reboot = await bootstrapSession(baseUrl, opts.apiKey, runId, expectedChunkCount, snap.eventsBytes, fetchImpl, signal);
258
+ token = reboot.session.uploadToken;
259
+ seq -= 1;
260
+ continue;
261
+ }
262
+ if (res.status === 409) {
263
+ // Duplicate chunk content with matching hash is treated as success
264
+ // by the server (RPC path); treat as success here too if hash agrees.
265
+ const text = await res.text().catch(() => '');
266
+ if (/duplicate/i.test(text)) {
267
+ prev = thisHash;
268
+ chainRoot = thisHash;
269
+ opts.onProgress?.({ kind: 'chunk-uploaded', seq, total: chunks.length });
270
+ continue;
271
+ }
272
+ throw new UploadError(`chunk ${seq} rejected: ${res.status} ${text}`, res.status);
273
+ }
274
+ const text = await res.text().catch(() => '');
275
+ throw new UploadError(`chunk ${seq} failed: ${res.status} ${text}`, res.status);
276
+ }
277
+ // (5) Finalize.
278
+ checkAborted(signal);
279
+ let stateJson = {};
280
+ try {
281
+ const raw = await fs.readFile(snap.state, 'utf-8');
282
+ stateJson = JSON.parse(raw);
283
+ }
284
+ catch {
285
+ stateJson = {};
286
+ }
287
+ // sha256 not strictly needed here — server recomputes — but include for parity.
288
+ void sha256OfCanonical(stateJson);
289
+ const finalizeUrl = `${baseUrl}/api/runs/${encodeURIComponent(runId)}/finalize`;
290
+ const finalRes = await fetchWithRetry(finalizeUrl, {
291
+ method: 'POST',
292
+ headers: {
293
+ authorization: `Bearer ${token}`,
294
+ 'content-type': 'application/json',
295
+ },
296
+ body: JSON.stringify({
297
+ chainRoot,
298
+ expectedChunkCount,
299
+ stateJson,
300
+ }),
301
+ signal,
302
+ }, fetchImpl, signal, true);
303
+ if (finalRes.status !== 200) {
304
+ const text = await finalRes.text().catch(() => '');
305
+ throw new UploadError(`finalize failed: ${finalRes.status} ${text}`, finalRes.status);
306
+ }
307
+ opts.onProgress?.({ kind: 'finalized' });
308
+ // (6) Cleanup snapshot.
309
+ await deleteSnapshot(runDir);
310
+ return {
311
+ ok: true,
312
+ url: `${baseUrl}/runs/${encodeURIComponent(runId)}`,
313
+ };
314
+ }
315
+ catch (err) {
316
+ // Phase 3 — let UploadLimitError bubble so the auto-upload entry point
317
+ // can print the friendly message + preserve the run's exit code.
318
+ if (err instanceof UploadLimitError) {
319
+ throw err;
320
+ }
321
+ if (err instanceof SnapshotMismatchError) {
322
+ return { ok: false, error: `snapshot mismatch: ${err.message}` };
323
+ }
324
+ if (err.message === 'aborted') {
325
+ return { ok: false, error: 'aborted' };
326
+ }
327
+ return { ok: false, error: err.message ?? String(err) };
328
+ }
329
+ }
330
+ //# sourceMappingURL=uploader.js.map
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "5.5.2",
3
+ "version": "7.2.0",
4
4
  "type": "module",
5
+ "publishConfig": {
6
+ "tag": "next"
7
+ },
5
8
  "description": "Autonomous development pipeline for Claude Code: brainstorm → spec → plan → implement → migrate → validate → PR → review → merge. Multi-model, local-first, every phase a skill you can intervene in.",
6
9
  "keywords": [
7
10
  "claude-autopilot",
@@ -15,6 +18,10 @@
15
18
  "pipeline"
16
19
  ],
17
20
  "license": "MIT",
21
+ "workspaces": [
22
+ "apps/*",
23
+ "packages/*"
24
+ ],
18
25
  "repository": {
19
26
  "type": "git",
20
27
  "url": "https://github.com/axledbetter/claude-autopilot.git"
@@ -52,20 +59,28 @@
52
59
  ],
53
60
  "scripts": {
54
61
  "test": "node scripts/test-runner.mjs",
62
+ "test:adapters:live": "node --test --import=tsx tests/adapters/live/vercel.cert.ts tests/adapters/live/fly.cert.ts tests/adapters/live/render.cert.ts",
63
+ "test:rls": "node --test --import=tsx tests/rls/*.test.ts",
55
64
  "typecheck": "tsc --noEmit",
56
65
  "build": "tsc -p tsconfig.build.json && node scripts/post-build-rewrite-imports.mjs",
57
66
  "prepublishOnly": "npm run build && npm test",
58
- "autoregress": "tsx scripts/autoregress.ts"
67
+ "autoregress": "tsx scripts/autoregress.ts",
68
+ "db:start": "bash scripts/db/start-supabase.sh",
69
+ "db:stop": "bash scripts/db/stop-supabase.sh",
70
+ "db:reset": "bash scripts/db/reset-supabase.sh"
59
71
  },
60
72
  "dependencies": {
73
+ "@supabase/supabase-js": "^2.97.0",
61
74
  "ajv": "^8",
62
75
  "ajv-formats": "^3.0.1",
76
+ "canonicalize": "^3.0.0",
63
77
  "dotenv": ">=16",
64
78
  "js-yaml": "^4",
65
79
  "minimatch": ">=9",
66
80
  "proper-lockfile": "^4.1.2",
67
81
  "shell-quote": "^1.8.3",
68
- "tsx": ">=4"
82
+ "tsx": ">=4",
83
+ "ulid": "^3.0.2"
69
84
  },
70
85
  "optionalDependencies": {
71
86
  "@anthropic-ai/sdk": "^0.91.1",
@@ -78,6 +93,7 @@
78
93
  "@types/node": "^25",
79
94
  "@types/proper-lockfile": "^4.1.4",
80
95
  "@types/shell-quote": "^1.7.5",
96
+ "supabase": "^2.20.0",
81
97
  "typescript": "^6"
82
98
  },
83
99
  "peerDependencies": {
@@ -259,7 +259,7 @@ async function cmdGenerate(args: string[]): Promise<number> {
259
259
  let snapContent: string;
260
260
  try {
261
261
  const response = await client.responses.create({
262
- model: process.env.CODEX_MODEL ?? 'gpt-5.3-codex',
262
+ model: process.env.CODEX_MODEL ?? 'gpt-5.5',
263
263
  instructions: 'You write TypeScript snapshot tests. Output ONLY the file contents, no markdown fences.',
264
264
  input: prompt,
265
265
  max_output_tokens: 2000,
@@ -4,6 +4,10 @@ import { spawnSync } from 'node:child_process';
4
4
 
5
5
  const files = [];
6
6
  for await (const f of glob('tests/**/*.test.ts')) {
7
+ // RLS tests require a live Supabase stack + env credentials; they run
8
+ // from a dedicated workflow (.github/workflows/db-tests.yml) via
9
+ // `npm run test:rls`, not from the general test runner.
10
+ if (f.startsWith('tests/rls/') || f.startsWith('tests\\rls\\')) continue;
7
11
  files.push(f);
8
12
  }
9
13
  files.sort();
@@ -47,7 +47,7 @@ Each phase writes its output to disk. Claude can stop, the user can edit the art
47
47
  - PR review finds criticals → fix on branch, push, re-review (max 2 rounds).
48
48
  - Bugbot finds real bugs → fix, push, re-triage (max 3 rounds).
49
49
  - Unrecoverable failure → stop, report what completed, show what remains.
50
- 4. **Codex review is part of the loop, not optional.** The pipeline explicitly dispatches to `gpt-5.3-codex` for spec review, plan review, and PR review. This is the multi-model moat — don't skip it.
50
+ 4. **Codex review is part of the loop, not optional.** The pipeline explicitly dispatches to `gpt-5.5` for spec review, plan review, and PR review. This is the multi-model moat — don't skip it.
51
51
  5. **Skills are swappable.** `review-2pass` and `council` are alternative review phases — a user can configure which runs. The pipeline doesn't hardcode Claude or Codex.
52
52
 
53
53
  ## Phase outputs
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: make-interfaces-feel-better
3
+ description: Craft-and-feel polish for an interface that already works correctly and is already simple. Use when the user says "feels off", "feels clunky", "not quite right", "doesn't feel polished", "lacks soul", "feels cheap", "add some life to it", "make it feel expensive", "feels like AI slop", or wants motion/typography/microcopy/color work that isn't about fixing bugs or alignment. This is the vibes layer — assumes correctness and subtraction are already handled. If the screen is broken or cluttered, route to /ui-ux-pro-max or /simplify-ui first. Complements frontend-design:frontend-design (creative vision) but is scoped to tuning what exists.
4
+ ---
5
+
6
+ # Make interfaces feel better — the craft layer
7
+
8
+ This skill is for the pass where the interface already *works* and is already *simple*, but still feels mediocre. You are tuning emotion, not structure. If the user hasn't had the basics fixed yet, recommend `/ui-ux-pro-max` and `/simplify-ui` first; great feel on top of a broken layout is lipstick.
9
+
10
+ ## The diagnostic
11
+
12
+ Before touching anything, ask yourself what specific feeling is off. Interfaces usually fail one of five feels:
13
+
14
+ 1. **Cheap** — cramped, low-contrast, unbranded, no texture, inconsistent.
15
+ 2. **Cold / clinical** — correct but soulless. Efficient but no character.
16
+ 3. **Heavy / laggy** — transitions stutter, state changes snap, nothing feels alive.
17
+ 4. **Disorganized** — elements fight for attention; no clear visual hierarchy.
18
+ 5. **Nervous / fussy** — too many animations, too many chips, too many accent colors.
19
+
20
+ The fix for each is different. Name the feeling first, then apply the matching lever below.
21
+
22
+ ## Levers — in order of impact-per-minute
23
+
24
+ ### 1. Typography (highest leverage)
25
+
26
+ - **Pair a display face with a body face.** One distinctive (Playfair, Fraunces, Söhne, Tiempos, Inter Display) + one refined (Inter, Figtree, Söhne, IBM Plex, Geist). Don't use Inter for everything.
27
+ - **Use the display only at top levels** — page title, card titles ≥ 18px. Everything else body.
28
+ - **Tighten line-height on display** (1.1–1.2) and loosen on body (1.5–1.65).
29
+ - **Letter-spacing for uppercase** — eyebrows and section labels get `letter-spacing: 0.05em` or more.
30
+ - **One tabular-nums for numeric data** — `font-variant-numeric: tabular-nums` on any column of amounts makes them snap into alignment.
31
+
32
+ ### 2. Color & contrast
33
+
34
+ - **One dominant color, one accent, one destructive. No fourth.**
35
+ - **Backgrounds are off-white or off-black, never pure.** `#F7F8F6`, `#0B0D0A` — feel warmer than `#FFFFFF` / `#000000`.
36
+ - **Shadows with color** — `box-shadow: 0 2px 12px rgba(brand-color, 0.08)` feels branded; `rgba(0,0,0,0.08)` feels generic.
37
+ - **Gradients only on one element at a time.** Usually the primary CTA or the hero. Gradients everywhere = AI-slop.
38
+
39
+ ### 3. Motion
40
+
41
+ - **Page-load stagger is cheap delight.** 40–80ms stagger on cards/rows hitting the viewport; nothing fancier.
42
+ - **Easing: `cubic-bezier(0.22, 1, 0.36, 1)` for enter/exit.** Not `ease-in-out`.
43
+ - **200–280ms for small transitions, 400–600ms for modals/layouts.** Outside that range feels wrong.
44
+ - **Never animate what the user didn't cause.** Auto-pulsing "new" badges are hostile.
45
+ - **Respect `prefers-reduced-motion`** — drop transitions to 0.01s, keep only opacity/color changes.
46
+
47
+ ### 4. Microcopy
48
+
49
+ - **Button verbs specific to the action.** Not "Submit" — "Send quote for review."
50
+ - **Empty states with personality.** "No quotes yet. Start one — it takes about 3 minutes." Beats "No data."
51
+ - **Error messages that acknowledge** — "That's not quite right — X needs to be Y" rather than "Invalid input."
52
+ - **Success states that celebrate proportionally.** Saved draft = small checkmark. Bound a policy = confetti-adjacent.
53
+ - **Loading copy that sets expectations.** "Matching you with carriers… usually 5 seconds" > spinner alone.
54
+
55
+ ### 5. Texture & depth
56
+
57
+ Not every interface needs these, but they're how screens stop feeling generic:
58
+ - **Noise overlay at 2–4% opacity** on dark backgrounds. Kills the plastic look.
59
+ - **Subtle inner shadow on inputs** — `inset 0 1px 0 rgba(255,255,255,0.4)` on light themes suggests depth.
60
+ - **Asymmetric card padding** — e.g., `24px 24px 20px 24px` sometimes feels better than all-24 because humans scan top-to-bottom.
61
+ - **Left-align eyebrow decoration** — a 2px × 18px accent-color rule before a section label reads as editorial, not chrome.
62
+
63
+ ### 6. The small details
64
+
65
+ - **Focus rings that feel on-brand** — `box-shadow: 0 0 0 3px rgba(brand, 0.2)` beats the default blue browser ring.
66
+ - **Checkmark animation on save** — 300ms stroke-dash reveal, not a static green dot.
67
+ - **Hover states that change border color**, not scale or shadow (which feel toy-like on dense forms).
68
+ - **Caret-color matched to brand.** `caret-color: var(--brand)`.
69
+ - **Chip alignment with inline icons** — baseline-align the icon to the text, don't center — centering looks off for small text.
70
+
71
+ ## What to avoid (AI-slop tells)
72
+
73
+ - Purple-to-pink gradients on white.
74
+ - "Gradient text" for things that aren't hero titles.
75
+ - Emoji in UI chrome (buttons, headers).
76
+ - `font-family: 'Inter', sans-serif` as the only typeface.
77
+ - Drop-shadows the same size on cards, buttons, and modals (varied elevation is the whole point).
78
+ - Buttons with `border-radius: 9999px` that are 32px tall (pill at small scale reads as "claimed to be premium, actually built in 20 min").
79
+ - Glass-morphism backdrops without a real background image behind them.
80
+
81
+ ## Workflow
82
+
83
+ 1. **Name the feel.** In one sentence, write what's wrong. "Feels cheap because inputs have no shadow and everything is pure white on pure white." This grounds the rest.
84
+ 2. **Apply at most 3 levers from the list above.** Do not touch everything — diminishing returns.
85
+ 3. **Reload and look away and back.** Judge fresh, not against your memory of before.
86
+ 4. **Name one thing you resisted adding.** This keeps you honest — feel upgrades are often about resisting maximalism, not piling it on.
87
+ 5. **Show the user which levers you pulled** and invite them to push back on any that felt wrong.
88
+
89
+ ## Red flags during the pass
90
+
91
+ If you catch yourself adding:
92
+ - A new color variable that isn't in the design system
93
+ - A third weight of the display font
94
+ - An animation > 600ms
95
+ - A shadow on a default-state button
96
+ - An emoji inside a form label
97
+
98
+ …stop. You're adding where you should be tuning.
99
+
100
+ ## Interactions
101
+
102
+ - Runs best on a screen that already passed `/ui-ux-pro-max` and `/simplify-ui`.
103
+ - Can safely coexist with `/ui` for a combined sweep.
104
+ - If the user wants a redesign, escalate to `frontend-design:frontend-design`.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: simplify-ui
3
+ description: Ruthless subtraction pass on an existing UI — visual/UX reduction only. Use when the user says "cut it down", "too much going on", "too cluttered", "remove noise", "pare down", "less is more", "it's too busy", or wants the page trimmed without rebuilding it. This is the "remove before adding" lens — complementary to /ui (full polish pass), /ui-ux-pro-max (correctness audit), and /make-interfaces-feel-better (craft layer). For code-level deduplication, use the plugin /simplify instead.
4
+ ---
5
+
6
+ # Simplify — remove before you add
7
+
8
+ You are looking at a UI and cutting what doesn't earn its place. The default answer is **delete**. Every element has to justify why it survives. If you can't state its purpose in a short sentence, it goes.
9
+
10
+ ## The guiding question
11
+
12
+ For every visible element on the screen, answer:
13
+
14
+ > "What would break for the user if this weren't here?"
15
+
16
+ If the answer is "nothing" or "aesthetics", delete it. If the answer names a concrete user failure, keep it — and then see if it can be smaller.
17
+
18
+ ## What to cut, in priority order
19
+
20
+ ### Tier 1 — cut without thinking
21
+
22
+ 1. **Section headers above single-item sections.** "LIABILITY COVERAGE" over a single field is structural vanity.
23
+ 2. **Helper text that restates the label.** "Enter your email address" under an "Email" field. Pick one.
24
+ 3. **Placeholders that duplicate labels.** Ditto.
25
+ 4. **Status pills identical to adjacent counts.** "5/5" badge next to a "5 of 5 complete" progress bar.
26
+ 5. **Default-zero values displayed as content.** `$0`, `0%`, `null` — render as empty.
27
+ 6. **Decorative icons** that don't carry meaning or affordance. Briefcase next to "Company" is ornament.
28
+ 7. **Explanatory captions** for patterns the user already knows ("Click Submit to submit").
29
+ 8. **"Powered by…" footers on internal tools.**
30
+ 9. **Animated spinners on < 200ms operations.** They flash and look broken.
31
+
32
+ ### Tier 2 — cut after a second look
33
+
34
+ 1. **Duplicate buttons.** "Save" at top and bottom of a form — keep one, usually the bottom if the form is long.
35
+ 2. **Multiple paths to the same action.** A "Create quote" primary button + a "+" FAB + a "New quote" menu item.
36
+ 3. **Breadcrumbs on 2-level-deep pages.** Overkill; use a back button.
37
+ 4. **Card shadows stacked on card borders.** Pick one.
38
+ 5. **Grid lines *and* alternating row backgrounds.** Pick one.
39
+ 6. **Count/progress indicators that update instantly.** If the user answered the field, they know.
40
+ 7. **Summary sentences above tables** that restate what the table shows. ("This table shows quotes. There are 3 quotes.")
41
+ 8. **Emoji or flag icons** next to text that already says the same thing.
42
+ 9. **Confirmation dialogs for non-destructive actions.** "Save draft?" — just save.
43
+
44
+ ### Tier 3 — cut with caution (verify with the user)
45
+
46
+ 1. **Tooltips on self-explanatory controls.** If the icon is standard (× for close), no tooltip needed — unless a11y is the reason.
47
+ 2. **Onboarding hints that persist after first use.**
48
+ 3. **Tutorial steps that could be inferred from labels.**
49
+ 4. **Analytics-only elements** that don't serve the user (tracking pixels belong in code, not chrome).
50
+
51
+ ## What not to cut (hold the line)
52
+
53
+ - Labels, even when obvious — they are your a11y surface.
54
+ - Error messages — always keep; tune the copy under `/make-interfaces-feel-better`.
55
+ - Validation — never cut; maybe defer to blur instead of on-change.
56
+ - Skip links and screen-reader-only text.
57
+ - The single "Undo" or "Back" that lets users recover.
58
+
59
+ ## Density rules after simplification
60
+
61
+ Once you've cut:
62
+ - **3–6 fields per card.** Fewer = sleepy; more = oppressive.
63
+ - **One accent color dominant per card.** Not one per chip.
64
+ - **At most 2 type sizes per card** (label + input/value). Title of the card is the third.
65
+ - **One CTA per screen region.** Multiple = the user has to choose, and they'd rather not.
66
+ - **Zero horizontal scroll.** If the layout forces it on common widths, the layout is wrong.
67
+
68
+ ## Workflow
69
+
70
+ 1. **Screenshot or open the screen.** Read every visible string. Don't cut blind.
71
+ 2. **List every distinct element.** Cards, headers, chips, buttons, helper text, icons, images.
72
+ 3. **Mark each: keep / cut / reduce.** "Reduce" means the element stays but smaller/shorter/less prominent.
73
+ 4. **Cut Tier 1 items immediately.** No discussion.
74
+ 5. **Surface Tier 2/3 cuts as proposals** to the user before applying.
75
+ 6. **After the cut, measure.** Did vertical density improve? Did the primary action get more visible? If neither, you cut the wrong things.
76
+
77
+ ## Micro-patterns
78
+
79
+ - **Collapse single-field sections into the parent card.** Don't delete the field, delete the section wrapper.
80
+ - **Merge two adjacent chips with the same color into one.** "Required + Auto-filled" → "Auto-filled (required)".
81
+ - **Replace "X of Y" with "Y - X remaining"** when remaining is what the user cares about.
82
+ - **Fold secondary actions into an overflow menu (⋯).** Don't show 5 buttons when 1 primary + ⋯ works.
83
+ - **Use whitespace as a divider** before reaching for a `<hr>` or border.
84
+
85
+ ## Red flags that you're over-cutting
86
+
87
+ - Users can't tell which field is required.
88
+ - A keyboard-only user can't move through the form.
89
+ - A screen-reader user can't distinguish regions.
90
+ - The page looks like a wireframe, not a product.
91
+ - You removed something and then had to re-add it in the next session.
92
+
93
+ ## Interactions
94
+
95
+ - Runs best after `/ui-ux-pro-max` (knows what's broken) and before `/make-interfaces-feel-better` (which adds back *quality*, not noise).
96
+ - Combine with `/ui` for a full pass in one shot.
97
+ - Do not run on a screen that's already minimalist — you'll cut into muscle.
98
+
99
+ ## One rule above all
100
+
101
+ > Every element survives by earning its pixel count.
102
+
103
+ If you wouldn't fight to keep it at the next design review, delete it now.