nfunc-mcp 0.3.0 → 0.4.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.
- package/README.md +84 -376
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mappers/labFieldComparator.d.ts +62 -0
- package/dist/mappers/labFieldComparator.js +134 -0
- package/dist/mappers/labFieldComparator.js.map +1 -0
- package/dist/mappers/psiAggregator.d.ts +130 -0
- package/dist/mappers/psiAggregator.js +293 -0
- package/dist/mappers/psiAggregator.js.map +1 -0
- package/dist/mappers/webVitalsMapper.d.ts +52 -0
- package/dist/mappers/webVitalsMapper.js +131 -0
- package/dist/mappers/webVitalsMapper.js.map +1 -0
- package/dist/tools/performanceAudit.d.ts +2 -0
- package/dist/tools/performanceAudit.js +446 -0
- package/dist/tools/performanceAudit.js.map +1 -0
- package/dist/tools/performanceAuditPlan.d.ts +2 -0
- package/dist/tools/performanceAuditPlan.js +438 -0
- package/dist/tools/performanceAuditPlan.js.map +1 -0
- package/dist/utils/csvReader.d.ts +20 -0
- package/dist/utils/csvReader.js +172 -0
- package/dist/utils/csvReader.js.map +1 -0
- package/dist/utils/httpClient.d.ts +84 -0
- package/dist/utils/httpClient.js +171 -0
- package/dist/utils/httpClient.js.map +1 -0
- package/dist/utils/psiAuth.d.ts +26 -0
- package/dist/utils/psiAuth.js +36 -0
- package/dist/utils/psiAuth.js.map +1 -0
- package/dist/utils/psiParser.d.ts +124 -0
- package/dist/utils/psiParser.js +200 -0
- package/dist/utils/psiParser.js.map +1 -0
- package/dist/utils/publicUrl.d.ts +17 -0
- package/dist/utils/publicUrl.js +115 -0
- package/dist/utils/publicUrl.js.map +1 -0
- package/dist/utils/sitemapReader.d.ts +27 -0
- package/dist/utils/sitemapReader.js +272 -0
- package/dist/utils/sitemapReader.js.map +1 -0
- package/dist/utils/urlClassifier.d.ts +45 -0
- package/dist/utils/urlClassifier.js +267 -0
- package/dist/utils/urlClassifier.js.map +1 -0
- package/docs/manual.md +558 -0
- package/docs/psi-report-spec.md +174 -0
- package/package.json +13 -3
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Web Vitals field data → priorities and defect prose.
|
|
3
|
+
*
|
|
4
|
+
* The lab side of a PSI response goes through the existing
|
|
5
|
+
* `formatLighthouseFinding` path unchanged. This module handles the CrUX half,
|
|
6
|
+
* where the inputs are 75th-percentile measurements from real users rather
|
|
7
|
+
* than audit scores, so neither the impact-weight mapping nor the WCAG
|
|
8
|
+
* technique table applies.
|
|
9
|
+
*
|
|
10
|
+
* Field findings are written to read differently from lab findings on purpose.
|
|
11
|
+
* A lab finding says the page did something under simulation; a field finding
|
|
12
|
+
* says a measurable share of real people already experienced it. That
|
|
13
|
+
* distinction is the whole reason to call PSI, and it should survive into the
|
|
14
|
+
* defect ticket.
|
|
15
|
+
*/
|
|
16
|
+
/** Google's official p75 boundaries. */
|
|
17
|
+
const VITALS = {
|
|
18
|
+
lcp: { label: "Largest Contentful Paint", good: 2500, needsImprovement: 4000, isCoreVital: true },
|
|
19
|
+
inp: { label: "Interaction to Next Paint", good: 200, needsImprovement: 500, isCoreVital: true },
|
|
20
|
+
cls: { label: "Cumulative Layout Shift", good: 0.1, needsImprovement: 0.25, isCoreVital: true },
|
|
21
|
+
fcp: { label: "First Contentful Paint", good: 1800, needsImprovement: 3000, isCoreVital: false },
|
|
22
|
+
ttfb: { label: "Time to First Byte", good: 800, needsImprovement: 1800, isCoreVital: false },
|
|
23
|
+
};
|
|
24
|
+
export function vitalLabel(vital) {
|
|
25
|
+
return VITALS[vital].label;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* LCP, INP and CLS gate a release and affect ranking; FCP and TTFB explain
|
|
29
|
+
* them. Exported because the priority cap has to hold everywhere a priority is
|
|
30
|
+
* decided, not just where one is first assigned.
|
|
31
|
+
*/
|
|
32
|
+
export function isCoreVital(vital) {
|
|
33
|
+
return VITALS[vital].isCoreVital;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Rate a p75 value against Google's thresholds.
|
|
37
|
+
*
|
|
38
|
+
* PSI also returns its own FAST/AVERAGE/SLOW `category` per metric, and the two
|
|
39
|
+
* agree in practice. We classify from the published thresholds anyway, so that
|
|
40
|
+
* the boundary a finding was raised at is a documented number in this file
|
|
41
|
+
* rather than a verdict from an opaque field — and so origin-level and
|
|
42
|
+
* URL-level metrics are graded identically.
|
|
43
|
+
*/
|
|
44
|
+
export function classifyVital(vital, p75) {
|
|
45
|
+
const spec = VITALS[vital];
|
|
46
|
+
if (p75 <= spec.good)
|
|
47
|
+
return "good";
|
|
48
|
+
if (p75 <= spec.needsImprovement)
|
|
49
|
+
return "needs-improvement";
|
|
50
|
+
return "poor";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Poor → P1, needs improvement → P2, good → no finding (never report a passing
|
|
54
|
+
* check). Non-core diagnostics cap at P2.
|
|
55
|
+
*/
|
|
56
|
+
export function fieldVitalToPriority(vital, p75) {
|
|
57
|
+
const rating = classifyVital(vital, p75);
|
|
58
|
+
if (rating === "good")
|
|
59
|
+
return null;
|
|
60
|
+
if (rating === "needs-improvement")
|
|
61
|
+
return "P2";
|
|
62
|
+
return VITALS[vital].isCoreVital ? "P1" : "P2";
|
|
63
|
+
}
|
|
64
|
+
/** Human-readable measurement. CLS is unitless; everything else is milliseconds. */
|
|
65
|
+
export function formatVitalValue(vital, value) {
|
|
66
|
+
if (vital === "cls")
|
|
67
|
+
return value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
68
|
+
return value >= 1000 ? `${(value / 1000).toFixed(1)} s` : `${Math.round(value)} ms`;
|
|
69
|
+
}
|
|
70
|
+
/** "7 in 10 users" reads more concretely in a ticket than "0.7019". */
|
|
71
|
+
function shareOfUsers(proportion) {
|
|
72
|
+
const pct = Math.round(proportion * 100);
|
|
73
|
+
return `${pct}% of real users`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Defect prose for each vital, in the register `defectFormatter.ts` established:
|
|
77
|
+
* what the user experiences, not what the metric is called.
|
|
78
|
+
*
|
|
79
|
+
* Every template states the share of real users in the poor bucket. A p75 alone
|
|
80
|
+
* invites the reply "that's just the tail" — naming the proportion answers it
|
|
81
|
+
* before it is asked, and the number is already in the response.
|
|
82
|
+
*/
|
|
83
|
+
const FIELD_DESCRIPTIONS = {
|
|
84
|
+
lcp: (_m, value, poor) => `Real users wait ${value} for the main page content to appear (75th percentile, trailing 28 days). ${poor} experience a load slow enough to be rated poor, well past the point where visitors begin abandoning the page.`,
|
|
85
|
+
inp: (_m, value, poor) => `The page takes ${value} to respond visibly after a real user taps or clicks (75th percentile, trailing 28 days). ${poor} experience responsiveness rated poor — taps appear to do nothing, so users tap again and trigger duplicate actions.`,
|
|
86
|
+
cls: (_m, value, poor) => `Real users see the layout shift by ${value} while the page loads (75th percentile, trailing 28 days). ${poor} experience shifting rated poor, which causes mis-taps on the wrong control and loss of reading position.`,
|
|
87
|
+
fcp: (_m, value, poor) => `Real users stare at a blank screen for ${value} before anything paints (75th percentile, trailing 28 days). ${poor} experience a first paint rated poor. This is a diagnostic for the slow Largest Contentful Paint rather than a defect to fix on its own.`,
|
|
88
|
+
ttfb: (_m, value, poor) => `The server takes ${value} to return the first byte for real users (75th percentile, trailing 28 days). ${poor} experience a response rated poor; every downstream resource waits on this, so it caps how fast the rest of the page can possibly be.`,
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* One field metric → a Finding, or null when real users are having a fine time.
|
|
92
|
+
*
|
|
93
|
+
* `source` reaches the evidence deliberately. An origin-level metric describes
|
|
94
|
+
* the whole site, not this page, and a reader deciding whether to act on the
|
|
95
|
+
* finding needs to know which they are looking at.
|
|
96
|
+
*/
|
|
97
|
+
export function formatFieldFinding(vital, metric) {
|
|
98
|
+
const priority = fieldVitalToPriority(vital, metric.p75);
|
|
99
|
+
if (!priority)
|
|
100
|
+
return null;
|
|
101
|
+
const value = formatVitalValue(vital, metric.p75);
|
|
102
|
+
const poor = shareOfUsers(metric.distribution.poor);
|
|
103
|
+
const spec = VITALS[vital];
|
|
104
|
+
const scope = metric.source === "origin" ? " (site-wide data)" : "";
|
|
105
|
+
return {
|
|
106
|
+
priority,
|
|
107
|
+
title: `${spec.label} is ${classifyVital(vital, metric.p75) === "poor" ? "poor" : "below target"} for real users${scope}`,
|
|
108
|
+
description: FIELD_DESCRIPTIONS[vital](metric, value, poor) +
|
|
109
|
+
(metric.source === "origin"
|
|
110
|
+
? " This URL has too little traffic for its own field data, so these figures describe the whole origin and may not reflect this page."
|
|
111
|
+
: ""),
|
|
112
|
+
evidence: {
|
|
113
|
+
audit_id: `crux.${vital}`,
|
|
114
|
+
value,
|
|
115
|
+
threshold: formatVitalValue(vital, spec.good),
|
|
116
|
+
field_source: metric.source,
|
|
117
|
+
users_affected_pct: Math.round(metric.distribution.poor * 100),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Every failing field metric in a parsed CrUX block, unsorted. */
|
|
122
|
+
export function formatFieldFindings(metrics) {
|
|
123
|
+
const findings = [];
|
|
124
|
+
for (const [vital, metric] of Object.entries(metrics)) {
|
|
125
|
+
const finding = formatFieldFinding(vital, metric);
|
|
126
|
+
if (finding)
|
|
127
|
+
findings.push(finding);
|
|
128
|
+
}
|
|
129
|
+
return findings;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=webVitalsMapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webVitalsMapper.js","sourceRoot":"","sources":["../../src/mappers/webVitalsMapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { httpGetJson } from "../utils/httpClient.js";
|
|
5
|
+
import { parsePsiResponse, PsiRuntimeError } from "../utils/psiParser.js";
|
|
6
|
+
import { checkPublicReachability } from "../utils/publicUrl.js";
|
|
7
|
+
import { resolveApiKey, keylessWarning, KEY_ENV_VAR, KEYLESS_RUN_CAP } from "../utils/psiAuth.js";
|
|
8
|
+
import { formatLighthouseFinding } from "../mappers/defectFormatter.js";
|
|
9
|
+
import { formatFieldFindings } from "../mappers/webVitalsMapper.js";
|
|
10
|
+
import { sortFindingsByPriority } from "../mappers/priorityMapper.js";
|
|
11
|
+
import { compareLabField, adjustmentFor, promote, demote, AUDIT_TO_VITAL, } from "../mappers/labFieldComparator.js";
|
|
12
|
+
import { aggregate, collapseSystemicFindings, suppressComponentFindings, } from "../mappers/psiAggregator.js";
|
|
13
|
+
/**
|
|
14
|
+
* Overridable so the tool can be exercised end to end against a captured
|
|
15
|
+
* response without spending quota, and so anyone behind an egress proxy can
|
|
16
|
+
* point at it. Defaults to the real API.
|
|
17
|
+
*/
|
|
18
|
+
const PSI_ENDPOINT = process.env.PAGESPEED_API_ENDPOINT ??
|
|
19
|
+
"https://www.googleapis.com/pagespeedonline/v5/runPagespeed";
|
|
20
|
+
/**
|
|
21
|
+
* Per-attempt ceiling, set from measurement rather than from the documentation.
|
|
22
|
+
*
|
|
23
|
+
* Across every successful live run observed, latency fell between 10 s and
|
|
24
|
+
* 57 s. A longer timeout therefore buys no additional successes — it only makes
|
|
25
|
+
* a hang more expensive. 75 s covers the slowest observed success with margin
|
|
26
|
+
* and leaves room inside a 150 s chunk for a second attempt, which is worth
|
|
27
|
+
* more than waiting: in a 6-run sample the attempt immediately after a timeout
|
|
28
|
+
* succeeded in 10 s.
|
|
29
|
+
*/
|
|
30
|
+
const PSI_TIMEOUT_MS = 75_000;
|
|
31
|
+
/** Typical successful run, measured against a live key. Used to reserve budget. */
|
|
32
|
+
const TYPICAL_RUN_MS = 45_000;
|
|
33
|
+
/** Spacing between calls. Sequencing beats fanning out — see the tool description. */
|
|
34
|
+
const INTER_CALL_DELAY_MS = 1_500;
|
|
35
|
+
const DEFAULT_OUTPUT_DIR = "./psi-reports";
|
|
36
|
+
/**
|
|
37
|
+
* PSI calls take 10-30 s. Six is roughly 90 seconds of work, which fits inside
|
|
38
|
+
* a typical MCP client timeout with room to spare; the caller loops on the
|
|
39
|
+
* cursor until the batch is done.
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_MAX_RUNS_PER_CALL = 2;
|
|
42
|
+
/**
|
|
43
|
+
* Wall-clock ceiling for one call, the real protection against an MCP timeout.
|
|
44
|
+
*
|
|
45
|
+
* A run count alone cannot bound the time, because per-call latency varies far
|
|
46
|
+
* more than expected: fivebelow.com's homepage took 47 s per run while its
|
|
47
|
+
* beauty PLP returned HTTP 500 and burned the retry budget. Counting runs let
|
|
48
|
+
* a two-run chunk exceed five minutes. The loop now stops starting new work
|
|
49
|
+
* once the budget is spent and hands back a cursor, so a chunk returns on time
|
|
50
|
+
* whatever the API does.
|
|
51
|
+
*/
|
|
52
|
+
const DEFAULT_MAX_SECONDS_PER_CALL = 150;
|
|
53
|
+
const pageShape = z.object({
|
|
54
|
+
template: z.string(),
|
|
55
|
+
label: z.string(),
|
|
56
|
+
url: z.string().url(),
|
|
57
|
+
slug: z.string().optional(),
|
|
58
|
+
});
|
|
59
|
+
const inputShape = {
|
|
60
|
+
pages: z.array(pageShape).min(1).describe("Approved pages from plan_performance_audit."),
|
|
61
|
+
strategy: z.enum(["mobile", "desktop", "both"]).optional(),
|
|
62
|
+
runs_per_url: z.number().int().min(1).max(5).optional(),
|
|
63
|
+
categories: z.array(z.string()).optional(),
|
|
64
|
+
output_dir: z.string().optional(),
|
|
65
|
+
origin_fallback: z.boolean().optional(),
|
|
66
|
+
api_key: z.string().optional(),
|
|
67
|
+
cursor: z.string().optional().describe("Resume token from a previous call. Omit on the first call."),
|
|
68
|
+
max_runs_per_call: z.number().int().min(1).max(20).optional(),
|
|
69
|
+
skip_completed: z
|
|
70
|
+
.boolean()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Skip page/strategy pairs already present in the output index (default true). " +
|
|
73
|
+
"PSI fails intermittently, so re-running to fill gaps is normal — this makes " +
|
|
74
|
+
"that cheap instead of re-spending quota on pages that already succeeded. " +
|
|
75
|
+
"Set false to force a fresh measurement."),
|
|
76
|
+
max_seconds_per_call: z
|
|
77
|
+
.number()
|
|
78
|
+
.int()
|
|
79
|
+
.min(30)
|
|
80
|
+
.max(900)
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(`Wall-clock ceiling for one call (default ${DEFAULT_MAX_SECONDS_PER_CALL}s). Must stay ` +
|
|
83
|
+
"below your MCP client's tool timeout; raise MCP_TOOL_TIMEOUT to use a bigger chunk."),
|
|
84
|
+
};
|
|
85
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
86
|
+
function slugFor(page) {
|
|
87
|
+
if (page.slug)
|
|
88
|
+
return page.slug;
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(page.url);
|
|
91
|
+
const path = url.pathname.replace(/^\/|\/$/g, "").replace(/\//g, "-");
|
|
92
|
+
const query = url.search ? `-${url.search.slice(1).replace(/[^a-z0-9]+/gi, "-")}` : "";
|
|
93
|
+
return (path || "homepage") + query;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return page.template;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function encodeCursor(index) {
|
|
100
|
+
return Buffer.from(JSON.stringify({ i: index })).toString("base64url");
|
|
101
|
+
}
|
|
102
|
+
function decodeCursor(cursor) {
|
|
103
|
+
if (!cursor)
|
|
104
|
+
return 0;
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
107
|
+
return typeof parsed.i === "number" && parsed.i >= 0 ? parsed.i : 0;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function buildRequestUrl(target, strategy, categories, key) {
|
|
114
|
+
const url = new URL(PSI_ENDPOINT);
|
|
115
|
+
url.searchParams.set("url", target);
|
|
116
|
+
url.searchParams.set("strategy", strategy);
|
|
117
|
+
if (key)
|
|
118
|
+
url.searchParams.set("key", key);
|
|
119
|
+
for (const category of categories)
|
|
120
|
+
url.searchParams.append("category", category);
|
|
121
|
+
return url;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Apply the lab-vs-field verdicts to the findings for one run.
|
|
125
|
+
*
|
|
126
|
+
* The lab finding is the one that moves. A confirmed failure is promoted
|
|
127
|
+
* because two independent measurements agree; a lab-only failure is demoted
|
|
128
|
+
* and tagged, because the simulation saw something real users do not. Field
|
|
129
|
+
* findings that the lab missed entirely are promoted and tagged `field_only` —
|
|
130
|
+
* those are the ones no local tool can produce.
|
|
131
|
+
*/
|
|
132
|
+
function applyComparisons(findings, comparisons) {
|
|
133
|
+
for (const comparison of comparisons) {
|
|
134
|
+
const { direction, tag } = adjustmentFor(comparison.verdict);
|
|
135
|
+
if (direction === "none")
|
|
136
|
+
continue;
|
|
137
|
+
const labAuditId = Object.entries(AUDIT_TO_VITAL).find(([, vital]) => vital === comparison.metric)?.[0];
|
|
138
|
+
const target = comparison.verdict === "worse_in_field"
|
|
139
|
+
? findings.find((f) => f.evidence.audit_id === `crux.${comparison.metric}`)
|
|
140
|
+
: findings.find((f) => f.evidence.audit_id === labAuditId);
|
|
141
|
+
if (!target)
|
|
142
|
+
continue;
|
|
143
|
+
target.priority =
|
|
144
|
+
direction === "promote"
|
|
145
|
+
? promote(target.priority, comparison.metric)
|
|
146
|
+
: demote(target.priority);
|
|
147
|
+
target.evidence = { ...target.evidence, lab_field_verdict: comparison.verdict, adjustment: tag };
|
|
148
|
+
target.description = `${target.description} ${comparison.note}`;
|
|
149
|
+
}
|
|
150
|
+
return findings;
|
|
151
|
+
}
|
|
152
|
+
/** Merge into the running index rather than overwriting, so batches accumulate. */
|
|
153
|
+
async function mergeIndex(dir, results) {
|
|
154
|
+
const indexPath = join(dir, "_index.json");
|
|
155
|
+
let existing = [];
|
|
156
|
+
try {
|
|
157
|
+
existing = JSON.parse(await readFile(indexPath, "utf8"));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
existing = [];
|
|
161
|
+
}
|
|
162
|
+
const byKey = new Map(existing.map((r) => [`${r.url}|${r.strategy}`, r]));
|
|
163
|
+
for (const result of results)
|
|
164
|
+
byKey.set(`${result.url}|${result.strategy}`, result);
|
|
165
|
+
await writeFile(indexPath, JSON.stringify([...byKey.values()], null, 2), "utf8");
|
|
166
|
+
return indexPath;
|
|
167
|
+
}
|
|
168
|
+
export function registerPerformanceAuditTool(server) {
|
|
169
|
+
server.registerTool("run_performance_audit", {
|
|
170
|
+
description: "Runs a PageSpeed Insights audit over an approved set of pages and " +
|
|
171
|
+
"returns lab scores, CrUX real-user field data, and the disagreements " +
|
|
172
|
+
"between them. " +
|
|
173
|
+
"\n\n" +
|
|
174
|
+
"**Call plan_performance_audit first** and get the user's approval on " +
|
|
175
|
+
"the page list — this tool spends real API quota and takes 10-30 " +
|
|
176
|
+
"seconds per page/device. " +
|
|
177
|
+
"\n\n" +
|
|
178
|
+
"Runs in chunks: each call performs at most `max_runs_per_call` PSI " +
|
|
179
|
+
"requests and returns a `cursor`. Keep calling with that cursor until " +
|
|
180
|
+
"`complete` is true. Full raw reports are written to `output_dir` " +
|
|
181
|
+
"(one JSON per page/device) and merged into a running `_index.json`, " +
|
|
182
|
+
"so a failure partway through never costs the completed runs. The " +
|
|
183
|
+
"final call also returns an `aggregate` block containing every " +
|
|
184
|
+
"cross-page number — quote those rather than recomputing them." +
|
|
185
|
+
"\n\n" +
|
|
186
|
+
"The unique value here is `lab_vs_field`: a metric that passes in the " +
|
|
187
|
+
"lab but fails for real users means the test environment is not " +
|
|
188
|
+
"reproducing production, which no local tool can detect.",
|
|
189
|
+
inputSchema: inputShape,
|
|
190
|
+
}, async ({ pages, strategy, runs_per_url, categories, output_dir, origin_fallback, api_key, cursor, max_runs_per_call, max_seconds_per_call, skip_completed, }) => {
|
|
191
|
+
const warnings = [];
|
|
192
|
+
const resolvedStrategy = strategy ?? "both";
|
|
193
|
+
const runsPerUrl = runs_per_url ?? 1;
|
|
194
|
+
const originFallback = origin_fallback ?? true;
|
|
195
|
+
const maxRuns = max_runs_per_call ?? DEFAULT_MAX_RUNS_PER_CALL;
|
|
196
|
+
const budgetMs = (max_seconds_per_call ?? DEFAULT_MAX_SECONDS_PER_CALL) * 1000;
|
|
197
|
+
const startedAt = Date.now();
|
|
198
|
+
const requestedCategories = categories ?? [
|
|
199
|
+
"performance", "accessibility", "best-practices", "seo",
|
|
200
|
+
];
|
|
201
|
+
const dir = resolve(output_dir ?? DEFAULT_OUTPUT_DIR);
|
|
202
|
+
const { key, source } = resolveApiKey(api_key);
|
|
203
|
+
const strategies = resolvedStrategy === "both" ? ["mobile", "desktop"] : [resolvedStrategy];
|
|
204
|
+
const units = [];
|
|
205
|
+
for (const page of pages)
|
|
206
|
+
for (const s of strategies)
|
|
207
|
+
units.push({ page, strategy: s });
|
|
208
|
+
/**
|
|
209
|
+
* Drop work that already succeeded.
|
|
210
|
+
*
|
|
211
|
+
* PSI fails intermittently on real sites — roughly one run in three
|
|
212
|
+
* against fivebelow.com timed out or 500'd, and the same URL succeeded
|
|
213
|
+
* minutes later. Filling those gaps is the normal workflow, not an edge
|
|
214
|
+
* case, so a second pass must not re-measure and re-charge for the pages
|
|
215
|
+
* that worked. Ordering is preserved, so a cursor from a previous call
|
|
216
|
+
* would not line up; re-running to fill gaps means starting without one.
|
|
217
|
+
*/
|
|
218
|
+
let skipped = 0;
|
|
219
|
+
if ((skip_completed ?? true) && !cursor) {
|
|
220
|
+
try {
|
|
221
|
+
const existing = JSON.parse(await readFile(join(dir, "_index.json"), "utf8"));
|
|
222
|
+
const done = new Set(existing.map((r) => `${r.url}|${r.strategy}`));
|
|
223
|
+
const before = units.length;
|
|
224
|
+
const remaining = units.filter((u) => !done.has(`${u.page.url}|${u.strategy}`));
|
|
225
|
+
skipped = before - remaining.length;
|
|
226
|
+
units.length = 0;
|
|
227
|
+
units.push(...remaining);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// No index yet — nothing to skip.
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (skipped > 0) {
|
|
234
|
+
warnings.push(`Skipped ${skipped} page/strategy pair(s) already in ${join(dir, "_index.json")}. ` +
|
|
235
|
+
"Pass skip_completed:false to re-measure them.");
|
|
236
|
+
}
|
|
237
|
+
if (units.length === 0) {
|
|
238
|
+
return {
|
|
239
|
+
content: [{
|
|
240
|
+
type: "text",
|
|
241
|
+
text: JSON.stringify({
|
|
242
|
+
complete: true,
|
|
243
|
+
progress: { done: 0, total: 0, failed: 0 },
|
|
244
|
+
message: "Every requested page/strategy is already in the index. Nothing to run.",
|
|
245
|
+
index_file: join(dir, "_index.json"),
|
|
246
|
+
warnings,
|
|
247
|
+
}, null, 2),
|
|
248
|
+
}],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const totalRuns = units.length * runsPerUrl;
|
|
252
|
+
if (!key && totalRuns > KEYLESS_RUN_CAP) {
|
|
253
|
+
return {
|
|
254
|
+
content: [{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: JSON.stringify({
|
|
257
|
+
error: "keyless_batch_too_large",
|
|
258
|
+
message: keylessWarning(totalRuns),
|
|
259
|
+
runs_requested: totalRuns,
|
|
260
|
+
keyless_cap: KEYLESS_RUN_CAP,
|
|
261
|
+
env_var: KEY_ENV_VAR,
|
|
262
|
+
}, null, 2),
|
|
263
|
+
}],
|
|
264
|
+
isError: true,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const unreachable = pages
|
|
268
|
+
.map((p) => ({ url: p.url, check: checkPublicReachability(p.url) }))
|
|
269
|
+
.filter((p) => !p.check.auditable);
|
|
270
|
+
if (unreachable.length > 0) {
|
|
271
|
+
return {
|
|
272
|
+
content: [{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: JSON.stringify({
|
|
275
|
+
error: "pages_not_auditable",
|
|
276
|
+
pages: unreachable.map((p) => ({ url: p.url, reason: p.check.reason })),
|
|
277
|
+
}, null, 2),
|
|
278
|
+
}],
|
|
279
|
+
isError: true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
await mkdir(dir, { recursive: true });
|
|
283
|
+
const start = decodeCursor(cursor);
|
|
284
|
+
const results = [];
|
|
285
|
+
const failures = [];
|
|
286
|
+
let index = start;
|
|
287
|
+
let callsUsed = 0;
|
|
288
|
+
let budgetExhausted = false;
|
|
289
|
+
while (index < units.length && callsUsed + runsPerUrl <= Math.max(maxRuns, runsPerUrl)) {
|
|
290
|
+
// Check before starting a unit, never mid-unit: abandoning a page
|
|
291
|
+
// half-way through its repeat runs would leave a median of one.
|
|
292
|
+
// Reserve room for the run about to start, rather than checking only
|
|
293
|
+
// whether the budget is already spent — otherwise a 40 s run at the
|
|
294
|
+
// 149 s mark starts a second and overshoots by a full run.
|
|
295
|
+
const reserve = TYPICAL_RUN_MS * runsPerUrl;
|
|
296
|
+
if (callsUsed > 0 && Date.now() - startedAt + reserve >= budgetMs) {
|
|
297
|
+
budgetExhausted = true;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
const unit = units[index];
|
|
301
|
+
// Raw bodies are kept alongside the parsed form: the parsed summary
|
|
302
|
+
// feeds the index, the raw response is what gets written to disk.
|
|
303
|
+
const attempts = [];
|
|
304
|
+
let lastError = "";
|
|
305
|
+
for (let run = 0; run < runsPerUrl; run++) {
|
|
306
|
+
if (callsUsed > 0)
|
|
307
|
+
await sleep(INTER_CALL_DELAY_MS);
|
|
308
|
+
const requestUrl = buildRequestUrl(unit.page.url, unit.strategy, requestedCategories, key);
|
|
309
|
+
// Clamp this run's timeout to the budget it has left. Without it the
|
|
310
|
+
// budget is only advisory: a single hang costs the full PSI timeout
|
|
311
|
+
// on top of whatever was already spent, and a 150 s chunk ran 188 s.
|
|
312
|
+
// The reserve check above guarantees at least TYPICAL_RUN_MS remains,
|
|
313
|
+
// so this never squeezes a run into an unwinnable window.
|
|
314
|
+
// The whole call, retries included, is bounded by what is left of the
|
|
315
|
+
// chunk budget — so a retry can never push the chunk past its ceiling.
|
|
316
|
+
const remainingMs = budgetMs - (Date.now() - startedAt);
|
|
317
|
+
const response = await httpGetJson(requestUrl, {
|
|
318
|
+
timeoutMs: PSI_TIMEOUT_MS,
|
|
319
|
+
totalBudgetMs: Math.max(TYPICAL_RUN_MS, remainingMs),
|
|
320
|
+
// One retry, and timeouts are included: measurement showed timeout
|
|
321
|
+
// is the dominant failure mode here and that it usually recovers,
|
|
322
|
+
// while 5xx is rare and cheap. Two attempts, not three.
|
|
323
|
+
retries: 1,
|
|
324
|
+
retryOnTimeout: true,
|
|
325
|
+
retryDelayMs: 3_000,
|
|
326
|
+
redact: key ? [key] : [],
|
|
327
|
+
});
|
|
328
|
+
callsUsed++;
|
|
329
|
+
if (!response.ok || !response.data) {
|
|
330
|
+
lastError = response.error ?? `HTTP ${response.status}`;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
attempts.push({
|
|
335
|
+
parsed: parsePsiResponse(response.body, {
|
|
336
|
+
strategy: unit.strategy,
|
|
337
|
+
originFallback,
|
|
338
|
+
}),
|
|
339
|
+
raw: response.body,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
// A 200 carrying a runtimeError is a failed run, not a zero score.
|
|
344
|
+
lastError =
|
|
345
|
+
err instanceof PsiRuntimeError
|
|
346
|
+
? `Lighthouse runtime error ${err.code}: ${err.message}`
|
|
347
|
+
: err.message;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (attempts.length === 0) {
|
|
351
|
+
failures.push({ url: unit.page.url, strategy: unit.strategy, error: lastError });
|
|
352
|
+
index++;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
// Median run by performance score. Averaging the metrics of different
|
|
356
|
+
// runs would invent a page that was never measured; picking the median
|
|
357
|
+
// run keeps every number internally consistent with one real load.
|
|
358
|
+
const ordered = [...attempts].sort((a, b) => (a.parsed.scores.performance ?? 0) - (b.parsed.scores.performance ?? 0));
|
|
359
|
+
const median = ordered[Math.floor(ordered.length / 2)];
|
|
360
|
+
const parsed = median.parsed;
|
|
361
|
+
// Write the **raw** PSI response, not the parsed summary.
|
|
362
|
+
//
|
|
363
|
+
// This originally saved the parsed object, which holds only failing
|
|
364
|
+
// audits — 9 KB where the real response is closer to a megabyte. The
|
|
365
|
+
// report spec sends the agent to these files to fill in the diagnostic
|
|
366
|
+
// checklist (unused JS, third-party weight, image formats), and none of
|
|
367
|
+
// that detail survived parsing. The parsed summary lives in _index.json;
|
|
368
|
+
// disk holds the full record.
|
|
369
|
+
const fileName = `${slugFor(unit.page)}_${unit.strategy}.json`;
|
|
370
|
+
await writeFile(join(dir, fileName), median.raw, "utf8");
|
|
371
|
+
const labFindings = parsed.lighthouse.failedAudits
|
|
372
|
+
.map(formatLighthouseFinding)
|
|
373
|
+
.filter((f) => f !== null);
|
|
374
|
+
const fieldFindings = parsed.field ? formatFieldFindings(parsed.field.metrics) : [];
|
|
375
|
+
const comparisons = compareLabField(parsed.lab, parsed.field);
|
|
376
|
+
let findings = suppressComponentFindings([...fieldFindings, ...labFindings]);
|
|
377
|
+
findings = sortFindingsByPriority(applyComparisons(findings, comparisons));
|
|
378
|
+
results.push({
|
|
379
|
+
template: unit.page.template,
|
|
380
|
+
label: unit.page.label,
|
|
381
|
+
url: unit.page.url,
|
|
382
|
+
strategy: unit.strategy,
|
|
383
|
+
runs: attempts.length,
|
|
384
|
+
scores: parsed.scores,
|
|
385
|
+
lab: parsed.lab,
|
|
386
|
+
field: parsed.field,
|
|
387
|
+
comparisons,
|
|
388
|
+
findings,
|
|
389
|
+
report_file: fileName,
|
|
390
|
+
});
|
|
391
|
+
index++;
|
|
392
|
+
}
|
|
393
|
+
if (budgetExhausted) {
|
|
394
|
+
warnings.push(`Stopped after ${Math.round((Date.now() - startedAt) / 1000)}s to stay inside the ` +
|
|
395
|
+
"per-call time budget. Call again with the cursor to continue.");
|
|
396
|
+
}
|
|
397
|
+
const indexPath = await mergeIndex(dir, results);
|
|
398
|
+
const complete = index >= units.length;
|
|
399
|
+
if (failures.length > 0) {
|
|
400
|
+
warnings.push(`${failures.length} run(s) failed. PSI fails intermittently — the same URL often ` +
|
|
401
|
+
"succeeds on a later attempt. Call this tool again with the same pages and no " +
|
|
402
|
+
"cursor: completed runs are skipped automatically, so only the gaps are retried.");
|
|
403
|
+
}
|
|
404
|
+
let aggregateBlock;
|
|
405
|
+
if (complete) {
|
|
406
|
+
// Aggregate over everything on disk, not just this chunk — the whole
|
|
407
|
+
// point of the running index is that the final numbers cover the batch.
|
|
408
|
+
let allRuns = results;
|
|
409
|
+
try {
|
|
410
|
+
allRuns = JSON.parse(await readFile(indexPath, "utf8"));
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
warnings.push("Could not re-read the index; aggregate covers this call's runs only.");
|
|
414
|
+
}
|
|
415
|
+
const systemic = collapseSystemicFindings(allRuns);
|
|
416
|
+
aggregateBlock = {
|
|
417
|
+
...aggregate(allRuns),
|
|
418
|
+
systemic_findings: systemic.findings,
|
|
419
|
+
collapsed_vitals: systemic.collapsedVitals,
|
|
420
|
+
...(systemic.collapsedVitals.length > 0
|
|
421
|
+
? {
|
|
422
|
+
collapse_note: `Per-page findings for ${systemic.collapsedVitals.join(", ")} are superseded by ` +
|
|
423
|
+
"the systemic findings above; report them once, site-wide, not per page.",
|
|
424
|
+
}
|
|
425
|
+
: {}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const payload = {
|
|
429
|
+
complete,
|
|
430
|
+
...(complete ? {} : { cursor: encodeCursor(index) }),
|
|
431
|
+
progress: { done: index, total: units.length, failed: failures.length },
|
|
432
|
+
api_key: { present: Boolean(key), source },
|
|
433
|
+
output_dir: dir,
|
|
434
|
+
index_file: indexPath,
|
|
435
|
+
results,
|
|
436
|
+
...(failures.length > 0 ? { failures } : {}),
|
|
437
|
+
...(aggregateBlock ? { aggregate: aggregateBlock } : {}),
|
|
438
|
+
...(complete
|
|
439
|
+
? {}
|
|
440
|
+
: { next_step: "Call run_performance_audit again with this cursor and the same pages." }),
|
|
441
|
+
warnings,
|
|
442
|
+
};
|
|
443
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=performanceAudit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performanceAudit.js","sourceRoot":"","sources":["../../src/tools/performanceAudit.ts"],"names":[],"mappings":"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"}
|