@mintlify/cli 4.0.1654 → 4.0.1656

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 (54) hide show
  1. package/bin/cli.js +1 -7
  2. package/bin/tsconfig.build.tsbuildinfo +1 -1
  3. package/package.json +9 -13
  4. package/src/cli.tsx +1 -12
  5. package/__test__/mintTestFilePreview.test.ts +0 -35
  6. package/__test__/mintTestOutput.test.ts +0 -256
  7. package/__test__/runHistory.test.ts +0 -240
  8. package/bin/agent-harness/agentPreflight.js +0 -24
  9. package/bin/agent-harness/buildTaskPrompt.js +0 -86
  10. package/bin/agent-harness/countReport.js +0 -10
  11. package/bin/agent-harness/discoverCodeBlocks.js +0 -101
  12. package/bin/agent-harness/executeCodeBlocks.js +0 -263
  13. package/bin/agent-harness/filterSelectedPages.js +0 -10
  14. package/bin/agent-harness/generateTestCode.js +0 -337
  15. package/bin/agent-harness/index.js +0 -95
  16. package/bin/agent-harness/loadAgentSdk.js +0 -46
  17. package/bin/agent-harness/manifest.js +0 -113
  18. package/bin/agent-harness/mapWithConcurrency.js +0 -26
  19. package/bin/agent-harness/runHistory.js +0 -256
  20. package/bin/agent-harness/runSave.js +0 -47
  21. package/bin/agent-harness/setupFolders.js +0 -73
  22. package/bin/agent-harness/tasks/checkTestability.js +0 -228
  23. package/bin/agent-harness/types.js +0 -66
  24. package/bin/mintTest.js +0 -122
  25. package/bin/mintTestFilePreview.js +0 -39
  26. package/bin/mintTestNav.js +0 -35
  27. package/bin/mintTestTelemetry.js +0 -63
  28. package/bin/mintTestText.js +0 -31
  29. package/bin/mintTestTree.js +0 -94
  30. package/bin/mintTestUi.js +0 -818
  31. package/src/agent-harness/MINT_TEST_SYSTEM_DESIGN.md +0 -64
  32. package/src/agent-harness/agentPreflight.ts +0 -13
  33. package/src/agent-harness/buildTaskPrompt.ts +0 -93
  34. package/src/agent-harness/countReport.ts +0 -17
  35. package/src/agent-harness/discoverCodeBlocks.ts +0 -90
  36. package/src/agent-harness/executeCodeBlocks.ts +0 -351
  37. package/src/agent-harness/filterSelectedPages.ts +0 -14
  38. package/src/agent-harness/generateTestCode.ts +0 -396
  39. package/src/agent-harness/index.ts +0 -127
  40. package/src/agent-harness/loadAgentSdk.ts +0 -35
  41. package/src/agent-harness/manifest.ts +0 -116
  42. package/src/agent-harness/mapWithConcurrency.ts +0 -21
  43. package/src/agent-harness/runHistory.ts +0 -255
  44. package/src/agent-harness/runSave.ts +0 -38
  45. package/src/agent-harness/setupFolders.ts +0 -87
  46. package/src/agent-harness/tasks/checkTestability.ts +0 -256
  47. package/src/agent-harness/types.ts +0 -239
  48. package/src/mintTest.tsx +0 -138
  49. package/src/mintTestFilePreview.ts +0 -32
  50. package/src/mintTestNav.ts +0 -31
  51. package/src/mintTestTelemetry.ts +0 -101
  52. package/src/mintTestText.ts +0 -35
  53. package/src/mintTestTree.ts +0 -134
  54. package/src/mintTestUi.tsx +0 -1378
package/bin/mintTestUi.js DELETED
@@ -1,818 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
- import { Box, render, Text, useInput } from 'ink';
12
- import path from 'node:path';
13
- import { useEffect, useMemo, useRef, useState } from 'react';
14
- import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, runCodeTests, } from './agent-harness/index.js';
15
- import { readFilePreview } from './mintTestFilePreview.js';
16
- import { trackMintTestGenerationResult, trackMintTestRunCompleted, trackMintTestRunStarted, } from './mintTestTelemetry.js';
17
- import { firstLine, footerSegments, taskProblemLabel } from './mintTestText.js';
18
- import { toggleScopeSelection, visibleScopeRows } from './mintTestTree.js';
19
- const HARNESSES = [
20
- { agent: 'claude', model: 'claude-opus-4-8', label: 'Claude Opus 4.8' },
21
- { agent: 'claude', model: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
22
- { agent: 'codex', model: 'claude-opus-4-8', label: 'Codex' },
23
- ];
24
- const STEPS = [
25
- { id: 'harnesses', label: 'Harnesses' },
26
- { id: 'scope', label: 'Scope' },
27
- { id: 'check', label: 'Check testability' },
28
- { id: 'generate', label: 'Generate tests' },
29
- { id: 'run', label: 'Run tests' },
30
- { id: 'report', label: 'Report' },
31
- ];
32
- function formatTokens(tokens) {
33
- if (tokens >= 1000000)
34
- return `${(tokens / 1000000).toFixed(1)}M`;
35
- if (tokens >= 1000)
36
- return `${Math.round(tokens / 1000)}k`;
37
- return String(tokens);
38
- }
39
- function padTokens(tokens) {
40
- return formatTokens(tokens).padStart(4);
41
- }
42
- export function formatDuration(durationMs) {
43
- const seconds = Math.max(0, Math.floor(durationMs / 1000));
44
- if (seconds < 60)
45
- return `${seconds}s`;
46
- return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, '0')}s`;
47
- }
48
- function padDuration(durationMs) {
49
- return formatDuration(durationMs).padStart(7);
50
- }
51
- function padCount(value, total) {
52
- return String(value).padStart(String(total).length);
53
- }
54
- function useTerminalSize() {
55
- const [size, setSize] = useState(() => ({
56
- columns: process.stdout.columns || 100,
57
- rows: process.stdout.rows || 30,
58
- }));
59
- useEffect(() => {
60
- const resize = () => setSize({ columns: process.stdout.columns || 100, rows: process.stdout.rows || 30 });
61
- process.stdout.on('resize', resize);
62
- return () => {
63
- process.stdout.off('resize', resize);
64
- };
65
- }, []);
66
- return size;
67
- }
68
- function stepState(step, screen, activeRunStage) {
69
- const order = ['harnesses', 'scope', 'check', 'generate', 'run', 'report'];
70
- const screenStep = screen === 'resume' || screen === 'existing'
71
- ? 'harnesses'
72
- : screen === 'running' || screen === 'error'
73
- ? activeRunStage
74
- : screen;
75
- const current = order.indexOf(screenStep);
76
- const index = order.indexOf(step);
77
- if (screen === 'error' && index === current)
78
- return 'error';
79
- if (index < current || screen === 'report')
80
- return 'complete';
81
- if (index === current)
82
- return 'running';
83
- return 'pending';
84
- }
85
- function StepGlyph({ state }) {
86
- if (state === 'complete')
87
- return _jsx(Text, { color: "green", children: "\u2713" });
88
- if (state === 'running')
89
- return _jsx(Text, { color: "cyan", children: "\u25CF" });
90
- if (state === 'error')
91
- return _jsx(Text, { color: "red", children: "\u2717" });
92
- return _jsx(Text, { dimColor: true, children: "\u25CB" });
93
- }
94
- const RESUME_OPTIONS = ['Resume', 'Start fresh'];
95
- const EXISTING_RUN_OPTIONS = [
96
- 'Update tests',
97
- 'Review last test run',
98
- 'Start a brand new test',
99
- ];
100
- const EMPTY_SELECTION = new Set();
101
- function ResumeScreen({ save, cursor }) {
102
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Resume previous run?" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Run ", _jsx(Text, { color: "cyan", children: save.runId })] }), _jsxs(Text, { dimColor: true, children: [save.selectedFiles.length, " ", save.selectedFiles.length === 1 ? 'page' : 'pages', " \u00B7", ' ', save.agents.join(', ')] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: RESUME_OPTIONS.map((option, index) => (_jsxs(Text, { color: cursor === index ? 'cyan' : undefined, children: [cursor === index ? '›' : ' ', " ", option] }, option))) })] }));
103
- }
104
- function ExistingRunScreen({ report, cursor }) {
105
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Mint test has already run in this directory." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Last run ", _jsx(Text, { color: "cyan", children: report.runId }), " \u00B7", ' ', _jsx(Text, { color: report.status === 'passed'
106
- ? 'green'
107
- : report.status === 'completed'
108
- ? 'cyan'
109
- : report.status === 'cancelled'
110
- ? 'yellow'
111
- : 'red', children: report.status })] }), _jsxs(Text, { dimColor: true, children: [report.selectedFiles.length, " ", report.selectedFiles.length === 1 ? 'page' : 'pages', " \u00B7", ' ', report.model, " \u00B7 ", new Date(report.completedAt).toLocaleString()] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: EXISTING_RUN_OPTIONS.map((option, index) => (_jsxs(Text, { color: cursor === index ? 'cyan' : undefined, children: [cursor === index ? '›' : ' ', " ", option] }, option))) })] }));
112
- }
113
- function HarnessScreen({ cursor }) {
114
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Select coding harness?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: HARNESSES.map((harness, index) => (_jsxs(Text, { color: cursor === index ? 'cyan' : undefined, children: [cursor === index ? '›' : ' ', " ", cursor === index ? '(•)' : '( )', " ", harness.label] }, harness.label))) })] }));
115
- }
116
- function ScopeScreen({ rows, cursor, height, }) {
117
- const viewport = Math.max(4, height - 18);
118
- const start = Math.max(0, Math.min(cursor - Math.floor(viewport / 2), rows.length - viewport));
119
- const visible = rows.slice(start, start + viewport);
120
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginTop: 1, marginRight: 2, flexDirection: "column", children: _jsx(Text, { bold: true, children: "Select pages to test" }) }), _jsx(Text, { bold: true, color: "green", children: "enter to confirm" })] }), _jsx(Text, { dimColor: true, children: "Folders select or clear every testable page beneath them." }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: visible.map((row, visibleIndex) => {
121
- const index = start + visibleIndex;
122
- const selection = row.selection === 'all' ? '[x]' : row.selection === 'some' ? '[-]' : '[ ]';
123
- const disclosure = row.kind === 'directory' ? (row.expanded ? '▾' : '▸') : ' ';
124
- return (_jsxs(Text, { color: index === cursor ? 'cyan' : undefined, children: [' '.repeat(row.depth * 2), index === cursor ? '›' : ' ', " ", selection, " ", disclosure, " ", row.label, ' ', _jsxs(Text, { dimColor: true, children: [row.blockCount, " ", row.blockCount === 1 ? 'code block' : 'code blocks'] })] }, row.id));
125
- }) })] }));
126
- }
127
- const ACTIVE_PHASES = new Set(['checking', 'generating', 'running']);
128
- const DONE_PHASES = new Set([
129
- 'passed',
130
- 'failed',
131
- 'agent_error',
132
- 'not_testable',
133
- 'cancelled',
134
- ]);
135
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
136
- const OUTPUT_PHASES = new Set([
137
- 'generating',
138
- 'generated',
139
- 'running',
140
- 'passed',
141
- 'failed',
142
- 'agent_error',
143
- 'cancelled',
144
- ]);
145
- function generatedPathRows(tasks) {
146
- const rows = [];
147
- for (const task of tasks) {
148
- if (!OUTPUT_PHASES.has(task.phase))
149
- continue;
150
- if (task.generatedFiles.length === 0) {
151
- rows.push({
152
- id: `${task.id}:directory`,
153
- path: task.directory,
154
- previewable: false,
155
- task,
156
- });
157
- continue;
158
- }
159
- for (const file of task.generatedFiles) {
160
- rows.push({ id: `${task.id}:${file}`, path: file, previewable: true, task });
161
- }
162
- }
163
- return rows.sort((left, right) => left.path.localeCompare(right.path));
164
- }
165
- function useFilePreview(row) {
166
- const [preview, setPreview] = useState({ status: 'empty' });
167
- useEffect(() => {
168
- if (!(row === null || row === void 0 ? void 0 : row.previewable)) {
169
- setPreview({ status: 'empty' });
170
- return;
171
- }
172
- let active = true;
173
- setPreview({ status: 'loading' });
174
- void readFilePreview(row.path)
175
- .then((result) => {
176
- if (!active)
177
- return;
178
- if (result.binary) {
179
- setPreview({ status: 'error', message: 'Binary files cannot be previewed.' });
180
- return;
181
- }
182
- setPreview({
183
- status: 'loaded',
184
- lines: result.text.split(/\r?\n/),
185
- truncated: result.truncated,
186
- });
187
- })
188
- .catch((error) => {
189
- if (!active)
190
- return;
191
- setPreview({
192
- status: 'error',
193
- message: error instanceof Error ? error.message : String(error),
194
- });
195
- });
196
- return () => {
197
- active = false;
198
- };
199
- }, [row === null || row === void 0 ? void 0 : row.path, row === null || row === void 0 ? void 0 : row.previewable]);
200
- return preview;
201
- }
202
- function generatedPathStatus(phase, spinner) {
203
- if (phase === 'generating' || phase === 'running')
204
- return { glyph: spinner, color: 'green' };
205
- if (phase === 'generated' || phase === 'passed')
206
- return { glyph: '✓', color: 'green' };
207
- if (phase === 'failed' || phase === 'agent_error')
208
- return { glyph: '✗', color: 'red' };
209
- if (phase === 'cancelled')
210
- return { glyph: '●', color: 'yellow' };
211
- return { glyph: '○', dim: true };
212
- }
213
- function FilePreview({ row, height }) {
214
- const preview = useFilePreview(row);
215
- // Border (2), title, path, margin, and one row kept for the truncation notice.
216
- const lineLimit = Math.max(1, height - 6);
217
- return (_jsxs(Box, { width: "52%", height: height, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { bold: true, children: "File preview" }), row ? _jsx(Text, { dimColor: true, children: row.path }) : _jsx(Text, { dimColor: true, children: "Select a generated file." }), !(row === null || row === void 0 ? void 0 : row.previewable) ? (_jsx(Text, { dimColor: true, children: (row === null || row === void 0 ? void 0 : row.task.phase) === 'generating'
218
- ? 'Files will appear here after generation finishes.'
219
- : 'No generated file is available to preview.' })) : preview.status === 'loading' ? (_jsx(Text, { dimColor: true, children: "Loading preview..." })) : preview.status === 'error' ? (_jsx(Text, { color: "yellow", children: preview.message })) : preview.status === 'loaded' ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [preview.lines.slice(0, lineLimit).map((line, index) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { dimColor: true, children: [String(index + 1).padStart(3), " "] }), line || ' '] }, `${index}:${line}`))), preview.truncated || preview.lines.length > lineLimit ? (_jsx(Text, { dimColor: true, children: "Preview truncated" })) : null] })) : null] }));
220
- }
221
- function GeneratedFilesView({ rows, cursor, height, spinner, relativeRoot, emptyMessage = 'Waiting for generated files...', }) {
222
- const safeCursor = Math.max(0, Math.min(cursor, rows.length - 1));
223
- const viewport = Math.max(2, height - 3);
224
- const start = Math.max(0, Math.min(safeCursor - Math.floor(viewport / 2), rows.length - viewport));
225
- const visible = rows.slice(start, start + viewport);
226
- const selected = rows[safeCursor];
227
- const showPreview = (selected === null || selected === void 0 ? void 0 : selected.previewable) === true;
228
- return (_jsxs(Box, { flexDirection: "row", gap: 1, flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { width: showPreview ? '48%' : '100%', borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { bold: true, children: "Generated files" }), visible.length === 0 ? _jsx(Text, { dimColor: true, children: emptyMessage }) : null, visible.map((row, visibleIndex) => {
229
- const index = start + visibleIndex;
230
- const status = generatedPathStatus(row.task.phase, spinner);
231
- const relativePath = path.relative(relativeRoot, row.path) || '.';
232
- return (_jsxs(Text, { color: index === safeCursor ? 'cyan' : undefined, wrap: "truncate-end", children: [index === safeCursor ? '›' : ' ', ' ', _jsx(Text, { color: status.color, dimColor: status.dim, children: status.glyph }), ' ', row.previewable
233
- ? row.path
234
- : row.task.phase === 'generating'
235
- ? `Writing under ${relativePath}`
236
- : `No generated file in ${relativePath}`, row.task.phase === 'failed' || row.task.phase === 'agent_error' ? (_jsxs(Text, { color: "red", children: [' ', taskProblemLabel(row.task.phase), row.task.error ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", firstLine(row.task.error)] }) : null] })) : null] }, row.id));
237
- })] }), showPreview ? _jsx(FilePreview, { row: selected, height: height }) : null] }));
238
- }
239
- function fileStatus(row, spinner) {
240
- if (row && ACTIVE_PHASES.has(row.phase))
241
- return { glyph: spinner, color: 'green' };
242
- if ((row === null || row === void 0 ? void 0 : row.phase) === 'failed' || (row === null || row === void 0 ? void 0 : row.phase) === 'agent_error')
243
- return { glyph: '✗', color: 'red' };
244
- if ((row === null || row === void 0 ? void 0 : row.phase) === 'checked' || (row === null || row === void 0 ? void 0 : row.phase) === 'generated' || (row === null || row === void 0 ? void 0 : row.phase) === 'passed') {
245
- return { glyph: '✓', color: 'green' };
246
- }
247
- if ((row === null || row === void 0 ? void 0 : row.phase) === 'not_testable' || (row === null || row === void 0 ? void 0 : row.phase) === 'cancelled') {
248
- return { glyph: '●', color: 'yellow' };
249
- }
250
- return { glyph: '○', dim: true };
251
- }
252
- const SETTLED_PHASES = new Set([...DONE_PHASES, 'checked', 'generated']);
253
- function directoryStatus(files, phases, spinner) {
254
- const rows = files.map((file) => phases.get(file));
255
- if (rows.some((row) => row && ACTIVE_PHASES.has(row.phase))) {
256
- return { glyph: spinner, color: 'green' };
257
- }
258
- if (rows.some((row) => (row === null || row === void 0 ? void 0 : row.phase) === 'failed' || (row === null || row === void 0 ? void 0 : row.phase) === 'agent_error')) {
259
- return { glyph: '✗', color: 'red' };
260
- }
261
- if (rows.every((row) => row && SETTLED_PHASES.has(row.phase))) {
262
- if (rows.every((row) => (row === null || row === void 0 ? void 0 : row.phase) === 'not_testable' || (row === null || row === void 0 ? void 0 : row.phase) === 'cancelled')) {
263
- return { glyph: '●', color: 'yellow' };
264
- }
265
- return { glyph: '✓', color: 'green' };
266
- }
267
- return { glyph: '○', dim: true };
268
- }
269
- function ActivityLegend({ spinner }) {
270
- return (_jsxs(Text, { dimColor: true, children: ["\u25CB waiting \u00B7 ", _jsx(Text, { color: "green", children: spinner }), " active \u00B7 ", _jsx(Text, { color: "green", children: "\u2713" }), ' ', "complete \u00B7 ", _jsx(Text, { color: "red", children: "\u2717" }), " failed \u00B7 ", _jsx(Text, { color: "yellow", children: "\u25CF" }), " skipped / cancelled"] }));
271
- }
272
- function ActivityScreen({ stage, cancelling, harness, docsRoot, rows, generatedRows, cursor, phases, height, totalTasks, spinnerFrame, runStartedAt, now, }) {
273
- var _a;
274
- const spinner = (_a = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length]) !== null && _a !== void 0 ? _a : '⠋';
275
- const updates = [...phases.values()];
276
- const message = cancelling
277
- ? 'Cancelling agent and test processes'
278
- : stage === 'check'
279
- ? 'Checking which code blocks can be tested'
280
- : stage === 'generate'
281
- ? 'Agents are generating test projects'
282
- : 'Running generated tests';
283
- const done = updates.filter((update) => DONE_PHASES.has(update.phase)).length;
284
- const notTestable = updates.filter((update) => update.phase === 'not_testable').length;
285
- const created = updates.filter((update) => ['generated', 'running', 'passed', 'failed'].includes(update.phase)).length;
286
- const tokens = updates.reduce((total, update) => total + update.tokens, 0);
287
- const checked = updates.filter((update) => update.phase !== 'checking').length;
288
- const progress = stage === 'check'
289
- ? `${padCount(checked, totalTasks)}/${totalTasks} pages checked`
290
- : stage === 'generate'
291
- ? `${padCount(created, totalTasks - notTestable)}/${totalTasks - notTestable} tests created`
292
- : `${padCount(done - notTestable, totalTasks - notTestable)}/${totalTasks - notTestable} tasks finished`;
293
- const viewport = Math.max(4, height - 13);
294
- const start = Math.max(0, Math.min(cursor - Math.floor(viewport / 2), rows.length - viewport));
295
- const visible = rows.slice(start, start + viewport);
296
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [padDuration(now - runStartedAt), " ", _jsxs(Text, { dimColor: true, children: [padTokens(tokens), " tokens"] })] }), _jsxs(Text, { color: "green", children: [_jsx(Text, { children: spinner }), " ", message] }), _jsxs(Text, { dimColor: true, children: ["Harness: ", harness] }), _jsx(Text, { dimColor: true, children: progress }), stage === 'check' || stage === 'generate' ? _jsx(ActivityLegend, { spinner: spinner }) : null] }), _jsx(Box, { marginTop: 1, flexGrow: 1, overflow: "hidden", children: stage === 'check' ? (_jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", flexGrow: 1, children: visible.map((row, visibleIndex) => {
297
- var _a;
298
- const index = start + visibleIndex;
299
- const disclosure = row.kind === 'directory' ? (row.expanded ? '▾' : '▸') : ' ';
300
- const status = row.kind === 'directory'
301
- ? directoryStatus(row.selectableFiles, phases, spinner)
302
- : fileStatus(row.file ? phases.get(row.file) : undefined, spinner);
303
- const task = row.file ? phases.get(row.file) : undefined;
304
- const settled = row.kind === 'directory'
305
- ? row.selectableFiles.filter((file) => {
306
- var _a;
307
- const phase = (_a = phases.get(file)) === null || _a === void 0 ? void 0 : _a.phase;
308
- return phase !== undefined && SETTLED_PHASES.has(phase);
309
- }).length
310
- : 0;
311
- return (_jsxs(Text, { color: index === cursor ? 'cyan' : undefined, wrap: "truncate-end", children: [' '.repeat(row.depth * 2), index === cursor ? '›' : ' ', ' ', _jsx(Text, { color: status.color, dimColor: status.dim, children: status.glyph }), ' ', disclosure, " ", row.label, row.kind === 'directory' ? (_jsxs(Text, { dimColor: true, children: [' ', padCount(settled, row.selectableFiles.length), "/", row.selectableFiles.length, ' ', "complete"] })) : null, task ? (_jsxs(Text, { dimColor: true, children: [" ", padDuration(((_a = task.finishedAt) !== null && _a !== void 0 ? _a : now) - task.startedAt)] })) : null, task && (task.phase === 'failed' || task.phase === 'agent_error') ? (_jsxs(Text, { color: "red", children: [' ', taskProblemLabel(task.phase), task.error ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", firstLine(task.error)] }) : null] })) : null] }, row.id));
312
- }) })) : (_jsx(GeneratedFilesView, { rows: generatedRows, cursor: cursor, height: Math.max(4, height - 7), spinner: spinner, relativeRoot: docsRoot })) })] }));
313
- }
314
- const MAX_PROBLEM_ROWS = 6;
315
- function ProblemsView({ report }) {
316
- const problems = report.tasks.filter((task) => task.status === 'failed' || task.status === 'agent_error');
317
- if (problems.length === 0)
318
- return null;
319
- const hidden = problems.length - MAX_PROBLEM_ROWS;
320
- return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", flexShrink: 0, children: [_jsxs(Text, { bold: true, color: "red", children: [problems.length, " ", problems.length === 1 ? 'page needs' : 'pages need', " attention"] }), problems.slice(0, MAX_PROBLEM_ROWS).map((task) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "red", children: "\u2717" }), " ", task.file, ' ', _jsx(Text, { dimColor: true, children: taskProblemLabel(task.status) }), task.error ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", firstLine(task.error)] }) : null] }, task.id))), hidden > 0 ? _jsxs(Text, { dimColor: true, children: ["+", hidden, " more in the report"] }) : null] }));
321
- }
322
- /**
323
- * Rows the report header takes, including its border and the margin below it. The optional
324
- * lines must be counted, or the screen is one row taller than the terminal and Ink drops the
325
- * heading (it showed as "Finished in 1m 37s passed").
326
- */
327
- function reportHeaderHeight(report, rows) {
328
- const optionalLines = (report.historyError ? 1 : 0) + (rows.some((row) => row.previewable) ? 1 : 0);
329
- return 2 + 4 + optionalLines + 1;
330
- }
331
- function problemsViewHeight(report) {
332
- const problems = report.tasks.filter((task) => task.status === 'failed' || task.status === 'agent_error').length;
333
- if (problems === 0)
334
- return 0;
335
- return Math.min(problems, MAX_PROBLEM_ROWS) + 4 + (problems > MAX_PROBLEM_ROWS ? 1 : 0);
336
- }
337
- function ReportScreen({ report, rows, cursor, height, }) {
338
- const statusColor = report.status === 'passed'
339
- ? 'green'
340
- : report.status === 'completed'
341
- ? 'cyan'
342
- : report.status === 'cancelled'
343
- ? 'yellow'
344
- : 'red';
345
- const heading = report.status === 'passed'
346
- ? 'All runnable tests passed'
347
- : report.status === 'completed'
348
- ? 'Run completed'
349
- : `Run ${report.status}`;
350
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", flexShrink: 0, children: [_jsx(Text, { bold: true, color: statusColor, children: heading }), _jsxs(Text, { children: ["Finished in ", _jsx(Text, { bold: true, children: formatDuration(report.durationMs) })] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Report at ", report.reportPath] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Generated files at ", report.outputDirectory] }), report.historyError ? (_jsxs(Text, { color: "yellow", wrap: "truncate-end", children: ["Warning: ", report.historyError] })) : null, rows.some((row) => row.previewable) ? (_jsx(Text, { children: "Select a file below to preview its contents." })) : null] }), _jsx(ProblemsView, { report: report }), _jsx(Box, { marginTop: 1, flexGrow: 1, overflow: "hidden", children: _jsx(GeneratedFilesView, { rows: rows, cursor: cursor, height: Math.max(4, height - reportHeaderHeight(report, rows) - problemsViewHeight(report)), spinner: "\u25CF", relativeRoot: report.docsRoot, emptyMessage: "No generated files." }) })] }));
351
- }
352
- function FooterHints({ text }) {
353
- return (_jsx(_Fragment, { children: footerSegments(text).map((segment, index) => (_jsxs(Text, { children: [index > 0 ? ' · ' : '', _jsx(Text, { bold: true, children: segment.key }), segment.action ? ` ${segment.action}` : ''] }, `${index}:${segment.key}`))) }));
354
- }
355
- function footerForScreen(screen, running, cancelled, activeRunStage, reviewingSavedRun) {
356
- if (running) {
357
- return activeRunStage === 'check'
358
- ? '↑/↓/j/k move · ←/→/h/l collapse/expand · Ctrl-C cancel'
359
- : '↑/↓/j/k preview generated files · Ctrl-C cancel';
360
- }
361
- if (screen === 'resume')
362
- return '↑/↓/j/k move · Enter confirm · Ctrl-C cancel';
363
- if (screen === 'existing')
364
- return '↑/↓/j/k move · Enter confirm · Ctrl-C cancel';
365
- if (screen === 'harnesses')
366
- return '↑/↓/j/k move · Enter continue · Ctrl-C cancel';
367
- if (screen === 'scope') {
368
- return '↑/↓/j/k move · ←/→/h/l collapse/expand · Space select · a all · Enter start';
369
- }
370
- if (screen === 'report') {
371
- if (reviewingSavedRun)
372
- return '↑/↓/j/k preview generated files · Enter exit';
373
- return cancelled
374
- ? '↑/↓/j/k preview generated files · Ctrl-C exit · save kept for resume'
375
- : '↑/↓/j/k preview generated files · Enter exit';
376
- }
377
- return 'Enter exit';
378
- }
379
- function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, totalPages, save, lastReport, onFinish, }) {
380
- var _a;
381
- const size = useTerminalSize();
382
- const files = useMemo(() => pages.map((page) => page.file), [pages]);
383
- const outputDirectory = path.join(docsRoot, 'tests', 'mint-test');
384
- const [screen, setScreen] = useState(save ? 'resume' : lastReport ? 'existing' : 'harnesses');
385
- const [resumeCursor, setResumeCursor] = useState(0);
386
- const [existingRunCursor, setExistingRunCursor] = useState(0);
387
- const [harnessIndex, setHarnessIndex] = useState(0);
388
- const [runHarness, setRunHarness] = useState('');
389
- const harness = (_a = HARNESSES[harnessIndex]) !== null && _a !== void 0 ? _a : HARNESSES[0];
390
- const [selectedFiles, setSelectedFiles] = useState(() => new Set(autoSelectedFiles));
391
- const [expandedDirectories, setExpandedDirectories] = useState(new Set());
392
- const [scopeCursor, setScopeCursor] = useState(0);
393
- const [activeRunStage, setActiveRunStage] = useState('check');
394
- const [cancelling, setCancelling] = useState(false);
395
- const [ctrlCArmed, setCtrlCArmed] = useState(false);
396
- const ctrlCTimer = useRef(undefined);
397
- const [taskPhases, setTaskPhases] = useState(new Map());
398
- const [totalTasks, setTotalTasks] = useState(0);
399
- const [runCursor, setRunCursor] = useState(0);
400
- const [runExpanded, setRunExpanded] = useState(new Set());
401
- const [runFiles, setRunFiles] = useState([]);
402
- const [runStartedAt, setRunStartedAt] = useState(0);
403
- const autoCollapsed = useRef(new Set());
404
- const manualCursor = useRef(false);
405
- const previousRunStage = useRef(activeRunStage);
406
- const [spinnerFrame, setSpinnerFrame] = useState(0);
407
- const [now, setNow] = useState(() => Date.now());
408
- const [report, setReport] = useState();
409
- const [reviewingSavedRun, setReviewingSavedRun] = useState(false);
410
- const [error, setError] = useState();
411
- const abortController = useRef(undefined);
412
- const finished = useRef(false);
413
- const rows = useMemo(() => visibleScopeRows(pages, expandedDirectories, selectedFiles), [pages, expandedDirectories, selectedFiles]);
414
- const phasesByFile = useMemo(() => new Map([...taskPhases.values()].map((row) => [row.file, row])), [taskPhases]);
415
- const visibleRunFiles = useMemo(() => runFiles.filter((file) => { var _a; return activeRunStage === 'check' || ((_a = phasesByFile.get(file)) === null || _a === void 0 ? void 0 : _a.phase) !== 'not_testable'; }), [runFiles, activeRunStage, phasesByFile]);
416
- const runRows = useMemo(() => {
417
- const included = new Set(visibleRunFiles);
418
- return visibleScopeRows(pages.filter((page) => included.has(page.file)), runExpanded, EMPTY_SELECTION);
419
- }, [pages, visibleRunFiles, runExpanded]);
420
- const generatedRows = useMemo(() => generatedPathRows(taskPhases.values()), [taskPhases]);
421
- useEffect(() => {
422
- if (screen !== 'running')
423
- return;
424
- const timer = setInterval(() => setSpinnerFrame((frame) => frame + 1), 120);
425
- const clock = setInterval(() => setNow(Date.now()), 1000);
426
- return () => {
427
- clearInterval(timer);
428
- clearInterval(clock);
429
- };
430
- }, [screen]);
431
- useEffect(() => {
432
- var _a;
433
- if (screen !== 'running' || activeRunStage !== 'check')
434
- return;
435
- const fileDone = (file) => {
436
- var _a;
437
- const phase = (_a = phasesByFile.get(file)) === null || _a === void 0 ? void 0 : _a.phase;
438
- return phase !== undefined && DONE_PHASES.has(phase);
439
- };
440
- const directoryFiles = new Map();
441
- for (const file of visibleRunFiles) {
442
- const parts = file.split('/');
443
- let currentPath = '';
444
- for (const part of parts.slice(0, -1)) {
445
- currentPath = currentPath ? `${currentPath}/${part}` : part;
446
- const id = `dir:${currentPath}`;
447
- directoryFiles.set(id, [...((_a = directoryFiles.get(id)) !== null && _a !== void 0 ? _a : []), file]);
448
- }
449
- }
450
- const toCollapse = [...directoryFiles.entries()]
451
- .filter(([id, files]) => !autoCollapsed.current.has(id) && files.every(fileDone))
452
- .map(([id]) => id);
453
- if (toCollapse.length > 0) {
454
- for (const id of toCollapse)
455
- autoCollapsed.current.add(id);
456
- setRunExpanded((current) => {
457
- const next = new Set(current);
458
- for (const id of toCollapse)
459
- next.delete(id);
460
- return next;
461
- });
462
- }
463
- const rowDone = (row) => {
464
- const files = row.kind === 'directory' ? row.selectableFiles : row.file ? [row.file] : [];
465
- return files.length > 0 && files.every(fileDone);
466
- };
467
- if (manualCursor.current)
468
- return;
469
- const current = runRows[runCursor];
470
- if (runRows.length > 0 && (!current || rowDone(current))) {
471
- const nextIndex = runRows.findIndex((row) => !rowDone(row));
472
- if (nextIndex !== -1 && nextIndex !== runCursor)
473
- setRunCursor(nextIndex);
474
- }
475
- }, [screen, activeRunStage, phasesByFile, visibleRunFiles, runRows, runCursor]);
476
- useEffect(() => {
477
- if (previousRunStage.current === activeRunStage)
478
- return;
479
- previousRunStage.current = activeRunStage;
480
- manualCursor.current = false;
481
- setRunCursor(0);
482
- }, [activeRunStage]);
483
- const finish = (exitCode) => {
484
- if (finished.current)
485
- return;
486
- finished.current = true;
487
- onFinish(exitCode);
488
- };
489
- const reviewRun = (savedReport) => {
490
- var _a, _b;
491
- const startedAt = Date.parse(savedReport.startedAt);
492
- const finishedAt = Date.parse(savedReport.completedAt);
493
- setTaskPhases(new Map(savedReport.tasks.map((task) => [
494
- task.id,
495
- {
496
- id: task.id,
497
- agent: task.agent,
498
- file: task.file,
499
- directory: task.directory,
500
- generatedFiles: task.generatedFiles,
501
- phase: !task.testable && task.status === 'passed' ? 'not_testable' : task.status,
502
- tokens: task.tokens,
503
- startedAt: Number.isNaN(startedAt) ? 0 : startedAt,
504
- finishedAt: Number.isNaN(finishedAt) ? undefined : finishedAt,
505
- },
506
- ])));
507
- setRunFiles(savedReport.selectedFiles);
508
- setRunHarness((_b = (_a = HARNESSES.find((entry) => entry.agent === savedReport.agents[0] && entry.model === savedReport.model)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : `${savedReport.agents.join(', ')} · ${savedReport.model}`);
509
- setReport(savedReport);
510
- setReviewingSavedRun(true);
511
- setActiveRunStage('run');
512
- setRunCursor(0);
513
- setScreen('report');
514
- };
515
- const startRun = ({ runId, runAgents, runModel, runFiles: runFiles_, }) => {
516
- var _a, _b;
517
- if (abortController.current)
518
- return;
519
- const resumed = runId !== null;
520
- const controller = new AbortController();
521
- abortController.current = controller;
522
- setTaskPhases(new Map());
523
- setReport(undefined);
524
- setReviewingSavedRun(false);
525
- setError(undefined);
526
- setTotalTasks(runAgents.length * runFiles_.length);
527
- setRunHarness((_b = (_a = HARNESSES.find((entry) => entry.agent === runAgents[0] && entry.model === runModel)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : `${runAgents.join(', ')} · ${runModel}`);
528
- setRunCursor(0);
529
- autoCollapsed.current = new Set();
530
- manualCursor.current = false;
531
- setRunStartedAt(Date.now());
532
- setNow(Date.now());
533
- setRunFiles(runFiles_);
534
- setRunExpanded(new Set(runFiles_.flatMap((file) => {
535
- const parts = file.split('/').slice(0, -1);
536
- return parts.map((_, index) => `dir:${parts.slice(0, index + 1).join('/')}`);
537
- })));
538
- setScreen('running');
539
- setActiveRunStage('check');
540
- void trackMintTestRunStarted({
541
- mode: 'interactive',
542
- resumed,
543
- agents: runAgents,
544
- model: runModel,
545
- selectedPageCount: new Set(runFiles_).size,
546
- totalPageCount: totalPages,
547
- });
548
- void runCodeTests({
549
- path: targetPath,
550
- agents: runAgents,
551
- model: runModel,
552
- selectedFiles: runFiles_,
553
- outputDirectory,
554
- runId,
555
- concurrency: DEFAULT_CONCURRENCY,
556
- commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
557
- signal: controller.signal,
558
- onGenerationResult: (result) => {
559
- void trackMintTestGenerationResult(Object.assign(Object.assign({}, result), { mode: 'interactive', resumed }));
560
- },
561
- onTaskUpdate: (update) => {
562
- const phaseStage = update.phase === 'running'
563
- ? 'run'
564
- : update.phase === 'generating' || update.phase === 'generated'
565
- ? 'generate'
566
- : null;
567
- if (phaseStage) {
568
- setActiveRunStage((stage) => {
569
- const order = ['check', 'generate', 'run'];
570
- return order.indexOf(phaseStage) > order.indexOf(stage) ? phaseStage : stage;
571
- });
572
- }
573
- const now = Date.now();
574
- setTaskPhases((current) => {
575
- var _a;
576
- const previous = current.get(update.id);
577
- return new Map(current).set(update.id, Object.assign(Object.assign(Object.assign({}, update), { startedAt: (_a = previous === null || previous === void 0 ? void 0 : previous.startedAt) !== null && _a !== void 0 ? _a : now }), (ACTIVE_PHASES.has(update.phase) ? {} : { finishedAt: now })));
578
- });
579
- },
580
- })
581
- .then((nextReport) => {
582
- void trackMintTestRunCompleted(nextReport, {
583
- mode: 'interactive',
584
- resumed,
585
- totalPageCount: totalPages,
586
- });
587
- setReport(nextReport);
588
- const failedFiles = nextReport.tasks
589
- .filter((task) => task.status === 'failed' || task.status === 'agent_error')
590
- .map((task) => task.file);
591
- setRunExpanded(new Set(failedFiles.flatMap((file) => {
592
- const parts = file.split('/').slice(0, -1);
593
- return parts.map((_, index) => `dir:${parts.slice(0, index + 1).join('/')}`);
594
- })));
595
- setRunCursor(0);
596
- setScreen('report');
597
- })
598
- .catch((runError) => {
599
- if (controller.signal.aborted) {
600
- finish(130);
601
- return;
602
- }
603
- setError(runError instanceof Error ? runError.message : String(runError));
604
- setScreen('error');
605
- });
606
- };
607
- useInput((input, key) => {
608
- if (key.ctrl && input === 'c') {
609
- if (ctrlCArmed) {
610
- if (abortController.current) {
611
- abortController.current.abort();
612
- setCancelling(true);
613
- }
614
- finish(130);
615
- }
616
- else {
617
- setCtrlCArmed(true);
618
- if (ctrlCTimer.current)
619
- clearTimeout(ctrlCTimer.current);
620
- ctrlCTimer.current = setTimeout(() => setCtrlCArmed(false), 2000);
621
- }
622
- return;
623
- }
624
- if (screen === 'running' || screen === 'report') {
625
- const collapse = key.leftArrow || input === 'h';
626
- const expand = key.rightArrow || input === 'l';
627
- const showingGeneratedFiles = screen === 'report' || activeRunStage !== 'check';
628
- const rowCount = showingGeneratedFiles ? generatedRows.length : runRows.length;
629
- if (key.upArrow || input === 'k') {
630
- manualCursor.current = true;
631
- setRunCursor((current) => Math.max(0, current - 1));
632
- }
633
- else if (key.downArrow || input === 'j') {
634
- manualCursor.current = true;
635
- setRunCursor((current) => Math.max(0, Math.min(rowCount - 1, current + 1)));
636
- }
637
- else if ((collapse || expand) && !showingGeneratedFiles) {
638
- manualCursor.current = true;
639
- const row = runRows[runCursor];
640
- if ((row === null || row === void 0 ? void 0 : row.kind) !== 'directory')
641
- return;
642
- setRunExpanded((current) => {
643
- const next = new Set(current);
644
- if (collapse)
645
- next.delete(row.id);
646
- else
647
- next.add(row.id);
648
- return next;
649
- });
650
- }
651
- else if (screen === 'report' && key.return && report) {
652
- finish(reviewingSavedRun
653
- ? 0
654
- : report.status === 'passed'
655
- ? 0
656
- : report.status === 'cancelled'
657
- ? 130
658
- : 1);
659
- }
660
- return;
661
- }
662
- if (screen === 'existing' && lastReport) {
663
- if (key.upArrow || input === 'k') {
664
- setExistingRunCursor((current) => (current + EXISTING_RUN_OPTIONS.length - 1) % EXISTING_RUN_OPTIONS.length);
665
- }
666
- else if (key.downArrow || input === 'j') {
667
- setExistingRunCursor((current) => (current + 1) % EXISTING_RUN_OPTIONS.length);
668
- }
669
- else if (key.return) {
670
- if (existingRunCursor === 0) {
671
- const knownFiles = new Set(files);
672
- const updateFiles = lastReport.selectedFiles.filter((file) => knownFiles.has(file));
673
- setSelectedFiles(new Set(updateFiles.length > 0 ? updateFiles : autoSelectedFiles));
674
- if (updateFiles.length === 0) {
675
- const previousHarness = HARNESSES.findIndex((entry) => entry.agent === lastReport.agents[0] && entry.model === lastReport.model);
676
- if (previousHarness !== -1)
677
- setHarnessIndex(previousHarness);
678
- setScreen('scope');
679
- }
680
- else {
681
- startRun({
682
- runId: null,
683
- runAgents: lastReport.agents,
684
- runModel: lastReport.model,
685
- runFiles: updateFiles,
686
- });
687
- }
688
- }
689
- else if (existingRunCursor === 1) {
690
- reviewRun(lastReport);
691
- }
692
- else {
693
- setSelectedFiles(new Set(autoSelectedFiles));
694
- setScreen('harnesses');
695
- }
696
- }
697
- return;
698
- }
699
- if (screen === 'resume' && save) {
700
- if (key.upArrow || input === 'k' || key.downArrow || input === 'j') {
701
- setResumeCursor((current) => (current + 1) % RESUME_OPTIONS.length);
702
- }
703
- else if (key.return) {
704
- if (resumeCursor === 0) {
705
- setSelectedFiles(new Set(save.selectedFiles));
706
- startRun({
707
- runId: save.runId,
708
- runAgents: save.agents,
709
- runModel: save.model,
710
- runFiles: save.selectedFiles,
711
- });
712
- }
713
- else {
714
- void clearRunSave(outputDirectory);
715
- setScreen('harnesses');
716
- }
717
- }
718
- return;
719
- }
720
- if (screen === 'harnesses') {
721
- if (key.upArrow || input === 'k')
722
- setHarnessIndex((current) => (current + HARNESSES.length - 1) % HARNESSES.length);
723
- else if (key.downArrow || input === 'j')
724
- setHarnessIndex((current) => (current + 1) % HARNESSES.length);
725
- else if (key.return)
726
- setScreen('scope');
727
- return;
728
- }
729
- if (screen === 'scope') {
730
- const collapse = key.leftArrow || input === 'h';
731
- const expand = key.rightArrow || input === 'l';
732
- if (key.upArrow || input === 'k')
733
- setScopeCursor((current) => Math.max(0, current - 1));
734
- else if (key.downArrow || input === 'j')
735
- setScopeCursor((current) => Math.max(0, Math.min(rows.length - 1, current + 1)));
736
- else if (collapse || expand) {
737
- const row = rows[scopeCursor];
738
- if ((row === null || row === void 0 ? void 0 : row.kind) !== 'directory')
739
- return;
740
- setExpandedDirectories((current) => {
741
- const next = new Set(current);
742
- if (collapse)
743
- next.delete(row.id);
744
- else
745
- next.add(row.id);
746
- return next;
747
- });
748
- }
749
- else if (input === ' ') {
750
- const row = rows[scopeCursor];
751
- if (row)
752
- setSelectedFiles((current) => toggleScopeSelection(current, row));
753
- }
754
- else if (input.toLowerCase() === 'a') {
755
- setSelectedFiles((current) => (current.size === files.length ? new Set() : new Set(files)));
756
- }
757
- else if (key.return && (selectedFiles.size > 0 || files.length === 0)) {
758
- startRun({
759
- runId: null,
760
- runAgents: [harness.agent],
761
- runModel: harness.model,
762
- runFiles: [...selectedFiles],
763
- });
764
- }
765
- return;
766
- }
767
- if (screen === 'error' && key.return) {
768
- finish(1);
769
- }
770
- });
771
- const leftWidth = size.columns >= 90 ? 28 : 24;
772
- const viewHeight = size.rows - 1;
773
- const running = screen === 'running';
774
- const fullBleed = running || (screen === 'report' && report !== undefined);
775
- let content;
776
- if (screen === 'resume' && save)
777
- content = _jsx(ResumeScreen, { save: save, cursor: resumeCursor });
778
- else if (screen === 'existing' && lastReport) {
779
- content = _jsx(ExistingRunScreen, { report: lastReport, cursor: existingRunCursor });
780
- }
781
- else if (screen === 'harnesses') {
782
- content = _jsx(HarnessScreen, { cursor: harnessIndex });
783
- }
784
- else if (screen === 'scope') {
785
- content = _jsx(ScopeScreen, { rows: rows, cursor: scopeCursor, height: viewHeight });
786
- }
787
- else if (screen === 'report' && report) {
788
- content = (_jsx(ReportScreen, { report: report, rows: generatedRows, cursor: runCursor, height: viewHeight }));
789
- }
790
- else if (screen === 'error') {
791
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", bold: true, children: "Mint test stopped" }), _jsx(Text, { children: error !== null && error !== void 0 ? error : 'Unknown error' })] }));
792
- }
793
- else {
794
- content = (_jsx(ActivityScreen, { stage: activeRunStage, cancelling: cancelling, harness: runHarness, docsRoot: docsRoot, rows: runRows, generatedRows: generatedRows, cursor: runCursor, phases: phasesByFile, height: viewHeight, totalTasks: totalTasks, spinnerFrame: spinnerFrame, runStartedAt: runStartedAt, now: now }));
795
- }
796
- return (_jsxs(Box, { flexDirection: "column", width: size.columns, height: viewHeight, overflow: "hidden", children: [_jsxs(Box, { flexGrow: 1, gap: 1, overflow: "hidden", children: [_jsxs(Box, { width: leftWidth, flexShrink: 0, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Steps" }), STEPS.map((step) => {
797
- const state = stepState(step.id, screen, activeRunStage);
798
- return (_jsxs(Text, { color: state === 'running' ? 'cyan' : undefined, children: [_jsx(StepGlyph, { state: state }), " ", step.label] }, step.id));
799
- })] }), _jsx(Box, { flexGrow: 1, borderStyle: fullBleed ? undefined : 'round', borderColor: "gray", paddingX: fullBleed ? 0 : 1, flexDirection: "column", overflow: "hidden", children: content })] }), _jsx(Text, { dimColor: true, children: ctrlCArmed ? (_jsx(FooterHints, { text: "Ctrl-C again to exit" })) : (_jsx(FooterHints, { text: footerForScreen(screen, running, (report === null || report === void 0 ? void 0 : report.status) === 'cancelled', activeRunStage, reviewingSavedRun) })) })] }));
800
- }
801
- export function runMintTestUi(options) {
802
- return __awaiter(this, void 0, void 0, function* () {
803
- process.stdout.write('\u001B[?1049h\u001B[?25l');
804
- let instance;
805
- try {
806
- return yield new Promise((resolve) => {
807
- instance = render(_jsx(MintTestApp, Object.assign({}, options, { onFinish: resolve })), {
808
- exitOnCtrlC: false,
809
- patchConsole: false,
810
- });
811
- });
812
- }
813
- finally {
814
- instance === null || instance === void 0 ? void 0 : instance.unmount();
815
- process.stdout.write('\u001B[?25h\u001B[?1049l');
816
- }
817
- });
818
- }