@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
@@ -1,255 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import fs from 'node:fs/promises';
3
- import type { FileHandle } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { setTimeout as delay } from 'node:timers/promises';
6
- import { z } from 'zod';
7
-
8
- import { countReport } from './countReport.js';
9
- import type { CodeTestReport, TestRunSummary, TestRunsIndex } from './types.js';
10
-
11
- const RUN_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
12
- const INDEX_LOCK_FILENAME = 'testRuns.lock';
13
- const LOCK_RETRY_MS = 20;
14
- const STALE_LOCK_MS = 30_000;
15
- const LOCK_TIMEOUT_MS = STALE_LOCK_MS + 10_000;
16
- const commandResultSchema = z
17
- .object({
18
- id: z.string(),
19
- phase: z.enum(['setup', 'test']),
20
- status: z.enum(['passed', 'failed', 'cancelled']),
21
- executable: z.string(),
22
- args: z.array(z.string()),
23
- cwd: z.string(),
24
- durationMs: z.number().nonnegative(),
25
- exitCode: z.number().int().nullable(),
26
- stdout: z.string(),
27
- stderr: z.string(),
28
- error: z.string().optional(),
29
- })
30
- .strict();
31
- const taskResultSchema = z
32
- .object({
33
- id: z.string(),
34
- agent: z.enum(['claude', 'codex']),
35
- file: z.string(),
36
- directory: z.string(),
37
- generatedFiles: z.array(z.string()),
38
- testable: z.boolean(),
39
- attempts: z.number().int().nonnegative(),
40
- tokens: z.number().nonnegative(),
41
- status: z.enum(['passed', 'failed', 'agent_error', 'cancelled']),
42
- error: z.string().optional(),
43
- commands: z.array(commandResultSchema),
44
- })
45
- .strict();
46
- const codeTestReportSchema = z
47
- .object({
48
- version: z.literal(1),
49
- runId: z.string().regex(RUN_ID_PATTERN),
50
- status: z.enum(['passed', 'completed', 'failed', 'cancelled']),
51
- docsRoot: z.string(),
52
- outputDirectory: z.string(),
53
- reportPath: z.string(),
54
- agents: z.array(z.enum(['claude', 'codex'])).min(1),
55
- model: z.string().min(1),
56
- selectedFiles: z.array(z.string()),
57
- startedAt: z.string(),
58
- completedAt: z.string(),
59
- durationMs: z.number().nonnegative(),
60
- tasks: z.array(taskResultSchema),
61
- historyError: z.string().optional(),
62
- })
63
- .strict();
64
- const countsSchema = z
65
- .object({
66
- passed: z.number().int().nonnegative(),
67
- failed: z.number().int().nonnegative(),
68
- cancelled: z.number().int().nonnegative(),
69
- agentErrors: z.number().int().nonnegative(),
70
- })
71
- .strict();
72
- const testRunSummarySchema = z
73
- .object({
74
- runId: z.string().regex(RUN_ID_PATTERN),
75
- status: z.enum(['passed', 'completed', 'failed', 'cancelled']),
76
- agents: z.array(z.enum(['claude', 'codex'])).min(1),
77
- model: z.string().min(1),
78
- selectedFiles: z.array(z.string()),
79
- startedAt: z.string(),
80
- completedAt: z.string(),
81
- durationMs: z.number().nonnegative(),
82
- reportPath: z.string(),
83
- outputDirectory: z.string(),
84
- counts: countsSchema,
85
- })
86
- .strict();
87
- const testRunsIndexSchema = z
88
- .object({ version: z.literal(1), runs: z.array(testRunSummarySchema) })
89
- .strict();
90
-
91
- function historyDirectory(docsRoot: string): string {
92
- return path.join(docsRoot, '.mintlify', 'test');
93
- }
94
-
95
- function runsDirectory(docsRoot: string): string {
96
- return path.join(historyDirectory(docsRoot), 'runs');
97
- }
98
-
99
- export function testRunReportPath(docsRoot: string, runId: string): string {
100
- if (!RUN_ID_PATTERN.test(runId)) throw new Error(`Invalid mint test run ID: ${runId}`);
101
- return path.join(runsDirectory(docsRoot), `${runId}.json`);
102
- }
103
-
104
- export function testRunsIndexPath(docsRoot: string): string {
105
- return path.join(historyDirectory(docsRoot), 'testRuns.json');
106
- }
107
-
108
- function summarizeReport(report: CodeTestReport): TestRunSummary {
109
- return {
110
- runId: report.runId,
111
- status: report.status,
112
- agents: report.agents,
113
- model: report.model,
114
- selectedFiles: report.selectedFiles,
115
- startedAt: report.startedAt,
116
- completedAt: report.completedAt,
117
- durationMs: report.durationMs,
118
- reportPath: report.reportPath,
119
- outputDirectory: report.outputDirectory,
120
- counts: countReport(report.tasks),
121
- };
122
- }
123
-
124
- function errorCode(error: unknown): string | undefined {
125
- return typeof error === 'object' && error !== null && 'code' in error
126
- ? String(error.code)
127
- : undefined;
128
- }
129
-
130
- async function writeJsonAtomically(filePath: string, value: unknown): Promise<void> {
131
- const temporaryPath = path.join(
132
- path.dirname(filePath),
133
- `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
134
- );
135
- try {
136
- await fs.writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' });
137
- await fs.rename(temporaryPath, filePath);
138
- } finally {
139
- await fs.unlink(temporaryPath).catch(() => undefined);
140
- }
141
- }
142
-
143
- async function acquireIndexLock(docsRoot: string): Promise<() => Promise<void>> {
144
- const lockPath = path.join(historyDirectory(docsRoot), INDEX_LOCK_FILENAME);
145
- const token = `${process.pid}:${randomUUID()}`;
146
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
147
-
148
- while (Date.now() < deadline) {
149
- let lock: FileHandle;
150
- try {
151
- lock = await fs.open(lockPath, 'wx');
152
- } catch (error) {
153
- const code = errorCode(error);
154
- // Windows can deny opening a lock while another writer's deletion is still pending.
155
- if (code !== 'EEXIST' && !(process.platform === 'win32' && code === 'EPERM')) throw error;
156
-
157
- const stats = await fs.stat(lockPath).catch(() => undefined);
158
- if (stats && Date.now() - stats.mtimeMs > STALE_LOCK_MS) {
159
- await fs.unlink(lockPath).catch(() => undefined);
160
- continue;
161
- }
162
- await delay(LOCK_RETRY_MS);
163
- continue;
164
- }
165
- try {
166
- await lock.writeFile(token);
167
- } finally {
168
- await lock.close();
169
- }
170
- return async () => {
171
- const owner = await fs.readFile(lockPath, 'utf8').catch(() => undefined);
172
- if (owner === token) await fs.unlink(lockPath).catch(() => undefined);
173
- };
174
- }
175
- throw new Error('Timed out updating mint test run history');
176
- }
177
-
178
- async function readReport(filePath: string): Promise<CodeTestReport | null> {
179
- try {
180
- return codeTestReportSchema.parse(JSON.parse(await fs.readFile(filePath, 'utf8')));
181
- } catch {
182
- return null;
183
- }
184
- }
185
-
186
- async function rebuildIndex(docsRoot: string): Promise<TestRunsIndex> {
187
- const entries = await fs
188
- .readdir(runsDirectory(docsRoot), { withFileTypes: true })
189
- .catch(() => []);
190
- const reports = await Promise.all(
191
- entries
192
- .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
193
- .map((entry) => readReport(path.join(runsDirectory(docsRoot), entry.name)))
194
- );
195
- return {
196
- version: 1,
197
- runs: reports
198
- .filter((report): report is CodeTestReport => report !== null)
199
- .map(summarizeReport)
200
- .sort((left, right) => right.completedAt.localeCompare(left.completedAt)),
201
- };
202
- }
203
-
204
- export async function loadTestRuns(docsRoot: string): Promise<TestRunsIndex> {
205
- try {
206
- return testRunsIndexSchema.parse(
207
- JSON.parse(await fs.readFile(testRunsIndexPath(docsRoot), 'utf8'))
208
- );
209
- } catch {
210
- return rebuildIndex(docsRoot);
211
- }
212
- }
213
-
214
- export async function loadLatestTestRun(docsRoot: string): Promise<CodeTestReport | null> {
215
- const index = await loadTestRuns(docsRoot);
216
- for (const run of index.runs) {
217
- const report = await readReport(testRunReportPath(docsRoot, run.runId));
218
- if (report) return report;
219
- }
220
- return null;
221
- }
222
-
223
- export async function writeTestRunHistory(docsRoot: string, report: CodeTestReport): Promise<void> {
224
- await fs.mkdir(runsDirectory(docsRoot), { recursive: true });
225
- await writeJsonAtomically(report.reportPath, report);
226
- const releaseLock = await acquireIndexLock(docsRoot);
227
- try {
228
- await writeJsonAtomically(testRunsIndexPath(docsRoot), await rebuildIndex(docsRoot));
229
- } finally {
230
- await releaseLock();
231
- }
232
- }
233
-
234
- /**
235
- * Saves a finished run to history without letting a history failure fail the run.
236
- * On failure the returned report points at `fallbackReportPath` (the report that was
237
- * already written) and carries the problem in `historyError`.
238
- */
239
- export async function recordTestRun(
240
- docsRoot: string,
241
- report: CodeTestReport,
242
- fallbackReportPath: string
243
- ): Promise<CodeTestReport> {
244
- try {
245
- await writeTestRunHistory(docsRoot, report);
246
- return report;
247
- } catch (error) {
248
- const message = error instanceof Error ? error.message : String(error);
249
- return {
250
- ...report,
251
- reportPath: fallbackReportPath,
252
- historyError: `run history could not be saved: ${message}`,
253
- };
254
- }
255
- }
@@ -1,38 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { z } from 'zod';
4
-
5
- import type { RunSave } from './types.js';
6
-
7
- const runSaveSchema = z
8
- .object({
9
- version: z.literal(1),
10
- runId: z.string().regex(/^[A-Za-z0-9._-]+$/),
11
- agents: z.array(z.enum(['claude', 'codex'])).min(1),
12
- model: z.string().min(1),
13
- selectedFiles: z.array(z.string().min(1)),
14
- createdAt: z.string(),
15
- })
16
- .strict();
17
-
18
- function savePath(outputDirectory: string): string {
19
- return path.join(outputDirectory, 'save.json');
20
- }
21
-
22
- export async function loadRunSave(outputDirectory: string): Promise<RunSave | null> {
23
- try {
24
- const content = await fs.readFile(savePath(outputDirectory), 'utf8');
25
- return runSaveSchema.parse(JSON.parse(content));
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- export async function writeRunSave(outputDirectory: string, save: RunSave): Promise<void> {
32
- await fs.mkdir(outputDirectory, { recursive: true });
33
- await fs.writeFile(savePath(outputDirectory), `${JSON.stringify(save, null, 2)}\n`);
34
- }
35
-
36
- export async function clearRunSave(outputDirectory: string): Promise<void> {
37
- await fs.unlink(savePath(outputDirectory)).catch(() => undefined);
38
- }
@@ -1,87 +0,0 @@
1
- import { randomBytes } from 'node:crypto';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
-
5
- import type { DocsPage, GeneratedTask, HarnessAgent } from './types.js';
6
-
7
- const PACKAGE_JSON = `${JSON.stringify(
8
- {
9
- name: 'mint-test-task',
10
- private: true,
11
- type: 'module',
12
- // No positional path: Node 21+ treats --test arguments as globs, so a bare
13
- // directory such as `tests/` fails with MODULE_NOT_FOUND. The default patterns
14
- // pick up tests/*.test.js on every supported Node version.
15
- scripts: { test: 'node --test' },
16
- },
17
- null,
18
- 2
19
- )}\n`;
20
-
21
- function isExistingFile(error: unknown): boolean {
22
- return (
23
- typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 'EEXIST'
24
- );
25
- }
26
-
27
- async function scaffoldTaskDirectory(directory: string): Promise<void> {
28
- await fs.mkdir(path.join(directory, 'tests'), { recursive: true });
29
- try {
30
- await fs.writeFile(path.join(directory, 'package.json'), PACKAGE_JSON, { flag: 'wx' });
31
- } catch (error) {
32
- if (!isExistingFile(error)) throw error;
33
- }
34
- }
35
-
36
- export async function scaffoldTaskDirectories(tasks: GeneratedTask[]): Promise<void> {
37
- for (const task of tasks) await scaffoldTaskDirectory(task.directory);
38
- }
39
-
40
- export async function setupFolders({
41
- pages,
42
- agents,
43
- docsRoot,
44
- outputDirectory,
45
- runId: savedRunId,
46
- }: {
47
- pages: DocsPage[];
48
- agents: HarnessAgent[];
49
- docsRoot: string;
50
- outputDirectory: string;
51
- runId: string | null;
52
- }): Promise<{ runId: string; runDirectory: string; reportPath: string; tasks: GeneratedTask[] }> {
53
- const runId =
54
- savedRunId ??
55
- `${new Date()
56
- .toISOString()
57
- .replace(/[-:TZ.]/g, '')
58
- .slice(0, 14)}-${randomBytes(4).toString('hex')}`;
59
- const runDirectory = path.join(path.resolve(outputDirectory), runId);
60
- const reportPath = path.join(runDirectory, 'report.json');
61
- await fs.mkdir(runDirectory, { recursive: true });
62
-
63
- const tasks: GeneratedTask[] = [];
64
- for (const agent of agents) {
65
- for (const [pageIndex, page] of pages.entries()) {
66
- const slug = page.file
67
- .replace(/\.[^.]+$/, '')
68
- .replace(/[^a-z0-9]+/gi, '-')
69
- .replace(/^-|-$/g, '')
70
- .slice(0, 56);
71
- tasks.push({
72
- id: `${agent}:${page.file}`,
73
- agent,
74
- docsRoot,
75
- file: page.file,
76
- directory: path.join(
77
- runDirectory,
78
- agent,
79
- `${String(pageIndex + 1).padStart(3, '0')}-${slug || 'page'}`
80
- ),
81
- attempts: 0,
82
- tokens: 0,
83
- });
84
- }
85
- }
86
- return { runId, runDirectory, reportPath, tasks };
87
- }
@@ -1,256 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import z from 'zod';
4
-
5
- import {
6
- buildCheckPrompt,
7
- CHECK_JSON_INSTRUCTION,
8
- CHECK_SYSTEM_PROMPT,
9
- CHECK_TOOL_INSTRUCTION,
10
- } from '../buildTaskPrompt.js';
11
- import { loadClaudeAgentSdk, loadCodexSdk } from '../loadAgentSdk.js';
12
- import {
13
- GeneratedTask,
14
- HarnessAgent,
15
- RunCodeTestsOptions,
16
- TaskUpdate,
17
- TestCheck,
18
- testCheckSchema,
19
- } from '../types.js';
20
-
21
- const CHECK_FILENAME = 'mint-test-check.json';
22
-
23
- const CHECK_MODEL = 'claude-haiku-4-5-20251001';
24
-
25
- async function readValidCheck(directory: string): Promise<TestCheck | null> {
26
- try {
27
- const check = testCheckSchema.parse(
28
- JSON.parse(await fs.readFile(path.join(directory, CHECK_FILENAME), 'utf8'))
29
- );
30
- return validateCheck(check).length === 0 ? check : null;
31
- } catch {
32
- return null;
33
- }
34
- }
35
-
36
- function validateCheck(check: TestCheck): string[] {
37
- return !check.testable && !check.reason ? ['Not-testable page needs a reason'] : [];
38
- }
39
-
40
- function parseJsonReply(reply: string): unknown {
41
- const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(reply);
42
- const raw = fenced?.[1] ?? reply;
43
- const start = raw.indexOf('{');
44
- const end = raw.lastIndexOf('}');
45
- if (start === -1 || end === -1) throw new Error('Reply contained no JSON object');
46
- return JSON.parse(raw.slice(start, end + 1));
47
- }
48
-
49
- async function runTestabilityCheck({
50
- file,
51
- content,
52
- agent,
53
- workingDirectory,
54
- signal,
55
- }: {
56
- file: string;
57
- content: string;
58
- agent: HarnessAgent;
59
- workingDirectory: string;
60
- signal: AbortSignal;
61
- }): Promise<{ tokens: number; check: TestCheck }> {
62
- const prompt = buildCheckPrompt(file, content);
63
- let tokens = 0;
64
- let raw: unknown;
65
- if (agent === 'claude') {
66
- const reply = await promptClaudeCheck({ prompt, workingDirectory, signal });
67
- tokens = reply.tokens;
68
- if (reply.captured === null) throw new Error('agent never called report_testability');
69
- raw = reply.captured;
70
- } else {
71
- const reply = await promptCodex({
72
- prompt,
73
- systemPrompt: CHECK_SYSTEM_PROMPT + CHECK_JSON_INSTRUCTION,
74
- workingDirectory,
75
- signal,
76
- });
77
- tokens = reply.tokens;
78
- raw = parseJsonReply(reply.result);
79
- }
80
- const check = testCheckSchema.parse(raw);
81
- const errors = validateCheck(check);
82
- if (errors.length > 0) throw new Error(errors.join('\n'));
83
- return { tokens, check };
84
- }
85
-
86
- export const checkFileTestability = async (
87
- task: GeneratedTask,
88
- {
89
- onTaskUpdate,
90
- signal,
91
- runDirectory,
92
- }: Pick<RunCodeTestsOptions, 'onTaskUpdate' | 'signal'> & { runDirectory: string }
93
- ): Promise<GeneratedTask> => {
94
- const emit = (phase: TaskUpdate['phase'], tokens: number) =>
95
- onTaskUpdate({
96
- id: task.id,
97
- agent: task.agent,
98
- file: task.file,
99
- directory: task.directory,
100
- generatedFiles: [],
101
- phase,
102
- tokens,
103
- });
104
- let attempts = 0;
105
- let tokens = 0;
106
- let lastError = 'run cancelled';
107
-
108
- let check = await readValidCheck(task.directory);
109
- if (!check) {
110
- emit('checking', tokens);
111
- const content = await fs.readFile(path.join(task.docsRoot, task.file), 'utf8');
112
- for (let attempt = 1; attempt <= 2 && !signal.aborted && !check; attempt++) {
113
- attempts = attempt;
114
- try {
115
- const reply = await runTestabilityCheck({
116
- file: task.file,
117
- content,
118
- agent: task.agent,
119
- workingDirectory: runDirectory,
120
- signal,
121
- });
122
- tokens += reply.tokens;
123
- check = reply.check;
124
- } catch (error) {
125
- lastError = error instanceof Error ? error.message : String(error);
126
- }
127
- }
128
- if (!check) {
129
- if (signal.aborted) {
130
- emit('cancelled', tokens);
131
- return { ...task, attempts, tokens, error: 'run cancelled' };
132
- }
133
- emit('agent_error', tokens);
134
- return { ...task, attempts, tokens, error: lastError };
135
- }
136
- }
137
-
138
- if (!check.testable) {
139
- emit('not_testable', tokens);
140
- return { ...task, attempts, tokens, testable: false };
141
- }
142
- await fs.mkdir(task.directory, { recursive: true });
143
- await fs.writeFile(
144
- path.join(task.directory, CHECK_FILENAME),
145
- `${JSON.stringify(check, null, 2)}\n`
146
- );
147
- emit('checked', tokens);
148
- return { ...task, attempts, tokens, testable: true };
149
- };
150
-
151
- async function promptClaudeCheck({
152
- prompt,
153
- workingDirectory,
154
- signal,
155
- }: {
156
- prompt: string;
157
- workingDirectory: string;
158
- signal: AbortSignal;
159
- }): Promise<{ tokens: number; captured: unknown }> {
160
- const { query, tool, createSdkMcpServer } = await loadClaudeAgentSdk();
161
- const abortController = new AbortController();
162
- const abort = () => abortController.abort(signal.reason);
163
- if (signal.aborted) abort();
164
- else signal.addEventListener('abort', abort, { once: true });
165
-
166
- let captured: unknown = null;
167
- const server = createSdkMcpServer({
168
- name: 'mint-test',
169
- tools: [
170
- tool(
171
- 'report_testability',
172
- 'Report whether the documentation page is testable. Call exactly once.',
173
- {
174
- version: z.literal(1),
175
- testable: z.boolean(),
176
- reason: z.string().optional(),
177
- },
178
- async (input) => {
179
- captured = input;
180
- return { content: [{ type: 'text', text: 'recorded' }] };
181
- }
182
- ),
183
- ],
184
- });
185
-
186
- const session = query({
187
- prompt,
188
- options: {
189
- abortController,
190
- allowedTools: ['mcp__mint-test__report_testability'],
191
- cwd: workingDirectory,
192
- mcpServers: { 'mint-test': server },
193
- model: CHECK_MODEL,
194
- permissionMode: 'dontAsk',
195
- persistSession: false,
196
- settingSources: [],
197
- strictMcpConfig: true,
198
- systemPrompt: CHECK_SYSTEM_PROMPT + CHECK_TOOL_INSTRUCTION,
199
- tools: [],
200
- },
201
- });
202
-
203
- try {
204
- for await (const message of session) {
205
- if (message.type !== 'result') continue;
206
- if (message.subtype !== 'success' || message.is_error) {
207
- const details = message.subtype === 'success' ? message.result : message.errors.join('; ');
208
- throw new Error(details || `Claude stopped with ${message.subtype}`);
209
- }
210
- const usage = message.usage;
211
- return {
212
- tokens:
213
- usage.input_tokens +
214
- usage.output_tokens +
215
- usage.cache_creation_input_tokens +
216
- usage.cache_read_input_tokens,
217
- captured,
218
- };
219
- }
220
- throw new Error('Claude finished without returning a result');
221
- } finally {
222
- signal.removeEventListener('abort', abort);
223
- session.close();
224
- }
225
- }
226
-
227
- async function promptCodex({
228
- prompt,
229
- systemPrompt,
230
- workingDirectory,
231
- signal,
232
- }: {
233
- prompt: string;
234
- systemPrompt: string;
235
- workingDirectory: string;
236
- signal: AbortSignal;
237
- }): Promise<{ tokens: number; result: string }> {
238
- const { Codex } = await loadCodexSdk();
239
- const thread = new Codex().startThread({
240
- approvalPolicy: 'never',
241
- networkAccessEnabled: true,
242
- sandboxMode: 'workspace-write',
243
- skipGitRepoCheck: true,
244
- workingDirectory,
245
- });
246
- const result = await thread.run(`${systemPrompt}\n\n${prompt}`, { signal });
247
- const usage = result.usage;
248
- const tokens = usage
249
- ? usage.input_tokens +
250
- usage.cached_input_tokens +
251
- usage.cache_write_input_tokens +
252
- usage.output_tokens +
253
- usage.reasoning_output_tokens
254
- : 0;
255
- return { tokens, result: result.finalResponse };
256
- }