@mintlify/cli 4.0.1437 → 4.0.1439

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 (35) hide show
  1. package/bin/agent-harness/buildTaskPrompt.js +76 -0
  2. package/bin/agent-harness/discoverCodeBlocks.js +101 -0
  3. package/bin/agent-harness/executeCodeBlocks.js +220 -0
  4. package/bin/agent-harness/filterSelectedPages.js +10 -0
  5. package/bin/agent-harness/generateTestCode.js +293 -0
  6. package/bin/agent-harness/index.js +85 -0
  7. package/bin/agent-harness/loadAgentSdk.js +46 -0
  8. package/bin/agent-harness/mapWithConcurrency.js +26 -0
  9. package/bin/agent-harness/runSave.js +47 -0
  10. package/bin/agent-harness/setupFolders.js +59 -0
  11. package/bin/agent-harness/tasks/checkTestability.js +226 -0
  12. package/bin/agent-harness/types.js +35 -0
  13. package/bin/format.js +15 -3
  14. package/bin/mintTest.js +1 -1
  15. package/bin/mintTestUi.js +1 -1
  16. package/bin/tsconfig.build.tsbuildinfo +1 -1
  17. package/bin/vendor/converter.js +37 -2
  18. package/package.json +7 -4
  19. package/src/agent-harness/MINT_TEST_SYSTEM_DESIGN.md +58 -0
  20. package/src/agent-harness/buildTaskPrompt.ts +82 -0
  21. package/src/agent-harness/discoverCodeBlocks.ts +90 -0
  22. package/src/agent-harness/executeCodeBlocks.ts +285 -0
  23. package/src/agent-harness/filterSelectedPages.ts +14 -0
  24. package/src/agent-harness/generateTestCode.ts +332 -0
  25. package/src/agent-harness/index.ts +109 -0
  26. package/src/agent-harness/loadAgentSdk.ts +35 -0
  27. package/src/agent-harness/mapWithConcurrency.ts +21 -0
  28. package/src/agent-harness/runSave.ts +38 -0
  29. package/src/agent-harness/setupFolders.ts +73 -0
  30. package/src/agent-harness/tasks/checkTestability.ts +247 -0
  31. package/src/agent-harness/types.ts +152 -0
  32. package/src/format.tsx +13 -3
  33. package/src/mintTest.tsx +3 -3
  34. package/src/mintTestTree.ts +1 -1
  35. package/src/mintTestUi.tsx +5 -5
@@ -0,0 +1,293 @@
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
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
11
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
12
+ var m = o[Symbol.asyncIterator], i;
13
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
14
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
15
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
16
+ };
17
+ import fs from 'node:fs/promises';
18
+ import path from 'node:path';
19
+ import { SYSTEM_PROMPT, buildTaskPrompt } from './buildTaskPrompt.js';
20
+ import { loadClaudeAgentSdk, loadCodexSdk } from './loadAgentSdk.js';
21
+ import { mapWithConcurrency } from './mapWithConcurrency.js';
22
+ import { checkFileTestability } from './tasks/checkTestability.js';
23
+ import { manifestSchema, } from './types.js';
24
+ const MANIFEST_FILENAME = 'mint-test.json';
25
+ const SAFE_EXECUTABLES = new Set([
26
+ 'bun',
27
+ 'bunx',
28
+ 'bundle',
29
+ 'cargo',
30
+ 'composer',
31
+ 'deno',
32
+ 'dotnet',
33
+ 'go',
34
+ 'gradle',
35
+ 'java',
36
+ 'javac',
37
+ 'mvn',
38
+ 'node',
39
+ 'npm',
40
+ 'npx',
41
+ 'php',
42
+ 'pip',
43
+ 'pip3',
44
+ 'pnpm',
45
+ 'python',
46
+ 'python3',
47
+ 'pytest',
48
+ 'ruby',
49
+ 'rustc',
50
+ 'uv',
51
+ 'vitest',
52
+ 'yarn',
53
+ ]);
54
+ function promptClaude(_a) {
55
+ return __awaiter(this, arguments, void 0, function* ({ prompt, systemPrompt, model, workingDirectory, docsRoot, tools, signal, }) {
56
+ var _b, e_1, _c, _d;
57
+ const { query } = yield loadClaudeAgentSdk();
58
+ const abortController = new AbortController();
59
+ const abort = () => abortController.abort(signal.reason);
60
+ if (signal.aborted)
61
+ abort();
62
+ else
63
+ signal.addEventListener('abort', abort, { once: true });
64
+ const session = query({
65
+ prompt,
66
+ options: {
67
+ abortController,
68
+ additionalDirectories: [docsRoot],
69
+ allowedTools: tools,
70
+ cwd: workingDirectory,
71
+ model,
72
+ permissionMode: 'dontAsk',
73
+ persistSession: false,
74
+ settingSources: [],
75
+ strictMcpConfig: true,
76
+ systemPrompt,
77
+ tools,
78
+ },
79
+ });
80
+ try {
81
+ try {
82
+ for (var _e = true, session_1 = __asyncValues(session), session_1_1; session_1_1 = yield session_1.next(), _b = session_1_1.done, !_b; _e = true) {
83
+ _d = session_1_1.value;
84
+ _e = false;
85
+ const message = _d;
86
+ if (message.type !== 'result')
87
+ continue;
88
+ if (message.subtype !== 'success' || message.is_error) {
89
+ const details = message.subtype === 'success' ? message.result : message.errors.join('; ');
90
+ throw new Error(details || `Claude stopped with ${message.subtype}`);
91
+ }
92
+ const usage = message.usage;
93
+ return {
94
+ tokens: usage.input_tokens +
95
+ usage.output_tokens +
96
+ usage.cache_creation_input_tokens +
97
+ usage.cache_read_input_tokens,
98
+ result: message.result,
99
+ };
100
+ }
101
+ }
102
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
103
+ finally {
104
+ try {
105
+ if (!_e && !_b && (_c = session_1.return)) yield _c.call(session_1);
106
+ }
107
+ finally { if (e_1) throw e_1.error; }
108
+ }
109
+ throw new Error('Claude finished without returning a result');
110
+ }
111
+ finally {
112
+ signal.removeEventListener('abort', abort);
113
+ session.close();
114
+ }
115
+ });
116
+ }
117
+ const AGENT_TOOLS = ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'Bash'];
118
+ function isPathInside(root, candidate) {
119
+ const relative = path.relative(root, candidate);
120
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
121
+ }
122
+ function validateManifest(manifest, directory) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ var _a;
125
+ const errors = [];
126
+ const realDirectory = yield fs.realpath(directory);
127
+ for (const file of manifest.generatedFiles) {
128
+ if (path.isAbsolute(file)) {
129
+ errors.push(`Generated file path must be relative: ${file}`);
130
+ continue;
131
+ }
132
+ const resolved = path.resolve(directory, file);
133
+ if (!isPathInside(directory, resolved)) {
134
+ errors.push(`Generated file leaves its task: ${file}`);
135
+ continue;
136
+ }
137
+ const realPath = yield fs.realpath(resolved).catch(() => undefined);
138
+ if (!realPath || !isPathInside(realDirectory, realPath)) {
139
+ errors.push(`Generated file leaves its task through a link: ${file}`);
140
+ continue;
141
+ }
142
+ const stats = yield fs.stat(realPath).catch(() => undefined);
143
+ if (!(stats === null || stats === void 0 ? void 0 : stats.isFile()))
144
+ errors.push(`Generated file does not exist: ${file}`);
145
+ }
146
+ const commandIds = new Set();
147
+ for (const command of [...manifest.setupCommands, ...manifest.testCommands]) {
148
+ if (commandIds.has(command.id))
149
+ errors.push(`Duplicate command ID: ${command.id}`);
150
+ commandIds.add(command.id);
151
+ if (path.isAbsolute(command.executable) || command.executable.split(/[\\/]/).includes('..')) {
152
+ errors.push(`Unsafe command executable: ${command.executable}`);
153
+ continue;
154
+ }
155
+ if (!SAFE_EXECUTABLES.has(command.executable) && !command.executable.startsWith('./')) {
156
+ errors.push(`Unsupported command executable: ${command.executable}`);
157
+ continue;
158
+ }
159
+ if (command.executable.startsWith('./')) {
160
+ const executablePath = yield fs
161
+ .realpath(path.resolve(directory, command.executable))
162
+ .catch(() => undefined);
163
+ const stats = executablePath
164
+ ? yield fs.stat(executablePath).catch(() => undefined)
165
+ : undefined;
166
+ if (!executablePath || !isPathInside(realDirectory, executablePath) || !(stats === null || stats === void 0 ? void 0 : stats.isFile())) {
167
+ errors.push(`Command executable leaves its task or is not a file: ${command.executable}`);
168
+ }
169
+ }
170
+ const cwd = (_a = command.cwd) !== null && _a !== void 0 ? _a : '.';
171
+ if (path.isAbsolute(cwd)) {
172
+ errors.push(`Command cwd must be relative: ${cwd}`);
173
+ continue;
174
+ }
175
+ const resolvedCwd = path.resolve(directory, cwd);
176
+ const realCwd = yield fs.realpath(resolvedCwd).catch(() => undefined);
177
+ const stats = realCwd ? yield fs.stat(realCwd).catch(() => undefined) : undefined;
178
+ if (!isPathInside(directory, resolvedCwd) ||
179
+ !realCwd ||
180
+ !isPathInside(realDirectory, realCwd) ||
181
+ !(stats === null || stats === void 0 ? void 0 : stats.isDirectory())) {
182
+ errors.push(`Command cwd is missing or leaves its task: ${cwd}`);
183
+ }
184
+ }
185
+ return errors;
186
+ });
187
+ }
188
+ function readValidManifest(directory) {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ try {
191
+ const manifest = manifestSchema.parse(JSON.parse(yield fs.readFile(path.join(directory, MANIFEST_FILENAME), 'utf8')));
192
+ const errors = yield validateManifest(manifest, directory);
193
+ return errors.length === 0 ? manifest : null;
194
+ }
195
+ catch (_a) {
196
+ return null;
197
+ }
198
+ });
199
+ }
200
+ export const CHECK_CONCURRENCY = 20;
201
+ export function checkTestability(_a) {
202
+ return __awaiter(this, arguments, void 0, function* ({ tasks, signal, onTaskUpdate, }) {
203
+ const folderOrdered = [...tasks].sort((left, right) => path.dirname(left.file).localeCompare(path.dirname(right.file)) ||
204
+ left.file.localeCompare(right.file) ||
205
+ left.agent.localeCompare(right.agent));
206
+ return mapWithConcurrency(folderOrdered, CHECK_CONCURRENCY, checkFileTestability, {
207
+ signal,
208
+ onTaskUpdate,
209
+ });
210
+ });
211
+ }
212
+ export function generateTestCode(_a) {
213
+ return __awaiter(this, arguments, void 0, function* ({ tasks, docsRoot, model, concurrency, signal, onTaskUpdate, }) {
214
+ const generateTask = (task) => __awaiter(this, void 0, void 0, function* () {
215
+ if (task.error || !task.testable)
216
+ return task;
217
+ const emit = (phase, tokens) => onTaskUpdate({ id: task.id, agent: task.agent, file: task.file, phase, tokens });
218
+ let attempts = task.attempts;
219
+ let tokens = task.tokens;
220
+ let lastError = 'run cancelled';
221
+ const existing = yield readValidManifest(task.directory);
222
+ if (existing) {
223
+ emit('generated', tokens);
224
+ return Object.assign(Object.assign({}, task), { manifest: existing });
225
+ }
226
+ for (let attempt = 1; attempt <= 2 && !signal.aborted; attempt++) {
227
+ attempts += 1;
228
+ if (attempt === 1)
229
+ emit('generating', tokens);
230
+ try {
231
+ const prompt = buildTaskPrompt(docsRoot, task.file, task.directory);
232
+ const reply = task.agent === 'claude'
233
+ ? yield promptClaude({
234
+ prompt,
235
+ systemPrompt: SYSTEM_PROMPT,
236
+ model,
237
+ workingDirectory: task.directory,
238
+ docsRoot,
239
+ tools: AGENT_TOOLS,
240
+ signal,
241
+ })
242
+ : yield promptCodex({
243
+ prompt,
244
+ systemPrompt: SYSTEM_PROMPT,
245
+ workingDirectory: task.directory,
246
+ docsRoot,
247
+ signal,
248
+ });
249
+ tokens += reply.tokens;
250
+ const manifest = manifestSchema.parse(JSON.parse(yield fs.readFile(path.join(task.directory, MANIFEST_FILENAME), 'utf8')));
251
+ const errors = yield validateManifest(manifest, task.directory);
252
+ if (errors.length > 0)
253
+ throw new Error(errors.join('\n'));
254
+ emit('generated', tokens);
255
+ return Object.assign(Object.assign({}, task), { attempts, tokens, manifest });
256
+ }
257
+ catch (error) {
258
+ lastError = error instanceof Error ? error.message : String(error);
259
+ }
260
+ }
261
+ if (signal.aborted) {
262
+ emit('cancelled', tokens);
263
+ return Object.assign(Object.assign({}, task), { attempts, tokens, error: 'run cancelled' });
264
+ }
265
+ emit('agent_error', tokens);
266
+ return Object.assign(Object.assign({}, task), { attempts, tokens, error: lastError });
267
+ });
268
+ return mapWithConcurrency(tasks, concurrency, generateTask);
269
+ });
270
+ }
271
+ function promptCodex(_a) {
272
+ return __awaiter(this, arguments, void 0, function* ({ prompt, systemPrompt, workingDirectory, docsRoot, signal, }) {
273
+ const { Codex } = yield loadCodexSdk();
274
+ const thread = new Codex().startThread({
275
+ additionalDirectories: [docsRoot],
276
+ approvalPolicy: 'never',
277
+ networkAccessEnabled: true,
278
+ sandboxMode: 'workspace-write',
279
+ skipGitRepoCheck: true,
280
+ workingDirectory,
281
+ });
282
+ const result = yield thread.run(`${systemPrompt}\n\n${prompt}`, { signal });
283
+ const usage = result.usage;
284
+ const tokens = usage
285
+ ? usage.input_tokens +
286
+ usage.cached_input_tokens +
287
+ usage.cache_write_input_tokens +
288
+ usage.output_tokens +
289
+ usage.reasoning_output_tokens
290
+ : 0;
291
+ return { tokens, result: result.finalResponse };
292
+ });
293
+ }
@@ -0,0 +1,85 @@
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 fs from 'node:fs/promises';
11
+ import { discoverCodeBlocks } from './discoverCodeBlocks.js';
12
+ import { executeCodeBlocks } from './executeCodeBlocks.js';
13
+ import { filterSelectedPages } from './filterSelectedPages.js';
14
+ import { checkTestability, generateTestCode } from './generateTestCode.js';
15
+ import { clearRunSave, writeRunSave } from './runSave.js';
16
+ import { setupFolders } from './setupFolders.js';
17
+ export const DEFAULT_CONCURRENCY = 5;
18
+ export const DEFAULT_COMMAND_TIMEOUT_MS = 2 * 60 * 1000;
19
+ export { discoverCodeBlocks } from './discoverCodeBlocks.js';
20
+ export { clearRunSave, loadRunSave } from './runSave.js';
21
+ export function runCodeTests(_a) {
22
+ return __awaiter(this, arguments, void 0, function* ({ path: docsPath, agents, model, selectedFiles, outputDirectory, runId: savedRunId, concurrency, commandTimeoutMs, signal, onTaskUpdate, }) {
23
+ const startedAt = new Date();
24
+ const uniqueAgents = [...new Set(agents)];
25
+ const { docsRoot, pages: allPages } = yield discoverCodeBlocks(docsPath);
26
+ const pages = filterSelectedPages(allPages, selectedFiles);
27
+ const { runId, runDirectory, reportPath, tasks } = yield setupFolders({
28
+ pages,
29
+ agents: uniqueAgents,
30
+ docsRoot,
31
+ outputDirectory,
32
+ runId: savedRunId,
33
+ });
34
+ yield writeRunSave(outputDirectory, {
35
+ version: 1,
36
+ runId,
37
+ agents: uniqueAgents,
38
+ model,
39
+ selectedFiles: pages.map((page) => page.file),
40
+ createdAt: startedAt.toISOString(),
41
+ });
42
+ const checked = yield checkTestability({ tasks, signal, onTaskUpdate });
43
+ const generated = yield generateTestCode({
44
+ tasks: checked,
45
+ docsRoot,
46
+ model,
47
+ concurrency,
48
+ signal,
49
+ onTaskUpdate,
50
+ });
51
+ const results = yield executeCodeBlocks({
52
+ tasks: generated,
53
+ docsRoot,
54
+ concurrency,
55
+ commandTimeoutMs,
56
+ signal,
57
+ onTaskUpdate,
58
+ });
59
+ const completedAt = new Date();
60
+ const report = {
61
+ version: 1,
62
+ runId,
63
+ status: signal.aborted
64
+ ? 'cancelled'
65
+ : results.every((task) => task.status === 'passed')
66
+ ? 'passed'
67
+ : results.some((task) => task.status === 'passed')
68
+ ? 'completed'
69
+ : 'failed',
70
+ docsRoot,
71
+ outputDirectory: runDirectory,
72
+ reportPath,
73
+ agents: uniqueAgents,
74
+ selectedFiles: pages.map((page) => page.file),
75
+ startedAt: startedAt.toISOString(),
76
+ completedAt: completedAt.toISOString(),
77
+ durationMs: completedAt.getTime() - startedAt.getTime(),
78
+ tasks: results,
79
+ };
80
+ yield fs.writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
81
+ if (!signal.aborted)
82
+ yield clearRunSave(outputDirectory);
83
+ return report;
84
+ });
85
+ }
@@ -0,0 +1,46 @@
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
+ const INSTALL_HINTS = {
11
+ '@anthropic-ai/claude-agent-sdk': 'npm install @anthropic-ai/claude-agent-sdk @anthropic-ai/sdk @modelcontextprotocol/sdk',
12
+ '@openai/codex-sdk': 'npm install @openai/codex-sdk',
13
+ };
14
+ function isModuleNotFound(error) {
15
+ if (typeof error !== 'object' || error === null)
16
+ return false;
17
+ const { code } = error;
18
+ return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';
19
+ }
20
+ function missingSdkError(packageName) {
21
+ return new Error(`mint test requires the optional dependency ${packageName}, which is not installed. Install it with: ${INSTALL_HINTS[packageName]}`);
22
+ }
23
+ export function loadClaudeAgentSdk() {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ try {
26
+ return yield import('@anthropic-ai/claude-agent-sdk');
27
+ }
28
+ catch (error) {
29
+ if (isModuleNotFound(error))
30
+ throw missingSdkError('@anthropic-ai/claude-agent-sdk');
31
+ throw error;
32
+ }
33
+ });
34
+ }
35
+ export function loadCodexSdk() {
36
+ return __awaiter(this, void 0, void 0, function* () {
37
+ try {
38
+ return yield import('@openai/codex-sdk');
39
+ }
40
+ catch (error) {
41
+ if (isModuleNotFound(error))
42
+ throw missingSdkError('@openai/codex-sdk');
43
+ throw error;
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,26 @@
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
+ export function mapWithConcurrency(items, concurrency, fn, ...args) {
11
+ return __awaiter(this, void 0, void 0, function* () {
12
+ const results = new Array(items.length);
13
+ let nextIndex = 0;
14
+ const worker = () => __awaiter(this, void 0, void 0, function* () {
15
+ for (;;) {
16
+ const index = nextIndex++;
17
+ const item = items[index];
18
+ if (index >= items.length || item === undefined)
19
+ return;
20
+ results[index] = yield fn(item, ...args);
21
+ }
22
+ });
23
+ yield Promise.all(Array.from({ length: Math.min(Math.max(1, concurrency), items.length) }, () => worker()));
24
+ return results;
25
+ });
26
+ }
@@ -0,0 +1,47 @@
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 fs from 'node:fs/promises';
11
+ import path from 'node:path';
12
+ import { z } from 'zod';
13
+ const runSaveSchema = z
14
+ .object({
15
+ version: z.literal(1),
16
+ runId: z.string().regex(/^[A-Za-z0-9._-]+$/),
17
+ agents: z.array(z.enum(['claude', 'codex'])).min(1),
18
+ model: z.string().min(1),
19
+ selectedFiles: z.array(z.string().min(1)),
20
+ createdAt: z.string(),
21
+ })
22
+ .strict();
23
+ function savePath(outputDirectory) {
24
+ return path.join(outputDirectory, 'save.json');
25
+ }
26
+ export function loadRunSave(outputDirectory) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ try {
29
+ const content = yield fs.readFile(savePath(outputDirectory), 'utf8');
30
+ return runSaveSchema.parse(JSON.parse(content));
31
+ }
32
+ catch (_a) {
33
+ return null;
34
+ }
35
+ });
36
+ }
37
+ export function writeRunSave(outputDirectory, save) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ yield fs.mkdir(outputDirectory, { recursive: true });
40
+ yield fs.writeFile(savePath(outputDirectory), `${JSON.stringify(save, null, 2)}\n`);
41
+ });
42
+ }
43
+ export function clearRunSave(outputDirectory) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ yield fs.unlink(savePath(outputDirectory)).catch(() => undefined);
46
+ });
47
+ }
@@ -0,0 +1,59 @@
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 { randomBytes } from 'node:crypto';
11
+ import fs from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ const PACKAGE_JSON = `${JSON.stringify({
14
+ name: 'mint-test-task',
15
+ private: true,
16
+ type: 'module',
17
+ scripts: { test: 'node --test tests/' },
18
+ }, null, 2)}\n`;
19
+ function scaffoldTaskDirectory(directory) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ yield fs.mkdir(path.join(directory, 'tests'), { recursive: true });
22
+ yield fs
23
+ .writeFile(path.join(directory, 'package.json'), PACKAGE_JSON, { flag: 'wx' })
24
+ .catch(() => { });
25
+ });
26
+ }
27
+ export function setupFolders(_a) {
28
+ return __awaiter(this, arguments, void 0, function* ({ pages, agents, docsRoot, outputDirectory, runId: savedRunId, }) {
29
+ const runId = savedRunId !== null && savedRunId !== void 0 ? savedRunId : `${new Date()
30
+ .toISOString()
31
+ .replace(/[-:TZ.]/g, '')
32
+ .slice(0, 14)}-${randomBytes(4).toString('hex')}`;
33
+ const runDirectory = path.join(path.resolve(outputDirectory), runId);
34
+ const reportPath = path.join(runDirectory, 'report.json');
35
+ yield fs.mkdir(runDirectory, { recursive: true });
36
+ const tasks = [];
37
+ for (const agent of agents) {
38
+ for (const [pageIndex, page] of pages.entries()) {
39
+ const slug = page.file
40
+ .replace(/\.[^.]+$/, '')
41
+ .replace(/[^a-z0-9]+/gi, '-')
42
+ .replace(/^-|-$/g, '')
43
+ .slice(0, 56);
44
+ tasks.push({
45
+ id: `${agent}:${page.file}`,
46
+ agent,
47
+ docsRoot,
48
+ file: page.file,
49
+ directory: path.join(runDirectory, agent, `${String(pageIndex + 1).padStart(3, '0')}-${slug || 'page'}`),
50
+ attempts: 0,
51
+ tokens: 0,
52
+ });
53
+ }
54
+ }
55
+ for (const task of tasks)
56
+ yield scaffoldTaskDirectory(task.directory);
57
+ return { runId, runDirectory, reportPath, tasks };
58
+ });
59
+ }