nfunc-mcp 0.2.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 +85 -335
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/mappers/a11yDedupe.d.ts +25 -0
- package/dist/mappers/a11yDedupe.js +95 -0
- package/dist/mappers/a11yDedupe.js.map +1 -0
- package/dist/mappers/compositeScore.d.ts +23 -0
- package/dist/mappers/compositeScore.js +107 -0
- package/dist/mappers/compositeScore.js.map +1 -0
- package/dist/mappers/correlator.js +94 -33
- package/dist/mappers/correlator.js.map +1 -1
- package/dist/mappers/defectFormatter.js +129 -15
- package/dist/mappers/defectFormatter.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/priorityMapper.d.ts +38 -0
- package/dist/mappers/priorityMapper.js +126 -0
- package/dist/mappers/priorityMapper.js.map +1 -1
- 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/accessibility.js +72 -31
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/lighthouse.d.ts +14 -0
- package/dist/tools/lighthouse.js +134 -34
- package/dist/tools/lighthouse.js.map +1 -1
- 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/tools/qaGate.js +109 -34
- package/dist/tools/qaGate.js.map +1 -1
- 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/outputParsers.d.ts +25 -0
- package/dist/utils/outputParsers.js +42 -0
- package/dist/utils/outputParsers.js.map +1 -1
- 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 +14 -4
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { httpGet } from "../utils/httpClient.js";
|
|
3
|
+
import { readSitemap } from "../utils/sitemapReader.js";
|
|
4
|
+
import { readUrlsFromCsv } from "../utils/csvReader.js";
|
|
5
|
+
import { classifyUrls } from "../utils/urlClassifier.js";
|
|
6
|
+
import { checkPublicReachability } from "../utils/publicUrl.js";
|
|
7
|
+
import { resolveApiKey, keylessWarning, KEY_ENV_VAR, KEYLESS_RUN_CAP, } from "../utils/psiAuth.js";
|
|
8
|
+
/**
|
|
9
|
+
* Wall-clock budget per PSI call.
|
|
10
|
+
*
|
|
11
|
+
* Measured, not guessed: fivebelow.com's homepage took 39-47 s per run against
|
|
12
|
+
* a live key, roughly three times the 10-30 s the documentation implies. An
|
|
13
|
+
* estimate exists to let someone decide whether to approve a batch, so it
|
|
14
|
+
* should err high — a 20-minute job quoted as 7 gets approved and then
|
|
15
|
+
* abandoned.
|
|
16
|
+
*/
|
|
17
|
+
const SECONDS_PER_RUN = 45;
|
|
18
|
+
const DAILY_QUOTA_WITH_KEY = 25_000;
|
|
19
|
+
/**
|
|
20
|
+
* How many templates get sampled by default.
|
|
21
|
+
*
|
|
22
|
+
* Classification is deliberately granular — merging "/en/blog/release/*" into
|
|
23
|
+
* "/en/*" would hide real differences — but granularity compounds: nodejs.org
|
|
24
|
+
* resolves to 100 templates, and sampling all of them is 364 PSI calls of
|
|
25
|
+
* mostly redundant blog posts. Templates are ranked by how much of the site
|
|
26
|
+
* they represent and the top slice is sampled; the rest are still reported, so
|
|
27
|
+
* the user can pull any of them in by name.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_MAX_TEMPLATES = 12;
|
|
30
|
+
const inputShape = {
|
|
31
|
+
origin: z
|
|
32
|
+
.string()
|
|
33
|
+
.url()
|
|
34
|
+
.describe("Site to plan an audit for, e.g. https://www.example.com"),
|
|
35
|
+
discovery: z
|
|
36
|
+
.enum(["sitemap", "list", "csv", "crawl"])
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("How to find URLs. 'sitemap' (default) reads robots.txt and " +
|
|
39
|
+
"/sitemap.xml — fastest and authoritative. 'list' uses the urls " +
|
|
40
|
+
"input. 'csv' reads csv_path. 'crawl' is not implemented yet."),
|
|
41
|
+
urls: z.array(z.string().url()).optional(),
|
|
42
|
+
csv_path: z.string().optional(),
|
|
43
|
+
strategy: z.enum(["mobile", "desktop", "both"]).optional(),
|
|
44
|
+
runs_per_url: z.number().int().min(1).max(5).optional(),
|
|
45
|
+
max_templates: z
|
|
46
|
+
.number()
|
|
47
|
+
.int()
|
|
48
|
+
.min(1)
|
|
49
|
+
.max(100)
|
|
50
|
+
.optional()
|
|
51
|
+
.describe(`How many templates to sample, largest first (default ${DEFAULT_MAX_TEMPLATES}). ` +
|
|
52
|
+
"Smaller templates are still listed so they can be requested by name."),
|
|
53
|
+
api_key: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe(`Optional PSI key. Prefer setting ${KEY_ENV_VAR} in the MCP client ` +
|
|
57
|
+
"config — a key passed here is stored in the conversation transcript."),
|
|
58
|
+
};
|
|
59
|
+
/** Same-host filter. A sitemap may list other properties; auditing them silently would be wrong. */
|
|
60
|
+
function sameOrigin(url, originHost) {
|
|
61
|
+
try {
|
|
62
|
+
return new URL(url).hostname.toLowerCase() === originHost;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** A leading /en/ or /en-us/ is a locale, not a section of the site. */
|
|
69
|
+
const LOCALE_SEGMENT = /^[a-z]{2}(-[a-z]{2})?$/i;
|
|
70
|
+
/**
|
|
71
|
+
* Which part of the site a template belongs to.
|
|
72
|
+
*
|
|
73
|
+
* The first path segment, except when that segment is a locale code — on
|
|
74
|
+
* nodejs.org every page lives under /en/, so keying on segment one put blog,
|
|
75
|
+
* docs and downloads in a single bucket and defeated the round-robin
|
|
76
|
+
* entirely. Locale-prefixed URLs are common enough (and this check narrow
|
|
77
|
+
* enough) to be worth special-casing.
|
|
78
|
+
*/
|
|
79
|
+
function sectionOf(pattern) {
|
|
80
|
+
const segments = pattern.replace(/^\//, "").split("?")[0].split("/").filter(Boolean);
|
|
81
|
+
if (segments.length > 1 && LOCALE_SEGMENT.test(segments[0]))
|
|
82
|
+
segments.shift();
|
|
83
|
+
return segments[0] ?? "/";
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Choose which templates to sample.
|
|
87
|
+
*
|
|
88
|
+
* Ranking purely by URL count fills the budget with whatever section is
|
|
89
|
+
* biggest: on nodejs.org that is eleven flavours of blog and nothing else,
|
|
90
|
+
* while a human would have picked a blog post, the docs, the download page and
|
|
91
|
+
* the homepage. Volume is a poor proxy for what a performance audit needs,
|
|
92
|
+
* because templates within one section usually share a codepath and therefore
|
|
93
|
+
* share their defects — the second blog variant tells you far less than the
|
|
94
|
+
* first download page.
|
|
95
|
+
*
|
|
96
|
+
* So selection round-robins across top-level sections, taking each section's
|
|
97
|
+
* largest remaining template in turn. Within a section, and between sections
|
|
98
|
+
* of equal standing, URL count still decides. The entry page — whatever URL was
|
|
99
|
+
* actually requested — is seeded first regardless: it is a single URL, so volume
|
|
100
|
+
* ranking always sorts it last, and it is the page the user named.
|
|
101
|
+
*/
|
|
102
|
+
function selectBudget(eligible, budget, entryPattern = "/") {
|
|
103
|
+
// The entry page is one URL, so volume ranking always sorts it last.
|
|
104
|
+
const homepage = eligible.find((t) => t.pattern === entryPattern);
|
|
105
|
+
const picked = homepage ? [homepage] : [];
|
|
106
|
+
const bySection = new Map();
|
|
107
|
+
for (const t of eligible) {
|
|
108
|
+
if (t === homepage)
|
|
109
|
+
continue;
|
|
110
|
+
const key = sectionOf(t.pattern);
|
|
111
|
+
const bucket = bySection.get(key);
|
|
112
|
+
if (bucket)
|
|
113
|
+
bucket.push(t);
|
|
114
|
+
else
|
|
115
|
+
bySection.set(key, [t]);
|
|
116
|
+
}
|
|
117
|
+
// Sections ordered by their largest template, so the dominant part of the
|
|
118
|
+
// site is still sampled first — just not exclusively.
|
|
119
|
+
const queues = [...bySection.values()]
|
|
120
|
+
.map((list) => [...list].sort((a, b) => b.urlCount - a.urlCount))
|
|
121
|
+
.sort((a, b) => b[0].urlCount - a[0].urlCount);
|
|
122
|
+
let progressed = true;
|
|
123
|
+
while (picked.length < budget && progressed) {
|
|
124
|
+
progressed = false;
|
|
125
|
+
for (const queue of queues) {
|
|
126
|
+
if (picked.length >= budget)
|
|
127
|
+
break;
|
|
128
|
+
const next = queue.shift();
|
|
129
|
+
if (!next)
|
|
130
|
+
continue;
|
|
131
|
+
picked.push(next);
|
|
132
|
+
progressed = true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return picked;
|
|
136
|
+
}
|
|
137
|
+
function buildQuestions(templates, gated, deferred, runs, runsPerUrl, hasKey, truncated) {
|
|
138
|
+
const questions = [];
|
|
139
|
+
questions.push(`Sample sizes: the plan audits ${templates.reduce((n, t) => n + t.suggestedSample, 0)} ` +
|
|
140
|
+
`URLs across ${templates.length} templates. Accept these, or set your own per template?`);
|
|
141
|
+
if (runsPerUrl === 1) {
|
|
142
|
+
const extra = Math.round((runs * 2 * SECONDS_PER_RUN) / 60);
|
|
143
|
+
questions.push(`runs_per_url is 1, so a single slow run becomes an unchallenged data ` +
|
|
144
|
+
`point. Raising it to 3 takes the median and removes that risk, at ` +
|
|
145
|
+
`roughly ${extra} extra minutes. Raise it?`);
|
|
146
|
+
}
|
|
147
|
+
if (deferred.length > 0) {
|
|
148
|
+
questions.push(`${deferred.length} smaller template(s) are listed but not sampled, to keep the run ` +
|
|
149
|
+
`affordable. Pull any of them in by pattern, or raise max_templates?`);
|
|
150
|
+
}
|
|
151
|
+
if (gated.length > 0) {
|
|
152
|
+
questions.push(`${gated.map((t) => t.label).join(", ")} cannot be audited by PSI — it ` +
|
|
153
|
+
`fetches anonymously, so it would measure an empty cart or a login ` +
|
|
154
|
+
`redirect. Run these through run_lighthouse separately?`);
|
|
155
|
+
}
|
|
156
|
+
if (!hasKey) {
|
|
157
|
+
questions.push(`No ${KEY_ENV_VAR} is set. Provide a key, or cut the run to ` +
|
|
158
|
+
`${KEYLESS_RUN_CAP} runs or fewer?`);
|
|
159
|
+
}
|
|
160
|
+
if (truncated) {
|
|
161
|
+
questions.push("Sitemap discovery hit its cap, so the template breakdown covers a " +
|
|
162
|
+
"subset of the site. Proceed with that, or narrow the audit to " +
|
|
163
|
+
"specific sections?");
|
|
164
|
+
}
|
|
165
|
+
return questions;
|
|
166
|
+
}
|
|
167
|
+
export function registerPerformanceAuditPlanTool(server) {
|
|
168
|
+
server.registerTool("plan_performance_audit", {
|
|
169
|
+
description: "Plans a PageSpeed Insights audit without spending any PSI quota. " +
|
|
170
|
+
"Discovers a site's URLs (sitemap, an explicit list, or a CSV), " +
|
|
171
|
+
"clusters them into page templates, flags the ones PSI physically " +
|
|
172
|
+
"cannot audit, and returns a costed run plan plus the questions that " +
|
|
173
|
+
"need answering before it runs. " +
|
|
174
|
+
"\n\n" +
|
|
175
|
+
"**Always call this before run_performance_audit.** Present the " +
|
|
176
|
+
"returned templates and `questions` to the user, get their answers, " +
|
|
177
|
+
"then pass the confirmed page list to run_performance_audit. Nothing " +
|
|
178
|
+
"is audited until they approve — a misclassified template should " +
|
|
179
|
+
"cost a conversation turn, not 40 API calls." +
|
|
180
|
+
"\n\n" +
|
|
181
|
+
"PSI is for publicly hosted sites. Localhost and private hosts are " +
|
|
182
|
+
"rejected here; use run_lighthouse for those.",
|
|
183
|
+
inputSchema: inputShape,
|
|
184
|
+
}, async ({ origin, discovery, urls, csv_path, strategy, runs_per_url, max_templates, api_key }) => {
|
|
185
|
+
const warnings = [];
|
|
186
|
+
const mode = discovery ?? "sitemap";
|
|
187
|
+
const resolvedStrategy = strategy ?? "both";
|
|
188
|
+
const runsPerUrl = runs_per_url ?? 1;
|
|
189
|
+
const maxTemplates = max_templates ?? DEFAULT_MAX_TEMPLATES;
|
|
190
|
+
const strategyCount = resolvedStrategy === "both" ? 2 : 1;
|
|
191
|
+
// --- Preflight ------------------------------------------------------
|
|
192
|
+
const reach = checkPublicReachability(origin);
|
|
193
|
+
if (!reach.auditable) {
|
|
194
|
+
return {
|
|
195
|
+
content: [
|
|
196
|
+
{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: JSON.stringify({ error: "origin_not_auditable", origin, reason: reach.reason }, null, 2),
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
isError: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (mode === "crawl") {
|
|
205
|
+
return {
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: "text",
|
|
209
|
+
text: JSON.stringify({
|
|
210
|
+
error: "crawl_not_implemented",
|
|
211
|
+
message: "Crawling is not built yet. Use discovery:'sitemap' " +
|
|
212
|
+
"(default), or supply URLs with discovery:'list' or " +
|
|
213
|
+
"discovery:'csv'.",
|
|
214
|
+
}, null, 2),
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const originHost = new URL(origin).hostname.toLowerCase();
|
|
221
|
+
const liveCheck = await httpGet(origin, { timeoutMs: 15_000, retries: 1 });
|
|
222
|
+
if (!liveCheck.ok) {
|
|
223
|
+
// A bot wall blocking *us* says nothing about PSI, which fetches from
|
|
224
|
+
// Google's own address space and is widely allowlisted — fivebelow.com
|
|
225
|
+
// returns a Cloudflare challenge here while having served PSI fine.
|
|
226
|
+
// Reporting that as "PSI will likely fail" would be a false alarm.
|
|
227
|
+
const botWalled = liveCheck.status === 403 || liveCheck.status === 503;
|
|
228
|
+
warnings.push(botWalled
|
|
229
|
+
? `${origin} returned HTTP ${liveCheck.status} to this preflight, which usually means ` +
|
|
230
|
+
"a bot-protection challenge rather than a broken site. PSI fetches from Google's " +
|
|
231
|
+
"infrastructure and is often allowlisted where we are not, so this is not a reason " +
|
|
232
|
+
"to stop — but if the audit comes back empty, that challenge page is the first suspect."
|
|
233
|
+
: `${origin} did not respond successfully (${liveCheck.error ?? `HTTP ${liveCheck.status}`}). ` +
|
|
234
|
+
"PSI will likely fail the same way; confirm the URL before running.");
|
|
235
|
+
}
|
|
236
|
+
// --- Discovery ------------------------------------------------------
|
|
237
|
+
let discovered = [];
|
|
238
|
+
let sitemapUrl = null;
|
|
239
|
+
let truncated = false;
|
|
240
|
+
if (mode === "list") {
|
|
241
|
+
if (!urls || urls.length === 0) {
|
|
242
|
+
return {
|
|
243
|
+
content: [
|
|
244
|
+
{
|
|
245
|
+
type: "text",
|
|
246
|
+
text: JSON.stringify({ error: "missing_urls", message: "discovery:'list' requires a non-empty urls array." }, null, 2),
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
discovered = urls;
|
|
253
|
+
}
|
|
254
|
+
else if (mode === "csv") {
|
|
255
|
+
if (!csv_path) {
|
|
256
|
+
return {
|
|
257
|
+
content: [
|
|
258
|
+
{
|
|
259
|
+
type: "text",
|
|
260
|
+
text: JSON.stringify({ error: "missing_csv_path", message: "discovery:'csv' requires csv_path." }, null, 2),
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
isError: true,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const csv = readUrlsFromCsv(csv_path);
|
|
268
|
+
discovered = csv.urls;
|
|
269
|
+
warnings.push(...csv.warnings);
|
|
270
|
+
warnings.push(`Read ${csv.urls.length} URLs from column "${csv.column}" of ${csv_path}.`);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
return {
|
|
274
|
+
content: [
|
|
275
|
+
{
|
|
276
|
+
type: "text",
|
|
277
|
+
text: JSON.stringify({ error: "csv_unreadable", message: err.message }, null, 2),
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
isError: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const sitemap = await readSitemap(origin);
|
|
286
|
+
discovered = sitemap.urls;
|
|
287
|
+
sitemapUrl = sitemap.sitemapsRead[0] ?? null;
|
|
288
|
+
truncated = sitemap.truncated;
|
|
289
|
+
warnings.push(...sitemap.warnings);
|
|
290
|
+
}
|
|
291
|
+
// Seed the URL the user actually gave.
|
|
292
|
+
//
|
|
293
|
+
// Sitemaps frequently omit their entry page — nodejs.org lists /en/... and
|
|
294
|
+
// never "/" — so a plan built purely from discovery can skip the very page
|
|
295
|
+
// that was asked about. This originally seeded `new URL("/", origin)`,
|
|
296
|
+
// which silently retargeted the audit whenever the input carried a path:
|
|
297
|
+
// a request for /ecommerce/ was planned as the site's domain homepage, a
|
|
298
|
+
// completely different page.
|
|
299
|
+
const entry = new URL(origin);
|
|
300
|
+
const entryUrl = entry.toString();
|
|
301
|
+
const entryPath = entry.pathname.replace(/\/$/, "") || "/";
|
|
302
|
+
const sameUrl = (a, b) => a.replace(/\/$/, "") === b.replace(/\/$/, "");
|
|
303
|
+
if (!discovered.some((u) => sameUrl(u, entryUrl))) {
|
|
304
|
+
discovered = [entryUrl, ...discovered];
|
|
305
|
+
warnings.push(`Added ${entryUrl} — it was not listed by ${mode} discovery.`);
|
|
306
|
+
}
|
|
307
|
+
let onOrigin = discovered.filter((u) => sameOrigin(u, originHost));
|
|
308
|
+
// A path in the input scopes the audit to that subtree. Someone auditing
|
|
309
|
+
// /ecommerce/ is asking about the shop, not the blog that shares the host.
|
|
310
|
+
if (entryPath !== "/") {
|
|
311
|
+
const before = onOrigin.length;
|
|
312
|
+
onOrigin = onOrigin.filter((u) => {
|
|
313
|
+
try {
|
|
314
|
+
const path = new URL(u).pathname.replace(/\/$/, "") || "/";
|
|
315
|
+
return path === entryPath || path.startsWith(`${entryPath}/`);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
if (before > onOrigin.length) {
|
|
322
|
+
warnings.push(`Scoped to ${entryPath}/ — dropped ${before - onOrigin.length} URL(s) outside that ` +
|
|
323
|
+
`path. Pass the bare origin instead to audit the whole site.`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (onOrigin.length < discovered.length) {
|
|
327
|
+
warnings.push(`Dropped ${discovered.length - onOrigin.length} URL(s) pointing at a different host than ${originHost}.`);
|
|
328
|
+
}
|
|
329
|
+
if (onOrigin.length === 0) {
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: JSON.stringify({
|
|
335
|
+
error: "no_urls_found",
|
|
336
|
+
origin,
|
|
337
|
+
discovery: mode,
|
|
338
|
+
warnings,
|
|
339
|
+
message: "No auditable URLs were discovered. Supply them directly " +
|
|
340
|
+
"with discovery:'list', or point at a CSV export.",
|
|
341
|
+
}, null, 2),
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
isError: true,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// --- Classify -------------------------------------------------------
|
|
348
|
+
const { templates, skipped } = classifyUrls(onOrigin);
|
|
349
|
+
if (skipped > 0)
|
|
350
|
+
warnings.push(`Skipped ${skipped} unparseable URL(s).`);
|
|
351
|
+
// classifyUrls returns largest-first, so the head of the list is the
|
|
352
|
+
// slice of the site worth spending calls on — with one correction. The
|
|
353
|
+
// homepage is a single URL and therefore ranks last by volume, which on
|
|
354
|
+
// nodejs.org pushed it out of the sample entirely in favour of an
|
|
355
|
+
// eleventh blog variant. It is the most-visited page on essentially
|
|
356
|
+
// every site, so it is seeded into the budget before ranking applies.
|
|
357
|
+
const eligible = templates.filter((t) => !t.anySessionGated);
|
|
358
|
+
const inBudget = new Set(selectBudget(eligible, maxTemplates, entryPath).map((t) => t.id));
|
|
359
|
+
const planned = templates.map((t) => {
|
|
360
|
+
if (!inBudget.has(t.id) && !t.anySessionGated) {
|
|
361
|
+
return {
|
|
362
|
+
...t,
|
|
363
|
+
suggestedSample: 0,
|
|
364
|
+
reason: "below_sampling_budget",
|
|
365
|
+
recommendation: `Not sampled by default — it is outside the top ${maxTemplates} templates by ` +
|
|
366
|
+
"URL count. Ask for it by pattern to include it.",
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (t.anySessionGated) {
|
|
370
|
+
return {
|
|
371
|
+
...t,
|
|
372
|
+
auditable: false,
|
|
373
|
+
suggestedSample: 0,
|
|
374
|
+
reason: "session_gated",
|
|
375
|
+
recommendation: "PSI fetches anonymously and would measure a logged-out or " +
|
|
376
|
+
"empty version of this page. Audit it with run_lighthouse, " +
|
|
377
|
+
"which runs Chrome locally and can carry session cookies.",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return { ...t, sampled_urls: t.candidates.slice(0, t.suggestedSample) };
|
|
381
|
+
});
|
|
382
|
+
const sampled = planned.filter((t) => t.auditable && t.suggestedSample > 0);
|
|
383
|
+
const gated = planned.filter((t) => !t.auditable);
|
|
384
|
+
const deferred = planned.filter((t) => t.auditable && t.suggestedSample === 0);
|
|
385
|
+
// --- Cost -----------------------------------------------------------
|
|
386
|
+
const sampledUrls = sampled.reduce((n, t) => n + t.suggestedSample, 0);
|
|
387
|
+
const urlsCovered = sampled.reduce((n, t) => n + t.urlCount, 0);
|
|
388
|
+
const runs = sampledUrls * strategyCount * runsPerUrl;
|
|
389
|
+
const { key, source } = resolveApiKey(api_key);
|
|
390
|
+
if (!key && runs > KEYLESS_RUN_CAP)
|
|
391
|
+
warnings.push(keylessWarning(runs));
|
|
392
|
+
const payload = {
|
|
393
|
+
origin,
|
|
394
|
+
discovery: {
|
|
395
|
+
source: mode,
|
|
396
|
+
urls_found: onOrigin.length,
|
|
397
|
+
sitemap_url: sitemapUrl,
|
|
398
|
+
truncated,
|
|
399
|
+
},
|
|
400
|
+
api_key: { present: Boolean(key), source },
|
|
401
|
+
strategy: resolvedStrategy,
|
|
402
|
+
runs_per_url: runsPerUrl,
|
|
403
|
+
templates: planned.map((t) => ({
|
|
404
|
+
id: t.id,
|
|
405
|
+
label: t.label,
|
|
406
|
+
pattern: t.pattern,
|
|
407
|
+
url_count: t.urlCount,
|
|
408
|
+
suggested_sample: t.suggestedSample,
|
|
409
|
+
candidates: t.candidates,
|
|
410
|
+
...(t.sampled_urls ? { sampled_urls: t.sampled_urls } : {}),
|
|
411
|
+
auditable: t.auditable,
|
|
412
|
+
...(t.reason ? { reason: t.reason } : {}),
|
|
413
|
+
...(t.recommendation ? { recommendation: t.recommendation } : {}),
|
|
414
|
+
})),
|
|
415
|
+
coverage: {
|
|
416
|
+
templates_total: planned.length,
|
|
417
|
+
templates_sampled: sampled.length,
|
|
418
|
+
templates_deferred: deferred.length,
|
|
419
|
+
urls_represented: urlsCovered,
|
|
420
|
+
urls_total: onOrigin.length,
|
|
421
|
+
represented_pct: Math.round((urlsCovered / onOrigin.length) * 100),
|
|
422
|
+
},
|
|
423
|
+
estimate: {
|
|
424
|
+
urls_sampled: sampledUrls,
|
|
425
|
+
runs,
|
|
426
|
+
wall_clock_minutes: Math.max(1, Math.round((runs * SECONDS_PER_RUN) / 60)),
|
|
427
|
+
quota_used: runs,
|
|
428
|
+
quota_limit: key ? DAILY_QUOTA_WITH_KEY : KEYLESS_RUN_CAP,
|
|
429
|
+
},
|
|
430
|
+
questions: buildQuestions(sampled, gated, deferred, runs, runsPerUrl, Boolean(key), truncated),
|
|
431
|
+
next_step: "Show the templates and questions to the user. Once they confirm " +
|
|
432
|
+
"the sample, call run_performance_audit with the approved pages.",
|
|
433
|
+
warnings,
|
|
434
|
+
};
|
|
435
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
//# sourceMappingURL=performanceAuditPlan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performanceAuditPlan.js","sourceRoot":"","sources":["../../src/tools/performanceAuditPlan.ts"],"names":[],"mappings":"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"}
|