@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,395 @@
1
+ // src/cli/runs-watch.ts
2
+ //
3
+ // `runs watch <runId>` — tails a run's events.ndjson and renders a live
4
+ // cost/token meter that updates as phases execute.
5
+ //
6
+ // Tail strategy: fs.watchFile with a 1s polling interval. fs.watch (inotify
7
+ // on Linux, FSEvents on macOS) is unreliable for append-only logs in our
8
+ // matrix — it sometimes never fires for tiny appends, sometimes fires twice
9
+ // per write. Polling is consistent across darwin / linux / win32 and the
10
+ // 1-second cadence is plenty for a human-facing meter.
11
+ //
12
+ // Modes:
13
+ // default pretty-rendered live tail (header + per-event lines + final summary)
14
+ // --no-follow render snapshot once, exit
15
+ // --json emit raw NDJSON to stdout (for piping to jq / dashboards)
16
+ // --since <seq> replay forward from a specific seq (resume after disconnect)
17
+ //
18
+ // Spec: tasks/v6.1-runs-watch.md.
19
+ import * as fs from 'node:fs';
20
+ import { GuardrailError } from "../core/errors.js";
21
+ import { foldEvents, readEvents, eventsPath } from "../core/run-state/events.js";
22
+ import { readStateSnapshot } from "../core/run-state/state.js";
23
+ import { runDirFor } from "../core/run-state/runs.js";
24
+ import { isValidULID } from "../core/run-state/ulid.js";
25
+ import { renderEventLine, renderFinalSummary, renderHeader, } from "./runs-watch-renderer.js";
26
+ // ----------------------------------------------------------------------------
27
+ // Implementation.
28
+ // ----------------------------------------------------------------------------
29
+ /** ULID validation, mirrors the helper in runs.ts. Inlined to avoid an
30
+ * internal import that isn't exported. */
31
+ function assertValidRunId(runId) {
32
+ if (!runId) {
33
+ throw new GuardrailError('a run id is required', {
34
+ code: 'invalid_config',
35
+ provider: 'runs-cli',
36
+ details: { runId },
37
+ });
38
+ }
39
+ if (!isValidULID(runId)) {
40
+ throw new GuardrailError(`run id is not a valid ULID: ${runId}`, {
41
+ code: 'invalid_config',
42
+ provider: 'runs-cli',
43
+ details: { runId },
44
+ });
45
+ }
46
+ }
47
+ function formatErr(err) {
48
+ if (err instanceof GuardrailError)
49
+ return `${err.code}: ${err.message}`;
50
+ if (err instanceof Error)
51
+ return err.message;
52
+ return String(err);
53
+ }
54
+ /** Pull a `BudgetConfig` out of `RunState.config` if one was recorded at
55
+ * run-creation time. Returns null on any shape mismatch — we never throw,
56
+ * the watcher should degrade gracefully when the budget block is absent. */
57
+ function extractBudget(state) {
58
+ const cfg = state.config;
59
+ if (!cfg || typeof cfg !== 'object')
60
+ return null;
61
+ // Two recognized shapes:
62
+ // 1. `config.budget = { perRunUSD: ..., ... }` (preferred — matches
63
+ // what `BudgetConfig` looks like in budget.ts)
64
+ // 2. `config.budgets = { perRunUSD: ..., ... }` (yaml plural alias —
65
+ // matches what guardrail.config.yaml uses; see migration-guide.md
66
+ // "Budget config" section)
67
+ const candidate = cfg.budget
68
+ ?? cfg.budgets;
69
+ if (!candidate || typeof candidate !== 'object')
70
+ return null;
71
+ const c = candidate;
72
+ if (typeof c.perRunUSD !== 'number')
73
+ return null;
74
+ const out = { perRunUSD: c.perRunUSD };
75
+ if (typeof c.perPhaseUSD === 'number')
76
+ out.perPhaseUSD = c.perPhaseUSD;
77
+ if (typeof c.conservativePhaseReserveUSD === 'number') {
78
+ out.conservativePhaseReserveUSD = c.conservativePhaseReserveUSD;
79
+ }
80
+ return out;
81
+ }
82
+ /** Fold the events list into a (totalCostUSD, terminalEvent) tuple. The
83
+ * watcher uses this to know when to stop following — a terminal event is
84
+ * any `run.complete`, the run-level `status` field on it, or a marker
85
+ * event the verb treats as terminal. */
86
+ function isTerminalEvent(ev) {
87
+ if (ev.event === 'run.complete')
88
+ return true;
89
+ return false;
90
+ }
91
+ /** Public entry point — exported so the dispatcher in runs.ts can call us
92
+ * uniformly with the other verbs. */
93
+ export async function runRunsWatch(opts) {
94
+ const cwd = opts.cwd ?? process.cwd();
95
+ const json = !!opts.json;
96
+ // ---- 1. Validate inputs --------------------------------------------------
97
+ try {
98
+ assertValidRunId(opts.runId);
99
+ }
100
+ catch (err) {
101
+ return {
102
+ exit: 1,
103
+ stdout: [],
104
+ stderr: [`[claude-autopilot] runs watch: ${formatErr(err)}`],
105
+ };
106
+ }
107
+ const runDir = runDirFor(cwd, opts.runId);
108
+ if (!fs.existsSync(runDir)) {
109
+ const err = new GuardrailError(`run not found: ${opts.runId}`, {
110
+ code: 'not_found',
111
+ provider: 'runs-cli',
112
+ details: { runId: opts.runId, runDir },
113
+ });
114
+ return {
115
+ exit: 2,
116
+ stdout: [],
117
+ stderr: [`[claude-autopilot] runs watch: ${formatErr(err)}`],
118
+ };
119
+ }
120
+ if (opts.since !== undefined && (!Number.isFinite(opts.since) || opts.since < 0)) {
121
+ const err = new GuardrailError(`--since must be a non-negative integer (got ${opts.since})`, { code: 'invalid_config', provider: 'runs-cli', details: { since: opts.since } });
122
+ return {
123
+ exit: 1,
124
+ stdout: [],
125
+ stderr: [`[claude-autopilot] runs watch: ${formatErr(err)}`],
126
+ };
127
+ }
128
+ // ---- 2. Decide ANSI mode -------------------------------------------------
129
+ //
130
+ // ansi=true iff:
131
+ // - not --json mode, AND
132
+ // - --no-color was not passed, AND
133
+ // - NO_COLOR env var not set, AND
134
+ // - stdout is a TTY (via the test seam or the real flag)
135
+ const realIsTTY = opts.__testIsTTY !== undefined ? opts.__testIsTTY : !!process.stdout.isTTY;
136
+ const ansi = !json && !opts.noColor && !process.env.NO_COLOR && realIsTTY;
137
+ const renderOpts = { ansi };
138
+ // ---- 3. Read initial state -----------------------------------------------
139
+ let state;
140
+ try {
141
+ state = readStateSnapshot(runDir);
142
+ }
143
+ catch {
144
+ state = null;
145
+ }
146
+ // Fall back to events replay if state.json is missing/corrupt — this is
147
+ // the same path runs show takes. Watch should never refuse to start on
148
+ // snapshot drift; events.ndjson is authoritative.
149
+ if (!state) {
150
+ try {
151
+ state = foldEvents(runDir, readEvents(runDir).events);
152
+ }
153
+ catch (err) {
154
+ return {
155
+ exit: 1,
156
+ stdout: [],
157
+ stderr: [`[claude-autopilot] runs watch: ${formatErr(err)}`],
158
+ };
159
+ }
160
+ }
161
+ const budget = extractBudget(state);
162
+ // The output sinks. In tests these are buffer-collectors; in production
163
+ // they wrap process.stdout / process.stderr.
164
+ const stdoutLines = [];
165
+ const stderrLines = [];
166
+ const writeStdout = opts.__testWriteStdout
167
+ ?? ((s) => { stdoutLines.push(s.endsWith('\n') ? s.slice(0, -1) : s); });
168
+ const writeStderr = opts.__testWriteStderr
169
+ ?? ((s) => { stderrLines.push(s.endsWith('\n') ? s.slice(0, -1) : s); });
170
+ // ---- 4. Snapshot mode (--no-follow) --------------------------------------
171
+ if (opts.noFollow) {
172
+ const since = opts.since ?? 0;
173
+ let runningTotal = 0;
174
+ let allEvents;
175
+ try {
176
+ allEvents = readEvents(runDir).events;
177
+ }
178
+ catch (err) {
179
+ return {
180
+ exit: 1,
181
+ stdout: [],
182
+ stderr: [`[claude-autopilot] runs watch: ${formatErr(err)}`],
183
+ };
184
+ }
185
+ if (json) {
186
+ // JSON mode: dump every event from `since` forward as raw NDJSON.
187
+ // Strict channel discipline — exactly the bytes that landed on disk.
188
+ for (const ev of allEvents) {
189
+ if (ev.seq < since)
190
+ continue;
191
+ writeStdout(JSON.stringify(ev) + '\n');
192
+ }
193
+ return finishOk(stdoutLines, stderrLines);
194
+ }
195
+ // Pretty: header + every event-line.
196
+ for (const line of renderHeader(state, budget, renderOpts))
197
+ writeStdout(line + '\n');
198
+ for (const ev of allEvents) {
199
+ if (ev.seq < since)
200
+ continue;
201
+ if (ev.event === 'phase.cost')
202
+ runningTotal += ev.costUSD;
203
+ writeStdout(renderEventLine(ev, runningTotal, renderOpts) + '\n');
204
+ }
205
+ // Final summary if the run terminated.
206
+ if (state.status === 'success' || state.status === 'failed' || state.status === 'aborted') {
207
+ const summary = {
208
+ runId: state.runId,
209
+ status: state.status,
210
+ totalCostUSD: state.totalCostUSD,
211
+ durationMs: computeDurationMs(state),
212
+ };
213
+ for (const line of renderFinalSummary(summary, renderOpts))
214
+ writeStdout(line + '\n');
215
+ }
216
+ return finishOk(stdoutLines, stderrLines);
217
+ }
218
+ // ---- 5. Live tail mode ---------------------------------------------------
219
+ // We re-read the file from byte 0 each poll cycle (simpler than offset
220
+ // tracking and the file is bounded). We track lastSeq to decide which
221
+ // events are new. The `since` flag is treated as a floor on what we
222
+ // print, not on what we read — we always need the full event stream
223
+ // to compute runningTotal correctly.
224
+ const since = opts.since ?? 0;
225
+ let lastSeq = 0;
226
+ let runningTotal = 0;
227
+ // Print header up front (default mode only — JSON pipes the raw stream).
228
+ if (!json) {
229
+ for (const line of renderHeader(state, budget, renderOpts))
230
+ writeStdout(line + '\n');
231
+ }
232
+ // Drain whatever's already on disk.
233
+ const initialEvents = readEvents(runDir).events;
234
+ for (const ev of initialEvents) {
235
+ if (ev.event === 'phase.cost')
236
+ runningTotal += ev.costUSD;
237
+ if (ev.seq >= since) {
238
+ if (json)
239
+ writeStdout(JSON.stringify(ev) + '\n');
240
+ else
241
+ writeStdout(renderEventLine(ev, runningTotal, renderOpts) + '\n');
242
+ }
243
+ lastSeq = Math.max(lastSeq, ev.seq);
244
+ }
245
+ // Did the run already terminate before we started watching? Short-circuit
246
+ // so the verb doesn't hang waiting for events that will never arrive.
247
+ const terminalAlready = initialEvents.some(isTerminalEvent);
248
+ if (terminalAlready) {
249
+ if (!json) {
250
+ const finalState = foldEvents(runDir, initialEvents);
251
+ const summary = {
252
+ runId: finalState.runId,
253
+ status: finalState.status,
254
+ totalCostUSD: finalState.totalCostUSD,
255
+ durationMs: computeDurationMs(finalState),
256
+ };
257
+ for (const line of renderFinalSummary(summary, renderOpts))
258
+ writeStdout(line + '\n');
259
+ }
260
+ return finishOk(stdoutLines, stderrLines);
261
+ }
262
+ // Set up the polling watcher.
263
+ const eventsFile = eventsPath(runDir);
264
+ const pollInterval = opts.__testPollIntervalMs ?? 1000;
265
+ let lastFileSize = fs.existsSync(eventsFile) ? fs.statSync(eventsFile).size : 0;
266
+ return await new Promise(resolve => {
267
+ let resolved = false;
268
+ let sigintHandler = null;
269
+ const finish = (status, errMsg) => {
270
+ if (resolved)
271
+ return;
272
+ resolved = true;
273
+ try {
274
+ fs.unwatchFile(eventsFile);
275
+ }
276
+ catch { /* ignore */ }
277
+ if (sigintHandler) {
278
+ try {
279
+ process.off('SIGINT', sigintHandler);
280
+ }
281
+ catch { /* ignore */ }
282
+ }
283
+ // Final summary in pretty mode.
284
+ if (!json) {
285
+ try {
286
+ const finalEvents = readEvents(runDir).events;
287
+ const finalState = foldEvents(runDir, finalEvents);
288
+ const summary = {
289
+ runId: finalState.runId,
290
+ // For Ctrl-C (interrupted) we tag the summary that way even
291
+ // though the run itself may still be running. The renderer
292
+ // colors `interrupted` as bold-yellow.
293
+ status: status === 'interrupted' ? 'interrupted' : finalState.status,
294
+ totalCostUSD: finalState.totalCostUSD,
295
+ durationMs: computeDurationMs(finalState),
296
+ };
297
+ for (const line of renderFinalSummary(summary, renderOpts)) {
298
+ writeStdout(line + '\n');
299
+ }
300
+ }
301
+ catch {
302
+ // Ignore — final summary is best-effort.
303
+ }
304
+ }
305
+ const exit = status === 'error' ? 1 : 0;
306
+ const result = {
307
+ exit,
308
+ stdout: stdoutLines,
309
+ stderr: errMsg
310
+ ? [...stderrLines, `[claude-autopilot] runs watch: ${errMsg}`]
311
+ : stderrLines,
312
+ };
313
+ resolve(result);
314
+ };
315
+ const tick = () => {
316
+ // File-shrink recovery: if the file is smaller than last poll the
317
+ // log was rotated/truncated externally — re-read from start.
318
+ let stat = null;
319
+ try {
320
+ stat = fs.statSync(eventsFile);
321
+ }
322
+ catch {
323
+ // File doesn't exist yet — wait for it.
324
+ return;
325
+ }
326
+ if (stat.size < lastFileSize) {
327
+ // Truncation. Reset and re-fold from scratch.
328
+ lastSeq = 0;
329
+ runningTotal = 0;
330
+ }
331
+ lastFileSize = stat.size;
332
+ let events;
333
+ try {
334
+ events = readEvents(runDir).events;
335
+ }
336
+ catch (err) {
337
+ // Mid-log corruption — surface and exit. The runs doctor verb
338
+ // can be used to diagnose / fix.
339
+ finish('error', formatErr(err));
340
+ return;
341
+ }
342
+ for (const ev of events) {
343
+ if (ev.seq <= lastSeq)
344
+ continue;
345
+ if (ev.event === 'phase.cost')
346
+ runningTotal += ev.costUSD;
347
+ if (ev.seq >= since) {
348
+ if (json)
349
+ writeStdout(JSON.stringify(ev) + '\n');
350
+ else
351
+ writeStdout(renderEventLine(ev, runningTotal, renderOpts) + '\n');
352
+ }
353
+ lastSeq = ev.seq;
354
+ if (isTerminalEvent(ev)) {
355
+ // Run terminated — drain remaining events (already done in the
356
+ // loop) and exit.
357
+ finish('completed');
358
+ return;
359
+ }
360
+ }
361
+ };
362
+ // fs.watchFile polls at the interval we pass; the listener fires when
363
+ // mtime changes. We do an explicit tick on each fire because the file
364
+ // size delta isn't enough on its own (an event could land between
365
+ // polls).
366
+ fs.watchFile(eventsFile, { interval: pollInterval, persistent: true }, () => {
367
+ tick();
368
+ });
369
+ // Ctrl-C — clean exit with summary. The handler is removed by `finish`.
370
+ if (!opts.__testStopAfterTerminal) {
371
+ sigintHandler = () => finish('interrupted');
372
+ process.on('SIGINT', sigintHandler);
373
+ }
374
+ // First tick — we already drained initial events above, but kicking
375
+ // tick() once here covers the rare race where new events appended
376
+ // between our drain and our watchFile registration.
377
+ tick();
378
+ });
379
+ }
380
+ /** Compute wall-clock duration from a state snapshot. Used in the final
381
+ * summary line. Falls back to 0 if the run hasn't started or the timestamps
382
+ * are malformed. */
383
+ function computeDurationMs(state) {
384
+ const start = Date.parse(state.startedAt);
385
+ if (!Number.isFinite(start))
386
+ return 0;
387
+ const end = state.endedAt ? Date.parse(state.endedAt) : Date.now();
388
+ if (!Number.isFinite(end))
389
+ return 0;
390
+ return Math.max(0, end - start);
391
+ }
392
+ function finishOk(stdout, stderr) {
393
+ return { exit: 0, stdout, stderr };
394
+ }
395
+ //# sourceMappingURL=runs-watch.js.map
@@ -0,0 +1,122 @@
1
+ import { statePath } from '../core/run-state/state.ts';
2
+ import type { ExternalRef, RunState, RunStatus } from '../core/run-state/types.ts';
3
+ interface RunsCliResult {
4
+ exit: number;
5
+ /** Lines (will be newline-joined) for stdout under text mode. Under --json
6
+ * mode this is replaced by a single envelope JSON line. */
7
+ stdout: string[];
8
+ /** Lines for stderr under text mode. Phase 5 will move all human warnings
9
+ * into NDJSON events on stderr; Phase 3 keeps text-mode warnings here. */
10
+ stderr: string[];
11
+ }
12
+ export interface RunRunsListOptions {
13
+ cwd?: string;
14
+ status?: string;
15
+ json?: boolean;
16
+ }
17
+ /** `runs list` — newest-first listing. Optional --status filter narrows to
18
+ * one RunStatus. `--json` emits an envelope; text mode prints a tight
19
+ * table. */
20
+ export declare function runRunsList(opts: RunRunsListOptions): Promise<RunsCliResult>;
21
+ export interface RunRunsShowOptions {
22
+ runId: string;
23
+ cwd?: string;
24
+ /** Tail the events.ndjson log after the state summary. */
25
+ events?: boolean;
26
+ /** How many tail events to show with --events. Default 20. */
27
+ eventsTail?: number;
28
+ json?: boolean;
29
+ }
30
+ /** `runs show <id>` — render state.json (or replay if missing) plus, with
31
+ * --events, the tail of events.ndjson. JSON mode bundles state + events into
32
+ * the envelope. */
33
+ export declare function runRunsShow(opts: RunRunsShowOptions): Promise<RunsCliResult>;
34
+ export interface RunRunsGcOptions {
35
+ cwd?: string;
36
+ /** Default 30. */
37
+ olderThanDays?: number;
38
+ dryRun?: boolean;
39
+ json?: boolean;
40
+ /** Skip the interactive confirmation prompt. */
41
+ yes?: boolean;
42
+ }
43
+ /** `runs gc` — wraps gcRuns with confirmation. Default cutoff 30 days. With
44
+ * --dry-run, lists what would be removed without touching disk. */
45
+ export declare function runRunsGc(opts: RunRunsGcOptions): Promise<RunsCliResult>;
46
+ export interface RunRunsDeleteOptions {
47
+ runId: string;
48
+ cwd?: string;
49
+ /** Override the terminal-status guard. */
50
+ force?: boolean;
51
+ json?: boolean;
52
+ }
53
+ /** `runs delete <id>` — explicit single-run delete. Refuses non-terminal
54
+ * status without --force; refuses if the run lock is currently held by
55
+ * another writer.
56
+ *
57
+ * We acquire the lock for the duration of the delete so we never race a
58
+ * concurrent writer. Lock acquisition uses a tiny timeout — we want
59
+ * fail-fast over blocking. */
60
+ export declare function runRunsDelete(opts: RunRunsDeleteOptions): Promise<RunsCliResult>;
61
+ export type ResumeDecision = 'retry' | 'skip-idempotent' | 'needs-human' | 'already-complete';
62
+ export interface RunResumeLookup {
63
+ runId: string;
64
+ status: RunStatus;
65
+ currentPhase: string | null;
66
+ nextPhase: string | null;
67
+ decision: ResumeDecision;
68
+ reason: string;
69
+ externalRefs: ExternalRef[];
70
+ }
71
+ export interface RunRunResumeOptions {
72
+ runId: string;
73
+ cwd?: string;
74
+ /** Optional explicit phase to resume from (by name). Surfaces as a
75
+ * validation hint here; actual execution lands in Phase 6+. */
76
+ fromPhase?: string;
77
+ json?: boolean;
78
+ }
79
+ /** `run resume <id>` — Phase 3 LOOKUP ONLY.
80
+ *
81
+ * This verb identifies which phase a future resume would pick up from and
82
+ * the decision the engine would make per the spec's idempotency table. It
83
+ * does NOT execute the phase — that wires in Phase 6+ once the budget
84
+ * enforcer (Phase 4) and the JSON event stream (Phase 5) are in place.
85
+ *
86
+ * Decision rules (mirror `runPhase` in src/core/run-state/phase-runner.ts):
87
+ * - already-complete : run.status === 'success' or every phase succeeded
88
+ * - skip-idempotent : nextPhase has a prior phase.success AND idempotent
89
+ * - needs-human : nextPhase has a prior phase.success AND side-effects
90
+ * - retry : default (no prior success — first attempt or retry
91
+ * of a failed attempt) */
92
+ export declare function runRunResume(opts: RunRunResumeOptions): Promise<RunsCliResult>;
93
+ /** Pure projection over a RunState that decides the next phase + replay rule.
94
+ * Exported for tests. */
95
+ export declare function computeResumeLookup(state: RunState, fromPhase?: string): RunResumeLookup;
96
+ export interface RunRunsDoctorOptions {
97
+ cwd?: string;
98
+ /** Limit the check to a single run id. */
99
+ runId?: string;
100
+ /** Rewrite state.json from the events.ndjson replay where drift is found. */
101
+ fix?: boolean;
102
+ json?: boolean;
103
+ }
104
+ export interface RunsDoctorRunReport {
105
+ runId: string;
106
+ drift: 'none' | 'snapshot-vs-replay' | 'snapshot-missing' | 'snapshot-corrupt' | 'events-corrupt';
107
+ details?: string;
108
+ fixed?: boolean;
109
+ }
110
+ /** `runs doctor` — replay events.ndjson per run, compare against state.json,
111
+ * report drift. With --fix, rewrite state.json from the replay where drift
112
+ * exists.
113
+ *
114
+ * Drift categories:
115
+ * snapshot-vs-replay : both readable but disagree on a key field
116
+ * snapshot-missing : state.json absent, replay successful
117
+ * snapshot-corrupt : state.json present but unparseable
118
+ * events-corrupt : events.ndjson can't be folded (bigger problem)
119
+ */
120
+ export declare function runRunsDoctor(opts: RunRunsDoctorOptions): Promise<RunsCliResult>;
121
+ export { statePath };
122
+ //# sourceMappingURL=runs.d.ts.map