@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,226 @@
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 z from 'zod';
20
+ import { buildCheckPrompt, CHECK_JSON_INSTRUCTION, CHECK_SYSTEM_PROMPT, CHECK_TOOL_INSTRUCTION, } from '../buildTaskPrompt.js';
21
+ import { loadClaudeAgentSdk, loadCodexSdk } from '../loadAgentSdk.js';
22
+ import { testCheckSchema, } from '../types.js';
23
+ const CHECK_FILENAME = 'mint-test-check.json';
24
+ const CHECK_MODEL = 'claude-haiku-4-5-20251001';
25
+ function readValidCheck(directory) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ try {
28
+ const check = testCheckSchema.parse(JSON.parse(yield fs.readFile(path.join(directory, CHECK_FILENAME), 'utf8')));
29
+ return validateCheck(check).length === 0 ? check : null;
30
+ }
31
+ catch (_a) {
32
+ return null;
33
+ }
34
+ });
35
+ }
36
+ function validateCheck(check) {
37
+ return !check.testable && !check.reason ? ['Not-testable page needs a reason'] : [];
38
+ }
39
+ function parseJsonReply(reply) {
40
+ var _a;
41
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(reply);
42
+ const raw = (_a = fenced === null || fenced === void 0 ? void 0 : fenced[1]) !== null && _a !== void 0 ? _a : reply;
43
+ const start = raw.indexOf('{');
44
+ const end = raw.lastIndexOf('}');
45
+ if (start === -1 || end === -1)
46
+ throw new Error('Reply contained no JSON object');
47
+ return JSON.parse(raw.slice(start, end + 1));
48
+ }
49
+ function runTestabilityCheck(_a) {
50
+ return __awaiter(this, arguments, void 0, function* ({ file, content, agent, outputPath, signal, }) {
51
+ const prompt = buildCheckPrompt(file, content);
52
+ const workingDirectory = path.dirname(outputPath);
53
+ let tokens = 0;
54
+ let raw;
55
+ if (agent === 'claude') {
56
+ const reply = yield promptClaudeCheck({ prompt, workingDirectory, signal });
57
+ tokens = reply.tokens;
58
+ if (reply.captured === null)
59
+ throw new Error('agent never called report_testability');
60
+ raw = reply.captured;
61
+ }
62
+ else {
63
+ const reply = yield promptCodex({
64
+ prompt,
65
+ systemPrompt: CHECK_SYSTEM_PROMPT + CHECK_JSON_INSTRUCTION,
66
+ workingDirectory,
67
+ signal,
68
+ });
69
+ tokens = reply.tokens;
70
+ raw = parseJsonReply(reply.result);
71
+ }
72
+ const check = testCheckSchema.parse(raw);
73
+ const errors = validateCheck(check);
74
+ if (errors.length > 0)
75
+ throw new Error(errors.join('\n'));
76
+ yield fs.writeFile(outputPath, `${JSON.stringify(check, null, 2)}\n`);
77
+ return { tokens, check };
78
+ });
79
+ }
80
+ export const checkFileTestability = (task_1, _a) => __awaiter(void 0, [task_1, _a], void 0, function* (task, { onTaskUpdate, signal }) {
81
+ const emit = (phase, tokens) => onTaskUpdate({
82
+ id: task.id,
83
+ agent: task.agent,
84
+ file: task.file,
85
+ phase,
86
+ tokens,
87
+ });
88
+ let attempts = 0;
89
+ let tokens = 0;
90
+ let lastError = 'run cancelled';
91
+ let check = yield readValidCheck(task.directory);
92
+ if (!check) {
93
+ emit('checking', tokens);
94
+ const content = yield fs.readFile(path.join(task.docsRoot, task.file), 'utf8');
95
+ for (let attempt = 1; attempt <= 2 && !signal.aborted && !check; attempt++) {
96
+ attempts = attempt;
97
+ try {
98
+ const reply = yield runTestabilityCheck({
99
+ file: task.file,
100
+ content,
101
+ agent: task.agent,
102
+ outputPath: path.join(task.directory, CHECK_FILENAME),
103
+ signal,
104
+ });
105
+ tokens += reply.tokens;
106
+ check = reply.check;
107
+ }
108
+ catch (error) {
109
+ lastError = error instanceof Error ? error.message : String(error);
110
+ }
111
+ }
112
+ if (!check) {
113
+ if (signal.aborted) {
114
+ emit('cancelled', tokens);
115
+ return Object.assign(Object.assign({}, task), { attempts, tokens, error: 'run cancelled' });
116
+ }
117
+ emit('agent_error', tokens);
118
+ return Object.assign(Object.assign({}, task), { attempts, tokens, error: lastError });
119
+ }
120
+ }
121
+ if (!check.testable) {
122
+ emit('not_testable', tokens);
123
+ return Object.assign(Object.assign({}, task), { attempts, tokens, testable: false });
124
+ }
125
+ emit('checked', tokens);
126
+ return Object.assign(Object.assign({}, task), { attempts, tokens, testable: true });
127
+ });
128
+ function promptClaudeCheck(_a) {
129
+ return __awaiter(this, arguments, void 0, function* ({ prompt, workingDirectory, signal, }) {
130
+ var _b, e_1, _c, _d;
131
+ const { query, tool, createSdkMcpServer } = yield loadClaudeAgentSdk();
132
+ const abortController = new AbortController();
133
+ const abort = () => abortController.abort(signal.reason);
134
+ if (signal.aborted)
135
+ abort();
136
+ else
137
+ signal.addEventListener('abort', abort, { once: true });
138
+ let captured = null;
139
+ const server = createSdkMcpServer({
140
+ name: 'mint-test',
141
+ tools: [
142
+ tool('report_testability', 'Report whether the documentation page is testable. Call exactly once.', {
143
+ version: z.literal(1),
144
+ testable: z.boolean(),
145
+ reason: z.string().optional(),
146
+ }, (input) => __awaiter(this, void 0, void 0, function* () {
147
+ captured = input;
148
+ return { content: [{ type: 'text', text: 'recorded' }] };
149
+ })),
150
+ ],
151
+ });
152
+ const session = query({
153
+ prompt,
154
+ options: {
155
+ abortController,
156
+ allowedTools: ['mcp__mint-test__report_testability'],
157
+ cwd: workingDirectory,
158
+ mcpServers: { 'mint-test': server },
159
+ model: CHECK_MODEL,
160
+ permissionMode: 'dontAsk',
161
+ persistSession: false,
162
+ settingSources: [],
163
+ strictMcpConfig: true,
164
+ systemPrompt: CHECK_SYSTEM_PROMPT + CHECK_TOOL_INSTRUCTION,
165
+ tools: [],
166
+ },
167
+ });
168
+ try {
169
+ try {
170
+ 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) {
171
+ _d = session_1_1.value;
172
+ _e = false;
173
+ const message = _d;
174
+ if (message.type !== 'result')
175
+ continue;
176
+ if (message.subtype !== 'success' || message.is_error) {
177
+ const details = message.subtype === 'success' ? message.result : message.errors.join('; ');
178
+ throw new Error(details || `Claude stopped with ${message.subtype}`);
179
+ }
180
+ const usage = message.usage;
181
+ return {
182
+ tokens: usage.input_tokens +
183
+ usage.output_tokens +
184
+ usage.cache_creation_input_tokens +
185
+ usage.cache_read_input_tokens,
186
+ captured,
187
+ };
188
+ }
189
+ }
190
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
191
+ finally {
192
+ try {
193
+ if (!_e && !_b && (_c = session_1.return)) yield _c.call(session_1);
194
+ }
195
+ finally { if (e_1) throw e_1.error; }
196
+ }
197
+ throw new Error('Claude finished without returning a result');
198
+ }
199
+ finally {
200
+ signal.removeEventListener('abort', abort);
201
+ session.close();
202
+ }
203
+ });
204
+ }
205
+ function promptCodex(_a) {
206
+ return __awaiter(this, arguments, void 0, function* ({ prompt, systemPrompt, workingDirectory, signal, }) {
207
+ const { Codex } = yield loadCodexSdk();
208
+ const thread = new Codex().startThread({
209
+ approvalPolicy: 'never',
210
+ networkAccessEnabled: true,
211
+ sandboxMode: 'workspace-write',
212
+ skipGitRepoCheck: true,
213
+ workingDirectory,
214
+ });
215
+ const result = yield thread.run(`${systemPrompt}\n\n${prompt}`, { signal });
216
+ const usage = result.usage;
217
+ const tokens = usage
218
+ ? usage.input_tokens +
219
+ usage.cached_input_tokens +
220
+ usage.cache_write_input_tokens +
221
+ usage.output_tokens +
222
+ usage.reasoning_output_tokens
223
+ : 0;
224
+ return { tokens, result: result.finalResponse };
225
+ });
226
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ const identifier = z.string().trim().min(1).max(120);
3
+ const commandSchema = z
4
+ .object({
5
+ id: identifier,
6
+ executable: z.string().trim().min(1).max(512),
7
+ args: z.array(z.string().max(10000)).max(100),
8
+ cwd: z.string().max(1024).optional(),
9
+ timeoutMs: z
10
+ .number()
11
+ .int()
12
+ .min(1000)
13
+ .max(30 * 60 * 1000)
14
+ .optional(),
15
+ })
16
+ .strict();
17
+ export const manifestSchema = z
18
+ .object({
19
+ version: z.literal(1),
20
+ generatedFiles: z.array(z.string().min(1).max(1024)).max(1000),
21
+ setupCommands: z.array(commandSchema).max(50),
22
+ testCommands: z.array(commandSchema).max(200),
23
+ })
24
+ .strict();
25
+ export const testCheckSchema = z
26
+ .object({
27
+ version: z.literal(1),
28
+ testable: z.boolean(),
29
+ reason: z
30
+ .string()
31
+ .max(2000)
32
+ .optional()
33
+ .transform((value) => ((value === null || value === void 0 ? void 0 : value.trim()) ? value.trim() : undefined)),
34
+ })
35
+ .strict();
package/bin/format.js CHANGED
@@ -71,7 +71,7 @@ function walk(dir, ignores) {
71
71
  }
72
72
  export const formatHandler = () => __awaiter(void 0, void 0, void 0, function* () {
73
73
  var _a, e_1, _b, _c;
74
- const { mdxToPm, pmToMdx } = yield import('./vendor/converter.js');
74
+ const { mdxToPm, pmToMdx, canonicalPmToMdxOptions } = yield import('./vendor/converter.js');
75
75
  const ignores = [...(yield getGitIgnore()), ...(yield getMintIgnore(CMD_EXEC_PATH))];
76
76
  let total = 0;
77
77
  let changed = 0;
@@ -85,8 +85,20 @@ export const formatHandler = () => __awaiter(void 0, void 0, void 0, function* (
85
85
  const relative = path.relative(CMD_EXEC_PATH, file);
86
86
  try {
87
87
  const raw = yield fs.readFile(file, 'utf-8');
88
- const { doc, frontmatter } = mdxToPm(raw, { singleDollarTextMath: false });
89
- const formatted = `${pmToMdx(doc, { frontmatter })}\n`;
88
+ let formatted = raw;
89
+ let originalFrontmatterKeys = 0;
90
+ for (let pass = 0; pass < 3; pass++) {
91
+ const { doc, frontmatter } = mdxToPm(formatted, { singleDollarTextMath: false });
92
+ if (pass === 0)
93
+ originalFrontmatterKeys = Object.keys(frontmatter).length;
94
+ const next = `${pmToMdx(doc, Object.assign({ frontmatter }, canonicalPmToMdxOptions))}\n`;
95
+ if (next === formatted)
96
+ break;
97
+ formatted = next;
98
+ }
99
+ if (originalFrontmatterKeys > 0 && !formatted.startsWith('---')) {
100
+ throw new Error('formatting would remove frontmatter; file left untouched');
101
+ }
90
102
  if (formatted !== raw) {
91
103
  yield fs.writeFile(file, formatted);
92
104
  changed++;
package/bin/mintTest.js CHANGED
@@ -7,8 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, discoverCodeBlocks, loadRunSave, runCodeTests, } from '@mintlify/agent-harness';
11
10
  import path from 'node:path';
11
+ import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, discoverCodeBlocks, loadRunSave, runCodeTests, } from './agent-harness/index.js';
12
12
  import { CMD_EXEC_PATH, findDocsRoot, isAI, terminate } from './helpers.js';
13
13
  import { getAccessToken } from './keyring.js';
14
14
  import { fileInNavPaths, loadDocsNavScope } from './mintTestNav.js';
package/bin/mintTestUi.js CHANGED
@@ -8,10 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
- import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, runCodeTests, } from '@mintlify/agent-harness';
12
11
  import { Box, render, Text, useInput } from 'ink';
13
12
  import path from 'node:path';
14
13
  import { useEffect, useMemo, useRef, useState } from 'react';
14
+ import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, runCodeTests, } from './agent-harness/index.js';
15
15
  import { toggleScopeSelection, visibleScopeRows } from './mintTestTree.js';
16
16
  const HARNESSES = [
17
17
  { agent: 'claude', model: 'claude-opus-4-8', label: 'Claude Opus 4.8' },