@mintlify/cli 4.0.1469 → 4.0.1471
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/__test__/mintTestOutput.test.ts +62 -0
- package/bin/agent-harness/generateTestCode.js +31 -2
- package/bin/agent-harness/index.js +2 -1
- package/bin/mintTest.js +26 -3
- package/bin/mintTestTelemetry.js +63 -0
- package/bin/mintTestUi.js +19 -1
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/agent-harness/generateTestCode.ts +39 -5
- package/src/agent-harness/index.ts +3 -0
- package/src/agent-harness/types.ts +11 -0
- package/src/mintTest.tsx +34 -3
- package/src/mintTestTelemetry.ts +101 -0
- package/src/mintTestUi.tsx +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintlify/cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1471",
|
|
4
4
|
"description": "The Mintlify CLI",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@inquirer/prompts": "7.9.0",
|
|
48
|
-
"@mintlify/common": "1.0.
|
|
49
|
-
"@mintlify/link-rot": "3.0.
|
|
48
|
+
"@mintlify/common": "1.0.1126",
|
|
49
|
+
"@mintlify/link-rot": "3.0.1328",
|
|
50
50
|
"@mintlify/models": "0.0.351",
|
|
51
|
-
"@mintlify/prebuild": "1.0.
|
|
52
|
-
"@mintlify/previewing": "4.0.
|
|
51
|
+
"@mintlify/prebuild": "1.0.1280",
|
|
52
|
+
"@mintlify/previewing": "4.0.1352",
|
|
53
53
|
"@mintlify/validation": "0.1.844",
|
|
54
54
|
"adm-zip": "0.6.0",
|
|
55
55
|
"chalk": "5.2.0",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"keytar": "7.9.0"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@mintlify/editor": "0.0.
|
|
89
|
+
"@mintlify/editor": "0.0.356",
|
|
90
90
|
"@mintlify/ts-config": "2.0.2",
|
|
91
91
|
"@tsconfig/recommended": "1.0.2",
|
|
92
92
|
"@types/detect-port": "1.3.2",
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"vitest": "2.1.9",
|
|
106
106
|
"vitest-mock-process": "1.0.4"
|
|
107
107
|
},
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "c1d2437e2890e97075390d6160f6bcf72541e574"
|
|
109
109
|
}
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
} from './manifest.js';
|
|
13
13
|
import { mapWithConcurrency } from './mapWithConcurrency.js';
|
|
14
14
|
import { checkFileTestability } from './tasks/checkTestability.js';
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
GeneratedTask,
|
|
17
|
+
GeneratedTestManifest,
|
|
18
|
+
TaskUpdate,
|
|
19
|
+
TestGenerationResult,
|
|
20
|
+
} from './types.js';
|
|
16
21
|
|
|
17
22
|
const SCAFFOLD_FILENAMES = new Set([MANIFEST_FILENAME, 'package.json']);
|
|
18
23
|
const IGNORED_GENERATED_DIRECTORIES = new Set(['.git', 'node_modules']);
|
|
@@ -228,6 +233,7 @@ export async function generateTestCode({
|
|
|
228
233
|
concurrency,
|
|
229
234
|
signal,
|
|
230
235
|
onTaskUpdate,
|
|
236
|
+
onGenerationResult,
|
|
231
237
|
}: {
|
|
232
238
|
tasks: GeneratedTask[];
|
|
233
239
|
docsRoot: string;
|
|
@@ -235,6 +241,7 @@ export async function generateTestCode({
|
|
|
235
241
|
concurrency: number;
|
|
236
242
|
signal: AbortSignal;
|
|
237
243
|
onTaskUpdate: (update: TaskUpdate) => void;
|
|
244
|
+
onGenerationResult?: (result: TestGenerationResult) => void;
|
|
238
245
|
}): Promise<GeneratedTask[]> {
|
|
239
246
|
const generateTask = async (task: GeneratedTask): Promise<GeneratedTask> => {
|
|
240
247
|
if (task.error || !task.testable) return task;
|
|
@@ -269,8 +276,18 @@ export async function generateTestCode({
|
|
|
269
276
|
return { ...task, manifest: existing };
|
|
270
277
|
}
|
|
271
278
|
|
|
279
|
+
const startedAt = Date.now();
|
|
280
|
+
const startingTokens = tokens;
|
|
281
|
+
let generationAttempts = 0;
|
|
282
|
+
const emitGenerationResult = (result: TestGenerationResult) => {
|
|
283
|
+
try {
|
|
284
|
+
onGenerationResult?.(result);
|
|
285
|
+
} catch {}
|
|
286
|
+
};
|
|
287
|
+
|
|
272
288
|
for (let attempt = 1; attempt <= 2 && !signal.aborted; attempt++) {
|
|
273
289
|
attempts += 1;
|
|
290
|
+
generationAttempts += 1;
|
|
274
291
|
if (attempt === 1) emit('generating', tokens);
|
|
275
292
|
try {
|
|
276
293
|
const prompt = buildTaskPrompt(docsRoot, task.file, task.directory);
|
|
@@ -301,11 +318,19 @@ export async function generateTestCode({
|
|
|
301
318
|
throw new Error([`${MANIFEST_FILENAME} has problems: ${first}`, ...rest].join('\n'));
|
|
302
319
|
}
|
|
303
320
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
tokens,
|
|
307
|
-
manifest.generatedFiles.map((file) => path.resolve(task.directory, file))
|
|
321
|
+
const generatedFiles = manifest.generatedFiles.map((file) =>
|
|
322
|
+
path.resolve(task.directory, file)
|
|
308
323
|
);
|
|
324
|
+
emit('generated', tokens, generatedFiles);
|
|
325
|
+
emitGenerationResult({
|
|
326
|
+
status: 'succeeded',
|
|
327
|
+
agent: task.agent,
|
|
328
|
+
model,
|
|
329
|
+
attemptCount: generationAttempts,
|
|
330
|
+
tokenCount: tokens - startingTokens,
|
|
331
|
+
durationMs: Date.now() - startedAt,
|
|
332
|
+
generatedFileCount: generatedFiles.length,
|
|
333
|
+
});
|
|
309
334
|
return { ...task, attempts, tokens, manifest };
|
|
310
335
|
} catch (error) {
|
|
311
336
|
lastError = error instanceof Error ? error.message : String(error);
|
|
@@ -318,6 +343,15 @@ export async function generateTestCode({
|
|
|
318
343
|
return { ...task, attempts, tokens, generatedFiles, error: 'run cancelled' };
|
|
319
344
|
}
|
|
320
345
|
const generatedFiles = await discoverGeneratedFiles(task.directory);
|
|
346
|
+
emitGenerationResult({
|
|
347
|
+
status: 'failed',
|
|
348
|
+
agent: task.agent,
|
|
349
|
+
model,
|
|
350
|
+
attemptCount: generationAttempts,
|
|
351
|
+
tokenCount: tokens - startingTokens,
|
|
352
|
+
durationMs: Date.now() - startedAt,
|
|
353
|
+
generatedFileCount: generatedFiles.length,
|
|
354
|
+
});
|
|
321
355
|
const detailsPath = await writeErrorDetails(task.directory, attemptErrors);
|
|
322
356
|
const error = detailsPath ? `${lastError}\nDetails: ${detailsPath}` : lastError;
|
|
323
357
|
emit('agent_error', tokens, generatedFiles, error);
|
|
@@ -30,6 +30,7 @@ export type {
|
|
|
30
30
|
TaskResult,
|
|
31
31
|
TaskStatus,
|
|
32
32
|
TaskUpdate,
|
|
33
|
+
TestGenerationResult,
|
|
33
34
|
TestRunSummary,
|
|
34
35
|
TestRunsIndex,
|
|
35
36
|
} from './types.js';
|
|
@@ -45,6 +46,7 @@ export async function runCodeTests({
|
|
|
45
46
|
commandTimeoutMs,
|
|
46
47
|
signal,
|
|
47
48
|
onTaskUpdate,
|
|
49
|
+
onGenerationResult,
|
|
48
50
|
}: RunCodeTestsOptions): Promise<CodeTestReport> {
|
|
49
51
|
const startedAt = new Date();
|
|
50
52
|
const uniqueAgents = [...new Set(agents)];
|
|
@@ -85,6 +87,7 @@ export async function runCodeTests({
|
|
|
85
87
|
concurrency,
|
|
86
88
|
signal,
|
|
87
89
|
onTaskUpdate,
|
|
90
|
+
onGenerationResult,
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
const results = await executeCodeBlocks({
|
|
@@ -105,6 +105,16 @@ export interface TaskUpdate {
|
|
|
105
105
|
error?: string;
|
|
106
106
|
}
|
|
107
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
|
+
|
|
108
118
|
export interface RunSave {
|
|
109
119
|
version: 1;
|
|
110
120
|
runId: string;
|
|
@@ -125,6 +135,7 @@ export interface RunCodeTestsOptions {
|
|
|
125
135
|
commandTimeoutMs: number;
|
|
126
136
|
signal: AbortSignal;
|
|
127
137
|
onTaskUpdate: (update: TaskUpdate) => void;
|
|
138
|
+
onGenerationResult?: (result: TestGenerationResult) => void;
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
const identifier = z.string().trim().min(1).max(120);
|
package/src/mintTest.tsx
CHANGED
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
import { CMD_EXEC_PATH, findDocsRoot, isAI, terminate } from './helpers.js';
|
|
15
15
|
import { getAccessToken } from './keyring.js';
|
|
16
16
|
import { fileInNavPaths, loadDocsNavScope } from './mintTestNav.js';
|
|
17
|
+
import {
|
|
18
|
+
trackMintTestGenerationResult,
|
|
19
|
+
trackMintTestRunCompleted,
|
|
20
|
+
trackMintTestRunStarted,
|
|
21
|
+
} from './mintTestTelemetry.js';
|
|
17
22
|
import { firstLine, taskProblemLabel } from './mintTestText.js';
|
|
18
23
|
import { formatDuration, runMintTestUi } from './mintTestUi.js';
|
|
19
24
|
|
|
@@ -41,6 +46,7 @@ export async function mintTestHandler(): Promise<void> {
|
|
|
41
46
|
const autoSelectedFiles = (
|
|
42
47
|
scope ? pages.filter((page) => fileInNavPaths(page.file, scope.autoSelectedPaths)) : pages
|
|
43
48
|
).map((page) => page.file);
|
|
49
|
+
const totalPages = scope ? scope.navPaths.size : pages.length;
|
|
44
50
|
const outputDirectory = path.join(docsRoot, 'tests', 'mint-test');
|
|
45
51
|
|
|
46
52
|
let save: RunSave | null = await loadRunSave(outputDirectory);
|
|
@@ -62,6 +68,7 @@ export async function mintTestHandler(): Promise<void> {
|
|
|
62
68
|
docsRoot,
|
|
63
69
|
pages,
|
|
64
70
|
autoSelectedFiles,
|
|
71
|
+
totalPages,
|
|
65
72
|
save,
|
|
66
73
|
lastReport,
|
|
67
74
|
});
|
|
@@ -69,18 +76,42 @@ export async function mintTestHandler(): Promise<void> {
|
|
|
69
76
|
return;
|
|
70
77
|
}
|
|
71
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;
|
|
72
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
|
+
});
|
|
73
92
|
const report = await runCodeTests({
|
|
74
93
|
path: targetPath,
|
|
75
|
-
agents
|
|
76
|
-
model
|
|
77
|
-
selectedFiles
|
|
94
|
+
agents,
|
|
95
|
+
model,
|
|
96
|
+
selectedFiles,
|
|
78
97
|
outputDirectory,
|
|
79
98
|
runId: save?.runId ?? null,
|
|
80
99
|
concurrency: DEFAULT_CONCURRENCY,
|
|
81
100
|
commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
|
|
82
101
|
signal: new AbortController().signal,
|
|
83
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,
|
|
84
115
|
});
|
|
85
116
|
const counts = countReport(report.tasks);
|
|
86
117
|
process.stdout.write(
|
|
@@ -0,0 +1,101 @@
|
|
|
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/mintTestUi.tsx
CHANGED
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
type TaskUpdate,
|
|
16
16
|
} from './agent-harness/index.js';
|
|
17
17
|
import { readFilePreview } from './mintTestFilePreview.js';
|
|
18
|
+
import {
|
|
19
|
+
trackMintTestGenerationResult,
|
|
20
|
+
trackMintTestRunCompleted,
|
|
21
|
+
trackMintTestRunStarted,
|
|
22
|
+
} from './mintTestTelemetry.js';
|
|
18
23
|
import { firstLine, footerSegments, taskProblemLabel } from './mintTestText.js';
|
|
19
24
|
import { toggleScopeSelection, visibleScopeRows, type ScopeTreeRow } from './mintTestTree.js';
|
|
20
25
|
|
|
@@ -25,6 +30,7 @@ interface MintTestUiProps {
|
|
|
25
30
|
docsRoot: string;
|
|
26
31
|
pages: DocsPage[];
|
|
27
32
|
autoSelectedFiles: string[];
|
|
33
|
+
totalPages: number;
|
|
28
34
|
save: RunSave | null;
|
|
29
35
|
lastReport: CodeTestReport | null;
|
|
30
36
|
onFinish: (exitCode: number) => void;
|
|
@@ -827,6 +833,7 @@ function MintTestApp({
|
|
|
827
833
|
docsRoot,
|
|
828
834
|
pages,
|
|
829
835
|
autoSelectedFiles,
|
|
836
|
+
totalPages,
|
|
830
837
|
save,
|
|
831
838
|
lastReport,
|
|
832
839
|
onFinish,
|
|
@@ -998,6 +1005,7 @@ function MintTestApp({
|
|
|
998
1005
|
runFiles: string[];
|
|
999
1006
|
}) => {
|
|
1000
1007
|
if (abortController.current) return;
|
|
1008
|
+
const resumed = runId !== null;
|
|
1001
1009
|
const controller = new AbortController();
|
|
1002
1010
|
abortController.current = controller;
|
|
1003
1011
|
setTaskPhases(new Map());
|
|
@@ -1025,6 +1033,14 @@ function MintTestApp({
|
|
|
1025
1033
|
);
|
|
1026
1034
|
setScreen('running');
|
|
1027
1035
|
setActiveRunStage('check');
|
|
1036
|
+
void trackMintTestRunStarted({
|
|
1037
|
+
mode: 'interactive',
|
|
1038
|
+
resumed,
|
|
1039
|
+
agents: runAgents,
|
|
1040
|
+
model: runModel,
|
|
1041
|
+
selectedPageCount: new Set(runFiles_).size,
|
|
1042
|
+
totalPageCount: totalPages,
|
|
1043
|
+
});
|
|
1028
1044
|
void runCodeTests({
|
|
1029
1045
|
path: targetPath,
|
|
1030
1046
|
agents: runAgents,
|
|
@@ -1035,6 +1051,13 @@ function MintTestApp({
|
|
|
1035
1051
|
concurrency: DEFAULT_CONCURRENCY,
|
|
1036
1052
|
commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
|
|
1037
1053
|
signal: controller.signal,
|
|
1054
|
+
onGenerationResult: (result) => {
|
|
1055
|
+
void trackMintTestGenerationResult({
|
|
1056
|
+
...result,
|
|
1057
|
+
mode: 'interactive',
|
|
1058
|
+
resumed,
|
|
1059
|
+
});
|
|
1060
|
+
},
|
|
1038
1061
|
onTaskUpdate: (update) => {
|
|
1039
1062
|
const phaseStage =
|
|
1040
1063
|
update.phase === 'running'
|
|
@@ -1060,6 +1083,11 @@ function MintTestApp({
|
|
|
1060
1083
|
},
|
|
1061
1084
|
})
|
|
1062
1085
|
.then((nextReport) => {
|
|
1086
|
+
void trackMintTestRunCompleted(nextReport, {
|
|
1087
|
+
mode: 'interactive',
|
|
1088
|
+
resumed,
|
|
1089
|
+
totalPageCount: totalPages,
|
|
1090
|
+
});
|
|
1063
1091
|
setReport(nextReport);
|
|
1064
1092
|
const failedFiles = nextReport.tasks
|
|
1065
1093
|
.filter((task) => task.status === 'failed' || task.status === 'agent_error')
|