nfunc-mcp 0.4.0 → 0.5.0

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.
@@ -1,6 +1,14 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
1
3
  import { z } from "zod";
2
4
  import { runShell } from "../utils/shellRunner.js";
3
5
  import { parseLighthouseJSON } from "../utils/outputParsers.js";
6
+ import { extractLabMetrics } from "../utils/psiParser.js";
7
+ import { resolveUrlInputs } from "../utils/urlInput.js";
8
+ import { compareRuns } from "../mappers/runComparator.js";
9
+ import { classifyUrls } from "../utils/urlClassifier.js";
10
+ import { aggregate } from "../mappers/psiAggregator.js";
11
+ import { DEFAULT_MAX_SECONDS_PER_CALL, decodeCursor, encodeCursor, elapsedSeconds, filterCompleted, hasBudget, mergeIndex, readIndex, slugForUrl, } from "../utils/batchState.js";
4
12
  import { shellErrorResponse, parseErrorResponse, } from "../utils/toolResponse.js";
5
13
  import { formatLighthouseFinding } from "../mappers/defectFormatter.js";
6
14
  import { sortFindingsByPriority } from "../mappers/priorityMapper.js";
@@ -19,8 +27,36 @@ import { sortFindingsByPriority } from "../mappers/priorityMapper.js";
19
27
  export function formFactorArgs(ff) {
20
28
  return ff === "desktop" ? ["--preset=desktop"] : ["--form-factor=mobile"];
21
29
  }
30
+ /** A single local Lighthouse run, measured. Used to reserve chunk budget. */
31
+ const TYPICAL_RUN_MS = 40_000;
32
+ const DEFAULT_OUTPUT_DIR = "./lighthouse-reports";
22
33
  const inputShape = {
23
- url: z.string().url(),
34
+ url: z
35
+ .string()
36
+ .min(1)
37
+ .describe("One URL, several comma- or newline-separated URLs, or a path to a CSV " +
38
+ "containing a URL column. More than one URL switches the tool into " +
39
+ "batch mode: results are written to output_dir and a cursor is " +
40
+ "returned to continue with."),
41
+ urls: z
42
+ .array(z.string())
43
+ .optional()
44
+ .describe("Explicit URL list, as an alternative to packing them into `url`."),
45
+ output_dir: z.string().optional(),
46
+ cursor: z.string().optional().describe("Resume token from a previous batch call."),
47
+ max_seconds_per_call: z.number().int().min(30).max(900).optional(),
48
+ skip_completed: z
49
+ .boolean()
50
+ .optional()
51
+ .describe("Batch mode only. Skip URL/form-factor pairs already in the output " +
52
+ "index (default true), so re-running fills gaps instead of redoing work."),
53
+ baseline_dir: z
54
+ .string()
55
+ .optional()
56
+ .describe("Compare this run against a previous run's index: which audits were " +
57
+ "fixed, which still fail, which are newly introduced, and how each " +
58
+ "category score moved. Point it at an earlier output_dir. May be the " +
59
+ "same as output_dir; the baseline is read before anything is written."),
24
60
  categories: z.array(z.string()).optional(),
25
61
  thresholds: z.record(z.string(), z.number()).optional(),
26
62
  form_factor: z
@@ -36,126 +72,358 @@ const inputShape = {
36
72
  "device-specific, since the two profiles render different DOM and " +
37
73
  "genuinely find different accessibility and SEO problems."),
38
74
  };
75
+ /** One Lighthouse invocation. Returns the parsed report and the raw LHR. */
76
+ async function runLighthouseOnce(url, factor, categories) {
77
+ const args = [
78
+ url,
79
+ "--output=json",
80
+ "--quiet",
81
+ "--chrome-flags=--headless",
82
+ ...formFactorArgs(factor),
83
+ ];
84
+ if (categories && categories.length > 0) {
85
+ args.push(`--only-categories=${categories.join(",")}`);
86
+ }
87
+ const result = await runShell("lighthouse", args, { timeoutMs: 180_000 });
88
+ if (!result.stdout)
89
+ return { result };
90
+ try {
91
+ return { run: { factor, parsed: parseLighthouseJSON(result.stdout), raw: result.stdout }, result };
92
+ }
93
+ catch (parseError) {
94
+ return { result, parseError };
95
+ }
96
+ }
97
+ function buildFindings(parsed, thresholds) {
98
+ const out = [];
99
+ for (const audit of parsed.failedAudits) {
100
+ const threshold = thresholds?.[audit.id];
101
+ if (typeof threshold === "number" && audit.score >= threshold)
102
+ continue;
103
+ const finding = formatLighthouseFinding(audit);
104
+ if (finding)
105
+ out.push(finding);
106
+ }
107
+ return out;
108
+ }
109
+ /**
110
+ * Merge findings across form factors on audit id, recording which profiles each
111
+ * affects so "fails on desktop only" is readable straight off the finding.
112
+ */
113
+ function mergeAcrossFactors(runs, thresholds) {
114
+ const merged = new Map();
115
+ for (const { factor, parsed } of runs) {
116
+ for (const finding of buildFindings(parsed, thresholds)) {
117
+ const key = String(finding.evidence["audit_id"]);
118
+ const existing = merged.get(key);
119
+ if (existing)
120
+ existing._ff.push(factor);
121
+ else
122
+ merged.set(key, { ...finding, _ff: [factor] });
123
+ }
124
+ }
125
+ const findings = [...merged.values()].map(({ _ff, ...finding }) => ({
126
+ ...finding,
127
+ evidence: {
128
+ ...finding.evidence,
129
+ affects_form_factors: _ff,
130
+ form_factor_specific: _ff.length === 1,
131
+ },
132
+ }));
133
+ return sortFindingsByPriority(findings);
134
+ }
135
+ const auditIdOf = (f) => String(f.evidence["audit_id"] ?? f.title);
136
+ function toDefects(findings) {
137
+ return findings.map((f) => ({ id: auditIdOf(f), priority: f.priority, title: f.title }));
138
+ }
139
+ function recordsToDefectRefs(records) {
140
+ const refs = [];
141
+ for (const record of records) {
142
+ for (const d of record.defects ?? []) {
143
+ refs.push({ url: record.url, variant: record.variant, id: d.id, priority: d.priority, title: d.title });
144
+ }
145
+ }
146
+ return refs;
147
+ }
148
+ /** Category scores keyed url|variant, for the score-delta half of a comparison. */
149
+ function recordsToScores(records) {
150
+ return new Map(records.map((r) => [`${r.url}|${r.variant}`, r.scores]));
151
+ }
152
+ const text = (payload) => ({
153
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
154
+ });
39
155
  export function registerLighthouseTool(server) {
40
156
  server.registerTool("run_lighthouse", {
41
- description: "Runs Google Lighthouse against a URL and returns a QA-style report " +
42
- "with category scores, TTFB, and prioritised findings (P1/P2/P3). " +
157
+ description: "Runs Google Lighthouse and returns a QA-style report with category " +
158
+ "scores, TTFB, and prioritised findings (P1/P2/P3). " +
159
+ "\n\n" +
160
+ "**Accepts one URL, a comma- or newline-separated list, or a path to a " +
161
+ "CSV with a URL column.** A single URL returns one report immediately. " +
162
+ "Several URLs switch to batch mode: each report is written to " +
163
+ "`output_dir`, a running `_index.json` accumulates, a `cursor` is " +
164
+ "returned to continue with, and the final call adds an `aggregate` " +
165
+ "block with cross-page means, per-template rollups and outliers — " +
166
+ "quote those rather than recomputing them. Re-run with the same URLs " +
167
+ "and no cursor to fill any gaps; completed work is skipped. " +
43
168
  "\n\n" +
44
- "`form_factor` selects the device profile: 'mobile' (default, " +
45
- "throttled slow 4G with 4x CPU slowdown), 'desktop' (unthrottled), or " +
46
- "'both'. **Prefer 'both' when auditing a page properly** — the two " +
47
- "profiles render different DOM and find different defects, not just " +
48
- "different performance numbers. With 'both', `scores` is keyed by " +
49
- "form factor and each finding carries affects_form_factors plus " +
50
- "form_factor_specific, so device-only regressions are obvious. " +
169
+ "`form_factor` selects the device profile: 'desktop' (default, " +
170
+ "unthrottled), 'mobile' (throttled slow 4G, 4x CPU slowdown), or " +
171
+ "'both'. **Prefer 'both' when auditing properly** — the two profiles " +
172
+ "render different DOM and find different defects, not just different " +
173
+ "performance numbers. " +
51
174
  "\n\n" +
52
175
  "Requires the Lighthouse CLI on PATH (`npm install -g lighthouse`) " +
53
- "and a Chrome/Chromium binary available.",
176
+ "and a Chrome/Chromium binary.",
54
177
  inputSchema: inputShape,
55
- }, async ({ url, categories, thresholds, form_factor }) => {
178
+ }, async ({ url, urls, categories, thresholds, form_factor, output_dir, cursor, max_seconds_per_call, skip_completed, baseline_dir, }) => {
56
179
  const requested = form_factor ?? "desktop";
57
180
  const factors = requested === "both" ? ["mobile", "desktop"] : [requested];
58
- const runOne = async (ff) => {
59
- const args = [
60
- url,
61
- "--output=json",
62
- "--quiet",
63
- "--chrome-flags=--headless",
64
- ...formFactorArgs(ff),
65
- ];
66
- if (categories && categories.length > 0) {
67
- args.push(`--only-categories=${categories.join(",")}`);
68
- }
69
- return { ff, result: await runShell("lighthouse", args, { timeoutMs: 180_000 }) };
181
+ let resolved;
182
+ try {
183
+ resolved = resolveUrlInputs(url, urls);
184
+ }
185
+ catch (err) {
186
+ return {
187
+ ...text({ error: "unusable_url_input", message: err.message }),
188
+ isError: true,
189
+ };
190
+ }
191
+ const warnings = [...resolved.warnings];
192
+ // Read the baseline before anything is written, so baseline_dir may be
193
+ // the same directory as output_dir.
194
+ const baselineRecords = baseline_dir
195
+ ? await readIndex(resolve(baseline_dir))
196
+ : null;
197
+ if (baseline_dir && baselineRecords && baselineRecords.length === 0) {
198
+ warnings.push(`No previous results found in ${resolve(baseline_dir)} — nothing to compare against. ` +
199
+ "Run once with output_dir set to create a baseline.");
200
+ }
201
+ const compareWith = async (records) => {
202
+ if (!baselineRecords)
203
+ return undefined;
204
+ const result = compareRuns(resolve(baseline_dir), recordsToDefectRefs(baselineRecords), recordsToDefectRefs(records), {
205
+ baselineScores: recordsToScores(baselineRecords),
206
+ currentScores: recordsToScores(records),
207
+ });
208
+ warnings.push(...result.warnings);
209
+ const { warnings: _dropped, ...rest } = result;
210
+ return rest;
70
211
  };
71
- // Concurrent, so "both" costs little more wall time than a single run.
72
- const runs = await Promise.all(factors.map(runOne));
73
- const parsedByFactor = new Map();
74
- for (const { ff, result } of runs) {
75
- if (!result.stdout) {
76
- if (factors.length === 1) {
77
- return shellErrorResponse("Lighthouse produced no JSON output", result);
212
+ // ---- Single URL: unchanged contract ------------------------------
213
+ if (resolved.urls.length === 1) {
214
+ const target = resolved.urls[0];
215
+ const outcomes = await Promise.all(factors.map((factor) => runLighthouseOnce(target, factor, categories)));
216
+ const good = outcomes.filter((o) => o.run).map((o) => o.run);
217
+ if (good.length === 0) {
218
+ const first = outcomes[0];
219
+ return first.parseError
220
+ ? parseErrorResponse("Failed to parse Lighthouse JSON", first.parseError, first.result)
221
+ : shellErrorResponse("Lighthouse produced no JSON output", first.result);
222
+ }
223
+ // Per-factor records, so a single-URL run can seed or be compared to a
224
+ // baseline exactly like a batch one.
225
+ const singleRecords = good.map(({ factor, parsed, raw }) => {
226
+ const findings = sortFindingsByPriority(buildFindings(parsed, thresholds));
227
+ return {
228
+ url: target,
229
+ variant: factor,
230
+ scores: parsed.categoryScores,
231
+ ttfb_ms: parsed.ttfbMs,
232
+ lab: extractLabMetrics(raw),
233
+ finding_count: findings.length,
234
+ p1_count: findings.filter((f) => f.priority === "P1").length,
235
+ defects: toDefects(findings),
236
+ report_file: "",
237
+ };
238
+ });
239
+ // Only touches disk when asked to: a one-off check stays a one-off.
240
+ let indexFile;
241
+ if (output_dir) {
242
+ const dir = resolve(output_dir);
243
+ await mkdir(dir, { recursive: true });
244
+ for (const [i, { factor, raw }] of good.entries()) {
245
+ const file = `${slugForUrl(target)}_${factor}.json`;
246
+ singleRecords[i].report_file = file;
247
+ await writeFile(join(dir, file), raw, "utf8");
78
248
  }
79
- continue; // one form factor failed; report the other
249
+ indexFile = (await mergeIndex(dir, singleRecords)).indexPath;
80
250
  }
81
- try {
82
- parsedByFactor.set(ff, parseLighthouseJSON(result.stdout));
251
+ const comparison = await compareWith(singleRecords);
252
+ const persisted = indexFile
253
+ ? { output_dir: resolve(output_dir), index_file: indexFile }
254
+ : {};
255
+ if (factors.length === 1) {
256
+ const { parsed } = good[0];
257
+ return text({
258
+ url: target,
259
+ form_factor: factors[0],
260
+ scores: parsed.categoryScores,
261
+ ttfb_ms: parsed.ttfbMs,
262
+ findings: sortFindingsByPriority(buildFindings(parsed, thresholds)),
263
+ ...persisted,
264
+ ...(comparison ? { comparison } : {}),
265
+ ...(warnings.length ? { warnings } : {}),
266
+ });
83
267
  }
84
- catch (err) {
85
- if (factors.length === 1) {
86
- return parseErrorResponse("Failed to parse Lighthouse JSON", err, result);
87
- }
268
+ const scores = {};
269
+ const ttfb = {};
270
+ for (const { factor, parsed } of good) {
271
+ scores[factor] = parsed.categoryScores;
272
+ ttfb[factor] = parsed.ttfbMs;
88
273
  }
274
+ return text({
275
+ url: target,
276
+ form_factor: "both",
277
+ form_factors_run: good.map((g) => g.factor),
278
+ scores,
279
+ ttfb_ms: ttfb,
280
+ findings: mergeAcrossFactors(good, thresholds),
281
+ ...persisted,
282
+ ...(comparison ? { comparison } : {}),
283
+ ...(warnings.length ? { warnings } : {}),
284
+ });
89
285
  }
90
- if (parsedByFactor.size === 0) {
91
- return shellErrorResponse("Lighthouse produced no usable output for any form factor", runs[0].result);
286
+ // ---- Batch -------------------------------------------------------
287
+ const dir = resolve(output_dir ?? DEFAULT_OUTPUT_DIR);
288
+ // Before anything writes into it. mergeIndex creates it too, but that
289
+ // runs after the loop, which is too late for the first report file.
290
+ await mkdir(dir, { recursive: true });
291
+ const budgetMs = (max_seconds_per_call ?? DEFAULT_MAX_SECONDS_PER_CALL) * 1000;
292
+ const startedAt = Date.now();
293
+ const allUnits = resolved.urls.flatMap((u) => factors.map((factor) => ({ url: u, variant: factor })));
294
+ // When comparing, re-measuring is the point — skipping completed work
295
+ // would compare a run against itself.
296
+ const { units, skipped } = await filterCompleted(dir, allUnits, skip_completed ?? !baseline_dir, cursor);
297
+ if (skipped > 0) {
298
+ warnings.push(`Skipped ${skipped} URL/form-factor pair(s) already in ${join(dir, "_index.json")}. ` +
299
+ "Pass skip_completed:false to re-measure them.");
92
300
  }
93
- const buildFindings = (parsed) => {
94
- const out = [];
95
- for (const audit of parsed.failedAudits) {
96
- const threshold = thresholds?.[audit.id];
97
- if (typeof threshold === "number" && audit.score >= threshold)
98
- continue;
99
- const f = formatLighthouseFinding(audit);
100
- if (f)
101
- out.push(f);
102
- }
103
- return out;
104
- };
105
- // Single form factor keeps the original flat report shape.
106
- if (parsedByFactor.size === 1 && factors.length === 1) {
107
- const parsed = parsedByFactor.get(factors[0]);
108
- const findings = buildFindings(parsed);
109
- sortFindingsByPriority(findings);
110
- return {
111
- content: [{ type: "text", text: JSON.stringify({
112
- url,
113
- form_factor: factors[0],
114
- scores: parsed.categoryScores,
115
- ttfb_ms: parsed.ttfbMs,
116
- findings,
117
- }, null, 2) }],
118
- };
301
+ if (units.length === 0) {
302
+ return text({
303
+ complete: true,
304
+ progress: { done: 0, total: 0, failed: 0 },
305
+ message: "Every requested URL/form-factor pair is already in the index.",
306
+ index_file: join(dir, "_index.json"),
307
+ warnings,
308
+ });
119
309
  }
120
- // Both: merge on audit_id and record which form factors each affects, so
121
- // "fails on desktop only" is readable straight off the finding.
122
- const merged = new Map();
123
- const scores = {};
124
- const ttfb = {};
125
- for (const ff of factors) {
126
- const parsed = parsedByFactor.get(ff);
127
- if (!parsed)
310
+ const start = decodeCursor(cursor);
311
+ const records = [];
312
+ const results = [];
313
+ const failures = [];
314
+ let index = start;
315
+ let budgetExhausted = false;
316
+ while (index < units.length) {
317
+ // Sequential, not concurrent. Two Chrome instances on one machine
318
+ // contend for CPU, and this tool's whole job is measuring how long
319
+ // things take — a half-busy machine reports numbers nobody can act on.
320
+ // The single-URL path keeps its concurrency: one page, documented.
321
+ if (!hasBudget(startedAt, budgetMs, TYPICAL_RUN_MS, index - start)) {
322
+ budgetExhausted = true;
323
+ break;
324
+ }
325
+ const unit = units[index];
326
+ const factor = unit.variant;
327
+ const outcome = await runLighthouseOnce(unit.url, factor, categories);
328
+ if (!outcome.run) {
329
+ failures.push({
330
+ url: unit.url,
331
+ form_factor: factor,
332
+ error: outcome.parseError
333
+ ? `unparseable Lighthouse JSON: ${outcome.parseError.message}`
334
+ : outcome.result.stderr.slice(0, 300) || `exit ${outcome.result.exitCode}`,
335
+ });
336
+ index++;
128
337
  continue;
129
- scores[ff] = parsed.categoryScores;
130
- ttfb[ff] = parsed.ttfbMs;
131
- for (const f of buildFindings(parsed)) {
132
- const key = String(f.evidence["audit_id"]);
133
- const existing = merged.get(key);
134
- if (existing)
135
- existing._ff.push(ff);
136
- else
137
- merged.set(key, { ...f, _ff: [ff] });
138
338
  }
339
+ const { parsed, raw } = outcome.run;
340
+ const reportFile = `${slugForUrl(unit.url)}_${factor}.json`;
341
+ // The raw LHR, not the parsed summary: the individual audits are what
342
+ // anyone diagnosing a finding actually needs, and they do not survive
343
+ // parsing. The summary lives in the index.
344
+ await writeFile(join(dir, reportFile), raw, "utf8");
345
+ const findings = sortFindingsByPriority(buildFindings(parsed, thresholds));
346
+ records.push({
347
+ url: unit.url,
348
+ variant: factor,
349
+ scores: parsed.categoryScores,
350
+ ttfb_ms: parsed.ttfbMs,
351
+ lab: extractLabMetrics(raw),
352
+ finding_count: findings.length,
353
+ p1_count: findings.filter((f) => f.priority === "P1").length,
354
+ defects: toDefects(findings),
355
+ report_file: reportFile,
356
+ });
357
+ results.push({
358
+ url: unit.url,
359
+ form_factor: factor,
360
+ scores: parsed.categoryScores,
361
+ ttfb_ms: parsed.ttfbMs,
362
+ findings,
363
+ report_file: reportFile,
364
+ });
365
+ index++;
139
366
  }
140
- const findings = Array.from(merged.values()).map(({ _ff, ...f }) => ({
141
- ...f,
142
- evidence: {
143
- ...f.evidence,
144
- affects_form_factors: _ff,
145
- form_factor_specific: _ff.length === 1,
146
- },
147
- }));
148
- sortFindingsByPriority(findings);
149
- return {
150
- content: [{ type: "text", text: JSON.stringify({
151
- url,
152
- form_factor: "both",
153
- form_factors_run: Array.from(parsedByFactor.keys()),
154
- scores,
155
- ttfb_ms: ttfb,
156
- findings,
157
- }, null, 2) }],
158
- };
367
+ const { indexPath } = await mergeIndex(dir, records);
368
+ const complete = index >= units.length;
369
+ if (budgetExhausted) {
370
+ warnings.push(`Stopped after ${elapsedSeconds(startedAt)}s to stay inside the per-call budget. ` +
371
+ "Call again with the cursor to continue.");
372
+ }
373
+ if (failures.length > 0) {
374
+ warnings.push(`${failures.length} run(s) failed. Call again with the same URLs and no cursor — ` +
375
+ "completed pairs are skipped, so only the gaps are retried.");
376
+ }
377
+ let aggregateBlock;
378
+ let comparison;
379
+ if (complete) {
380
+ const all = await readIndex(dir);
381
+ comparison = await compareWith(all);
382
+ // Group by page template so the rollup says "PDPs average 61" rather
383
+ // than listing twelve product URLs. Same classifier the PSI plan uses.
384
+ const { templates } = classifyUrls(all.map((r) => r.url));
385
+ const templateOf = new Map();
386
+ for (const t of templates) {
387
+ for (const candidate of t.candidates)
388
+ templateOf.set(candidate, { id: t.id, label: t.label });
389
+ }
390
+ const runs = all.map((r) => {
391
+ const t = templateOf.get(r.url);
392
+ return {
393
+ template: t?.id ?? "all",
394
+ label: t?.label ?? "All pages",
395
+ url: r.url,
396
+ strategy: r.variant,
397
+ runs: 1,
398
+ scores: r.scores,
399
+ lab: r.lab,
400
+ field: null,
401
+ comparisons: [],
402
+ findings: [],
403
+ report_file: r.report_file,
404
+ };
405
+ });
406
+ const { lab_vs_field_summary, ...rest } = aggregate(runs);
407
+ // No field data in a local run, so the lab-vs-field block would be an
408
+ // empty shape inviting a wrong conclusion. PSI is where that lives.
409
+ aggregateBlock = { ...rest, field_data: "not available — use run_performance_audit for real-user data" };
410
+ }
411
+ return text({
412
+ complete,
413
+ ...(complete ? {} : { cursor: encodeCursor(index) }),
414
+ progress: { done: index, total: units.length, failed: failures.length },
415
+ input_kind: resolved.kind,
416
+ ...(resolved.source ? { input_source: resolved.source } : {}),
417
+ form_factor: requested,
418
+ output_dir: dir,
419
+ index_file: indexPath,
420
+ results,
421
+ ...(failures.length ? { failures } : {}),
422
+ ...(aggregateBlock ? { aggregate: aggregateBlock } : {}),
423
+ ...(comparison ? { comparison } : {}),
424
+ ...(complete ? {} : { next_step: "Call run_lighthouse again with this cursor and the same input." }),
425
+ warnings,
426
+ });
159
427
  });
160
428
  }
161
429
  //# sourceMappingURL=lighthouse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../../src/tools/lighthouse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../../src/tools/lighthouse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Shared machinery for tools that process a list of URLs across several calls.
3
+ *
4
+ * Extracted from the PSI runner, where every piece of it was earned the hard
5
+ * way: an MCP client backgrounds a tool call at 120 seconds, local Lighthouse
6
+ * takes 20-40 seconds per URL per form factor, and both PSI and pa11y fail
7
+ * intermittently on real sites. A batch therefore cannot be one long call, and
8
+ * a failure partway through must not cost the runs that already succeeded.
9
+ *
10
+ * The contract is: bound each call by a clock, write results to disk as they
11
+ * land, merge rather than overwrite an index, and hand back a cursor.
12
+ */
13
+ /** Default per-call ceiling. Below the 120s at which Claude Code backgrounds a call. */
14
+ export declare const DEFAULT_MAX_SECONDS_PER_CALL = 100;
15
+ export interface BatchUnit {
16
+ url: string;
17
+ /** Device profile, engine, or whatever second dimension the tool runs over. */
18
+ variant: string;
19
+ }
20
+ /** Anything the index can hold. Tools define their own richer result types. */
21
+ export interface BatchRecord {
22
+ url: string;
23
+ variant: string;
24
+ [key: string]: unknown;
25
+ }
26
+ export declare function encodeCursor(index: number): string;
27
+ export declare function decodeCursor(cursor: string | undefined): number;
28
+ /** Cross-product of URLs and variants, in URL-major order so a partial run covers whole pages. */
29
+ export declare function buildUnits(urls: string[], variants: string[]): BatchUnit[];
30
+ /**
31
+ * A filesystem-safe stem for a URL.
32
+ *
33
+ * Derived from the path rather than hashed so the files are browsable: someone
34
+ * opening `output_dir` should be able to tell which page each report is for.
35
+ */
36
+ export declare function slugForUrl(url: string): string;
37
+ export interface IndexMergeResult {
38
+ indexPath: string;
39
+ total: number;
40
+ }
41
+ /**
42
+ * Merge records into the running index, keyed on URL + variant.
43
+ *
44
+ * Merging rather than overwriting is what lets a second pass fill gaps without
45
+ * discarding the first pass, and what lets the final aggregate cover the whole
46
+ * batch instead of only the last chunk.
47
+ */
48
+ export declare function mergeIndex<T extends BatchRecord>(dir: string, records: T[], fileName?: string): Promise<IndexMergeResult>;
49
+ export declare function readIndex<T extends BatchRecord>(dir: string, fileName?: string): Promise<T[]>;
50
+ /**
51
+ * Drop work already recorded in the index.
52
+ *
53
+ * Re-running to fill gaps is the normal workflow, not an edge case, because
54
+ * these tools fail intermittently on real sites. Without this, the advice
55
+ * "just run it again" silently re-does — and on metered APIs re-charges for —
56
+ * everything that already worked.
57
+ *
58
+ * Only applies when no cursor is in play: a cursor indexes into the unfiltered
59
+ * list, so filtering would misalign it.
60
+ */
61
+ export declare function filterCompleted(dir: string, units: BatchUnit[], skipCompleted: boolean, cursor: string | undefined): Promise<{
62
+ units: BatchUnit[];
63
+ skipped: number;
64
+ }>;
65
+ /**
66
+ * Whether there is room to start another unit.
67
+ *
68
+ * Checked before a unit begins, never mid-unit, and it reserves the expected
69
+ * cost of the work about to start rather than only asking whether the budget is
70
+ * already spent — otherwise a 40-second run beginning at the 99-second mark
71
+ * overshoots by a full unit. The PSI runner overran a 150s budget by 38s
72
+ * before this reserve existed.
73
+ */
74
+ export declare function hasBudget(startedAt: number, budgetMs: number, reserveMs: number, unitsDone: number): boolean;
75
+ export declare function elapsedSeconds(startedAt: number): number;