@mintlify/cli 4.0.1655 → 4.0.1657
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.
- package/bin/cli.js +1 -7
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -12
- package/src/cli.tsx +1 -12
- package/__test__/mintTestFilePreview.test.ts +0 -35
- package/__test__/mintTestOutput.test.ts +0 -256
- package/__test__/runHistory.test.ts +0 -240
- package/bin/agent-harness/agentPreflight.js +0 -24
- package/bin/agent-harness/buildTaskPrompt.js +0 -86
- package/bin/agent-harness/countReport.js +0 -10
- package/bin/agent-harness/discoverCodeBlocks.js +0 -101
- package/bin/agent-harness/executeCodeBlocks.js +0 -263
- package/bin/agent-harness/filterSelectedPages.js +0 -10
- package/bin/agent-harness/generateTestCode.js +0 -337
- package/bin/agent-harness/index.js +0 -95
- package/bin/agent-harness/loadAgentSdk.js +0 -46
- package/bin/agent-harness/manifest.js +0 -113
- package/bin/agent-harness/mapWithConcurrency.js +0 -26
- package/bin/agent-harness/runHistory.js +0 -256
- package/bin/agent-harness/runSave.js +0 -47
- package/bin/agent-harness/setupFolders.js +0 -73
- package/bin/agent-harness/tasks/checkTestability.js +0 -228
- package/bin/agent-harness/types.js +0 -66
- package/bin/mintTest.js +0 -122
- package/bin/mintTestFilePreview.js +0 -39
- package/bin/mintTestNav.js +0 -35
- package/bin/mintTestTelemetry.js +0 -63
- package/bin/mintTestText.js +0 -31
- package/bin/mintTestTree.js +0 -94
- package/bin/mintTestUi.js +0 -818
- package/src/agent-harness/MINT_TEST_SYSTEM_DESIGN.md +0 -64
- package/src/agent-harness/agentPreflight.ts +0 -13
- package/src/agent-harness/buildTaskPrompt.ts +0 -93
- package/src/agent-harness/countReport.ts +0 -17
- package/src/agent-harness/discoverCodeBlocks.ts +0 -90
- package/src/agent-harness/executeCodeBlocks.ts +0 -351
- package/src/agent-harness/filterSelectedPages.ts +0 -14
- package/src/agent-harness/generateTestCode.ts +0 -396
- package/src/agent-harness/index.ts +0 -127
- package/src/agent-harness/loadAgentSdk.ts +0 -35
- package/src/agent-harness/manifest.ts +0 -116
- package/src/agent-harness/mapWithConcurrency.ts +0 -21
- package/src/agent-harness/runHistory.ts +0 -255
- package/src/agent-harness/runSave.ts +0 -38
- package/src/agent-harness/setupFolders.ts +0 -87
- package/src/agent-harness/tasks/checkTestability.ts +0 -256
- package/src/agent-harness/types.ts +0 -239
- package/src/mintTest.tsx +0 -138
- package/src/mintTestFilePreview.ts +0 -32
- package/src/mintTestNav.ts +0 -31
- package/src/mintTestTelemetry.ts +0 -101
- package/src/mintTestText.ts +0 -35
- package/src/mintTestTree.ts +0 -134
- package/src/mintTestUi.tsx +0 -1378
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
export type HarnessAgent = 'claude' | 'codex';
|
|
4
|
-
|
|
5
|
-
export interface DocsPage {
|
|
6
|
-
file: string;
|
|
7
|
-
blockCount: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type CommandStatus = 'passed' | 'failed' | 'cancelled';
|
|
11
|
-
|
|
12
|
-
export interface CommandResult {
|
|
13
|
-
id: string;
|
|
14
|
-
phase: 'setup' | 'test';
|
|
15
|
-
status: CommandStatus;
|
|
16
|
-
executable: string;
|
|
17
|
-
args: string[];
|
|
18
|
-
cwd: string;
|
|
19
|
-
durationMs: number;
|
|
20
|
-
exitCode: number | null;
|
|
21
|
-
stdout: string;
|
|
22
|
-
stderr: string;
|
|
23
|
-
error?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type TaskStatus = 'passed' | 'failed' | 'agent_error' | 'cancelled';
|
|
27
|
-
|
|
28
|
-
export interface TaskResult {
|
|
29
|
-
id: string;
|
|
30
|
-
agent: HarnessAgent;
|
|
31
|
-
file: string;
|
|
32
|
-
directory: string;
|
|
33
|
-
generatedFiles: string[];
|
|
34
|
-
testable: boolean;
|
|
35
|
-
attempts: number;
|
|
36
|
-
tokens: number;
|
|
37
|
-
status: TaskStatus;
|
|
38
|
-
error?: string;
|
|
39
|
-
commands: CommandResult[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface CodeTestReport {
|
|
43
|
-
version: 1;
|
|
44
|
-
runId: string;
|
|
45
|
-
status: 'passed' | 'completed' | 'failed' | 'cancelled';
|
|
46
|
-
docsRoot: string;
|
|
47
|
-
outputDirectory: string;
|
|
48
|
-
reportPath: string;
|
|
49
|
-
agents: HarnessAgent[];
|
|
50
|
-
model: string;
|
|
51
|
-
selectedFiles: string[];
|
|
52
|
-
startedAt: string;
|
|
53
|
-
completedAt: string;
|
|
54
|
-
durationMs: number;
|
|
55
|
-
tasks: TaskResult[];
|
|
56
|
-
/** Set when the run finished but could not be saved to `.mintlify/test` history. */
|
|
57
|
-
historyError?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface TestRunSummary {
|
|
61
|
-
runId: string;
|
|
62
|
-
status: CodeTestReport['status'];
|
|
63
|
-
agents: HarnessAgent[];
|
|
64
|
-
model: string;
|
|
65
|
-
selectedFiles: string[];
|
|
66
|
-
startedAt: string;
|
|
67
|
-
completedAt: string;
|
|
68
|
-
durationMs: number;
|
|
69
|
-
reportPath: string;
|
|
70
|
-
outputDirectory: string;
|
|
71
|
-
counts: {
|
|
72
|
-
passed: number;
|
|
73
|
-
failed: number;
|
|
74
|
-
cancelled: number;
|
|
75
|
-
agentErrors: number;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface TestRunsIndex {
|
|
80
|
-
version: 1;
|
|
81
|
-
runs: TestRunSummary[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type TaskPhase =
|
|
85
|
-
| 'checking'
|
|
86
|
-
| 'checked'
|
|
87
|
-
| 'not_testable'
|
|
88
|
-
| 'generating'
|
|
89
|
-
| 'generated'
|
|
90
|
-
| 'agent_error'
|
|
91
|
-
| 'running'
|
|
92
|
-
| 'passed'
|
|
93
|
-
| 'failed'
|
|
94
|
-
| 'cancelled';
|
|
95
|
-
|
|
96
|
-
export interface TaskUpdate {
|
|
97
|
-
id: string;
|
|
98
|
-
agent: HarnessAgent;
|
|
99
|
-
file: string;
|
|
100
|
-
directory: string;
|
|
101
|
-
generatedFiles: string[];
|
|
102
|
-
phase: TaskPhase;
|
|
103
|
-
tokens: number;
|
|
104
|
-
/** Why the task ended in `agent_error` or `failed`; the first line is shown in the UI. */
|
|
105
|
-
error?: string;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface TestGenerationResult {
|
|
109
|
-
status: 'succeeded' | 'failed';
|
|
110
|
-
agent: HarnessAgent;
|
|
111
|
-
model: string;
|
|
112
|
-
attemptCount: number;
|
|
113
|
-
tokenCount: number;
|
|
114
|
-
durationMs: number;
|
|
115
|
-
generatedFileCount: number;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface RunSave {
|
|
119
|
-
version: 1;
|
|
120
|
-
runId: string;
|
|
121
|
-
agents: HarnessAgent[];
|
|
122
|
-
model: string;
|
|
123
|
-
selectedFiles: string[];
|
|
124
|
-
createdAt: string;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface RunCodeTestsOptions {
|
|
128
|
-
path: string;
|
|
129
|
-
agents: HarnessAgent[];
|
|
130
|
-
model: string;
|
|
131
|
-
selectedFiles: string[];
|
|
132
|
-
outputDirectory: string;
|
|
133
|
-
runId: string | null;
|
|
134
|
-
concurrency: number;
|
|
135
|
-
commandTimeoutMs: number;
|
|
136
|
-
signal: AbortSignal;
|
|
137
|
-
onTaskUpdate: (update: TaskUpdate) => void;
|
|
138
|
-
onGenerationResult?: (result: TestGenerationResult) => void;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const identifier = z.string().trim().min(1).max(120);
|
|
142
|
-
|
|
143
|
-
const commandSchema = z
|
|
144
|
-
.object({
|
|
145
|
-
id: identifier.optional(),
|
|
146
|
-
executable: z.string().trim().min(1).max(512),
|
|
147
|
-
args: z.array(z.string().max(10_000)).max(100),
|
|
148
|
-
cwd: z.string().max(1_024).optional(),
|
|
149
|
-
timeoutMs: z
|
|
150
|
-
.number()
|
|
151
|
-
.int()
|
|
152
|
-
.min(1_000)
|
|
153
|
-
.max(30 * 60 * 1_000)
|
|
154
|
-
.optional(),
|
|
155
|
-
})
|
|
156
|
-
.strict();
|
|
157
|
-
|
|
158
|
-
const manifestFileSchema = z
|
|
159
|
-
.object({
|
|
160
|
-
version: z.literal(1),
|
|
161
|
-
generatedFiles: z.array(z.string().min(1).max(1_024)).max(1_000),
|
|
162
|
-
setupCommands: z.array(commandSchema).max(50),
|
|
163
|
-
testCommands: z.array(commandSchema).max(200),
|
|
164
|
-
})
|
|
165
|
-
.strict();
|
|
166
|
-
|
|
167
|
-
type ManifestFile = z.infer<typeof manifestFileSchema>;
|
|
168
|
-
type ManifestFileCommand = ManifestFile['testCommands'][number];
|
|
169
|
-
|
|
170
|
-
export type ManifestCommand = Omit<ManifestFileCommand, 'id'> & { id: string };
|
|
171
|
-
|
|
172
|
-
export interface GeneratedTestManifest {
|
|
173
|
-
version: 1;
|
|
174
|
-
generatedFiles: string[];
|
|
175
|
-
setupCommands: ManifestCommand[];
|
|
176
|
-
testCommands: ManifestCommand[];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Agents often copy the manifest example verbatim and omit command ids. The id only needs to be
|
|
181
|
-
* unique within the manifest, so assign `setup-N` / `test-N` instead of rejecting the whole page.
|
|
182
|
-
*/
|
|
183
|
-
export function assignCommandIds(manifest: ManifestFile): GeneratedTestManifest {
|
|
184
|
-
const taken = new Set(
|
|
185
|
-
[...manifest.setupCommands, ...manifest.testCommands]
|
|
186
|
-
.map((command) => command.id)
|
|
187
|
-
.filter((id): id is string => id !== undefined)
|
|
188
|
-
);
|
|
189
|
-
const withIds = (commands: ManifestFileCommand[], phase: 'setup' | 'test'): ManifestCommand[] => {
|
|
190
|
-
let next = 1;
|
|
191
|
-
return commands.map((command) => {
|
|
192
|
-
if (command.id !== undefined) return { ...command, id: command.id };
|
|
193
|
-
let id = `${phase}-${next}`;
|
|
194
|
-
while (taken.has(id)) {
|
|
195
|
-
next += 1;
|
|
196
|
-
id = `${phase}-${next}`;
|
|
197
|
-
}
|
|
198
|
-
taken.add(id);
|
|
199
|
-
next += 1;
|
|
200
|
-
return { ...command, id };
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
return {
|
|
204
|
-
version: manifest.version,
|
|
205
|
-
generatedFiles: manifest.generatedFiles,
|
|
206
|
-
setupCommands: withIds(manifest.setupCommands, 'setup'),
|
|
207
|
-
testCommands: withIds(manifest.testCommands, 'test'),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export const manifestSchema = manifestFileSchema.transform(assignCommandIds);
|
|
212
|
-
|
|
213
|
-
export const testCheckSchema = z
|
|
214
|
-
.object({
|
|
215
|
-
version: z.literal(1),
|
|
216
|
-
testable: z.boolean(),
|
|
217
|
-
reason: z
|
|
218
|
-
.string()
|
|
219
|
-
.max(2_000)
|
|
220
|
-
.optional()
|
|
221
|
-
.transform((value) => (value?.trim() ? value.trim() : undefined)),
|
|
222
|
-
})
|
|
223
|
-
.strict();
|
|
224
|
-
|
|
225
|
-
export type TestCheck = z.infer<typeof testCheckSchema>;
|
|
226
|
-
|
|
227
|
-
export interface GeneratedTask {
|
|
228
|
-
id: string;
|
|
229
|
-
agent: HarnessAgent;
|
|
230
|
-
docsRoot: string;
|
|
231
|
-
file: string;
|
|
232
|
-
directory: string;
|
|
233
|
-
attempts: number;
|
|
234
|
-
tokens: number;
|
|
235
|
-
testable?: boolean;
|
|
236
|
-
manifest?: GeneratedTestManifest;
|
|
237
|
-
generatedFiles?: string[];
|
|
238
|
-
error?: string;
|
|
239
|
-
}
|
package/src/mintTest.tsx
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
5
|
-
DEFAULT_CONCURRENCY,
|
|
6
|
-
clearRunSave,
|
|
7
|
-
countReport,
|
|
8
|
-
discoverCodeBlocks,
|
|
9
|
-
loadLatestTestRun,
|
|
10
|
-
loadRunSave,
|
|
11
|
-
runCodeTests,
|
|
12
|
-
type RunSave,
|
|
13
|
-
} from './agent-harness/index.js';
|
|
14
|
-
import { CMD_EXEC_PATH, findDocsRoot, isAI, terminate } from './helpers.js';
|
|
15
|
-
import { getAccessToken } from './keyring.js';
|
|
16
|
-
import { fileInNavPaths, loadDocsNavScope } from './mintTestNav.js';
|
|
17
|
-
import {
|
|
18
|
-
trackMintTestGenerationResult,
|
|
19
|
-
trackMintTestRunCompleted,
|
|
20
|
-
trackMintTestRunStarted,
|
|
21
|
-
} from './mintTestTelemetry.js';
|
|
22
|
-
import { firstLine, taskProblemLabel } from './mintTestText.js';
|
|
23
|
-
import { formatDuration, runMintTestUi } from './mintTestUi.js';
|
|
24
|
-
|
|
25
|
-
export async function mintTestHandler(): Promise<void> {
|
|
26
|
-
try {
|
|
27
|
-
const accessToken = await getAccessToken().catch(() => null);
|
|
28
|
-
if (!accessToken) {
|
|
29
|
-
process.stderr.write('not logged in. Run `mint login` to authenticate.\n');
|
|
30
|
-
await terminate(1);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const targetPath = await findDocsRoot(CMD_EXEC_PATH);
|
|
34
|
-
const interactive =
|
|
35
|
-
process.stdin.isTTY === true &&
|
|
36
|
-
process.stdout.isTTY === true &&
|
|
37
|
-
!isAI() &&
|
|
38
|
-
!process.env.CI &&
|
|
39
|
-
process.env.CLI_TEST_MODE !== 'true';
|
|
40
|
-
|
|
41
|
-
const { docsRoot, pages: discoveredPages } = await discoverCodeBlocks(targetPath);
|
|
42
|
-
const scope = await loadDocsNavScope(docsRoot);
|
|
43
|
-
const pages = scope
|
|
44
|
-
? discoveredPages.filter((page) => fileInNavPaths(page.file, scope.navPaths))
|
|
45
|
-
: discoveredPages;
|
|
46
|
-
const autoSelectedFiles = (
|
|
47
|
-
scope ? pages.filter((page) => fileInNavPaths(page.file, scope.autoSelectedPaths)) : pages
|
|
48
|
-
).map((page) => page.file);
|
|
49
|
-
const totalPages = scope ? scope.navPaths.size : pages.length;
|
|
50
|
-
const outputDirectory = path.join(docsRoot, 'tests', 'mint-test');
|
|
51
|
-
|
|
52
|
-
let save: RunSave | null = await loadRunSave(outputDirectory);
|
|
53
|
-
if (save) {
|
|
54
|
-
const known = new Set(pages.map((page) => page.file));
|
|
55
|
-
const selectedFiles = save.selectedFiles.filter((file) => known.has(file));
|
|
56
|
-
if (selectedFiles.length === 0) {
|
|
57
|
-
await clearRunSave(outputDirectory);
|
|
58
|
-
save = null;
|
|
59
|
-
} else {
|
|
60
|
-
save = { ...save, selectedFiles };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const lastReport = await loadLatestTestRun(docsRoot);
|
|
64
|
-
|
|
65
|
-
if (interactive) {
|
|
66
|
-
const exitCode = await runMintTestUi({
|
|
67
|
-
targetPath,
|
|
68
|
-
docsRoot,
|
|
69
|
-
pages,
|
|
70
|
-
autoSelectedFiles,
|
|
71
|
-
totalPages,
|
|
72
|
-
save,
|
|
73
|
-
lastReport,
|
|
74
|
-
});
|
|
75
|
-
await terminate(exitCode);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const agents = save?.agents ?? ['claude'];
|
|
80
|
-
const model = save?.model ?? 'claude-opus-4-8';
|
|
81
|
-
const selectedFiles = save?.selectedFiles ?? autoSelectedFiles;
|
|
82
|
-
const resumed = save !== null;
|
|
83
|
-
if (save) process.stdout.write(`resuming run ${save.runId}\n`);
|
|
84
|
-
void trackMintTestRunStarted({
|
|
85
|
-
mode: 'non_interactive',
|
|
86
|
-
resumed,
|
|
87
|
-
agents,
|
|
88
|
-
model,
|
|
89
|
-
selectedPageCount: new Set(selectedFiles).size,
|
|
90
|
-
totalPageCount: totalPages,
|
|
91
|
-
});
|
|
92
|
-
const report = await runCodeTests({
|
|
93
|
-
path: targetPath,
|
|
94
|
-
agents,
|
|
95
|
-
model,
|
|
96
|
-
selectedFiles,
|
|
97
|
-
outputDirectory,
|
|
98
|
-
runId: save?.runId ?? null,
|
|
99
|
-
concurrency: DEFAULT_CONCURRENCY,
|
|
100
|
-
commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
|
|
101
|
-
signal: new AbortController().signal,
|
|
102
|
-
onTaskUpdate: () => {},
|
|
103
|
-
onGenerationResult: (result) => {
|
|
104
|
-
void trackMintTestGenerationResult({
|
|
105
|
-
...result,
|
|
106
|
-
mode: 'non_interactive',
|
|
107
|
-
resumed,
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
await trackMintTestRunCompleted(report, {
|
|
112
|
-
mode: 'non_interactive',
|
|
113
|
-
resumed,
|
|
114
|
-
totalPageCount: totalPages,
|
|
115
|
-
});
|
|
116
|
-
const counts = countReport(report.tasks);
|
|
117
|
-
process.stdout.write(
|
|
118
|
-
`mint test ${report.status}: ${counts.passed} passed, ${counts.failed} failed, ${counts.agentErrors} agent errors\n`
|
|
119
|
-
);
|
|
120
|
-
process.stdout.write(`finished in ${formatDuration(report.durationMs)}\n`);
|
|
121
|
-
process.stdout.write(`report: ${report.reportPath}\n`);
|
|
122
|
-
for (const task of report.tasks) {
|
|
123
|
-
if (task.status !== 'failed' && task.status !== 'agent_error') continue;
|
|
124
|
-
const reason = firstLine(task.error);
|
|
125
|
-
process.stdout.write(
|
|
126
|
-
` ✗ ${task.file}: ${taskProblemLabel(task.status)}${reason ? ` (${reason})` : ''}\n`
|
|
127
|
-
);
|
|
128
|
-
process.stdout.write(` output: ${task.directory}\n`);
|
|
129
|
-
}
|
|
130
|
-
if (report.historyError) process.stderr.write(`warning: ${report.historyError}\n`);
|
|
131
|
-
await terminate(report.status === 'passed' ? 0 : 1);
|
|
132
|
-
} catch (error) {
|
|
133
|
-
process.stderr.write(
|
|
134
|
-
`mint test failed: ${error instanceof Error ? error.message : 'unknown error'}\n`
|
|
135
|
-
);
|
|
136
|
-
await terminate(1);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
export const PREVIEW_BYTE_LIMIT = 64 * 1024;
|
|
4
|
-
|
|
5
|
-
export type FilePreview = { binary: true } | { binary: false; text: string; truncated: boolean };
|
|
6
|
-
|
|
7
|
-
export async function readFilePreview(filePath: string): Promise<FilePreview> {
|
|
8
|
-
const buffer = new Uint8Array(PREVIEW_BYTE_LIMIT + 1);
|
|
9
|
-
const file = await fs.open(filePath, 'r');
|
|
10
|
-
let bytesRead = 0;
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
while (bytesRead < buffer.length) {
|
|
14
|
-
const result = await file.read(buffer, bytesRead, buffer.length - bytesRead, bytesRead);
|
|
15
|
-
if (result.bytesRead === 0) break;
|
|
16
|
-
bytesRead += result.bytesRead;
|
|
17
|
-
}
|
|
18
|
-
} finally {
|
|
19
|
-
await file.close();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const contents = buffer.subarray(0, bytesRead);
|
|
23
|
-
if (contents.includes(0)) return { binary: true };
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
binary: false,
|
|
27
|
-
text: new TextDecoder()
|
|
28
|
-
.decode(contents.subarray(0, PREVIEW_BYTE_LIMIT))
|
|
29
|
-
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '�'),
|
|
30
|
-
truncated: bytesRead > PREVIEW_BYTE_LIMIT,
|
|
31
|
-
};
|
|
32
|
-
}
|
package/src/mintTestNav.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getAllPathsInDocsNav } from '@mintlify/common';
|
|
2
|
-
import { getConfigObj } from '@mintlify/prebuild';
|
|
3
|
-
import type { DocsConfig, NavigationConfig } from '@mintlify/validation';
|
|
4
|
-
|
|
5
|
-
export interface DocsNavScope {
|
|
6
|
-
navPaths: Set<string>;
|
|
7
|
-
autoSelectedPaths: Set<string>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function loadDocsNavScope(docsRoot: string): Promise<DocsNavScope | null> {
|
|
11
|
-
const config = (await getConfigObj(docsRoot, 'docs').catch(() => null)) as DocsConfig | null;
|
|
12
|
-
const navigation = config?.navigation;
|
|
13
|
-
if (!navigation) return null;
|
|
14
|
-
const navPaths = new Set(getAllPathsInDocsNav(navigation));
|
|
15
|
-
if (navPaths.size === 0) return null;
|
|
16
|
-
const firstLanguage =
|
|
17
|
-
'languages' in navigation ? (navigation.languages[0] as NavigationConfig | undefined) : null;
|
|
18
|
-
const autoSelectedPaths = firstLanguage ? new Set(getAllPathsInDocsNav(firstLanguage)) : navPaths;
|
|
19
|
-
return { navPaths, autoSelectedPaths };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function navCandidatesForFile(file: string): string[] {
|
|
23
|
-
const base = file.replace(/\.mdx?$/i, '');
|
|
24
|
-
const candidates = [base];
|
|
25
|
-
if (base.endsWith('/index')) candidates.push(base.slice(0, -'/index'.length));
|
|
26
|
-
return candidates;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function fileInNavPaths(file: string, paths: Set<string>): boolean {
|
|
30
|
-
return navCandidatesForFile(file).some((candidate) => paths.has(candidate));
|
|
31
|
-
}
|
package/src/mintTestTelemetry.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
countReport,
|
|
3
|
-
type CodeTestReport,
|
|
4
|
-
type HarnessAgent,
|
|
5
|
-
type TestGenerationResult,
|
|
6
|
-
} from './agent-harness/index.js';
|
|
7
|
-
import { trackEvent } from './telemetry/track.js';
|
|
8
|
-
|
|
9
|
-
export type MintTestMode = 'interactive' | 'non_interactive';
|
|
10
|
-
|
|
11
|
-
export interface MintTestRunStartedOptions {
|
|
12
|
-
mode: MintTestMode;
|
|
13
|
-
resumed: boolean;
|
|
14
|
-
agents: HarnessAgent[];
|
|
15
|
-
model: string;
|
|
16
|
-
selectedPageCount: number;
|
|
17
|
-
totalPageCount: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface MintTestRunCompletedOptions {
|
|
21
|
-
mode: MintTestMode;
|
|
22
|
-
resumed: boolean;
|
|
23
|
-
totalPageCount: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface MintTestGenerationResultOptions extends TestGenerationResult {
|
|
27
|
-
mode: MintTestMode;
|
|
28
|
-
resumed: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function trackMintTestRunStarted({
|
|
32
|
-
mode,
|
|
33
|
-
resumed,
|
|
34
|
-
agents,
|
|
35
|
-
model,
|
|
36
|
-
selectedPageCount,
|
|
37
|
-
totalPageCount,
|
|
38
|
-
}: MintTestRunStartedOptions): Promise<void> {
|
|
39
|
-
await trackEvent('cli.test.started', {
|
|
40
|
-
mode,
|
|
41
|
-
resumed,
|
|
42
|
-
agents,
|
|
43
|
-
model,
|
|
44
|
-
selected_page_count: selectedPageCount,
|
|
45
|
-
total_page_count: totalPageCount,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function trackMintTestRunCompleted(
|
|
50
|
-
report: CodeTestReport,
|
|
51
|
-
{ mode, resumed, totalPageCount }: MintTestRunCompletedOptions
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
const counts = countReport(report.tasks);
|
|
54
|
-
const skippedCheckCount = report.tasks.filter(
|
|
55
|
-
(task) => !task.testable && task.status === 'passed'
|
|
56
|
-
).length;
|
|
57
|
-
const tokenCount = report.tasks.reduce((total, task) => total + task.tokens, 0);
|
|
58
|
-
const runnableCheckCount = report.tasks.filter((task) => task.testable).length;
|
|
59
|
-
|
|
60
|
-
await trackEvent('cli.test.completed', {
|
|
61
|
-
mode,
|
|
62
|
-
resumed,
|
|
63
|
-
status: report.status,
|
|
64
|
-
agents: report.agents,
|
|
65
|
-
model: report.model,
|
|
66
|
-
selected_page_count: new Set(report.selectedFiles).size,
|
|
67
|
-
total_page_count: totalPageCount,
|
|
68
|
-
total_check_count: report.tasks.length,
|
|
69
|
-
runnable_check_count: runnableCheckCount,
|
|
70
|
-
passed_check_count: counts.passed - skippedCheckCount,
|
|
71
|
-
failed_check_count: counts.failed,
|
|
72
|
-
skipped_check_count: skippedCheckCount,
|
|
73
|
-
agent_error_check_count: counts.agentErrors,
|
|
74
|
-
cancelled_check_count: counts.cancelled,
|
|
75
|
-
token_count: tokenCount,
|
|
76
|
-
duration_ms: report.durationMs,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function trackMintTestGenerationResult({
|
|
81
|
-
status,
|
|
82
|
-
mode,
|
|
83
|
-
resumed,
|
|
84
|
-
agent,
|
|
85
|
-
model,
|
|
86
|
-
attemptCount,
|
|
87
|
-
tokenCount,
|
|
88
|
-
durationMs,
|
|
89
|
-
generatedFileCount,
|
|
90
|
-
}: MintTestGenerationResultOptions): Promise<void> {
|
|
91
|
-
await trackEvent(`cli.test.generation.${status}`, {
|
|
92
|
-
mode,
|
|
93
|
-
resumed,
|
|
94
|
-
agent,
|
|
95
|
-
model,
|
|
96
|
-
attempt_count: attemptCount,
|
|
97
|
-
token_count: tokenCount,
|
|
98
|
-
duration_ms: durationMs,
|
|
99
|
-
generated_file_count: generatedFileCount,
|
|
100
|
-
});
|
|
101
|
-
}
|
package/src/mintTestText.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export interface FooterSegment {
|
|
2
|
-
key: string;
|
|
3
|
-
action: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Split a footer hint such as `↑/↓/j/k move · Enter exit` into key/action pairs so the key can
|
|
8
|
-
* be rendered bold and the action plain.
|
|
9
|
-
*/
|
|
10
|
-
export function footerSegments(text: string): FooterSegment[] {
|
|
11
|
-
return text
|
|
12
|
-
.split(' · ')
|
|
13
|
-
.map((segment) => segment.trim())
|
|
14
|
-
.filter((segment) => segment.length > 0)
|
|
15
|
-
.map((segment) => {
|
|
16
|
-
const split = segment.indexOf(' ');
|
|
17
|
-
if (split === -1) return { key: segment, action: '' };
|
|
18
|
-
return { key: segment.slice(0, split), action: segment.slice(split + 1).trim() };
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** First non-empty line of a multi-line message, trimmed to fit a terminal row. */
|
|
23
|
-
export function firstLine(text: string | undefined, maxLength = 160): string {
|
|
24
|
-
if (!text) return '';
|
|
25
|
-
const line = text
|
|
26
|
-
.split(/\r?\n/)
|
|
27
|
-
.map((candidate) => candidate.trim())
|
|
28
|
-
.find((candidate) => candidate.length > 0);
|
|
29
|
-
if (!line) return '';
|
|
30
|
-
return line.length > maxLength ? `${line.slice(0, maxLength - 1)}…` : line;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function taskProblemLabel(status: 'failed' | 'agent_error'): string {
|
|
34
|
-
return status === 'agent_error' ? 'test could not be generated' : 'tests failed';
|
|
35
|
-
}
|
package/src/mintTestTree.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import type { DocsPage } from './agent-harness/index.js';
|
|
2
|
-
|
|
3
|
-
export type ScopeSelectionState = 'all' | 'some' | 'none';
|
|
4
|
-
|
|
5
|
-
export interface ScopeTreeRow {
|
|
6
|
-
id: string;
|
|
7
|
-
kind: 'directory' | 'file';
|
|
8
|
-
label: string;
|
|
9
|
-
depth: number;
|
|
10
|
-
file?: string;
|
|
11
|
-
blockCount: number;
|
|
12
|
-
selectableFiles: string[];
|
|
13
|
-
selection: ScopeSelectionState;
|
|
14
|
-
expanded?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface TreeNode {
|
|
18
|
-
id: string;
|
|
19
|
-
kind: 'directory' | 'file';
|
|
20
|
-
label: string;
|
|
21
|
-
file?: string;
|
|
22
|
-
blockCount: number;
|
|
23
|
-
children: TreeNode[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function buildTree(pages: DocsPage[]): TreeNode {
|
|
27
|
-
const root: TreeNode = { id: 'dir:', kind: 'directory', label: '.', blockCount: 0, children: [] };
|
|
28
|
-
const directories = new Map<string, TreeNode>([['', root]]);
|
|
29
|
-
|
|
30
|
-
for (const page of pages) {
|
|
31
|
-
const parts = page.file.split('/');
|
|
32
|
-
let parent = root;
|
|
33
|
-
let directoryPath = '';
|
|
34
|
-
for (const part of parts.slice(0, -1)) {
|
|
35
|
-
directoryPath = directoryPath ? `${directoryPath}/${part}` : part;
|
|
36
|
-
let directory = directories.get(directoryPath);
|
|
37
|
-
if (!directory) {
|
|
38
|
-
directory = {
|
|
39
|
-
id: `dir:${directoryPath}`,
|
|
40
|
-
kind: 'directory',
|
|
41
|
-
label: part,
|
|
42
|
-
blockCount: 0,
|
|
43
|
-
children: [],
|
|
44
|
-
};
|
|
45
|
-
directories.set(directoryPath, directory);
|
|
46
|
-
parent.children.push(directory);
|
|
47
|
-
}
|
|
48
|
-
parent = directory;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
parent.children.push({
|
|
52
|
-
id: `file:${page.file}`,
|
|
53
|
-
kind: 'file',
|
|
54
|
-
label: parts[parts.length - 1] ?? page.file,
|
|
55
|
-
file: page.file,
|
|
56
|
-
blockCount: page.blockCount,
|
|
57
|
-
children: [],
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const sumBlocks = (node: TreeNode): number => {
|
|
62
|
-
if (node.kind === 'file') return node.blockCount;
|
|
63
|
-
node.blockCount = node.children.reduce((total, child) => total + sumBlocks(child), 0);
|
|
64
|
-
return node.blockCount;
|
|
65
|
-
};
|
|
66
|
-
sumBlocks(root);
|
|
67
|
-
|
|
68
|
-
const sortChildren = (node: TreeNode): void => {
|
|
69
|
-
node.children.sort((left, right) => {
|
|
70
|
-
if (left.kind !== right.kind) return left.kind === 'directory' ? -1 : 1;
|
|
71
|
-
return left.label.localeCompare(right.label);
|
|
72
|
-
});
|
|
73
|
-
for (const child of node.children) sortChildren(child);
|
|
74
|
-
};
|
|
75
|
-
sortChildren(root);
|
|
76
|
-
return root;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function descendantFiles(node: TreeNode): string[] {
|
|
80
|
-
if (node.kind === 'file') return node.file ? [node.file] : [];
|
|
81
|
-
return node.children.flatMap(descendantFiles);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function selectionState(files: string[], selectedFiles: ReadonlySet<string>): ScopeSelectionState {
|
|
85
|
-
if (files.length === 0) return 'none';
|
|
86
|
-
const selectedCount = files.filter((file) => selectedFiles.has(file)).length;
|
|
87
|
-
if (selectedCount === 0) return 'none';
|
|
88
|
-
return selectedCount === files.length ? 'all' : 'some';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function visibleScopeRows(
|
|
92
|
-
pages: DocsPage[],
|
|
93
|
-
expandedDirectories: ReadonlySet<string>,
|
|
94
|
-
selectedFiles: ReadonlySet<string>
|
|
95
|
-
): ScopeTreeRow[] {
|
|
96
|
-
const root = buildTree(pages);
|
|
97
|
-
const rows: ScopeTreeRow[] = [];
|
|
98
|
-
|
|
99
|
-
const visit = (node: TreeNode, depth: number) => {
|
|
100
|
-
const files = descendantFiles(node);
|
|
101
|
-
const expanded = node.kind === 'directory' ? expandedDirectories.has(node.id) : undefined;
|
|
102
|
-
rows.push({
|
|
103
|
-
id: node.id,
|
|
104
|
-
kind: node.kind,
|
|
105
|
-
label: node.label,
|
|
106
|
-
depth,
|
|
107
|
-
...(node.file ? { file: node.file } : {}),
|
|
108
|
-
blockCount: node.blockCount,
|
|
109
|
-
selectableFiles: files,
|
|
110
|
-
selection: selectionState(files, selectedFiles),
|
|
111
|
-
...(expanded !== undefined ? { expanded } : {}),
|
|
112
|
-
});
|
|
113
|
-
if (node.kind === 'directory' && expanded) {
|
|
114
|
-
for (const child of node.children) visit(child, depth + 1);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
for (const child of root.children) visit(child, 0);
|
|
119
|
-
return rows;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function toggleScopeSelection(
|
|
123
|
-
selectedFiles: ReadonlySet<string>,
|
|
124
|
-
row: ScopeTreeRow
|
|
125
|
-
): Set<string> {
|
|
126
|
-
const next = new Set(selectedFiles);
|
|
127
|
-
if (row.selectableFiles.length === 0) return next;
|
|
128
|
-
const shouldSelect = row.selectableFiles.some((file) => !next.has(file));
|
|
129
|
-
for (const file of row.selectableFiles) {
|
|
130
|
-
if (shouldSelect) next.add(file);
|
|
131
|
-
else next.delete(file);
|
|
132
|
-
}
|
|
133
|
-
return next;
|
|
134
|
-
}
|