@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.
@@ -79,6 +79,7 @@ it('includes partial generated files when generation fails', async () => {
79
79
  throw new Error('generation failed');
80
80
  });
81
81
  const updates: TaskUpdate[] = [];
82
+ const onGenerationResult = vi.fn();
82
83
 
83
84
  const generated = await generateTestCode({
84
85
  tasks: [generatedTask(directory)],
@@ -87,12 +88,22 @@ it('includes partial generated files when generation fails', async () => {
87
88
  concurrency: 1,
88
89
  signal: new AbortController().signal,
89
90
  onTaskUpdate: (update) => updates.push(update),
91
+ onGenerationResult,
90
92
  });
91
93
 
92
94
  expect(updates.at(-1)).toMatchObject({
93
95
  phase: 'agent_error',
94
96
  generatedFiles: [generatedFile],
95
97
  });
98
+ expect(onGenerationResult).toHaveBeenCalledWith({
99
+ status: 'failed',
100
+ agent: 'codex',
101
+ model: 'unused',
102
+ attemptCount: 2,
103
+ tokenCount: 0,
104
+ durationMs: expect.any(Number),
105
+ generatedFileCount: 1,
106
+ });
96
107
  const results = await executeCodeBlocks({
97
108
  tasks: generated,
98
109
  docsRoot: directory,
@@ -108,6 +119,57 @@ it('includes partial generated files when generation fails', async () => {
108
119
  });
109
120
  });
110
121
 
122
+ it('reports a successful generation run', async () => {
123
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-output-'));
124
+ temporaryDirectories.push(directory);
125
+ const generatedFile = path.join(directory, 'tests', 'generated.test.ts');
126
+ await fs.mkdir(path.dirname(generatedFile), { recursive: true });
127
+ await fs.writeFile(path.join(directory, 'package.json'), '{}\n');
128
+ agentMocks.codexRun.mockImplementation(async () => {
129
+ await fs.writeFile(generatedFile, 'export const generated = true;\n');
130
+ await fs.writeFile(
131
+ path.join(directory, 'mint-test.json'),
132
+ `${JSON.stringify({
133
+ version: 1,
134
+ generatedFiles: ['tests/generated.test.ts'],
135
+ setupCommands: [],
136
+ testCommands: [],
137
+ })}\n`
138
+ );
139
+ return {
140
+ finalResponse: 'done',
141
+ usage: {
142
+ input_tokens: 100,
143
+ cached_input_tokens: 25,
144
+ cache_write_input_tokens: 10,
145
+ output_tokens: 50,
146
+ reasoning_output_tokens: 15,
147
+ },
148
+ };
149
+ });
150
+ const onGenerationResult = vi.fn();
151
+
152
+ await generateTestCode({
153
+ tasks: [generatedTask(directory)],
154
+ docsRoot: directory,
155
+ model: 'gpt-test',
156
+ concurrency: 1,
157
+ signal: new AbortController().signal,
158
+ onTaskUpdate: () => {},
159
+ onGenerationResult,
160
+ });
161
+
162
+ expect(onGenerationResult).toHaveBeenCalledWith({
163
+ status: 'succeeded',
164
+ agent: 'codex',
165
+ model: 'gpt-test',
166
+ attemptCount: 1,
167
+ tokenCount: 200,
168
+ durationMs: expect.any(Number),
169
+ generatedFileCount: 1,
170
+ });
171
+ });
172
+
111
173
  it('includes partial generated files when generation is cancelled', async () => {
112
174
  const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-output-'));
113
175
  temporaryDirectories.push(directory);
@@ -214,7 +214,7 @@ export function checkTestability(_a) {
214
214
  });
215
215
  }
216
216
  export function generateTestCode(_a) {
217
- return __awaiter(this, arguments, void 0, function* ({ tasks, docsRoot, model, concurrency, signal, onTaskUpdate, }) {
217
+ return __awaiter(this, arguments, void 0, function* ({ tasks, docsRoot, model, concurrency, signal, onTaskUpdate, onGenerationResult, }) {
218
218
  const generateTask = (task) => __awaiter(this, void 0, void 0, function* () {
219
219
  if (task.error || !task.testable)
220
220
  return task;
@@ -230,8 +230,18 @@ export function generateTestCode(_a) {
230
230
  emit('generated', tokens, existing.generatedFiles.map((file) => path.resolve(task.directory, file)));
231
231
  return Object.assign(Object.assign({}, task), { manifest: existing });
232
232
  }
233
+ const startedAt = Date.now();
234
+ const startingTokens = tokens;
235
+ let generationAttempts = 0;
236
+ const emitGenerationResult = (result) => {
237
+ try {
238
+ onGenerationResult === null || onGenerationResult === void 0 ? void 0 : onGenerationResult(result);
239
+ }
240
+ catch (_a) { }
241
+ };
233
242
  for (let attempt = 1; attempt <= 2 && !signal.aborted; attempt++) {
234
243
  attempts += 1;
244
+ generationAttempts += 1;
235
245
  if (attempt === 1)
236
246
  emit('generating', tokens);
237
247
  try {
@@ -261,7 +271,17 @@ export function generateTestCode(_a) {
261
271
  const [first = '', ...rest] = errors;
262
272
  throw new Error([`${MANIFEST_FILENAME} has problems: ${first}`, ...rest].join('\n'));
263
273
  }
264
- emit('generated', tokens, manifest.generatedFiles.map((file) => path.resolve(task.directory, file)));
274
+ const generatedFiles = manifest.generatedFiles.map((file) => path.resolve(task.directory, file));
275
+ emit('generated', tokens, generatedFiles);
276
+ emitGenerationResult({
277
+ status: 'succeeded',
278
+ agent: task.agent,
279
+ model,
280
+ attemptCount: generationAttempts,
281
+ tokenCount: tokens - startingTokens,
282
+ durationMs: Date.now() - startedAt,
283
+ generatedFileCount: generatedFiles.length,
284
+ });
265
285
  return Object.assign(Object.assign({}, task), { attempts, tokens, manifest });
266
286
  }
267
287
  catch (error) {
@@ -275,6 +295,15 @@ export function generateTestCode(_a) {
275
295
  return Object.assign(Object.assign({}, task), { attempts, tokens, generatedFiles, error: 'run cancelled' });
276
296
  }
277
297
  const generatedFiles = yield discoverGeneratedFiles(task.directory);
298
+ emitGenerationResult({
299
+ status: 'failed',
300
+ agent: task.agent,
301
+ model,
302
+ attemptCount: generationAttempts,
303
+ tokenCount: tokens - startingTokens,
304
+ durationMs: Date.now() - startedAt,
305
+ generatedFileCount: generatedFiles.length,
306
+ });
278
307
  const detailsPath = yield writeErrorDetails(task.directory, attemptErrors);
279
308
  const error = detailsPath ? `${lastError}\nDetails: ${detailsPath}` : lastError;
280
309
  emit('agent_error', tokens, generatedFiles, error);
@@ -23,7 +23,7 @@ export { countReport } from './countReport.js';
23
23
  export { loadLatestTestRun, loadTestRuns } from './runHistory.js';
24
24
  export { clearRunSave, loadRunSave } from './runSave.js';
25
25
  export function runCodeTests(_a) {
26
- return __awaiter(this, arguments, void 0, function* ({ path: docsPath, agents, model, selectedFiles, outputDirectory, runId: savedRunId, concurrency, commandTimeoutMs, signal, onTaskUpdate, }) {
26
+ return __awaiter(this, arguments, void 0, function* ({ path: docsPath, agents, model, selectedFiles, outputDirectory, runId: savedRunId, concurrency, commandTimeoutMs, signal, onTaskUpdate, onGenerationResult, }) {
27
27
  const startedAt = new Date();
28
28
  const uniqueAgents = [...new Set(agents)];
29
29
  const { docsRoot, pages: allPages } = yield discoverCodeBlocks(docsPath);
@@ -55,6 +55,7 @@ export function runCodeTests(_a) {
55
55
  concurrency,
56
56
  signal,
57
57
  onTaskUpdate,
58
+ onGenerationResult,
58
59
  });
59
60
  const results = yield executeCodeBlocks({
60
61
  tasks: generated,
package/bin/mintTest.js CHANGED
@@ -12,6 +12,7 @@ import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, countRep
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';
15
+ import { trackMintTestGenerationResult, trackMintTestRunCompleted, trackMintTestRunStarted, } from './mintTestTelemetry.js';
15
16
  import { firstLine, taskProblemLabel } from './mintTestText.js';
16
17
  import { formatDuration, runMintTestUi } from './mintTestUi.js';
17
18
  export function mintTestHandler() {
@@ -36,6 +37,7 @@ export function mintTestHandler() {
36
37
  ? discoveredPages.filter((page) => fileInNavPaths(page.file, scope.navPaths))
37
38
  : discoveredPages;
38
39
  const autoSelectedFiles = (scope ? pages.filter((page) => fileInNavPaths(page.file, scope.autoSelectedPaths)) : pages).map((page) => page.file);
40
+ const totalPages = scope ? scope.navPaths.size : pages.length;
39
41
  const outputDirectory = path.join(docsRoot, 'tests', 'mint-test');
40
42
  let save = yield loadRunSave(outputDirectory);
41
43
  if (save) {
@@ -56,25 +58,46 @@ export function mintTestHandler() {
56
58
  docsRoot,
57
59
  pages,
58
60
  autoSelectedFiles,
61
+ totalPages,
59
62
  save,
60
63
  lastReport,
61
64
  });
62
65
  yield terminate(exitCode);
63
66
  return;
64
67
  }
68
+ const agents = (_a = save === null || save === void 0 ? void 0 : save.agents) !== null && _a !== void 0 ? _a : ['claude'];
69
+ const model = (_b = save === null || save === void 0 ? void 0 : save.model) !== null && _b !== void 0 ? _b : 'claude-opus-4-8';
70
+ const selectedFiles = (_c = save === null || save === void 0 ? void 0 : save.selectedFiles) !== null && _c !== void 0 ? _c : autoSelectedFiles;
71
+ const resumed = save !== null;
65
72
  if (save)
66
73
  process.stdout.write(`resuming run ${save.runId}\n`);
74
+ void trackMintTestRunStarted({
75
+ mode: 'non_interactive',
76
+ resumed,
77
+ agents,
78
+ model,
79
+ selectedPageCount: new Set(selectedFiles).size,
80
+ totalPageCount: totalPages,
81
+ });
67
82
  const report = yield runCodeTests({
68
83
  path: targetPath,
69
- agents: (_a = save === null || save === void 0 ? void 0 : save.agents) !== null && _a !== void 0 ? _a : ['claude'],
70
- model: (_b = save === null || save === void 0 ? void 0 : save.model) !== null && _b !== void 0 ? _b : 'claude-opus-4-8',
71
- selectedFiles: (_c = save === null || save === void 0 ? void 0 : save.selectedFiles) !== null && _c !== void 0 ? _c : autoSelectedFiles,
84
+ agents,
85
+ model,
86
+ selectedFiles,
72
87
  outputDirectory,
73
88
  runId: (_d = save === null || save === void 0 ? void 0 : save.runId) !== null && _d !== void 0 ? _d : null,
74
89
  concurrency: DEFAULT_CONCURRENCY,
75
90
  commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
76
91
  signal: new AbortController().signal,
77
92
  onTaskUpdate: () => { },
93
+ onGenerationResult: (result) => {
94
+ void trackMintTestGenerationResult(Object.assign(Object.assign({}, result), { mode: 'non_interactive', resumed }));
95
+ },
96
+ });
97
+ yield trackMintTestRunCompleted(report, {
98
+ mode: 'non_interactive',
99
+ resumed,
100
+ totalPageCount: totalPages,
78
101
  });
79
102
  const counts = countReport(report.tasks);
80
103
  process.stdout.write(`mint test ${report.status}: ${counts.passed} passed, ${counts.failed} failed, ${counts.agentErrors} agent errors\n`);
@@ -0,0 +1,63 @@
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 { countReport, } from './agent-harness/index.js';
11
+ import { trackEvent } from './telemetry/track.js';
12
+ export function trackMintTestRunStarted(_a) {
13
+ return __awaiter(this, arguments, void 0, function* ({ mode, resumed, agents, model, selectedPageCount, totalPageCount, }) {
14
+ yield trackEvent('cli.test.started', {
15
+ mode,
16
+ resumed,
17
+ agents,
18
+ model,
19
+ selected_page_count: selectedPageCount,
20
+ total_page_count: totalPageCount,
21
+ });
22
+ });
23
+ }
24
+ export function trackMintTestRunCompleted(report_1, _a) {
25
+ return __awaiter(this, arguments, void 0, function* (report, { mode, resumed, totalPageCount }) {
26
+ const counts = countReport(report.tasks);
27
+ const skippedCheckCount = report.tasks.filter((task) => !task.testable && task.status === 'passed').length;
28
+ const tokenCount = report.tasks.reduce((total, task) => total + task.tokens, 0);
29
+ const runnableCheckCount = report.tasks.filter((task) => task.testable).length;
30
+ yield trackEvent('cli.test.completed', {
31
+ mode,
32
+ resumed,
33
+ status: report.status,
34
+ agents: report.agents,
35
+ model: report.model,
36
+ selected_page_count: new Set(report.selectedFiles).size,
37
+ total_page_count: totalPageCount,
38
+ total_check_count: report.tasks.length,
39
+ runnable_check_count: runnableCheckCount,
40
+ passed_check_count: counts.passed - skippedCheckCount,
41
+ failed_check_count: counts.failed,
42
+ skipped_check_count: skippedCheckCount,
43
+ agent_error_check_count: counts.agentErrors,
44
+ cancelled_check_count: counts.cancelled,
45
+ token_count: tokenCount,
46
+ duration_ms: report.durationMs,
47
+ });
48
+ });
49
+ }
50
+ export function trackMintTestGenerationResult(_a) {
51
+ return __awaiter(this, arguments, void 0, function* ({ status, mode, resumed, agent, model, attemptCount, tokenCount, durationMs, generatedFileCount, }) {
52
+ yield trackEvent(`cli.test.generation.${status}`, {
53
+ mode,
54
+ resumed,
55
+ agent,
56
+ model,
57
+ attempt_count: attemptCount,
58
+ token_count: tokenCount,
59
+ duration_ms: durationMs,
60
+ generated_file_count: generatedFileCount,
61
+ });
62
+ });
63
+ }
package/bin/mintTestUi.js CHANGED
@@ -13,6 +13,7 @@ import path from 'node:path';
13
13
  import { useEffect, useMemo, useRef, useState } from 'react';
14
14
  import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, runCodeTests, } from './agent-harness/index.js';
15
15
  import { readFilePreview } from './mintTestFilePreview.js';
16
+ import { trackMintTestGenerationResult, trackMintTestRunCompleted, trackMintTestRunStarted, } from './mintTestTelemetry.js';
16
17
  import { firstLine, footerSegments, taskProblemLabel } from './mintTestText.js';
17
18
  import { toggleScopeSelection, visibleScopeRows } from './mintTestTree.js';
18
19
  const HARNESSES = [
@@ -375,7 +376,7 @@ function footerForScreen(screen, running, cancelled, activeRunStage, reviewingSa
375
376
  }
376
377
  return 'Enter exit';
377
378
  }
378
- function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, save, lastReport, onFinish, }) {
379
+ function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, totalPages, save, lastReport, onFinish, }) {
379
380
  var _a;
380
381
  const size = useTerminalSize();
381
382
  const files = useMemo(() => pages.map((page) => page.file), [pages]);
@@ -515,6 +516,7 @@ function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, save, las
515
516
  var _a, _b;
516
517
  if (abortController.current)
517
518
  return;
519
+ const resumed = runId !== null;
518
520
  const controller = new AbortController();
519
521
  abortController.current = controller;
520
522
  setTaskPhases(new Map());
@@ -535,6 +537,14 @@ function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, save, las
535
537
  })));
536
538
  setScreen('running');
537
539
  setActiveRunStage('check');
540
+ void trackMintTestRunStarted({
541
+ mode: 'interactive',
542
+ resumed,
543
+ agents: runAgents,
544
+ model: runModel,
545
+ selectedPageCount: new Set(runFiles_).size,
546
+ totalPageCount: totalPages,
547
+ });
538
548
  void runCodeTests({
539
549
  path: targetPath,
540
550
  agents: runAgents,
@@ -545,6 +555,9 @@ function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, save, las
545
555
  concurrency: DEFAULT_CONCURRENCY,
546
556
  commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
547
557
  signal: controller.signal,
558
+ onGenerationResult: (result) => {
559
+ void trackMintTestGenerationResult(Object.assign(Object.assign({}, result), { mode: 'interactive', resumed }));
560
+ },
548
561
  onTaskUpdate: (update) => {
549
562
  const phaseStage = update.phase === 'running'
550
563
  ? 'run'
@@ -566,6 +579,11 @@ function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, save, las
566
579
  },
567
580
  })
568
581
  .then((nextReport) => {
582
+ void trackMintTestRunCompleted(nextReport, {
583
+ mode: 'interactive',
584
+ resumed,
585
+ totalPageCount: totalPages,
586
+ });
569
587
  setReport(nextReport);
570
588
  const failedFiles = nextReport.tasks
571
589
  .filter((task) => task.status === 'failed' || task.status === 'agent_error')