
Vercel Optimize
- 41.5k installs
- 29.5k repo stars
- Updated July 24, 2026
- vercel-labs/agent-skills
Vercel Optimize is a skill that audits Vercel deployments for performance and cost optimization using production metrics.
About
Skill for auditing Vercel-deployed projects with an observability-first approach. Collects production metrics, scans codebases, and produces ranked recommendations grounded in verified files and framework-aware Vercel documentation. Supports Next.js, SvelteKit, Nuxt, and Astro with limited Hono/Remix support.
- Observability-first audits using production metrics before code investigation
- Framework-specific optimization for Next.js, SvelteKit, Nuxt, and Astro
- Cost analysis covering Function Invocations, Build Minutes, and Data Transfer
Vercel Optimize by the numbers
- 41,528 all-time installs (skills.sh)
- +4,055 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #27 of 1,041 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
vercel-optimize capabilities & compatibility
- Capabilities
- metrics collection · performance audit · cost analysis · framework specific optimization
- Works with
- vercel
- Use cases
- seo · web design
- Runs
- Hosted SaaS
npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-optimizeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 41.5k |
|---|---|
| repo stars | ★ 29.5k |
| Last updated | July 24, 2026 |
| Repository | vercel-labs/agent-skills ↗ |
What it does
Audit Vercel deployments for performance and cost optimization using production metrics and codebase analysis.
Who is it for?
Developers maintaining Vercel-deployed applications who want to reduce costs and improve Core Web Vitals
Skip if: Greenfield projects without Vercel traffic, non-Vercel deployments, or general code review unrelated to Vercel billing or observability signals.
When should I use this skill?
The user asks to optimize a Vercel project, reduce a Vercel bill, find caching opportunities, cut function invocations, or produce a Vercel cost and performance report.
What you get
Metric-backed recommendations identify specific code and configuration changes to reduce costs and improve performance
- optimization report
- verified recommendations
- signals.json audit bundle
By the numbers
- Skill version 1.2.0 requiring Vercel CLI v53+
- Default investigation gate budgets 6 code-scope candidates on a 14-day metrics window
Files
vercel-optimize
Cross-agent entry point for the Vercel Optimize skill. The full procedure is in SKILL.md.
Use this skill when the user asks to optimize a Vercel project, reduce a Vercel bill, investigate slow or expensive routes, find caching opportunities, reduce function invocations, or produce a Vercel cost/performance report.
Do not use it for projects that are not deployed on Vercel, greenfield projects with no traffic, or general code review.
Requirements
- Node.js 20+
- Vercel CLI with
vercel metrics,vercel usage,vercel contract, andvercel apisupport; v53+ is this skill's compatibility floor - Authenticated Vercel CLI session
- Linked Vercel project directory (
vercel link) for route metrics.VERCEL_PROJECT_IDcan help resolve project config, but it does not replace directory linkage forvercel metrics. The project must resolve to a CLI-safe team or personal scope sovercel metrics,vercel usage, andvercel contractall run against the same account. - Observability Plus for per-route metric analysis
Procedure
1. Read SKILL.md. 2. Collect Vercel signals before reading source files. 3. Gate candidates with deterministic scripts. 4. Investigate only files named by launched candidates. 5. Verify recommendations mechanically before rendering the report.
The hard rules are in references/doctrine.md: observability first, deterministic gates, candidate-bound scope, and version-aware citations.
Install
Preferred:
npx skills add vercel-labs/agent-skills --skill vercel-optimizeManual project install:
mkdir -p .agents/skills
cp -R <agent-skills-repo>/skills/vercel-optimize .agents/skills/Then add this to the project AGENTS.md:
When optimizing Vercel cost or performance, follow
`.agents/skills/vercel-optimize/SKILL.md` before proposing changes.
Collect Vercel metrics before reading source files.Contributing to vercel-optimize
Keep changes small, metric-grounded, and fixture-tested. Runtime code lives in skills/vercel-optimize; tests and fixtures live in packages/vercel-optimize-tests so installed skills stay small.
Common changes
| Change | Edit | Test |
|---|---|---|
| Gate | lib/gates/<id>.mjs, lib/gates/index.mjs | node --test packages/vercel-optimize-tests/test/*gate*.test.mjs |
| Scanner | lib/scanners/<id>.mjs, lib/scanners/index.mjs | Scanner-specific test in packages/vercel-optimize-tests/test/ |
| Citation | references/docs-library.json | node skills/vercel-optimize/scripts/check-citations.mjs |
| Support topic | references/support-topics/<id>.md | node --test packages/vercel-optimize-tests/test/support-topics.test.mjs |
| Playbook | references/playbooks/<profile>.md and selection matrix in references/scoring.md | node --test packages/vercel-optimize-tests/test/support-topics.test.mjs packages/vercel-optimize-tests/test/investigation-brief.test.mjs |
| Renderer or verifier | lib/render-report.mjs, lib/verify-claim.mjs, or related module | Focused test plus full test suite |
Generated docs:
node skills/vercel-optimize/scripts/build-docs.mjs
node skills/vercel-optimize/scripts/check-docs-fresh.mjsFull test loop:
node --test packages/vercel-optimize-tests/test/*.test.mjs
node skills/vercel-optimize/scripts/check-docs-fresh.mjs
node skills/vercel-optimize/scripts/check-citations.mjsRules
- No runtime dependencies. Scripts use Node.js 20+ built-ins and the Vercel CLI.
- No recommendation without a Vercel metric signal, code evidence when code changes are proposed, and an allow-listed citation.
- No invented URLs, exact savings projections, or version-mismatched framework APIs.
- No internal repo paths, service names, customer names, or captured private output in fixtures.
- Keep generated report copy customer-facing. Put debug details behind
--debug-out.
Output contracts
Every JSON-emitting script mus
vercel-optimize
Cross-agent entry point for the Vercel Optimize skill. The full procedure is in SKILL.md.
Use this skill when the user asks to optimize a Vercel project, reduce a Vercel bill, investigate slow or expensive routes, find caching opportunities, reduce function invocations, or produce a Vercel cost/performance report.
Do not use it for projects that are not deployed on Vercel, greenfield projects with no traffic, or general code review.
Requirements
- Node.js 20+
- Vercel CLI with
vercel metrics,vercel usage,vercel contract, andvercel apisupport; v53+ is this skill's compatibility floor - Authenticated Vercel CLI session
- Linked Vercel project directory (
vercel link) for route metrics.VERCEL_PROJECT_IDcan help resolve project config, but it does not replace directory linkage forvercel metrics. The project must resolve to a CLI-safe team or personal scope sovercel metrics,vercel usage, andvercel contractall run against the same account. - Observability Plus for per-route metric analysis
Procedure
1. Read SKILL.md. 2. Collect Vercel signals before reading source files. 3. Gate candidates with deterministic scripts. 4. Investigate only files named by launched candidates. 5. Verify recommendations mechanically before rendering the report.
The hard rules are in references/doctrine.md: observability first, deterministic gates, candidate-bound scope, and version-aware citations.
Install
Preferred:
npx skills add vercel-labs/agent-skills --skill vercel-optimizeManual project install:
mkdir -p .agents/skills
cp -R <agent-skills-repo>/skills/vercel-optimize .agents/skills/Then add this to the project AGENTS.md:
When optimizing Vercel cost or performance, follow
`.agents/skills/vercel-optimize/SKILL.md` before proposing changes.
Collect Vercel metrics before reading source files.Contributing to vercel-optimize
Keep changes small, metric-grounded, and fixture-tested. Runtime code lives in skills/vercel-optimize; tests and fixtures live in packages/vercel-optimize-tests so installed skills stay small.
Common changes
| Change | Edit | Test |
|---|---|---|
| Gate | lib/gates/<id>.mjs, lib/gates/index.mjs | node --test packages/vercel-optimize-tests/test/*gate*.test.mjs |
| Scanner | lib/scanners/<id>.mjs, lib/scanners/index.mjs | Scanner-specific test in packages/vercel-optimize-tests/test/ |
| Citation | references/docs-library.json | node skills/vercel-optimize/scripts/check-citations.mjs |
| Support topic | references/support-topics/<id>.md | node --test packages/vercel-optimize-tests/test/support-topics.test.mjs |
| Playbook | references/playbooks/<profile>.md and selection matrix in references/scoring.md | node --test packages/vercel-optimize-tests/test/support-topics.test.mjs packages/vercel-optimize-tests/test/investigation-brief.test.mjs |
| Renderer or verifier | lib/render-report.mjs, lib/verify-claim.mjs, or related module | Focused test plus full test suite |
Generated docs:
node skills/vercel-optimize/scripts/build-docs.mjs
node skills/vercel-optimize/scripts/check-docs-fresh.mjsFull test loop:
node --test packages/vercel-optimize-tests/test/*.test.mjs
node skills/vercel-optimize/scripts/check-docs-fresh.mjs
node skills/vercel-optimize/scripts/check-citations.mjsRules
- No runtime dependencies. Scripts use Node.js 20+ built-ins and the Vercel CLI.
- No recommendation without a Vercel metric signal, code evidence when code changes are proposed, and an allow-listed citation.
- No invented URLs, exact savings projections, or version-mismatched framework APIs.
- No internal repo paths, service names, customer names, or captured private output in fixtures.
- Keep generated report copy customer-facing. Put debug details behind
--debug-out.
Output contracts
Every JSON-emitting script must be deterministic: stable key order, stable sort order, 2-space indentation, trailing newline. If a consumed schema changes, update the schema version and the fixture tests in the same PR.
// Auth routes carry user state and must not be cached at CDN edge.
export const AUTH_ROUTE_REGEX =
/(login|logout|auth|account|dashboard|checkout|cart|profile|session|me)(?:\/|$)/i;
export function isAuthRoute(route) {
return AUTH_ROUTE_REGEX.test(String(route ?? ''));
}
// Non-cache candidates pass through — errors/slowness on auth routes still warrant investigation.
export function applyAuthDisqualifier(candidate) {
const cacheKinds = new Set(['uncached_route', 'cache_header_gap']);
if (!cacheKinds.has(candidate.kind)) return candidate;
if (!candidate.route) return candidate;
if (isAuthRoute(candidate.route)) {
return {
...candidate,
disqualified: true,
disqualifyReason: 'auth-like route — should not be cached at edge',
};
}
return candidate;
}
// Checkpoint between gate and deep-dive. Asks only when budget was default AND >=1 candidate got skipped — every question is a tax on the user.
import { createHash } from 'node:crypto';
import { formatCandidateLine } from './display-labels.mjs';
const TOP_INVESTIGATING_PREVIEW = 5;
const MAX_FULL_INVESTIGATING_PREVIEW = 10;
export function buildBudgetSummary(gate) {
const toLaunch = Array.isArray(gate?.toLaunch) ? gate.toLaunch : [];
const gated = Array.isArray(gate?.gated) ? gate.gated : [];
const budgetSource = gate?.budget?.source ?? 'default';
const currentBudget =
typeof gate?.budget?.maxCandidates === 'number'
? gate.budget.maxCandidates
: (gate?.budget?.maxCandidates === 'all' ? Infinity : 6);
// Only budget skips can be reached by raising the budget; disqualified/coveredBy can't.
const skippedByBudget = gated.filter((g) =>
typeof g.gatedReason === 'string' && g.gatedReason.startsWith('skippedByBudget')
);
const skipped = skippedByBudget.length;
const totalPassed = toLaunch.length + skipped;
const reasonParts = [];
if (budgetSource !== 'default') reasonParts.push(`user pre-set budget via ${budgetSource}`);
if (skipped === 0) reasonParts.push('no candidates skipped by budget');
const shouldAsk = budgetSource === 'default' && skipped > 0;
const reason = shouldAsk
? `default budget skipped ${skipped} candidate(s); ask user whether to expand`
: reasonParts.join('; ') || 'no expansion possible';
const summarize = (c) => ({
kind: c.kind,
route: c.route ?? c.hostname ?? null,
displayRoute: c.displayRoute ?? null,
o11ySignal: c.o11ySignal ?? null,
priority: c.priority ?? null,
});
const investigatingPreviewCount = typeof currentBudget === 'number' && currentBudget <= MAX_FULL_INVESTIGATING_PREVIEW
? currentBudget
: TOP_INVESTIGATING_PREVIEW;
const topInvestigating = toLaunch.slice(0, investigatingPreviewCount).map(summarize);
const topSkipped = skippedByBudget.map(summarize);
const options = buildOptions(toLaunch.length, skipped);
const questionText = buildQuestionText({ shouldAsk, totalPassed, currentBudget });
const printContract = shouldAsk
? 'Print chatPreview verbatim by copying exactChatMessage.body as a chat message before asking questionText. Do not summarize, truncate, reorder, shorten, or rewrite options.'
: null;
const questionPayload = shouldAsk ? buildQuestionPayload(questionText, options) : null;
const chatPreview = buildChatPreview({ shouldAsk, totalPassed, currentBudget, skipped, topInvestigating, topSkipped, reason });
const exactChatMessage = buildExactChatMessage(chatPreview);
return {
shouldAsk,
reason,
totalPassed,
currentBudget: currentBudget === Infinity ? 'all' : currentBudget,
budgetSource,
skipped,
topInvestigating,
topSkipped,
options,
printContract,
chatPreview,
exactChatMessage,
printCheck: shouldAsk ? buildPrintCheck({ exactChatMessage, skipped }) : null,
questionText,
questionPayload,
};
}
function buildChatPreview({ shouldAsk, totalPassed, currentBudget, skipped, topInvestigating, topSkipped, reason }) {
if (!shouldAsk) return `Audit scope: no question needed — ${reason}.`;
const lines = [];
lines.push(`Found ${totalPassed} potential issue${totalPassed === 1 ? '' : 's'} worth checking. By default I'll inspect the ${currentBudget} strongest now; ${skipped} will stay in the report for a larger run.`);
lines.push(`Choose a larger scope if you want broader coverage. More checks take longer.`);
if (topInvestigating.length > 0) {
lines.push('');
lines.push(`Checking now${topInvestigating.length < currentBudget ? ` (${topInvestigating.length} shown)` : ''}:`);
topInvestigating.forEach((c, i) => lines.push(` ${i + 1}. ${formatCandidateLine(c)}`));
}
if (topSkipped.length > 0) {
lines.push('');
lines.push(`Only checked if you expand this run (${topSkipped.length}):`);
topSkipped.forEach((c, i) => lines.push(` ${i + 1}. ${formatCandidateLine(c)}`));
}
return lines.join('\n');
}
function buildExactChatMessage(body) {
return {
body,
lineCount: body.split('\n').length,
sha256: createHash('sha256').update(body).digest('hex'),
};
}
function buildPrintCheck({ exactChatMessage, skipped }) {
return {
bodyField: 'exactChatMessage.body',
sameAs: 'chatPreview',
requiredLineCount: exactChatMessage.lineCount,
requiredSha256: exactChatMessage.sha256,
requiredSkippedRows: skipped,
requiredSkippedHeading: `Only checked if you expand this run (${skipped}):`,
forbiddenSummaryPatterns: [
'\\btop skipped\\b',
'\\bmore (?:candidate|candidates|routes|entries|items|in gated list)\\b',
'\\b\\d+\\s*[-–—]\\s*\\d+\\.\\s+\\d+\\s+more\\b',
'\\betc\\.\\b',
],
instruction: 'The budget message is valid only when every line from exactChatMessage.body is preserved exactly. If you cannot verify that, print exactChatMessage.body again before asking the question.',
};
}
function buildQuestionText({ shouldAsk, totalPassed, currentBudget }) {
if (!shouldAsk) return '';
return `How many potential issues should I check in this run?`;
}
function buildOptions(currentCount, skippedCount) {
if (skippedCount === 0) return [];
const total = currentCount + skippedCount;
return [
{
label: `Check ${currentCount} (default)`,
value: currentCount,
recommended: true,
description: 'Fastest first pass; checks the strongest cost and performance signals.',
rationale: 'fastest first pass; checks the strongest cost and performance signals',
},
{
label: `Check all ${total}`,
value: 'all',
recommended: false,
description: 'Most complete; takes longer because every flagged route is investigated.',
rationale: 'most complete; takes longer because every flagged route is investigated',
},
{
label: 'Pick a number',
value: 'custom',
recommended: false,
description: `Check more than ${currentCount} without running the full ${total}.`,
rationale: `checks more than ${currentCount} without running the full ${total}`,
},
];
}
function buildQuestionPayload(questionText, options) {
return {
questions: [{
question: questionText,
header: 'Audit scope',
multiSelect: false,
options: options.map((o) => ({
label: o.label,
description: o.description ?? o.rationale,
})),
}],
};
}
export function renderBudgetSummaryMarkdown(s) {
const lines = [];
lines.push(`## Audit scope`);
lines.push('');
if (!s.shouldAsk) {
lines.push(`_No question needed — ${s.reason}._`);
return lines.join('\n');
}
for (const ln of s.chatPreview.split('\n')) lines.push(ln);
lines.push('');
lines.push('### Options');
lines.push('');
for (const o of s.options) {
const tag = o.recommended ? ' (recommended)' : '';
lines.push(`- **${o.label}${tag}** — ${o.rationale}`);
}
lines.push('');
lines.push(`**Question:** ${s.questionText}`);
return lines.join('\n');
}
// Curated doc library — the allow-list for recommender citations.
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const HERE = dirname(fileURLToPath(import.meta.url));
const LIBRARY_PATH = join(HERE, '..', 'references', 'docs-library.json');
let cached;
export async function loadLibrary() {
if (cached) return cached;
const raw = await readFile(LIBRARY_PATH, 'utf-8');
cached = JSON.parse(raw);
return cached;
}
export async function isKnownUrl(url) {
const lib = await loadLibrary();
return lib.urls.some(e => e.url === url);
}
export async function lookupUrl(url) {
const lib = await loadLibrary();
return lib.urls.find(e => e.url === url);
}
export async function lookupSkillRule(ref) {
const lib = await loadLibrary();
const m = ref.match(/^([\w-]+):([\w-]+)$/);
if (!m) return undefined;
return lib.ruleSkillRefs.find(r => r.skill === m[1] && r.rule === m[2]);
}
// Narrow semver subset: "*", "fw@*", "fw@14", "fw@>=15.0.0", "fw@<X", "fw@X.Y", "fw@X.Y.Z", "a || b".
export function matchesFrameworkVersion(pattern, framework, version) {
if (pattern === '*') return true;
if (pattern.includes('||')) {
return pattern.split('||').map(p => p.trim()).some(p =>
matchesFrameworkVersion(p, framework, version)
);
}
const m = pattern.match(/^([\w-]+)@(.+)$/);
if (!m) return false;
const [, fw, range] = m;
if (fw !== framework) return false;
if (range === '*') return true;
const verParts = parseVersion(version);
if (!verParts) return false;
let m2 = range.match(/^>=\s*(.+)$/);
if (m2) {
const min = parseVersion(m2[1]);
return min ? compareVersion(verParts, min) >= 0 : false;
}
m2 = range.match(/^<\s*(.+)$/);
if (m2) {
const max = parseVersion(m2[1]);
return max ? compareVersion(verParts, max) < 0 : false;
}
if (/^\d+$/.test(range)) {
return verParts[0] === Number(range);
}
m2 = range.match(/^(\d+)\.(\d+)$/);
if (m2) {
return verParts[0] === Number(m2[1]) && verParts[1] === Number(m2[2]);
}
const exact = parseVersion(range);
if (exact) return compareVersion(verParts, exact) === 0;
return false;
}
function parseVersion(v) {
const m = String(v).replace(/^[v^~]+/, '').match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
if (!m) return null;
return [Number(m[1]) || 0, Number(m[2]) || 0, Number(m[3]) || 0];
}
function compareVersion(a, b) {
for (let i = 0; i < 3; i++) {
if (a[i] !== b[i]) return a[i] - b[i];
}
return 0;
}
// Filtered subset embedded in recommender prompt — LLM never sees URLs for features not in user's stack.
export async function libraryForStack(framework, version) {
const lib = await loadLibrary();
const matches = (frameworks) =>
frameworks.some(p => matchesFrameworkVersion(p, framework, version) || p === '*');
return {
urls: lib.urls.filter(e => matches(e.applicableFrameworks)),
ruleSkillRefs: lib.ruleSkillRefs.filter(r => matches(r.applicableFrameworks)),
};
}
export async function sanitizeCitations(rec, framework, version) {
const lib = await loadLibrary();
const strippedUnknown = [];
const strippedVersion = [];
const kept = [];
for (const cite of rec.citations ?? []) {
const ruleRef = await lookupSkillRule(cite);
if (ruleRef) {
if (matchesFrameworkVersion(ruleRef.applicableFrameworks.join(' || '), framework, version) || ruleRef.applicableFrameworks.includes('*')) {
kept.push(cite);
} else {
strippedVersion.push(cite);
}
continue;
}
const entry = lib.urls.find(e => e.url === cite);
if (!entry) {
strippedUnknown.push(cite);
continue;
}
if (entry.applicableFrameworks.includes('*') ||
entry.applicableFrameworks.some(p => matchesFrameworkVersion(p, framework, version))) {
kept.push(cite);
} else {
strippedVersion.push(cite);
}
}
rec.citations = kept;
return { rec, strippedUnknown, strippedVersion };
}
// Maps billing line items to gate coverage so report surfaces uncovered dimensions (Sandbox, AI Gateway, Build, …) as blind spots.
// Service → billing dimension. dim=null means uncovered. Substring match — Vercel billing names are stable but untyped.
const SERVICE_DIMENSION = [
{ match: /^Function Duration$/i, dim: 'function-duration' },
{ match: /^Function Invocations$/i, dim: 'function-duration' },
{ match: /^Fluid Active CPU$/i, dim: 'function-duration' },
{ match: /^Fluid Provisioned Memory$/i, dim: 'function-duration' },
{ match: /^Edge Requests$/i, dim: 'edge-requests' },
{ match: /^Edge Requests.*Additional CPU Duration/i, dim: 'edge-requests' },
{ match: /^Edge Function Execution Units$/i, dim: 'edge-requests' },
{ match: /^Edge Middleware Invocations$/i, dim: 'edge-requests' },
{ match: /^ISR (Reads|Writes)$/i, dim: 'isr' },
{ match: /^Speed Insights( Data Points)?$/i, dim: 'speed-insights' },
{ match: /^Image Optimization/i, dim: 'image-optimization' },
// Indirect: bot-protection gate addresses bandwidth/edge spend.
{ match: /^Fast Data Transfer$/i, dim: 'edge-requests' },
{ match: /^Fast Origin Transfer$/i, dim: 'edge-requests' },
// Uncovered.
{ match: /^Sandbox/i, dim: null, family: 'sandbox' },
{ match: /^AI Gateway$/i, dim: null, family: 'ai-gateway' },
{ match: /^Build Minutes$/i, dim: 'build', family: 'build' },
{ match: /^Build CPU Minutes$/i, dim: 'build', family: 'build' },
{ match: /^Private Data Transfer$/i, dim: null, family: 'private-network' },
{ match: /^Secure Compute Network$/i, dim: null, family: 'private-network' },
{ match: /^Drains Volume$/i, dim: null, family: 'drains' },
{ match: /^Observability Events$/i, dim: 'observability-events', family: 'observability-events' },
{ match: /^Blob/i, dim: null, family: 'blob' },
{ match: /^Edge Config (Reads|Writes)$/i, dim: null, family: 'edge-config' },
{ match: /^Runtime Cache/i, dim: null, family: 'runtime-cache' },
{ match: /^Microfrontends/i, dim: null, family: 'microfrontends' },
{ match: /^Workflow/i, dim: null, family: 'workflow' },
{ match: /^Queue/i, dim: null, family: 'queues' },
{ match: /^Flag Requests$/i, dim: null, family: 'flags' },
{ match: /^Flags Explorer/i, dim: null, family: 'flags' },
{ match: /^BotID/i, dim: null, family: 'botid' },
{ match: /^Firewall/i, dim: null, family: 'firewall' },
{ match: /^Vercel Agent$/i, dim: null, family: 'vercel-agent' },
// Fixed costs (seats, contracts) — not actionable.
{ match: /^v0 /i, dim: null, family: 'fixed', actionable: false },
{ match: /^Additional Team Seats$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^SAML$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^HIPAA BAA$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^SIEM Integration$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Web Analytics/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Static IPs$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Bulk Redirects$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Preview Deployment Suffix$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Rolling Releases$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Observability Plus$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Platform Customer Usage$/i, dim: null, family: 'fixed', actionable: false },
{ match: /^Advanced Deployment Protection$/i, dim: null, family: 'fixed', actionable: false },
];
export function classifyService(serviceName, activeDims) {
if (!serviceName) return { covered: false, family: 'unknown' };
for (const e of SERVICE_DIMENSION) {
if (e.match.test(serviceName)) {
if (e.dim && activeDims.has(e.dim)) return { covered: true, dim: e.dim };
return { covered: false, family: e.family ?? 'unknown', actionable: e.actionable ?? true };
}
}
return { covered: false, family: 'unknown', actionable: true };
}
export function computeCostCoverage(usage, gates) {
const services = Array.isArray(usage?.services) ? usage.services : [];
const activeDims = new Set(
(gates ?? [])
.map((g) => g?.metadata?.billingDimension)
.filter((d) => typeof d === 'string' && d !== 'mixed')
);
let total = 0;
let covered = 0;
let uncovered = 0;
const byFamily = new Map();
for (const s of services) {
const billed = Number(s.billedCost ?? 0);
if (!Number.isFinite(billed) || billed <= 0) continue;
total += billed;
const c = classifyService(s.name, activeDims);
if (c.covered) {
covered += billed;
continue;
}
uncovered += billed;
const key = c.family;
const prev = byFamily.get(key) ?? { family: key, billed: 0, services: [], actionable: c.actionable !== false };
prev.billed += billed;
prev.services.push({ name: s.name, billed });
prev.actionable = prev.actionable && (c.actionable !== false);
byFamily.set(key, prev);
}
const uncoveredByFamily = [...byFamily.values()]
.sort((a, b) => b.billed - a.billed)
.map((f) => ({ ...f, services: f.services.sort((a, b) => b.billed - a.billed) }));
// Pick top gaps globally so multiple families surface (Sandbox + AI Gateway + Build, not 5 Sandbox sub-services). Exclude fixed costs — seats aren't actionable workload.
const allActionableServices = [];
for (const family of uncoveredByFamily) {
if (!family.actionable) continue;
for (const s of family.services) {
allActionableServices.push({ name: s.name, billed: s.billed, family: family.family });
}
}
allActionableServices.sort((a, b) => b.billed - a.billed);
const topGaps = allActionableServices.slice(0, 5).map((s) => ({
...s,
share: total > 0 ? s.billed / total : 0,
}));
return { totalBilled: total, coveredBilled: covered, uncoveredBilled: uncovered, uncoveredByFamily, topGaps };
}
export function renderCostCoverageMarkdown(coverage) {
if (!coverage || !Number.isFinite(coverage.totalBilled) || coverage.totalBilled <= 0) return [];
const { totalBilled, coveredBilled, uncoveredBilled, topGaps } = coverage;
const actionableGaps = topGaps.filter((g) => g.share >= 0.01); // 1%+ share
if (actionableGaps.length === 0) return [];
const lines = [];
lines.push('');
lines.push('### Coverage gaps');
lines.push('');
const coveredPct = totalBilled > 0 ? (coveredBilled / totalBilled) * 100 : 0;
const uncoveredPct = totalBilled > 0 ? (uncoveredBilled / totalBilled) * 100 : 0;
lines.push(`This audit has metric coverage for **$${coveredBilled.toFixed(0)} (${coveredPct.toFixed(0)}%)** of this bill via function-duration, edge-requests, ISR, middleware, and image-optimization dimensions. **$${uncoveredBilled.toFixed(0)} (${uncoveredPct.toFixed(0)}%)** sits in billed areas this run cannot analyze safely, including the top actionable items below:`);
lines.push('');
lines.push('| Service | Billed | Share | Family | Coverage |');
lines.push('|---|---|---|---|---|');
for (const g of actionableGaps) {
lines.push(`| ${escapeCell(g.name)} | $${g.billed.toFixed(2)} | ${(g.share * 100).toFixed(1)}% | ${g.family} | _not analyzed in this run_ |`);
}
lines.push('');
lines.push('_Recommendations in this report address the covered dimensions. The uncovered rows are not ignored; they need a separate investigation before we can make safe recommendations._');
return lines;
}
function escapeCell(s) {
return String(s ?? '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
}
const NO_VALUE = '<none>';
export function dedupeRecommendations(recommendations = []) {
if (!Array.isArray(recommendations)) {
throw new TypeError('dedupeRecommendations recommendations must be an array');
}
const byKey = new Map();
const order = [];
for (const rec of recommendations) {
if (!rec || typeof rec !== 'object' || rec.abstain === true) {
order.push(rec);
continue;
}
const key = recommendationKey(rec);
if (!byKey.has(key)) {
const normalized = withDedupMetadata(rec);
byKey.set(key, normalized);
order.push({ __dedupKey: key });
continue;
}
const current = byKey.get(key);
const merged = mergeDuplicateRecs(current, rec);
byKey.set(key, merged);
}
return order.map((entry) => entry?.__dedupKey ? byKey.get(entry.__dedupKey) : entry);
}
export function recommendationKey(rec) {
const intent = dedupIntent(rec);
const bucket = intent === 'cache-control:s-maxage'
? NO_VALUE
: String(rec?.bucket ?? NO_VALUE);
return JSON.stringify([
bucket,
dedupEditTarget(rec),
primarySkillRule(rec),
intent,
]);
}
export function normalizePath(path) {
if (typeof path !== 'string' || path.trim() === '') return NO_VALUE;
return path
.trim()
.replace(/\\/g, '/')
.replace(/^\.\//, '')
.replace(/\/+/g, '/')
.replace(/:(\d+)(?::\d+)?$/, '');
}
export function primarySkillRule(rec) {
const citations = Array.isArray(rec?.citations) ? rec.citations : [];
return citations.find((c) => typeof c === 'string' && /^[A-Za-z][\w-]*:[A-Za-z][\w-]*$/.test(c)) ?? NO_VALUE;
}
export function fixShape(rec) {
if (typeof rec?.fixShape === 'string' && rec.fixShape.trim()) {
return normalizeFixText(rec.fixShape);
}
const primaryText = [rec?.fix, rec?.desiredBehavior]
.filter((v) => typeof v === 'string' && v.trim())
.join('\n');
const text = primaryText || rec?.what;
return normalizeFixText(text);
}
export function dedupIntent(rec) {
if (isSMaxageCacheHeaderRec(rec)) return 'cache-control:s-maxage';
if (isCacheLifeRec(rec)) return cacheLifeIntent(rec);
const sharedFunction = sharedFunctionTarget(rec);
if (sharedFunction) return `parallel-shared-helper:${sharedFunction}`;
return fixShape(rec);
}
export function dedupEditTarget(rec) {
return sharedFunctionTarget(rec) ?? normalizePath(firstAffectedFile(rec));
}
function firstAffectedFile(rec) {
const direct = affectedFiles(rec);
const editTarget = referencedCodeFiles(rec, ['fix', 'desiredBehavior', 'currentBehavior'])[0];
if (editTarget) return editTarget;
const referenced = referencedCodeFiles(rec)
.find((file) => direct.includes(file));
if (referenced) return referenced;
return Array.isArray(rec?.affectedFiles) ? rec.affectedFiles[0] : null;
}
function affectedFiles(rec) {
return Array.isArray(rec?.affectedFiles)
? rec.affectedFiles.map(normalizePath).filter((file) => file !== NO_VALUE)
: [];
}
function referencedCodeFiles(rec, fields = ['what', 'why', 'fix', 'currentBehavior', 'desiredBehavior', 'verify']) {
const text = fields
.map((field) => rec?.[field])
.filter((v) => typeof v === 'string' && v.trim())
.join('\n');
const matches = text.match(/(?:^|[\s`'"(])((?:\.{1,2}\/|[A-Za-z0-9_.@-]+\/)[A-Za-z0-9_./@[\]()-]+\.(?:mjs|cjs|js|jsx|ts|tsx))/g) ?? [];
return unique(matches.map((m) =>
normalizePath(m.replace(/^[\s`'"(]+/, ''))
).filter((file) => file !== NO_VALUE));
}
function isSMaxageCacheHeaderRec(rec) {
const text = [
rec?.what,
rec?.why,
rec?.fix,
rec?.desiredBehavior,
...(Array.isArray(rec?.citations) ? rec.citations : []),
].filter(Boolean).join('\n');
return /\bs-maxage\b/i.test(text) &&
/\b(?:Cache-Control|CDN cache|cdn-cache|caching\/cdn-cache)\b/i.test(text);
}
function isCacheLifeRec(rec) {
const text = [
rec?.candidateRef,
rec?.what,
rec?.why,
rec?.fix,
rec?.desiredBehavior,
...(Array.isArray(rec?.citations) ? rec.citations : []),
].filter(Boolean).join('\n');
return /^isr_overrevalidation:/.test(String(rec?.candidateRef ?? '')) &&
/\bcacheLife\s*\(|\bcacheLife\b/i.test(text);
}
function sharedFunctionTarget(rec) {
const rule = primarySkillRule(rec);
if (!/(?:^|:)async-parallel$|(?:^|:)server-parallel-fetching$|(?:^|:)async-suspense-boundaries$/.test(rule)) {
return null;
}
const text = [
rec?.what,
rec?.why,
rec?.fix,
rec?.currentBehavior,
rec?.desiredBehavior,
].filter((v) => typeof v === 'string' && v.trim()).join('\n');
const names = [
...text.matchAll(/\b(?:get|fetch|load|read|render|create|generate|filter|resolve)[A-Z][A-Za-z0-9_]*\b/g),
].map((m) => m[0]);
const stop = new Set([
'getPayload',
'draftMode',
'notFound',
'redirect',
'Promise',
'Response',
'NextResponse',
]);
const candidates = names.filter((name) => !stop.has(name));
if (candidates.length === 0) return null;
const score = new Map();
for (const name of candidates) {
score.set(name, (score.get(name) ?? 0) + 1);
}
return [...score.entries()]
.sort((a, b) => b[1] - a[1] || text.indexOf(a[0]) - text.indexOf(b[0]))
.map(([name]) => `function:${name}`)[0] ?? null;
}
function cacheLifeIntent(rec) {
const text = [
rec?.what,
rec?.why,
rec?.fix,
rec?.desiredBehavior,
rec?.verify,
].filter(Boolean).join('\n');
const profiles = unique(
[...text.matchAll(/\bcacheLife\s*\(\s*['"`]([^'"`]+)['"`]/g)]
.map((m) => m[1])
);
const tags = unique([
...[...text.matchAll(/\bcacheTag\s*\(([^)]*)\)/gs)].flatMap((m) => {
const args = m[1] ?? '';
return [
...[...args.matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]),
...[...args.matchAll(/`([^`]+)`/g)].map((x) => x[1].includes('${') ? `${x[1].split('${')[0]}*` : x[1]),
];
}),
]);
const invalidation = /\b(?:revalidateTag|updateTag)\s*\(/.test(text) ? 'with-invalidation-api' : 'no-invalidation-api';
return [
'next-cache:cache-life',
profiles.join('|') || NO_VALUE,
tags.join('|') || NO_VALUE,
invalidation,
].join(':');
}
function unique(values) {
return Array.from(new Set(values.filter((v) => typeof v === 'string' && v.trim()).map((v) => v.trim()))).sort();
}
function normalizeFixText(text) {
if (typeof text !== 'string' || text.trim() === '') return NO_VALUE;
return text
.toLowerCase()
.replace(/```[\s\S]*?```/g, ' codeblock ')
.replace(/`[^`]*`/g, ' code ')
.replace(/\b\d+(?:\.\d+)?(?:ms|s|%|kb|mb|gb|k|m)?\b/g, '#')
.replace(/[^a-z0-9#]+/g, ' ')
.trim()
.split(/\s+/)
.slice(0, 80)
.join(' ') || NO_VALUE;
}
function withDedupMetadata(rec) {
const existing = normalizedAppliesAlsoTo(rec.appliesAlsoTo);
const count = Math.max(
numericCount(rec.corroborationCount),
1 + existing.length,
);
return existing.length > 0 || count > 1
? { ...rec, appliesAlsoTo: existing, corroborationCount: count }
: { ...rec };
}
function mergeDuplicateRecs(a, b) {
const aScore = recScore(a);
const bScore = recScore(b);
const winner = bScore > aScore ? b : a;
const loser = winner === a ? b : a;
const winnerExisting = normalizedAppliesAlsoTo(winner.appliesAlsoTo);
const loserExisting = normalizedAppliesAlsoTo(loser.appliesAlsoTo);
const appliesAlsoTo = uniqueAppliesAlsoTo([
...winnerExisting,
appliesAlsoEntry(loser),
...loserExisting,
]);
const corroborationCount =
numericCount(winner.corroborationCount) + numericCount(loser.corroborationCount);
return {
...winner,
appliesAlsoTo,
corroborationCount: Math.max(corroborationCount, 1 + appliesAlsoTo.length),
};
}
function recScore(rec) {
const priority = typeof rec?.priority === 'number' ? rec.priority : 0;
const quality = typeof rec?.quality?.overall === 'number' ? rec.quality.overall : 0;
return (priority * 1_000_000_000_000) + signalMagnitude(rec) + quality;
}
function signalMagnitude(rec) {
const text = [
rec?.o11ySignal,
rec?.why,
rec?.what,
rec?.impact,
].filter((v) => typeof v === 'string' && v.trim()).join('\n');
const inv = parseNumber(text, /(?:inv|invocations?|function invocations?|requests?)[:=]\s*([\d,]+)/i);
const p95 = parseNumber(text, /(?:p95|95th percentile(?: duration)?)[:=]?\s*([\d,]+)\s*ms/i);
const errors = parseNumber(text, /(?:errs|errors?)[:=]\s*([\d,]+)/i);
const writes = parseNumber(text, /writes[:=]\s*([\d,]+)/i);
const reads = parseNumber(text, /reads[:=]\s*([\d,]+)/i);
if (inv != null && p95 != null) return inv * p95;
if (errors != null) return errors;
if (writes != null && reads != null) return writes + reads;
if (inv != null) return inv;
return 0;
}
function parseNumber(text, re) {
const match = re.exec(text);
if (!match) return null;
const value = Number(String(match[1]).replace(/,/g, ''));
return Number.isFinite(value) ? value : null;
}
function numericCount(value) {
return Number.isFinite(value) && value > 0 ? value : 1;
}
function appliesAlsoEntry(rec) {
return {
candidateRef: rec?.candidateRef ?? null,
affectedFiles: Array.isArray(rec?.affectedFiles)
? rec.affectedFiles.map(normalizePath).filter((p) => p !== NO_VALUE)
: [],
o11ySignal: rec?.o11ySignal ?? null,
what: rec?.what ?? null,
};
}
function normalizedAppliesAlsoTo(entries) {
if (!Array.isArray(entries)) return [];
return entries
.filter((e) => e && typeof e === 'object')
.map((e) => ({
candidateRef: e.candidateRef ?? null,
affectedFiles: Array.isArray(e.affectedFiles)
? e.affectedFiles.map(normalizePath).filter((p) => p !== NO_VALUE)
: [],
o11ySignal: e.o11ySignal ?? null,
what: e.what ?? null,
}));
}
function uniqueAppliesAlsoTo(entries) {
const seen = new Set();
const out = [];
for (const entry of entries) {
const key = JSON.stringify([
entry.candidateRef ?? NO_VALUE,
entry.affectedFiles?.join(',') ?? NO_VALUE,
entry.what ?? NO_VALUE,
]);
if (seen.has(key)) continue;
seen.add(key);
out.push(entry);
}
return out;
}
// Per-candidate deep-dive query specs. Runs after gate, before sub-agent reads source.
//
// CLI quirks:
// - Multi `-a` flag is NOT supported. One percentile per query.
// - External-API "calling route" dim is `origin_route` (NOT `route`).
// Same window as broad pass so rolls are comparable.
import { TIME_WINDOW } from './queries.mjs';
export { TIME_WINDOW };
// Per-query is scoped to one route/hostname, so cardinality stays small — higher than broad-pass caps.
const DEPLOYMENT_LIMIT = 10;
const ERROR_DEPLOYMENT_LIMIT = 30;
const ERROR_CODE_LIMIT = 50;
const WAF_RULE_LIMIT = 20;
const MIDDLEWARE_PATH_LIMIT = 50;
const CALLER_LIMIT = 20;
// OData escapes a literal `'` inside a string by doubling it (`it's` → `it''s`).
export function escapeODataString(s) {
if (typeof s !== 'string') return '';
return s.replace(/'/g, "''");
}
export function odataEq(dim, value) {
return `${dim} eq '${escapeODataString(value)}'`;
}
export function odataAnd(...conds) {
return conds.filter(Boolean).join(' and ');
}
export const SPEC_GENERATORS = {
slow_route(c) {
const route = c.route;
if (!route) return [];
const f = odataEq('route', route);
// cacheBreakdown/bandwidthByCache let sub-agent see miss-path cost on static routes (dynamic='error' can still show p95=900ms over millions of requests).
return [
...latencyPercentiles('latency', 'vercel.function_invocation.function_duration_ms', f),
...latencyPercentiles('ttfb', 'vercel.function_invocation.ttfb_ms', f),
...latencyPercentiles('cpu', 'vercel.function_invocation.function_cpu_time_ms', f, ['p95']),
{
id: 'startTypeSplit',
metricId: 'vercel.function_invocation.count',
aggregation: 'sum',
groupBy: ['function_start_type'],
filter: f,
broadPassEquivalent: { key: 'fnStartTypeByRoute', routeFilter: route, projectDims: ['function_start_type'] },
},
// function-invocation status (5xx from function) — distinct from request-level status, can't reuse broad-pass.
{
id: 'statusDistribution',
metricId: 'vercel.function_invocation.count',
aggregation: 'sum',
groupBy: ['http_status'],
filter: f,
},
{
id: 'perDeployment',
metricId: 'vercel.function_invocation.function_duration_ms',
aggregation: 'p95',
groupBy: ['deployment_id'],
filter: f,
limit: DEPLOYMENT_LIMIT,
},
{
id: 'cacheBreakdown',
metricId: 'vercel.request.count',
aggregation: 'sum',
groupBy: ['cache_result'],
filter: f,
broadPassEquivalent: { key: 'requestsByRouteCache', routeFilter: route, projectDims: ['cache_result'] },
},
// broad-pass bandwidthByCacheResult is account-wide, so per-route still required.
{
id: 'bandwidthByCache',
metricId: 'vercel.request.fdt_total_bytes',
aggregation: 'sum',
groupBy: ['cache_result'],
filter: f,
},
];
},
uncached_route(c) {
const route = c.route;
if (!route) return [];
const f = odataEq('route', route);
return [
{
id: 'cacheBreakdown',
metricId: 'vercel.request.count',
aggregation: 'sum',
groupBy: ['cache_result'],
filter: f,
broadPassEquivalent: { key: 'requestsByRouteCache', routeFilter: route, projectDims: ['cache_result'] },
},
{
id: 'methodDistribution',
metricId: 'vercel.request.count',
aggregation: 'sum',
groupBy: ['request_method'],
filter: f,
broadPassEquivalent: { key: 'requestsByRouteMethod', routeFilter: route, projectDims: ['request_method'] },
},
{
id: 'botShare',
metricId: 'vercel.request.fdt_total_bytes',
aggregation: 'sum',
groupBy: ['bot_category'],
filter: f,
},
{
id: 'bandwidthByCache',
metricId: 'vercel.request.fdt_total_bytes',
aggregation: 'sum',
groupBy: ['cache_result'],
filter: f,
},
];
},
cold_start(c) {
const route = c.route;
if (!route) return [];
const f = odataEq('route', route);
return [
{
id: 'startTypeSplit',
metricId: 'vercel.function_invocation.count',
aggregation: 'sum',
groupBy: ['function_start_type'],
filter: f,
},
{
id: 'coldVsWarmLatencyP95',
metricId: 'vercel.function_invocation.function_duration_ms',
aggregation: 'p95',
groupBy: ['function_start_type'],
filter: f,
},
{
id: 'coldByDeployment',
metricId: 'vercel.function_invocation.count',
aggregation: 'sum',
groupBy: ['deployment_id'],
filter: odataAnd(f, odataEq('function_start_type', 'cold')),
limit: DEPLOYMENT_LIMIT,
},
];
},
route_errors(c) {
const route = c.route;
if (!route) return [];
const f = odataEq('route', route);
return [
{
id: 'errorStatusPattern',
metricId: 'vercel.request.count',
aggregation: 'sum',
groupBy: ['http_status'],
filter: odataAnd(f, "http_status ge '500'"),
},
{
id: 'errorCodes',
metricId: 'vercel.function_invocation.count',
aggregation: 'sum',
groupBy: ['error_code'],
filter: f,
limit: ERROR_CODE_LIMIT,
},
{
id: 'errorsByDeployment',
metricId: 'vercel.function_invocation.count',
aggregation: 'sum',
groupBy: ['deployment_id', 'http_status'],
filter: f,
limit: ERROR_DEPLOYMENT_LIMIT,
},
];
},
external_api_slow(c) {
const host = c.hostname;
if (!host) return [];
const f = odataEq('origin_hostname', host);
return [
...latencyPercentiles('latency', 'vercel.external_api_request.request_duration_ms', f),
{
// "calling route" dim is origin_route (verified via metrics schema).
id: 'callersByRoute',
metricId: 'vercel.external_api_request.count',
aggregation: 'sum',
groupBy: ['origin_route'],
filter: f,
limit: CALLER_LIMIT,
},
{
id: 'transferBytes',
metricId: 'vercel.external_api_request.transfer_bytes',
aggregation: 'sum',
groupBy: [],
filter: f,
},
];
},
isr_overrevalidation(c) {
const route = c.route;
if (!route) return [];
const f = odataEq('route', route);
return [
{
id: 'writePattern',
metricId: 'vercel.isr_operation.write_units',
aggregation: 'sum',
groupBy: ['cache_result'],
filter: f,
},
{
id: 'readPattern',
metricId: 'vercel.isr_operation.read_units',
aggregation: 'sum',
groupBy: ['cache_result'],
filter: f,
},
];
},
cwv_poor(c) {
const route = c.route;
if (!route) return [];
const f = odataEq('route', route);
return [
...latencyPercentiles('lcp', 'vercel.speed_insights_metric.lcp', f, ['p50', 'p75', 'p95']),
...latencyPercentiles('inp', 'vercel.speed_insights_metric.inp', f, ['p50', 'p75', 'p95']),
...latencyPercentiles('cls', 'vercel.speed_insights_metric.cls', f, ['p50', 'p75', 'p95']),
];
},
middleware_heavy(_c) {
// Account-scope. Surface top middleware-paths so recommender has named targets.
return [
{
id: 'topMiddlewarePaths',
metricId: 'vercel.middleware_invocation.count',
aggregation: 'sum',
groupBy: ['request_path'],
limit: MIDDLEWARE_PATH_LIMIT,
},
];
},
platform_fluid_compute(_c) {
// Broad-pass fnStartTypeByRoute already covers this account-scope rec; runner notes reuse.
return [];
},
platform_bot_protection(_c) {
return [
{
id: 'wafRuleFirings',
metricId: 'vercel.firewall_action.count',
aggregation: 'sum',
groupBy: ['waf_rule_id'],
limit: WAF_RULE_LIMIT,
},
];
},
observability_events_attribution(_c) {
// Account-scope billing signal; broad-pass usage and existing route/cache/middleware metrics carry the evidence.
return [];
},
usage_spike_triage(_c) {
// Daily billing breakdown is already in the gate evidence; no per-candidate metrics query exists.
return [];
},
build_minutes_fanout(_c) {
// Account-scope billing signal + scanner findings carry the evidence; no per-candidate query.
return [];
},
region_misconfig(_c) {
// Branch 2 (scanner-only) — per-region TTFB metric unavailable today, so no deep-dive query.
return [];
},
};
// Scanner-driven kinds skip deep-dive — evidence already in scanner findings (file + line).
export const SCANNER_KINDS = new Set([
'image_optimization',
'cache_header_gap',
'rendering_candidate',
'use_cache_date_stamp',
'cache_components_suspense_dedupe',
]);
export function specsForCandidate(candidate) {
const kind = candidate?.kind;
if (!kind) return [];
if (SCANNER_KINDS.has(kind)) return [];
const gen = SPEC_GENERATORS[kind];
if (!gen) return [];
return gen(candidate).map((s) => ({ since: TIME_WINDOW, ...s }));
}
// One spec per percentile — CLI does not support `-a p50 -a p95` multi-aggregation.
function latencyPercentiles(idPrefix, metricId, filter, percentiles = ['p50', 'p75', 'p95', 'p99']) {
return percentiles.map((p) => ({
id: `${idPrefix}.${p}`,
metricId,
aggregation: p,
groupBy: [],
filter,
}));
}
// Dot-notation spec ids (`latency.p95`) nest under their group prefix.
export function mergeIntoEvidence(results) {
const out = {};
for (const r of results) {
const id = r?.spec?.id;
if (!id) continue;
const dot = id.indexOf('.');
if (dot > -1) {
const head = id.slice(0, dot);
const leaf = id.slice(dot + 1);
if (!out[head]) out[head] = {};
out[head][leaf] = simplify(r);
} else {
out[id] = simplify(r);
}
}
return out;
}
// Avoid leaking raw CLI payload / candidate+spec wrapper into evidence — keep summary-only.
function simplify(r) {
if (!r || r.ok === false) return { error: r?.error ?? 'unknown' };
// Check rows before value so tabular results with both stay tabular.
if (Array.isArray(r.rows)) return r.rows;
if ('value' in r) return r.value;
return null;
}
import { canonicalizeRoute } from './route-normalize.mjs';
const KIND_LABELS = new Map([
['slow_route', 'Slow route'],
['uncached_route', 'Low cache-hit route'],
['cold_start', 'Cold starts'],
['route_errors', 'Route errors'],
['cache_header_gap', 'Missing cache headers'],
['image_optimization', 'Image optimization'],
['external_api_slow', 'Slow external API'],
['isr_overrevalidation', 'ISR over-revalidation'],
['middleware_heavy', 'Heavy middleware'],
['cwv_poor', 'Poor Core Web Vitals'],
['platform_fluid_compute', 'Fluid Compute usage'],
['platform_bot_protection', 'Bot traffic'],
['rendering_candidate', 'Rendering opportunity'],
['missing_cache_headers', 'Missing cache headers'],
['max_age_without_s_maxage', 'Browser-only cache header'],
['force_dynamic', 'Forced dynamic rendering'],
['headers_in_page', 'Dynamic API in page'],
['unoptimized_image', 'Image optimization gap'],
['large_static_asset', 'Large static asset'],
['source_maps_production', 'Production source maps'],
['edge_heavy_import', 'Heavy Edge import'],
]);
const SIGNAL_LABELS = new Map([
['inv', 'function invocations'],
['runs', 'function invocations'],
['middleware_inv', 'middleware invocations'],
['total_req', 'total requests'],
['requests', 'requests'],
['p95', '95th percentile duration'],
['p75', '75th percentile duration'],
['5xx', '5xx error rate'],
['errs', '5xx errors'],
['rate', '5xx error rate'],
['cache', 'cache hit rate'],
['get', 'GET request share'],
['cold', 'cold start rate'],
['writes', 'ISR write units'],
['reads', 'ISR read units'],
['w/r', 'ISR writes per read'],
['ratio', 'ratio'],
['host', 'host'],
['calls', 'external API calls'],
['edge_cost', 'Edge Request cost units'],
['bot_protection', 'Bot Protection'],
['bot_fdt_pct', 'bot Fast Data Transfer share'],
['LCP', 'Largest Contentful Paint (LCP)'],
['INP', 'Interaction to Next Paint (INP)'],
['CLS', 'Cumulative Layout Shift (CLS)'],
]);
const REQUEST_COUNT_KINDS = new Set([
'uncached_route',
]);
const PUBLIC_ASSIGNMENT_LABELS = new Map([
...SIGNAL_LABELS,
['deepDive.latency.p95', 'deepDive latency p95'],
['deepDive.cpu.p95', 'deepDive CPU p95'],
['deepDive.ttfb.p95', 'deepDive TTFB p95'],
['cpu.p95', 'CPU p95'],
['latency.p95', 'latency p95'],
['ttfb.p95', 'TTFB p95'],
['cache_result', 'cache result'],
['http_status', 'HTTP status'],
['error_code', 'error code'],
['status', 'status'],
['count', 'count'],
]);
export function formatKind(kind) {
if (!kind) return 'Candidate';
if (KIND_LABELS.has(kind)) return KIND_LABELS.get(kind);
return String(kind)
.split(/[_-]+/g)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ') || 'Candidate';
}
export function formatRoute(candidate) {
const route = candidate?.displayRoute ?? candidate?.route ?? candidate?.hostname ?? null;
if (route) return String(canonicalizeRoute(route));
if (Array.isArray(candidate?.files) && candidate.files.length > 0) return candidate.files[0];
return 'account-wide';
}
export function formatSignal(signal, context = {}) {
if (typeof signal !== 'string' || signal.trim() === '') return 'no signal recorded';
const parts = signal
.split(',')
.map((part) => part.trim())
.filter(Boolean)
.map((part) => formatSignalPart(part, context));
return parts.length > 0 ? parts.join('; ') : signal;
}
export function formatPublicText(value) {
if (value == null) return '';
return normalizeObservedWindowUnits(String(value))
.replace(/\bo11y\b/gi, 'observability')
.replace(/\bcache[- ]components gotcha\b/gi, 'Cache Components edge case')
.replace(/\bcache_result\b(?!\s*=)/g, 'cache result')
.replace(/\bhttp_status\b(?!\s*=)/g, 'HTTP status')
.replace(/\berror_code\b(?!\s*=)/g, 'error code')
.replace(/,(?=\s*([A-Za-z0-9][\w./-]*)=)/g, (match, key) =>
PUBLIC_ASSIGNMENT_LABELS.has(key) ? '; ' : match
)
.replace(/\b([A-Za-z0-9][\w./-]*)=([^,;\s]+)/g, (match, key, rawValue) => {
const label = PUBLIC_ASSIGNMENT_LABELS.get(key);
if (!label) return match;
return `${label}: ${formatSignalValue(key, rawValue)}`;
})
.replace(/\b(cache breakdown[^.!?\n;]{0,160}?)\b(?:function\s+)?invocations\b/gi, (match, prefix) =>
/\bstatus distribution\b/i.test(prefix) ? match : `${prefix}requests`
)
.replace(/\b(cache breakdown[^.!?\n;]{0,220}?\bout of\s+[\d,]+)\s+invocations\b/gi, '$1 requests')
.replace(/\b(cache hits over\s+[\d,.]+(?:\s?(?:K|M|B))?)\s+invocations\b/gi, '$1 requests')
.replace(/\b(?:function\s+)?invocations\b([^.!?\n;]{0,120}\b(?:empty\s+)?cache result(?: label)?\b)/gi, 'requests$1');
}
export function normalizeObservedWindowUnits(value) {
if (value == null) return '';
return String(value)
.replace(/(?<!\$)\b(\d[\d,.]*(?:\s?(?:K|M|B|KB|MB|GB|TB))?)\/mo\b/gi, '$1/window')
.replace(/\bmonthly\s+function\s+invocations\b/gi, 'function invocations/window')
.replace(/\b(requests?|invocations?|GETs|bytes|egress|bandwidth|writes?|reads?|errors?)\/mo\b/gi, '$1/window')
.replace(/\bmonthly\s+(requests?|invocations?|GETs|bytes|egress|bandwidth|writes?|reads?|errors?)\b/gi, '$1/window')
.replace(/\b(\d[\d,.]*(?:\s?(?:K|M|B|KB|MB|GB|TB))?)\s+function\s+invocations\s+per month\b/gi, '$1 function invocations/window')
.replace(/\b(\d[\d,.]*(?:\s?(?:K|M|B|KB|MB|GB|TB))?)\s+(requests?|GETs|invocations?|bytes|writes?|reads?|errors?)\s+per month\b/gi, '$1 $2/window')
.replace(/\b(\d[\d,.]*(?:\s?(?:K|M|B|KB|MB|GB|TB))?)\/window\s+(requests?|GETs|(?:function\s+)?invocations?|bytes|egress|bandwidth|writes?|reads?|errors?)\b/gi, '$1 $2 in this window')
.replace(/\b(\d[\d,.]*(?:\s?(?:K|M|B|KB|MB|GB|TB))?)\s+(requests?|GETs|(?:function\s+)?invocations?|bytes|egress|bandwidth|writes?|reads?|errors?)\/window\b/gi, '$1 $2 in this window')
.replace(/\b(\d[\d,.]*(?:\s?(?:K|M|B|KB|MB|GB|TB))?)\/window\b/gi, '$1 in this window')
.replace(/\b(requests?|invocations?|GETs|bytes|egress|bandwidth|writes?|reads?|errors?)\/window\b/gi, '$1 in this window');
}
export function formatCandidateLine(candidate) {
return `${formatKind(candidate?.kind)} on ${formatRoute(candidate)} - ${formatSignal(candidate?.o11ySignal, candidate)}`;
}
export function formatCandidateLabel(candidate) {
return `${formatKind(candidate?.kind)} on ${formatRoute(candidate)}`;
}
function formatSignalPart(part, context = {}) {
const eq = part.indexOf('=');
if (eq === -1) return part;
const key = part.slice(0, eq).trim();
const value = part.slice(eq + 1).trim();
const label = signalLabel(key, context);
return `${label}: ${formatSignalValue(key, value)}`;
}
function signalLabel(key, context = {}) {
const kind = typeof context === 'string' ? context : context?.kind;
if (key === 'inv' && REQUEST_COUNT_KINDS.has(kind)) return 'requests';
return SIGNAL_LABELS.get(key) ?? humanizeKey(key);
}
function humanizeKey(key) {
return String(key)
.replaceAll('.', ' ')
.replaceAll('_', ' ')
.replaceAll('-', ' ')
.trim();
}
function formatSignalValue(key, value) {
if (key === 'inv' || key === 'runs' || key === 'middleware_inv' || key === 'total_req' || key === 'requests' || key === 'calls' || key === 'errs' || key === 'writes' || key === 'reads') {
return formatNumberLike(value);
}
return value;
}
function formatNumberLike(value) {
const n = Number(value);
if (!Number.isFinite(n)) return value;
return new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format(n);
}
// Extract mechanically-verifiable claims from a rec without parsing LLM prose. High precision, not recall.
export function extractClaims(rec, ctx = {}) {
const claims = [];
const repoRoot = ctx.repoRoot;
const projectRootDirectory = normalizeProjectRootDirectory(ctx.projectRootDirectory);
const framework = ctx.framework;
const frameworkVersion = ctx.version;
const cacheComponents = ctx.cacheComponents;
const signals = ctx.signals;
const projectFacts = Array.isArray(ctx.projectFacts) ? ctx.projectFacts : [];
// One synthetic claim asserts rec doesn't contradict any already-on project fact (Fluid, in-function concurrency, …).
if (projectFacts.length > 0) {
claims.push({
type: 'does_not_contradict_project_config',
rec,
projectFacts,
sourceField: 'projectFacts',
});
}
for (const cite of asArray(rec.citations)) {
// Skill-rule refs are filtered upstream — skip version check.
if (/^[\w-]+:[\w-]+$/.test(cite)) {
claims.push({ type: 'citation_in_library', url: cite, sourceField: 'citations' });
continue;
}
claims.push({ type: 'citation_in_library', url: cite, sourceField: 'citations' });
if (framework && frameworkVersion) {
claims.push({
type: 'citation_applies_to_version',
url: cite,
framework,
frameworkVersion,
sourceField: 'citations',
});
}
}
for (const f of asArray(rec.affectedFiles)) {
claims.push({ type: 'file_exists', file: f, repoRoot, projectRootDirectory, sourceField: 'affectedFiles' });
}
// findingRefs lack a pattern, so we only check file existence.
for (const ref of asArray(rec.findingRefs)) {
const m = String(ref).match(/^(.+?):\d+$/);
if (m && !claims.some((c) => c.type === 'file_exists' && c.file === m[1])) {
claims.push({ type: 'file_exists', file: m[1], repoRoot, projectRootDirectory, sourceField: 'findingRefs' });
}
}
const cacheFiles = cacheRecommendationFiles(rec);
if (isCacheCandidate(rec)) {
claims.push({
type: 'cache_policy_positive_or_no_ready_rec',
rec,
sourceField: 'cache-policy',
});
}
if (cacheFiles.length > 0) {
claims.push({
type: 'cache_vary_matches_dynamic_inputs',
rec,
files: cacheFiles,
repoRoot,
projectRootDirectory,
sourceField: 'cache-safety',
});
if (mentionsVaryHeader(rec)) {
claims.push({
type: 'cache_vary_cardinality_safe',
rec,
sourceField: 'cache-vary-cardinality',
});
}
claims.push({
type: 'cache_rec_not_error_dominated_or_acknowledged',
rec,
signals,
sourceField: 'cache-error-safety',
});
claims.push({
type: 'cache_control_header_syntax',
rec,
sourceField: 'cache-header-syntax',
});
claims.push({
type: 'cache_control_headers_citation',
rec,
sourceField: 'cache-header-citation',
});
if (mentionsCachedNotFoundOr404(rec)) {
claims.push({
type: 'cache_404_long_ttl_safety',
rec,
sourceField: 'cache-404-safety',
});
}
}
if (mentionsNextCachedNotFound(rec)) {
claims.push({
type: 'next_cached_not_found_causal_support',
rec,
framework,
frameworkVersion,
sourceField: 'next-cache-not-found',
});
}
if (mentionsNextStableCacheApi(rec)) {
claims.push({
type: 'next_stable_cache_api_for_version',
rec,
framework,
frameworkVersion,
sourceField: 'next-cache-api-version',
});
}
if (mentionsNext16RuntimeCacheApiMismatch(rec)) {
claims.push({
type: 'next_runtime_cache_api_for_version',
rec,
framework,
frameworkVersion,
sourceField: 'next-runtime-cache-api-version',
});
}
if (mentionsRuntimeCacheWhenCacheComponents(rec)) {
claims.push({
type: 'next_cache_components_runtime_cache_preference',
rec,
framework,
frameworkVersion,
cacheComponents,
sourceField: 'next-cache-components-runtime-cache-preference',
});
}
if (mentionsMultipleCacheLifeCalls(rec)) {
claims.push({
type: 'next_cache_life_single_execution',
rec,
framework,
frameworkVersion,
sourceField: 'next-cache-life-single-execution',
});
}
if (mentionsCacheLifetimeChange(rec)) {
claims.push({
type: 'next_cache_lifetime_freshness_supported',
rec,
files: recommendationFiles(rec),
repoRoot,
projectRootDirectory,
sourceField: 'next-cache-lifetime-freshness',
});
}
if (mentionsNextCacheComponentsStaticShellTarget(rec)) {
claims.push({
type: 'next_cache_components_route_chain_file',
rec,
framework,
frameworkVersion,
cacheComponents,
signals,
sourceField: 'next-cache-components-route-chain',
});
}
if (mentionsCacheLifeCdnHeaderClaim(rec)) {
claims.push({
type: 'next_cache_life_cdn_header_semantics',
rec,
framework,
frameworkVersion,
sourceField: 'next-cache-life-cdn-header-semantics',
});
}
if (mentionsImageResponseHeaders(rec)) {
claims.push({
type: 'image_response_headers_citation',
rec,
framework,
frameworkVersion,
sourceField: 'image-response-headers',
});
}
if (mentionsNextImagePriorityRecommendation(rec)) {
claims.push({
type: 'next_image_priority_api_for_version',
rec,
framework,
frameworkVersion,
sourceField: 'next-image-priority-api',
});
}
if (mentionsNextCacheComponentsRouteSegmentConfig(rec)) {
claims.push({
type: 'next_cache_components_route_segment_config',
rec,
framework,
frameworkVersion,
cacheComponents,
sourceField: 'next-route-segment-config',
});
}
if (mentionsRouteLevelRevalidate(rec)) {
claims.push({
type: 'next_route_revalidate_static_prereq',
rec,
framework,
frameworkVersion,
cacheComponents,
repoRoot,
projectRootDirectory,
sourceField: 'next-route-revalidate-static-prereq',
});
}
if (mentionsExistingCacheTagInvalidation(rec)) {
claims.push({
type: 'next_cache_tag_invalidation_supported',
rec,
repoRoot,
projectRootDirectory,
sourceField: 'next-cache-tag-invalidation',
});
}
if (mentionsUnsafeImmutableDynamicRoute(rec)) {
claims.push({
type: 'immutable_dynamic_route_safety',
rec,
sourceField: 'immutable-dynamic-route',
});
}
if (mentionsAuthSensitiveParallelization(rec)) {
claims.push({
type: 'auth_guard_parallelization_safety',
rec,
sourceField: 'auth-parallelization',
});
}
if (mentionsParallelizationImpactOverclaim(rec)) {
claims.push({
type: 'parallelization_impact_not_overclaimed',
rec,
sourceField: 'parallelization-impact',
});
}
if (mentionsCpuBoundParallelization(rec)) {
claims.push({
type: 'parallelization_not_cpu_bound_work',
rec,
sourceField: 'parallelization-cpu-bound',
});
}
if (mentionsRuntimeErrorCause(rec)) {
claims.push({
type: 'runtime_error_cause_supported',
rec,
sourceField: 'runtime-error-cause',
});
}
if (mentionsCatchToNotFound(rec)) {
claims.push({
type: 'route_error_not_found_status_and_scope',
rec,
sourceField: 'route-error-catch-safety',
});
}
if (mentionsIgnoredBuildStepRecommendation(rec)) {
claims.push({
type: 'vercel_ignore_command_project_state',
rec,
signals,
sourceField: 'ignored-build-step-state',
});
}
if (mentionsTurboBuildCacheRecommendation(rec)) {
claims.push({
type: 'turbo_build_cache_safety',
rec,
files: recommendationFiles(rec),
repoRoot,
projectRootDirectory,
framework,
sourceField: 'turbo-build-cache-safety',
});
}
for (const c of asArray(rec.verifiableClaims)) {
if (c && typeof c === 'object' && typeof c.type === 'string') {
claims.push({
...c,
repoRoot: c.repoRoot ?? repoRoot,
projectRootDirectory: c.projectRootDirectory ?? projectRootDirectory,
sourceField: 'verifiableClaims',
});
}
}
return claims;
}
function normalizeProjectRootDirectory(value) {
if (typeof value !== 'string' || value.trim() === '') return null;
return value.replace(/\\/g, '/').replace(/^\.\/+/, '').replace(/\/+$/, '');
}
function cacheRecommendationFiles(rec) {
if (!recommendsSharedCache(rec)) return [];
return recommendationFiles(rec);
}
function isCacheCandidate(rec) {
return /^(?:uncached_route|cache_header_gap):/.test(String(rec?.candidateRef ?? ''));
}
function recommendationFiles(rec) {
const files = [
...asArray(rec.affectedFiles),
...asArray(rec.findingRefs)
.map((ref) => String(ref).match(/^(.+?):\d+$/)?.[1])
.filter(Boolean),
];
return Array.from(new Set(files));
}
function recommendsSharedCache(rec) {
const haystack = [
rec?.what,
rec?.why,
rec?.fix,
rec?.desiredBehavior,
rec?.verify,
].filter(Boolean).join('\n');
return /\b(?:s-maxage|CDN-Cache-Control|Vercel-CDN-Cache-Control|Cache-Control)\b/i.test(haystack);
}
function mentionsVaryHeader(rec) {
return /\bVary\b/i.test(recText(rec));
}
function mentionsNextCachedNotFound(rec) {
const haystack = recText(rec);
return /\bnotFound\b/.test(haystack) &&
/['"`]use cache['"`]|\buse cache\b/i.test(haystack) &&
/\b(?:500|5xx|error rate|errors?)\b/i.test(haystack);
}
function mentionsNextStableCacheApi(rec) {
const haystack = recText(rec);
return /\bunstable_(?:cacheLife|cacheTag)\b/.test(haystack) ||
/\brevalidateTag\s*\([^)]*['"`][^'"`]+['"`]\s*\)/.test(haystack);
}
function mentionsNext16RuntimeCacheApiMismatch(rec) {
const haystack = recText(rec);
const citations = asArray(rec?.citations).join('\n');
return /\bunstable_cache\b/.test(haystack) &&
(/\bRuntime Cache\b/i.test(haystack) || /vercel\.com\/docs\/caching\/runtime-cache/i.test(citations));
}
function mentionsRuntimeCacheWhenCacheComponents(rec) {
const haystack = recText(rec);
const citations = asArray(rec?.citations).join('\n');
return /\b(?:Runtime Cache|@vercel\/functions|getCache\s*\(|setCache\s*\()\b/i.test(haystack) ||
/vercel\.com\/docs\/caching\/runtime-cache/i.test(citations);
}
function mentionsMultipleCacheLifeCalls(rec) {
const haystack = recText(rec);
const matches = haystack.match(/\bcacheLife\s*\(/g) ?? [];
return matches.length > 1;
}
function mentionsCacheLifetimeChange(rec) {
return /\bcacheLife\s*\(/.test(recText(rec));
}
function mentionsCacheLifeCdnHeaderClaim(rec) {
const haystack = recText(rec);
if (!/\bcacheLife\b/.test(haystack)) return false;
return /\bcacheLife\b[^.\n]{0,240}\b(?:Cache-Control|s-maxage|CDN|edge cache|cache breakdown|x-vercel-cache|HIT|MISS|function (?:still )?runs per request|every request invokes the function)\b/i.test(haystack) ||
/\b(?:Cache-Control|s-maxage|CDN|edge cache|cache breakdown|x-vercel-cache|HIT|MISS|function (?:still )?runs per request|every request invokes the function)\b[^.\n]{0,240}\bcacheLife\b/i.test(haystack) ||
/\b(?:no|never|without|missing)\s+cacheLife\b[^.\n]{0,240}\b(?:no|not|never|0%|every|per request|function)\b[^.\n]{0,120}\b(?:cache|cached|hit|runs?|invoke)/i.test(haystack);
}
function mentionsNextCacheComponentsStaticShellTarget(rec) {
const haystack = recText(rec);
if (!/\b(?:cacheComponents|Cache Components|cacheLife|cacheTag|['"`]use cache['"`]|use cache|static shell|pre[- ]?render|prerender)\b/i.test(haystack)) {
return false;
}
const files = [
...asArray(rec?.affectedFiles),
...asArray(rec?.findingRefs).map((ref) => String(ref).match(/^(.+?):\d+$/)?.[1]).filter(Boolean),
];
return files.some((file) => /(^|\/)layout\.(?:tsx?|jsx?)$/.test(String(file)));
}
function mentionsImageResponseHeaders(rec) {
const haystack = recText(rec);
return /\bImageResponse\b/.test(haystack) &&
/\bheaders?\b[\s\S]{0,200}\b(?:Cache-Control|s-maxage|CDN|response)\b|\b(?:Cache-Control|s-maxage|CDN)\b[\s\S]{0,200}\bheaders?\b/i.test(haystack);
}
function mentionsNextImagePriorityRecommendation(rec) {
const haystack = recText(rec);
if (!/\b(?:next\/image|<Image\b|Image component|image)\b/i.test(haystack)) return false;
if (!/\bpriority\b/i.test(haystack)) return false;
if (/\b(?:deprecated|replace|remove|avoid)\b[^.\n]{0,120}\bpriority\b/i.test(haystack) ||
/\bpriority\b[^.\n]{0,120}\b(?:deprecated|replace|remove|avoid)\b/i.test(haystack)) {
return false;
}
return /\b(?:set|add|use|enable|mark|make|turn on|with)\b[^.\n]{0,120}\bpriority\b/i.test(haystack) ||
/<Image\b[^>]*\bpriority(?:\s|=|>)/i.test(haystack);
}
function mentionsNextCacheComponentsRouteSegmentConfig(rec) {
const haystack = recText(rec);
return /\b(?:export\s+const\s+)?(?:dynamicParams|fetchCache)\s*=/.test(haystack) ||
/\bexport\s+const\s+(?:dynamic|revalidate)\b/.test(haystack) ||
/\b(?:set|add|configure|use)\s+[^.\n]{0,80}\b(?:dynamicParams|fetchCache)\b/i.test(haystack) ||
/\broute segment config options?\b[^.\n]{0,120}\b(?:Route Handlers?|handlers?)\b[^.\n]{0,120}\b(?:no longer apply|do not apply|removed)\b/i.test(haystack) ||
/\b(?:revalidate|dynamic|fetchCache)\b[^.\n]{0,80}\broute segment (?:config|export)\b/i.test(haystack);
}
function mentionsRouteLevelRevalidate(rec) {
const haystack = recText(rec);
return /\bexport\s+const\s+revalidate\b/.test(haystack) ||
/\broute[- ]level\s+revalidate\b/i.test(haystack) ||
/\brevalidate\s*(?:=|:)\s*\d+\b[^.\n]{0,120}\b(?:page|layout|route segment|segment export)\b/i.test(haystack);
}
function mentionsExistingCacheTagInvalidation(rec) {
const haystack = recText(rec);
if (!/\bcacheTag\s*\(/.test(haystack)) return false;
if (!/\b(?:revalidateTag|updateTag|invalidate|invalidation|revalidation|webhook|CMS|content-sync|content sync|publish|deploy)\b/i.test(haystack)) {
return false;
}
return /\b(?:existing|current|already|keep|keeps|preserve|preserves|continue|continues|maintain|maintains|via)\b[\s\S]{0,180}\b(?:revalidateTag|updateTag|invalidate|invalidation|revalidation|event-driven|webhook|CMS|content-sync|content sync|publish|deploy|tags?)\b/i.test(haystack) ||
/\b(?:invalidation|revalidation)\s+is\s+already\b/i.test(haystack) ||
/\balready\s+event-driven\b/i.test(haystack);
}
function mentionsUnsafeImmutableDynamicRoute(rec) {
const haystack = recText(rec);
if (!/\bimmutable\b/i.test(haystack)) return false;
const files = [
...asArray(rec?.affectedFiles),
...asArray(rec?.findingRefs).map((ref) => String(ref).match(/^(.+?):\d+$/)?.[1]).filter(Boolean),
];
const routeHandler = files.some((file) => /(?:^|\/)route\.[cm]?[jt]sx?$/.test(String(file)));
const apiRoute = /^cache_header_gap:\/api\//.test(String(rec?.candidateRef ?? ''));
return routeHandler || apiRoute;
}
function mentionsAuthSensitiveParallelization(rec) {
const haystack = recText(rec);
if (!/\b(?:parallelize|Promise\.all|run concurrently|start .* early)\b/i.test(haystack)) return false;
if (!/\b(?:auth|authorize|authorization|ownership|owns|owner|private|session|permission|access)\b/i.test(haystack)) return false;
return /\b(?:private|secret|token|registrant|account|user|ticket|payment|session)\w*\b/i.test(haystack);
}
function mentionsParallelizationImpactOverclaim(rec) {
const haystack = recText(rec);
if (!/\b(?:parallelize|Promise\.all|run concurrently|start .* early)\b/i.test(haystack)) return false;
return /\b(?:drop|drops|reduce|reduces|reduction|save|saves|shave|shaves)\b[^.\n]{0,200}\b(?:roughly|approximately|about|around|equal\s+to)?\s*(?:the\s+)?(?:duration\s+of\s+[A-Za-z_$][\w$]*\s*\(\s*\)|min\s*\([^)]*duration[^)]*\)|one\s+[\w-]+\s+round[- ]trip|one\s+await|one\s+network\s+call|one\s+database\s+query)/i.test(haystack);
}
function mentionsCpuBoundParallelization(rec) {
const haystack = recText(rec);
if (!/\b(?:parallelize|Promise\.all|run concurrently|start .* early)\b/i.test(haystack)) return false;
return /\b(?:cpu\.p95|CPU p95|cpu p95|CPU-bound|compute-bound|in-process compute|compileMDX|MDX compilation|compilation|render compute)\b/i.test(haystack);
}
function mentionsCachedNotFoundOr404(rec) {
const haystack = recText(rec);
if (!/\b(?:s-maxage|CDN-Cache-Control|Vercel-CDN-Cache-Control|Cache-Control)\b/i.test(haystack)) return false;
return /\b(?:404|not[- ]found|notFound|not found branch|not-found branch)\b/i.test(haystack);
}
function mentionsRuntimeErrorCause(rec) {
if (!/^route_errors:/.test(String(rec?.candidateRef ?? ''))) return false;
const haystack = recText(rec);
return /\b(?:ENOENT|ETIMEDOUT|ECONNRESET|outputFileTracing|missing\s+(?:file|mdx|module)|no\s+(?:matching|corresponding)\s+(?:file|mdx|post)|does\s+not\s+exist|signature\s+of|root cause|caused by|unhandled\s+exceptions?|uncaught(?:-exception)?|throws?|bubbles?\s+to\s+the\s+runtime|reads?\s+[^.]{0,80}(?:filePath|filesystem|file system|disk)|readFile)\b/i.test(haystack);
}
function mentionsCatchToNotFound(rec) {
if (!/^route_errors:/.test(String(rec?.candidateRef ?? ''))) return false;
const haystack = recText(rec);
return /\bcatch\b/i.test(haystack) &&
/\b(?:404|not[- ]found|not found|notFound)\b/i.test(haystack);
}
function mentionsIgnoredBuildStepRecommendation(rec) {
const haystack = recText(rec);
return /\b(?:Ignored Build Step|ignoreCommand|turbo-ignore|skip unaffected|unaffected projects?)\b/i.test(haystack) &&
/\b(?:add|set|configure|enable|use|introduce|wire|adopt|turn on)\b[^.\n]{0,180}\b(?:Ignored Build Step|ignoreCommand|turbo-ignore|skip unaffected|unaffected projects?)\b/i.test(haystack);
}
function mentionsTurboBuildCacheRecommendation(rec) {
const haystack = recText(rec);
if (!/\b(?:Turbo|Turborepo|turbo\.json|tasks\.build|build cache|build caching)\b/i.test(haystack)) return false;
return /\b(?:enable|re-enable|restore|turn on|set|remove)\b[^.\n]{0,220}\b(?:cache\s*:\s*false|tasks\.build\.cache|build cache|build caching|Turbo cache|Turborepo cache)\b/i.test(haystack) ||
/\b(?:cache\s*:\s*false|tasks\.build\.cache|build cache|build caching|Turbo cache|Turborepo cache)\b[^.\n]{0,220}\b(?:enable|re-enable|restore|turn on|set|remove)\b/i.test(haystack);
}
function recText(rec) {
return [
rec?.what,
rec?.why,
rec?.fix,
rec?.currentBehavior,
rec?.desiredBehavior,
rec?.verify,
].filter(Boolean).join('\n');
}
function asArray(v) {
return Array.isArray(v) ? v : [];
}
export function summarizeClaimResults(results) {
const counts = { verified: 0, failed: 0, unsupported: 0, unverifiable: 0 };
for (const r of results) {
if (r?.disposition && counts[r.disposition] !== undefined) counts[r.disposition]++;
}
const verifiable = counts.verified + counts.failed;
const passRate = verifiable > 0 ? counts.verified / verifiable : 1;
return { ...counts, verifiable, passRate, total: results.length };
}
export const CORE_SUPPORTED_FRAMEWORKS = ['next', 'sveltekit', 'nuxt'];
export const LIMITED_FRAMEWORKS = ['astro'];
const LABELS = {
next: 'Next.js',
sveltekit: 'SvelteKit',
nuxt: 'Nuxt',
astro: 'Astro',
hono: 'Hono',
remix: 'Remix',
unknown: 'unknown framework',
};
export function frameworkLabel(framework) {
return LABELS[normalizeFramework(framework)] ?? String(framework ?? 'unknown');
}
export function classifyFrameworkSupport(stack = {}) {
const framework = normalizeFramework(stack.framework);
const label = frameworkLabel(framework);
const supportedLabels = CORE_SUPPORTED_FRAMEWORKS.map(frameworkLabel);
const limitedLabels = LIMITED_FRAMEWORKS.map(frameworkLabel);
if (CORE_SUPPORTED_FRAMEWORKS.includes(framework)) {
return {
ok: true,
status: 'supported',
blocker: null,
framework,
label,
supportedFrameworks: supportedLabels,
limitedFrameworks: limitedLabels,
detail: `${label} is supported for metric-backed route-to-file investigations.`,
};
}
if (LIMITED_FRAMEWORKS.includes(framework)) {
return {
ok: true,
status: 'limited',
blocker: null,
framework,
label,
supportedFrameworks: supportedLabels,
limitedFrameworks: limitedLabels,
detail: `${label} support is limited. The skill can use Vercel metrics and generic platform checks, but framework-specific route-to-file recommendations may be sparse.`,
};
}
return {
ok: false,
status: 'unsupported',
blocker: 'unsupported_framework',
framework,
label,
supportedFrameworks: supportedLabels,
limitedFrameworks: limitedLabels,
detail: `${label} is not supported for metric-backed route-to-file investigations. Supported frameworks: ${supportedLabels.join(', ')}. Limited support: ${limitedLabels.join(', ')}.`,
};
}
function normalizeFramework(value) {
const raw = String(value ?? 'unknown').trim().toLowerCase();
if (raw === 'nextjs' || raw === 'next.js') return 'next';
if (raw === 'svelte' || raw === 'svelte-kit') return 'sveltekit';
return raw || 'unknown';
}
// Build Minutes climb on monorepos when Turborepo cache is bypassed or every project rebuilds on every commit.
// Threshold: Build Minutes line > 15% of total bill OR scanner emits any turbo-force-bypass finding (even at lower share).
// Account-scoped because the lever is project-settings (Ignored Build Step, Elastic Build Machines), not code.
export const metadata = {
id: 'build_minutes_fanout',
threshold: 'Build Minutes share > 0.15 OR turbo-force-bypass finding present',
billingDimension: 'build',
scope: 'account',
sourceCitation: 'vercel-optimize gate threshold',
description:
'Build Minutes line dominates the bill or Turborepo cache is bypassed. On monorepos, unchanged work should be skipped through Vercel skip-unaffected behavior, a verified Ignored Build Step, and a complete Turbo cache contract.',
};
const BUILD_RE = /^Build (CPU )?Minutes$/i;
const SCANNER_PATTERN = 'turbo-force-bypass';
const SHARE_FLOOR = 0.15;
export function gate(signals) {
const services = signals?.usage?.services;
const total = Array.isArray(services)
? services.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0)
: 0;
const buildBilled = Array.isArray(services)
? services
.filter((s) => BUILD_RE.test(String(s?.name ?? '')))
.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0)
: 0;
const buildShare = total > 0 ? buildBilled / total : 0;
const findings = (signals?.codebase?.findings ?? []).filter((f) => f.pattern === SCANNER_PATTERN);
if (buildShare <= SHARE_FLOOR && findings.length === 0) return [];
const subtypes = unique(findings.map((f) => f.subtype).filter(Boolean));
const sampleFiles = unique(findings.map((f) => f.file).filter(Boolean)).slice(0, 4);
const reason = findings.length > 0
? (buildShare > SHARE_FLOOR
? 'Build Minutes share is high and Turborepo cache bypass detected in repo'
: 'Turborepo cache bypass detected in repo')
: 'Build Minutes line exceeds 15% of total billed cost';
return [{
kind: metadata.id,
scope: 'account',
files: sampleFiles,
priority: findings.length > 0 ? 65 : 50,
confidence: findings.length > 0 ? 0.86 : 0.74,
o11ySignal: `build_minutes_share=${(buildShare * 100).toFixed(0)}% scanner_findings=${findings.length}`,
reason,
question: findings.length > 0
? `Turborepo cache bypass detected (${subtypes.join(', ')}). Which build pipeline forces a rebuild on every commit, and can Ignored Build Step + cache re-enable cut the project fan-out?`
: 'Build Minutes exceed 15% of the bill. Is Ignored Build Step configured? Is Turborepo cache active across builds? Would Elastic Build Machines reduce duration on hot builds?',
evidence: {
metric: 'usage.services',
buildBilled,
totalBilled: total,
buildShare,
scannerFindings: findings.length,
scannerSubtypes: subtypes,
sampleFiles,
},
}];
}
function unique(values) {
return [...new Set(values)];
}
// Signal: `function_start_type` dimension on `vercel.function_invocation.count` (cold|hot|prewarmed).
// Threshold WHY: 40%+ cold is fixable via Fluid keep-warm; 30% is the noise floor for serverless without keep-warm.
// total>=1000/14d (~3/hr) keeps Poisson CI on cold rate at ~±5% near the 40% threshold.
export const metadata = {
id: 'cold_start',
threshold: 'coldPct > 0.4 AND total >= 1000',
billingDimension: 'function-duration',
scope: 'route',
sourceCitation: 'vercel-optimize gate threshold',
description:
'Routes where > 40% of invocations are cold-start, at meaningful traffic (>=1,000 total invocations in window). Cold starts add 200-800ms per request and break the perceived latency budget on cache-miss paths. The 40% threshold is where cold-rate becomes a real signal vs Poisson noise on serverless. Sourced from vercel.function_invocation.count grouped by function_start_type.',
};
export function gate(signals) {
const cs = extractColdStarts(signals);
return cs
.filter((r) => r.coldPct > 0.4 && r.total >= 1000)
.map((r) => ({
kind: metadata.id,
scope: 'route',
route: r.route,
files: [],
priority: Math.round(r.total * r.coldPct),
confidence: 0.92,
o11ySignal: `cold=${(r.coldPct * 100).toFixed(0)}%,inv=${r.total}`,
reason: 'high cold-start rate on hot route',
question: `What initialization or bundle overhead makes ${r.route} cold-start ${(r.coldPct * 100).toFixed(0)}% of ${r.total} invocations?`,
evidence: { metric: 'fnStartTypeByRoute', route: r.route, coldPct: r.coldPct, total: r.total, coldCount: r.coldCount ?? null },
}));
}
function extractColdStarts(signals) {
const live = signals.metrics?.fnStartTypeByRoute;
if (Array.isArray(live?.rows) && live.rows.some((r) => 'coldCount' in r || 'coldPct' in r)) {
return live.rows
.filter((r) => r.route)
.map((r) => ({
route: r.route,
total: r.total ?? 0,
coldCount: r.coldCount ?? 0,
coldPct: r.coldPct ?? 0,
}));
}
// Legacy fixture: pre-derived coldStartByRoute rows.
const direct = signals.metrics?.coldStartByRoute;
if (Array.isArray(direct?.rows)) {
return direct.rows
.filter((r) => r.route)
.map((r) => ({ route: r.route, coldPct: r.coldPct ?? 0, total: r.total ?? 0 }));
}
// Older legacy fixture: series + summary shape.
const legacy = signals.metrics?.coldStarts;
if (Array.isArray(legacy?.series)) {
return legacy.series
.map((s) => {
const total = s.summary?.count ?? 0;
const coldCount = s.summary?.coldCount ?? s.summary?.sum ?? 0;
return { route: s.groupValues?.route, total, coldPct: total > 0 ? coldCount / total : 0 };
})
.filter((r) => r.route);
}
return [];
}
const VALID_SCOPES = new Set(['route', 'file', 'account']);
export class CandidateContractError extends Error {
constructor(errors) {
super(`gate candidate contract failed:\n${errors.map((e) => `- ${e}`).join('\n')}`);
this.name = 'CandidateContractError';
this.errors = errors;
}
}
export function validateCandidates(candidates, ctx = {}) {
if (!Array.isArray(candidates)) {
throw new CandidateContractError([`${ctx.source ?? 'gate'}: expected candidate array`]);
}
const errors = [];
for (let i = 0; i < candidates.length; i++) {
errors.push(...validateCandidate(candidates[i], { ...ctx, index: i }).errors);
}
if (errors.length > 0) throw new CandidateContractError(errors);
return candidates;
}
export function validateCandidate(candidate, ctx = {}) {
const label = candidateLabel(candidate, ctx);
const errors = [];
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
return { ok: false, errors: [`${label}: candidate must be an object`] };
}
if (!nonEmptyString(candidate.kind)) errors.push(`${label}: kind must be a non-empty string`);
if (!VALID_SCOPES.has(candidate.scope)) {
errors.push(`${label}: scope must be one of route, file, account`);
}
if (!Number.isFinite(candidate.priority)) errors.push(`${label}: priority must be a finite number`);
if (!Number.isFinite(candidate.confidence)) errors.push(`${label}: confidence must be a finite number`);
if (Array.isArray(candidate.files)) {
if (!candidate.files.every((f) => typeof f === 'string' && f.length > 0)) {
errors.push(`${label}: files must contain only non-empty strings`);
}
} else {
errors.push(`${label}: files must be an array`);
}
if (!nonEmptyString(candidate.reason)) errors.push(`${label}: reason must be a non-empty string`);
if (!nonEmptyString(candidate.question)) errors.push(`${label}: question must be a non-empty string`);
if (candidate.scope === 'route') {
const hasRoute = nonEmptyString(candidate.route);
const hasHostname = nonEmptyString(candidate.hostname);
if (!hasRoute && !hasHostname) {
errors.push(`${label}: route-scoped candidates must set route or hostname`);
}
}
if (candidate.scope === 'file') {
if (candidate.route != null || candidate.hostname != null) {
errors.push(`${label}: file-scoped candidates must not set route or hostname`);
}
if (!Array.isArray(candidate.files) || candidate.files.length === 0) {
errors.push(`${label}: file-scoped candidates must include at least one file`);
}
}
if (candidate.scope === 'account') {
if (candidate.route != null || candidate.hostname != null) {
errors.push(`${label}: account-scoped candidates must not set route or hostname`);
}
}
return { ok: errors.length === 0, errors };
}
function candidateLabel(candidate, ctx) {
const source = ctx.source ?? 'gate';
const index = ctx.index == null ? '?' : ctx.index;
const kind = candidate?.kind ?? '?';
return `${source}[${index}] ${kind}`;
}
function nonEmptyString(value) {
return typeof value === 'string' && value.trim().length > 0;
}
// Thresholds are Google's "Poor" band (https://web.dev/articles/vitals): LCP p75 > 2500ms, INP > 200ms, CLS > 0.1.
// When Speed Insights isn't wired up the metrics come back empty and the gate is a no-op.
import { withRouteShapeWarnings } from '../route-normalize.mjs';
export const metadata = {
id: 'cwv_poor',
threshold: 'LCP p75>2500 OR INP p75>200 OR CLS p75>0.1, AND speed_insights count > 50',
billingDimension: 'speed-insights',
scope: 'route',
sourceCitation: 'https://web.dev/articles/vitals',
description:
'Routes where Core Web Vitals fall into Google\'s "Poor" band on real-user traffic. LCP > 2500ms, INP > 200ms, or CLS > 0.1 each hurt SEO and conversion. Surfaces one candidate per (route, metric) pair to keep recommendations focused.',
};
// Below this floor p75 is too noisy to act on.
const MIN_PER_ROUTE_SAMPLES = 50;
export function gate(signals) {
const totalSamples = sumRows(signals.metrics?.cwvCount?.rows);
if (totalSamples === 0) return [];
const countByRoute = byRoute(signals.metrics?.cwvCountByRoute?.rows);
const lcpBy = byRoute(signals.metrics?.cwvLcpByRoute?.rows);
const inpBy = byRoute(signals.metrics?.cwvInpByRoute?.rows);
const clsBy = byRoute(signals.metrics?.cwvClsByRoute?.rows);
const routes = new Set([...lcpBy.keys(), ...inpBy.keys(), ...clsBy.keys()]);
const out = [];
for (const route of routes) {
const routeSamples = countByRoute.get(route) ?? 0;
if (routeSamples < MIN_PER_ROUTE_SAMPLES) continue;
const lcp = lcpBy.get(route);
const inp = inpBy.get(route);
const cls = clsBy.get(route);
const issues = [];
if (lcp != null && lcp > 2500) issues.push({ metric: 'LCP', value: Math.round(lcp), threshold: 2500, unit: 'ms' });
if (inp != null && inp > 200) issues.push({ metric: 'INP', value: Math.round(inp), threshold: 200, unit: 'ms' });
if (cls != null && cls > 0.1) issues.push({ metric: 'CLS', value: round2(cls), threshold: 0.1, unit: '' });
if (issues.length === 0) continue;
const summary = issues.map((i) => `${i.metric}=${i.value}${i.unit}`).join(',');
out.push(withRouteShapeWarnings({
kind: metadata.id,
scope: 'route',
route,
files: [],
priority: issues.reduce((s, i) => s + ratioOverThreshold(i), 0) * 10,
confidence: 0.82,
o11ySignal: summary,
reason: 'real-user Core Web Vitals in poor band',
question: `On ${route}, ${summary}. Which client-side work (bundle weight, blocking scripts, layout shifts, hydration) is responsible, and which change would land first?`,
evidence: {
metric: 'cwv',
route,
lcpMs: lcp != null ? Math.round(lcp) : null,
inpMs: inp != null ? Math.round(inp) : null,
cls: cls != null ? round2(cls) : null,
issues,
totalSpeedInsightsSamples: totalSamples,
routeSpeedInsightsSamples: routeSamples,
},
}, signals));
}
return out;
}
function byRoute(rows) {
const m = new Map();
for (const r of rows ?? []) {
if (!r.route || r.value == null) continue;
m.set(r.route, r.value);
}
return m;
}
function sumRows(rows) {
if (!Array.isArray(rows)) return 0;
return rows.reduce((s, r) => s + (r.value ?? 0), 0);
}
function round2(n) {
return Math.round(n * 100) / 100;
}
function ratioOverThreshold(i) {
return i.value / (i.threshold || 1);
}
// Volume floor pairs p75 with call_count so a single 5s cron/day doesn't fire the gate.
const MIN_CALL_COUNT = 500;
export const metadata = {
id: 'external_api_slow',
threshold: `p75Ms > 2000 AND callCount >= ${MIN_CALL_COUNT}`,
billingDimension: 'function-duration',
scope: 'route',
sourceCitation: 'vercel-optimize gate threshold',
description:
'External API hostnames with p75 latency above 2 seconds AND at least 500 calls in the window. External API latency is a primary driver of function duration cost when the upstream is on a hot path; a single slow stale call isn\'t worth recommending against.',
};
export function gate(signals) {
const apis = extractExternalApis(signals);
const calls = extractCallCounts(signals);
return apis
.map((a) => ({ ...a, callCount: calls.get(a.hostname) ?? 0 }))
.filter((a) => a.p75Ms > 2000 && a.callCount >= MIN_CALL_COUNT)
.map((a) => ({
kind: metadata.id,
scope: 'route',
route: null,
files: [],
hostname: a.hostname,
// Weight by latency × call volume so 100k-call/2.1s outranks 1k-call/8s.
priority: Math.round((a.p75Ms * a.callCount) / 1000),
confidence: 0.88,
o11ySignal: `host=${a.hostname},p75=${a.p75Ms}ms,calls=${a.callCount}`,
reason: 'slow external dependency on hot path',
question: `Which routes call ${a.hostname} (p75=${a.p75Ms}ms across ${a.callCount} calls), and can the call be parallelized, cached, or moved off the critical path?`,
evidence: { metric: 'externalApiP75', hostname: a.hostname, p75Ms: a.p75Ms, callCount: a.callCount },
}));
}
function extractExternalApis(signals) {
const m = signals.metrics?.externalApiP75;
if (!m?.ok && !Array.isArray(m?.rows)) return [];
return (m?.rows ?? [])
.map((r) => ({
hostname: r.origin_hostname,
p75Ms: Math.round(r.value ?? 0),
}))
.filter((a) => a.hostname);
}
function extractCallCounts(signals) {
const m = signals.metrics?.externalApiCount;
const out = new Map();
if (!m) return out;
for (const r of m.rows ?? []) {
if (r?.origin_hostname) out.set(r.origin_hostname, r.value ?? 0);
}
return out;
}
import { canonicalizeRoute } from '../route-normalize.mjs';
export const FLAGS_ENDPOINT = '/.well-known/vercel/flags';
export const VERCEL_FLAGS_PACKAGES = [
'@vercel/flags',
'@vercel/flags/next',
'@vercel/flags/sveltekit',
'@vercel/flags/nuxt',
];
export const WORKFLOW_ENDPOINT_PREFIXES = [
'/.well-known/workflow',
'/api/.well-known/workflow',
];
export function applyHardGates(candidates, signals = {}) {
const allowed = [];
const gated = [];
for (const candidate of candidates) {
if (isFlagsEndpointCandidate(candidate)) {
gated.push({
...candidate,
gatedReason: flagsEndpointReason(signals),
});
continue;
}
if (isWorkflowRuntimeEndpointCandidate(candidate)) {
gated.push({
...candidate,
gatedReason: workflowEndpointReason(signals),
});
continue;
}
allowed.push(candidate);
}
return { allowed, gated };
}
export function isFlagsEndpointCandidate(candidate) {
if (!candidate || candidate.scope === 'account') return false;
const route = normalizeRoute(candidate.route);
return route === FLAGS_ENDPOINT;
}
export function isWorkflowRuntimeEndpointCandidate(candidate) {
if (!candidate || candidate.scope === 'account') return false;
const route = normalizeRoute(candidate.route);
if (!route) return false;
return WORKFLOW_ENDPOINT_PREFIXES.some((prefix) => (
route === prefix || route.startsWith(`${prefix}/`)
));
}
function normalizeRoute(route) {
if (typeof route !== 'string') return null;
const normalized = canonicalizeRoute(route).replace(/\/+$/, '');
return normalized === '' ? '/' : normalized;
}
function flagsEndpointReason(signals) {
const packages = signals.stack?.vercelFlagsPackages;
if (Array.isArray(packages) && packages.length > 0) {
return `hardGated: ${FLAGS_ENDPOINT} is the Vercel Flags endpoint (${packages.join(', ')} detected), not an optimization target`;
}
return `hardGated: ${FLAGS_ENDPOINT} is the Vercel Flags endpoint, not an optimization target`;
}
function workflowEndpointReason(signals) {
const packages = signals.stack?.workflowPackages;
if (Array.isArray(packages) && packages.length > 0) {
return `hardGated: Vercel Workflow runtime endpoint (${packages.join(', ')} detected); long-running step/flow requests are expected orchestration, not an app-route optimization target`;
}
return 'hardGated: Vercel Workflow runtime endpoint; long-running step/flow requests are expected orchestration, not an app-route optimization target';
}
import * as uncachedRoute from './uncached-route.mjs';
import * as slowRoute from './slow-route.mjs';
import * as routeErrors from './route-errors.mjs';
import * as coldStart from './cold-start.mjs';
import * as isrOverrevalidation from './isr-overrevalidation.mjs';
import * as cwvPoor from './cwv-poor.mjs';
import * as platformFluidCompute from './platform-fluid-compute.mjs';
import * as platformBotProtection from './platform-bot-protection.mjs';
import * as middlewareHeavy from './middleware-heavy.mjs';
import * as externalApiSlow from './external-api-slow.mjs';
import * as scannerDriven from './scanner-driven.mjs';
import * as observabilityEventsAttribution from './observability-events-attribution.mjs';
import * as usageSpikeTriage from './usage-spike-triage.mjs';
import * as buildMinutesFanout from './build-minutes-fanout.mjs';
import * as regionMisconfig from './region-misconfig.mjs';
// Intentionally NOT registered:
// - `oversized_memory`: Fluid Compute floor is 2GB; per-route memory right-sizing isn't a customer lever.
// - `deploy_regression`: overlaps Vercel Agent Investigations; `vercel inspect` 404s across teams. slow_route deep-dive already carries per-deployment p95 trend.
export const gates = [
uncachedRoute,
slowRoute,
routeErrors,
coldStart,
isrOverrevalidation,
cwvPoor,
externalApiSlow,
scannerDriven,
// Account-scoped last so platform-scoped sort doesn't dilute code-scoped priority ordering during budget application.
platformFluidCompute,
platformBotProtection,
middlewareHeavy,
observabilityEventsAttribution,
usageSpikeTriage,
buildMinutesFanout,
regionMisconfig,
];
// Overridable via `--max-candidates N` or `VERCEL_OPTIMIZE_MAX_CANDIDATES` (accepts `all`).
// `MAX_CODE_CANDIDATES` is a back-compat alias for tests importing the old name.
export const DEFAULT_MAX_CODE_CANDIDATES = 6;
export const MAX_CODE_CANDIDATES = DEFAULT_MAX_CODE_CANDIDATES;
// Bump on any threshold change so report + iteration baselines can detect gate-logic drift.
export const GATE_VERSION = '1.8.0';
// ISR writes re-execute the page render. A w/r ratio above 0.5 means writes are
// happening at least once for every two reads — high enough for the default
// audit to spend investigation budget. writes>100 avoids flapping on quiet routes.
export const metadata = {
id: 'isr_overrevalidation',
threshold: 'writes/reads > 0.5 AND writes > 100',
billingDimension: 'isr',
scope: 'route',
sourceCitation: 'https://vercel.com/docs/incremental-static-regeneration',
description:
'ISR routes with > 1 write per 2 reads. The revalidate interval is too aggressive relative to read traffic — many reads pay to regenerate. Investigate whether the page can tolerate a longer revalidate window or on-demand revalidation via revalidateTag.',
};
export function gate(signals) {
const rows = extractRows(signals);
return rows
.filter((r) => r.writes > 100 && r.reads > 0 && r.writes / r.reads > 0.5)
.map((r) => {
const ratio = r.writes / r.reads;
return {
kind: metadata.id,
scope: 'route',
route: r.route,
files: [],
priority: Math.round(r.writes),
confidence: 0.88,
o11ySignal: `writes=${r.writes},reads=${r.reads},w/r=${ratio.toFixed(2)}`,
reason: 'ISR revalidating faster than read traffic justifies',
question: `On ${r.route}, ${r.writes} ISR writes against ${r.reads} reads (${(ratio * 100).toFixed(0)} writes per 100 reads) — what is the current revalidate interval and can it be lengthened or switched to on-demand?`,
evidence: {
metric: 'isrWritesByRoute',
route: r.route,
writes: r.writes,
reads: r.reads,
ratio,
},
};
});
}
function extractRows(signals) {
const writes = signals.metrics?.isrWritesByRoute?.rows ?? [];
const reads = signals.metrics?.isrReadsByRoute?.rows ?? [];
const writeByRoute = new Map();
for (const r of writes) {
if (!r.route) continue;
writeByRoute.set(r.route, (writeByRoute.get(r.route) ?? 0) + (r.value ?? 0));
}
const readByRoute = new Map();
for (const r of reads) {
if (!r.route) continue;
readByRoute.set(r.route, (readByRoute.get(r.route) ?? 0) + (r.value ?? 0));
}
const routes = new Set([...writeByRoute.keys(), ...readByRoute.keys()]);
return [...routes].map((route) => ({
route,
writes: writeByRoute.get(route) ?? 0,
reads: readByRoute.get(route) ?? 0,
}));
}
// Middleware runs in front of every matching request and is billed as edge invocations.
// If >50% of traffic hits middleware, the matcher is probably broader than necessary.
export const metadata = {
id: 'middleware_heavy',
threshold: 'middlewareInv/totalInv > 0.5 AND middlewareInv > 1000',
billingDimension: 'edge-requests',
scope: 'account',
sourceCitation: 'https://nextjs.org/docs/app/building-your-application/routing/middleware',
description:
'Middleware invocations cover > 50% of total requests at non-trivial volume. The matcher is probably broader than necessary; narrow it to the paths that actually need auth/rewrites/headers.',
};
export function gate(signals) {
const middlewareInv = sumRows(signals.metrics?.middlewareCount?.rows);
if (middlewareInv < 1000) return [];
const totalInv = sumRows(signals.metrics?.requestsByRouteCache?.rows);
if (totalInv === 0) return [];
const ratio = middlewareInv / totalInv;
if (ratio <= 0.5) return [];
const top = [...(signals.metrics?.middlewareCount?.rows ?? [])]
.filter((r) => r.request_path)
.sort((a, b) => (b.value ?? 0) - (a.value ?? 0))
.slice(0, 5)
.map((r) => ({ request_path: r.request_path, count: r.value ?? 0 }));
return [{
kind: metadata.id,
scope: 'account',
files: [],
priority: Math.round(middlewareInv / 1000),
confidence: 0.84,
o11ySignal: `middleware_inv=${middlewareInv},total_req=${totalInv},ratio=${(ratio * 100).toFixed(0)}%`,
reason: 'middleware ran on more than half of all requests',
question: `Middleware invocations (${middlewareInv}) are ${(ratio * 100).toFixed(0)}% of all requests (${totalInv}). Which paths in middleware.ts require interception, and can the matcher be narrowed to exclude static assets, images, and routes that do not need rewriting?`,
evidence: {
metric: 'middlewareCount',
middlewareInv,
totalInv,
ratio,
topPaths: top,
},
}];
}
function sumRows(rows) {
if (!Array.isArray(rows)) return 0;
return rows.reduce((s, r) => s + (r.value ?? 0), 0);
}
// Observability Events is the metered SKU under Observability Plus.
// Threshold at >20% surfaces material spend; >30% is the critical band.
// Drivers correlate with low cache hit rate, high middleware invocation, and high custom-span cardinality.
export const metadata = {
id: 'observability_events_attribution',
threshold: 'observabilityEventsShare > 0.20 (critical at > 0.30)',
billingDimension: 'observability-events',
scope: 'account',
sourceCitation: 'vercel-optimize gate threshold',
description:
'Observability Events line item exceeds 20% of total billed cost. High share usually traces to low cache hit rate, middleware-heavy traffic, or unconstrained custom-span cardinality. No sampling lever exists for Observability Plus; reduce upstream invocations instead.',
};
const EVENTS_RE = /^Observability Events$/i;
export function gate(signals) {
const services = signals?.usage?.services;
if (!Array.isArray(services) || services.length === 0) return [];
const total = sumBilled(services);
if (total <= 0) return [];
const eventsBilled = services
.filter((s) => EVENTS_RE.test(String(s?.name ?? '')))
.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0);
if (eventsBilled <= 0) return [];
const share = eventsBilled / total;
if (share <= 0.20) return [];
const critical = share > 0.30;
return [{
kind: metadata.id,
scope: 'account',
files: [],
priority: critical ? 70 : 55,
confidence: 0.82,
o11ySignal: `observability_events_share=${(share * 100).toFixed(0)}%`,
reason: critical
? 'observability events exceed 30% of total billed cost'
: 'observability events exceed 20% of total billed cost',
question: `Observability Events are ${(share * 100).toFixed(0)}% of the bill. Which routes drive event volume — low-cache-hit traffic, broad middleware invocation, or high custom-span cardinality — and can event volume be reduced upstream of the meter?`,
evidence: {
metric: 'usage.services',
eventsBilled,
totalBilled: total,
observabilityEventsShare: share,
critical,
},
}];
}
function sumBilled(services) {
return services.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0);
}
// Recommend BotID only when there's EVIDENCE of bot traffic or scale large enough that the rec is defensible.
// Without an evidence gate the rec fires on quiet hobby sites and erodes trust.
const MIN_BOT_PCT = 0.05;
const MIN_EDGE_COST = 25; // halved for 14d window
const MIN_TOTAL_REQUESTS = 14_000; // ~14k/14d matches the prior 30k/30d rate
const MIN_TOTAL_FDT_BYTES = 1_000_000;
export const metadata = {
id: 'platform_bot_protection',
threshold: 'botIdEnabled=false AND (botPct >= 0.05 OR edge_cost >= $25/window OR requests >= 14k/14d)',
billingDimension: 'edge-requests',
scope: 'account',
sourceCitation: 'vercel-optimize gate threshold',
description:
'When BotID is disabled AND there is evidence (observed bot bandwidth share, edge cost, or substantial request volume) that bot traffic is non-trivial. Bot traffic inflates edge request counts without delivering user value; staged bot protection can reduce waste on bot-heavy projects. Skipped on quiet projects with no bot evidence — the recommendation would be noise.',
};
export function gate(signals) {
// BotID surfaces under several legacy fields; check all.
const botEnabled =
signals.project?.security?.botIdEnabled === true
|| signals.project?.security?.botProtection === true
|| signals.project?.botProtection?.enabled === true
|| signals.project?.delegatedProtection?.bot === true;
if (botEnabled) return [];
// Project config failed — we can't tell if BotID is on, so stay silent.
if (signals.project?.error) return [];
const totalRequests = totalRequestsFromSignals(signals);
const botShare = computeBotShare(signals);
const edgeService = (signals.usage?.services ?? []).find(
(s) => /edge.request/i.test(s.name ?? '')
);
const edgeCost = edgeService?.billedCost ?? null;
// Require observable bot share, edge cost, OR substantial traffic — otherwise rec is just config nagging.
const hasObservedBots = botShare?.botPct != null && botShare.botPct >= MIN_BOT_PCT;
const hasMaterialEdgeCost = edgeCost != null && edgeCost >= MIN_EDGE_COST;
const hasSubstantialTraffic = totalRequests >= MIN_TOTAL_REQUESTS;
if (!hasObservedBots && !hasMaterialEdgeCost && !hasSubstantialTraffic) return [];
const challengeRule = signals.project?.security?.managedRules?.bot_filter;
const ruleNote = challengeRule?.active
? `firewall bot_filter rule active (action=${challengeRule.action ?? '?'})`
: 'no firewall bot_filter rule';
// Kicker on high observed bot share — harder evidence than config alone.
let priority = edgeCost != null ? Math.max(20, Math.round(edgeCost)) : 30;
if (botShare?.botPct != null && botShare.botPct > 0.2) priority += 20;
// Confidence bumps when we can SEE bot traffic, not just infer from config.
let confidence = edgeCost != null ? 0.85 : 0.6;
if (botShare?.botPct != null && botShare.botPct > 0.2) confidence = Math.min(0.95, confidence + 0.05);
const botShareNote = botShare?.botPct != null
? `bot_fdt_pct=${(botShare.botPct * 100).toFixed(0)}%`
: 'bot_fdt_pct=unknown';
return [{
kind: metadata.id,
scope: 'account',
files: [],
priority,
confidence,
o11ySignal: edgeCost != null
? `edge_cost=${edgeCost.toFixed(0)},bot_protection=disabled,${botShareNote},${ruleNote}`
: `requests=${totalRequests},bot_protection=disabled,${botShareNote},${ruleNote}`,
reason: botShare?.botPct != null && botShare.botPct > 0.2
? 'BotID disabled with observable bot bandwidth share'
: 'BotID disabled with observable traffic',
question: botShare?.botPct != null && botShare.botPct > 0.2
? `Bot traffic accounts for ${(botShare.botPct * 100).toFixed(0)}% of FDT bytes (top category: ${botShare.topCategory ?? 'unknown'}). Would enabling BotID + a challenge rule reduce that share?`
: 'Would enabling BotID (Bot Protection) reduce edge request volume from automated traffic?',
evidence: {
botEnabled: false,
edgeCost,
totalRequests,
managedRules: challengeRule ?? null,
botShare: botShare ?? null,
},
}];
}
function totalRequestsFromSignals(signals) {
const rows = signals.metrics?.requestsByRouteCache?.rows;
if (!Array.isArray(rows)) return 0;
return rows.reduce((s, r) => s + (r.value ?? 0), 0);
}
// CLI convention: bot_category="" means "not classified as a bot" (human + unclassified); any non-empty = bot.
function computeBotShare(signals) {
const rows = signals.metrics?.fdtByBot?.rows;
if (!Array.isArray(rows) || rows.length === 0) return null;
let humanBytes = 0;
let botBytes = 0;
let topCategory = null;
let topBytes = 0;
for (const r of rows) {
const v = r.value ?? 0;
const cat = r.bot_category ?? '';
if (cat === '') {
humanBytes += v;
} else {
botBytes += v;
if (v > topBytes) {
topBytes = v;
topCategory = cat;
}
}
}
const total = humanBytes + botBytes;
if (total < MIN_TOTAL_FDT_BYTES) return null;
return { humanBytes, botBytes, botPct: botBytes / total, topCategory };
}
// Second branch (slow p95 + traffic floor) keeps the gate useful on teams where
// cold-start isn't directly observable — common on CLI v53 — trading specificity for coverage.
export const metadata = {
id: 'platform_fluid_compute',
threshold: 'fluid=false AND (any cold_start signal OR any route with p95>1000ms AND inv>1000)',
billingDimension: 'function-duration',
scope: 'account',
sourceCitation: 'vercel-optimize gate threshold',
description:
'When Fluid Compute is disabled on a project that shows cold-start pressure (high cold-start rate) or sustained slow function p95 on hot routes. Fluid Compute reduces cold starts via instance reuse — recommend turning it on at the project level rather than per-route.',
};
export function gate(signals) {
// If project config failed to load we can't tell if Fluid is on; recommending it when already-on
// erodes trust badly, so stay silent and let Strengths note the gap.
if (signals.project?.error) return [];
const fluidEnabled =
signals.project?.resourceConfig?.fluid === true
|| signals.project?.defaultResourceConfig?.fluid === true;
if (fluidEnabled) return [];
const cold = extractHighColdRoutes(signals);
const slow = extractSlowHotRoutes(signals);
if (cold.length === 0 && slow.length === 0) return [];
return [{
kind: metadata.id,
scope: 'account',
files: [],
priority: 50,
confidence: cold.length > 0 ? 0.85 : 0.65,
o11ySignal: cold.length > 0
? `${cold.length} route(s) with high cold-start rate`
: `${slow.length} hot route(s) with p95>1s; cold-start not directly observable`,
reason: cold.length > 0
? 'cold starts observed and Fluid Compute is disabled'
: 'slow hot routes and Fluid Compute is disabled',
question: 'Would enabling Fluid Compute reduce cold-start and warm-instance reuse overhead for the observed hot routes?',
evidence: { fluidEnabled, highColdRoutes: cold.slice(0, 5), slowHotRoutes: slow.slice(0, 5) },
}];
}
function extractHighColdRoutes(signals) {
const live = signals.metrics?.fnStartTypeByRoute?.rows;
if (Array.isArray(live) && live.some((r) => 'coldCount' in r || 'coldPct' in r)) {
return live.filter((r) => r.route && (r.coldPct ?? 0) > 0.3 && (r.total ?? 0) > 100);
}
// Legacy pre-derived fixture shape.
const direct = signals.metrics?.coldStartByRoute?.rows;
if (Array.isArray(direct)) {
return direct.filter((r) => r.route && (r.coldPct ?? 0) > 0.3 && (r.total ?? 0) > 100);
}
const legacy = signals.metrics?.coldStarts?.series;
if (Array.isArray(legacy)) {
return legacy
.map((s) => {
const total = s.summary?.count ?? 0;
const coldCount = s.summary?.coldCount ?? s.summary?.sum ?? 0;
return { route: s.groupValues?.route, total, coldPct: total > 0 ? coldCount / total : 0 };
})
.filter((r) => r.route && r.coldPct > 0.3 && r.total > 100);
}
return [];
}
function extractSlowHotRoutes(signals) {
const dur = signals.metrics?.fnDurationP95ByRoute?.rows;
const cache = signals.metrics?.requestsByRouteCache?.rows;
if (!Array.isArray(dur)) return [];
// Sum requests per route across cache_result.
const inv = new Map();
for (const r of (cache ?? [])) {
if (!r.route) continue;
inv.set(r.route, (inv.get(r.route) ?? 0) + (r.value ?? 0));
}
return dur
.filter((r) => r.route)
.map((r) => ({ route: r.route, p95Ms: Math.round(r.value ?? 0), invocations: inv.get(r.route) ?? 0 }))
// inv>500 floor is the 14d-window equivalent of the old 1000/30d.
.filter((r) => r.p95Ms > 1000 && r.invocations > 500);
}
// Region-misconfig gate. Branch 2 (scanner-only) — per-region TTFB data gap.
//
// The intended Branch 1 (region-grouped TTFB metric) was preflight-tested but the
// CLI returned INTERNAL_ERROR for the `--group-by route --group-by function_region`
// combination, and SAML re-auth blocked single-dim verification (see Phase 0 in
// plans/wild-splashing-flamingo.md). Ship scanner-only with `evidence.dataGap` and
// add the query later when verifiable.
//
// Fires when a single-region pin is found AND the project has meaningful surface area
// (routes.length > 20). Skips multi-region configs (informational only).
export const metadata = {
id: 'region_misconfig',
threshold: 'single-region pin found AND routes.length > 20 (scanner-only branch)',
billingDimension: 'function-duration',
scope: 'account',
sourceCitation: 'vercel-optimize gate threshold',
description:
"A single function region is pinned in `vercel.json` or per-route `preferredRegion`. Without per-region TTFB data (data gap), the gate can't quantify the geographic latency cost — but a single-region pin on a project with 20+ routes is worth auditing against Speed Insights traffic geo.",
};
const ROUTE_FLOOR = 20;
const SCANNER_PATTERN = 'region-pin-in-config';
export function gate(signals) {
const findings = (signals?.codebase?.findings ?? []).filter((f) => f.pattern === SCANNER_PATTERN);
if (findings.length === 0) return [];
const routes = signals?.codebase?.routes ?? [];
if (routes.length < ROUTE_FLOOR) return [];
const singleRegionFindings = findings.filter((f) => Array.isArray(f.regions) && f.regions.length === 1);
if (singleRegionFindings.length === 0) return [];
const allPinned = new Set();
for (const f of singleRegionFindings) {
for (const r of f.regions ?? []) allPinned.add(r);
}
const regionList = [...allPinned];
// If multiple distinct single-region pins exist across files, the surface is partly
// multi-region by accident; that's noteworthy but lower priority.
const homogeneous = regionList.length === 1;
return [{
kind: metadata.id,
scope: 'account',
files: singleRegionFindings.map((f) => f.file).slice(0, 6),
priority: homogeneous ? 42 : 38,
confidence: 0.6, // low — no per-region TTFB data
o11ySignal: `pinned_regions=${regionList.join(',')} routes=${routes.length}`,
reason: homogeneous
? `all functions pinned to a single region (${regionList[0]}) on a project with ${routes.length} routes`
: `${regionList.length} different single-region pins across files`,
question: 'Are the pinned function regions aligned with the dominant user geography and the data source location? Speed Insights TTFB-by-country can ground the comparison.',
evidence: {
metric: 'codebase.findings',
pinnedRegions: regionList,
findingsCount: singleRegionFindings.length,
routeCount: routes.length,
sampleFiles: singleRegionFindings.slice(0, 3).map((f) => ({ file: f.file, regions: f.regions, subtype: f.subtype })),
dataGap: 'region-grouped-TTFB-unavailable',
},
}];
}
// Errored function invocations still bill at full duration, so high-volume 5xx is a cost issue, not just reliability.
import { withRouteShapeWarnings } from '../route-normalize.mjs';
const MIN_VOLUME_FOR_RATE_EMISSION = 1000;
export const metadata = {
id: 'route_errors',
threshold: `count > 250 OR (totalRequests >= ${MIN_VOLUME_FOR_RATE_EMISSION} AND errorRate > 0.01)`,
billingDimension: 'function-duration',
scope: 'route',
sourceCitation: 'vercel-optimize gate threshold',
description:
'Routes producing > 250 5xx errors over the window, or with > 1% error rate on at least 1,000 total requests. Errored function invocations still bill at full duration; high error rates also poison user experience.',
};
export function gate(signals) {
const errors = extractErrors(signals);
return errors
.filter((e) => e.count > 250 || (e.total >= MIN_VOLUME_FOR_RATE_EMISSION && (e.errorRate ?? 0) > 0.01))
.map((e) => withRouteShapeWarnings({
kind: metadata.id,
scope: 'route',
route: e.route,
files: [],
priority: e.count,
confidence: 0.93,
o11ySignal: e.errorRate != null
? `errs=${e.count},rate=${(e.errorRate * 100).toFixed(1)}%`
: `errs=${e.count}`,
reason: 'concentrated 5xx errors',
question: `Why does ${e.route} produce ${e.count} 5xx errors over the window, and what code path is failing?`,
evidence: { metric: e.metric, route: e.route, count: e.count, totalRequests: e.total, errorRate: e.errorRate },
}, signals));
}
function extractErrors(signals) {
const fnStatus = signals.metrics?.fnStatusByRoute;
if (Array.isArray(fnStatus?.rows)) return extractFromStatusRows(fnStatus.rows, 'fnStatusByRoute');
const m = signals.metrics?.requestsByRouteStatus;
const cache = signals.metrics?.requestsByRouteCache;
if (!m?.ok && !Array.isArray(m?.rows)) return [];
const errors = extractFromStatusRows(m?.rows ?? [], 'requestsByRouteStatus');
// cache rollup is summed across cache_result, giving per-route total request count.
const totalByRoute = new Map();
for (const row of (cache?.rows ?? [])) {
if (!row.route) continue;
totalByRoute.set(row.route, (totalByRoute.get(row.route) ?? 0) + (row.value ?? 0));
}
return errors.map((e) => {
const total = totalByRoute.get(e.route) ?? 0;
return {
...e,
total,
errorRate: total > 0 ? e.count / total : null,
};
});
}
function extractFromStatusRows(rows, metric) {
const errByRoute = new Map();
const totalByRoute = new Map();
for (const row of rows) {
const route = row.route;
if (!route) continue;
const v = row.value ?? 0;
const status = String(row.http_status ?? '');
if (/^5\d\d$/.test(status)) errByRoute.set(route, (errByRoute.get(route) ?? 0) + v);
totalByRoute.set(route, (totalByRoute.get(route) ?? 0) + v);
}
return [...errByRoute.entries()].map(([route, count]) => {
const total = totalByRoute.get(route) ?? 0;
const errorRate = total > 0 ? count / total : null;
return { route, count, total, errorRate, metric };
});
}
// Signal source is the codebase itself, not traffic. COLD-PATH and NO-ROUTE-MAPPING findings
// are dropped unless the scanner sets trafficIndependent (build configs, middleware matchers, etc.).
// Annotation happens in scan-codebase.mjs; gates here just read scanner.o11ySignal.
export const SCANNER_GATES = [
{ id: 'image_optimization', patterns: ['unoptimized-image'], threshold: 2,
billingDimension: 'image-optimization', priority: 30 },
{ id: 'cache_header_gap', patterns: ['max-age-without-s-maxage', 'missing-cache-headers'], threshold: 1,
billingDimension: 'edge-requests', priority: 40 },
{ id: 'rendering_candidate', patterns: ['force-dynamic', 'headers-in-page'], threshold: 3,
billingDimension: 'function-duration', priority: 35 },
{ id: 'use_cache_date_stamp', patterns: ['use-cache-date-stamp'], threshold: 1,
billingDimension: 'isr', priority: 45 },
{ id: 'cache_components_suspense_dedupe', patterns: ['cache-components-suspense-dedupe'], threshold: 1,
billingDimension: 'function-duration', priority: 38 },
];
export const metadata = {
id: 'scanner-driven',
threshold: 'per-kind: scanner matches.length >= threshold',
billingDimension: 'mixed',
scope: 'mixed',
sourceCitation: 'vercel-optimize gate threshold',
description:
'Configured kinds emitted from scanner output. Each requires a minimum match count to avoid noise. Findings on cold-path or unmappable files are dropped unless the underlying scanner is trafficIndependent.',
};
export function gate(signals) {
const findings = signals.codebase?.findings ?? [];
if (findings.length === 0) return [];
const candidates = [];
for (const cfg of SCANNER_GATES) {
const matched = findings.filter((f) => {
if (!cfg.patterns.includes(f.pattern)) return false;
if (!f.trafficIndependent) {
if (!f.o11ySignal || f.o11ySignal === 'scanner-only') return false;
if (f.o11ySignal === 'COLD-PATH') return false;
if (f.o11ySignal === 'NO-ROUTE-MAPPING') return false;
}
if (cfg.id === 'cache_header_gap' && observedCacheHitRate(f.o11ySignal) >= 90) return false;
return true;
});
for (const group of groupFindings(cfg, matched)) {
if (group.findings.length < cfg.threshold) continue;
candidates.push(candidateForGroup(cfg, group));
}
}
return candidates;
}
function groupFindings(cfg, findings) {
const groups = new Map();
for (const finding of findings) {
const scope = finding.route ? 'route' : 'file';
const target = scope === 'route' ? finding.route : finding.file;
if (!target) continue;
const key = `${cfg.id}:${scope}:${target}`;
if (!groups.has(key)) groups.set(key, { scope, target, findings: [] });
groups.get(key).findings.push(finding);
}
return [...groups.values()];
}
function candidateForGroup(cfg, group) {
const matched = group.findings;
const route = group.scope === 'route' ? group.target : null;
return {
kind: cfg.id,
scope: group.scope,
route,
files: uniqueStrings(matched.map((m) => m.file)).slice(0, 6),
priority: cfg.priority + Math.min(matched.length, 10),
confidence: 0.88,
o11ySignal: matched
.map((m) => m.o11ySignal)
.find((s) => s && s !== 'COLD-PATH' && s !== 'NO-ROUTE-MAPPING')
?? 'scanner-only',
reason: `${matched.length} ${cfg.patterns.join('+')} finding(s)`,
question: questionFor(cfg.id, matched),
evidence: {
scannerMatches: matched.length,
patterns: cfg.patterns,
scope: group.scope,
route,
sampleFiles: matched.slice(0, 3).map((m) => ({ file: m.file, line: m.line })),
},
};
}
function questionFor(kindId, matched) {
const sample = matched.slice(0, 3).map((m) => m.file).join(', ');
switch (kindId) {
case 'image_optimization':
return `Which raw <img> tags in ${sample} should move to next/image (or the framework's image component)?`;
case 'cache_header_gap':
return `Should the route handlers in ${sample} set Cache-Control with s-maxage to serve from the CDN?`;
case 'rendering_candidate':
return `Why are the routes in ${sample} forced to dynamic rendering, and can any of them tolerate ISR or static generation?`;
case 'use_cache_date_stamp':
return `Which 'use cache' boundaries in ${sample} embed new Date()/Date.now()/Math.random() that destabilizes cache keys, and can the timestamps be hoisted to a build constant or moved into a client useEffect?`;
case 'cache_components_suspense_dedupe':
return `In ${sample}, which repeated fetch or helper is being re-invoked across separate <Suspense> boundaries, and can the promise be hoisted to the page level or moved to 'use cache: remote' for cross-boundary dedupe?`;
default:
return `Investigate ${matched.length} ${kindId} finding(s).`;
}
}
function uniqueStrings(values) {
return [...new Set(values.filter((v) => typeof v === 'string' && v.length > 0))];
}
function observedCacheHitRate(signal) {
if (typeof signal !== 'string') return null;
const m = /\bcache=([\d.]+)%/.exec(signal);
if (!m) return null;
const n = Number(m[1]);
return Number.isFinite(n) ? n : null;
}
// Deterministic launch selection for the code-scope investigation budget.
//
// Raw priority still orders candidates inside each pass. The default budget is
// impact-first, with failure-mode diversity when a kind's top signal is large
// enough to justify taking a first-pass slot.
const DEFAULT_KIND_CAPS = new Map([
['slow_route', 2],
['uncached_route', 2],
['route_errors', 2],
]);
const DIVERSITY_ELIGIBILITY = new Map([
// A handful of 5xx errors can pass the route_errors gate because the rate is
// high, but that should not displace much larger cost/performance signals in
// the default six-candidate pass.
['route_errors', (candidate) => numberFromEvidence(candidate, 'count') >= 1000],
// Scanner-driven cache findings are valuable, but the default pass should
// spend a slot only when observability shows meaningful route traffic or a
// very slow route handler.
['cache_header_gap', (candidate) => {
const invocations = numberFromSignal(candidate?.o11ySignal, 'inv');
const p95Ms = durationMsFromSignal(candidate?.o11ySignal, 'p95');
return invocations >= 50_000 || p95Ms >= 2000;
}],
['rendering_candidate', (candidate) => numberFromSignal(candidate?.o11ySignal, 'inv') >= 50_000],
]);
export function selectLaunchCandidates(candidates, budget, { diversify = false } = {}) {
const pool = Array.isArray(candidates) ? candidates : [];
if (budget === Infinity) {
return { selected: pool, skipped: [], selectionMode: 'all' };
}
if (!Number.isInteger(budget) || budget < 1) {
throw new TypeError('selectLaunchCandidates budget must be a positive integer or Infinity');
}
if (!diversify) {
return {
selected: pool.slice(0, budget),
skipped: pool.slice(budget),
selectionMode: 'priority',
};
}
const selected = [];
const selectedKeys = new Set();
const countsByKind = new Map();
const add = (candidate) => {
const key = candidateIdentity(candidate);
if (selectedKeys.has(key)) return false;
selectedKeys.add(key);
selected.push(candidate);
const kind = candidate.kind ?? '<unknown>';
countsByKind.set(kind, (countsByKind.get(kind) ?? 0) + 1);
return true;
};
// First pass: one candidate per failure mode, preserving the existing sorted
// order. This is where the default run gets broad coverage, but only for
// kinds whose signal is strong enough for a default slot.
for (const candidate of pool) {
if (selected.length >= budget) break;
const kind = candidate.kind ?? '<unknown>';
if ((countsByKind.get(kind) ?? 0) > 0) continue;
if (!isDiversityEligible(candidate)) continue;
add(candidate);
}
// Second pass: allow a second entry for high-frequency families, but avoid
// letting slow_route consume the entire default budget when other kinds exist.
for (const candidate of pool) {
if (selected.length >= budget) break;
const kind = candidate.kind ?? '<unknown>';
const cap = DEFAULT_KIND_CAPS.get(kind) ?? 1;
if ((countsByKind.get(kind) ?? 0) >= cap) continue;
if (!isDiversityEligible(candidate)) continue;
add(candidate);
}
// Final fill: if the project only has one or two candidate kinds, use the
// whole requested budget rather than leaving slots empty.
for (const candidate of pool) {
if (selected.length >= budget) break;
add(candidate);
}
return {
selected,
skipped: pool.filter((candidate) => !selectedKeys.has(candidateIdentity(candidate))),
selectionMode: 'diverse-default',
};
}
function candidateIdentity(candidate) {
return [
candidate?.kind ?? '',
candidate?.route ?? '',
candidate?.hostname ?? '',
candidate?.scope ?? '',
candidate?.o11ySignal ?? '',
].join('\u0000');
}
function isDiversityEligible(candidate) {
const fn = DIVERSITY_ELIGIBILITY.get(candidate?.kind);
return fn ? fn(candidate) : true;
}
function numberFromEvidence(candidate, key) {
const value = candidate?.evidence?.[key];
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
}
function numberFromSignal(signal, key) {
if (typeof signal !== 'string') return 0;
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(`(?:^|,)${escaped}=([\\d.]+)`);
const m = re.exec(signal);
if (!m) return 0;
const n = Number(m[1]);
return Number.isFinite(n) ? n : 0;
}
function durationMsFromSignal(signal, key) {
if (typeof signal !== 'string') return 0;
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(`(?:^|,)${escaped}=([\\d.]+)ms`);
const m = re.exec(signal);
if (!m) return 0;
const n = Number(m[1]);
return Number.isFinite(n) ? n : 0;
}
export type CandidateScope = 'route' | 'file' | 'account';
export interface GateMetadata {
id: string;
threshold: string;
billingDimension: string;
scope: CandidateScope | 'mixed';
sourceCitation?: string;
description?: string;
}
export interface Candidate {
kind: string;
scope: CandidateScope;
route?: string | null;
hostname?: string | null;
files: string[];
priority: number;
confidence: number;
o11ySignal?: string;
reason: string;
question: string;
evidence?: Record<string, unknown>;
disqualified?: boolean;
disqualifyReason?: string;
warnings?: string[];
}
export interface Signals {
metrics?: Record<string, unknown>;
codebase?: {
findings?: Array<Record<string, unknown>>;
routes?: Array<Record<string, unknown>>;
};
project?: Record<string, unknown>;
usage?: Record<string, unknown>;
stack?: Record<string, unknown>;
}
// Final-gate sanitizer: drops a rec with no citations left after
// unknown-citation + version-mismatch have run. Every rec must carry ≥1
// citation.
export const metadata = {
id: 'missing-citation',
description: 'Drop rec when citations[] is empty after other sanitizers.',
};
export function apply(rec, _ctx = {}) {
const cites = Array.isArray(rec.citations) ? rec.citations : [];
if (cites.length === 0) {
return { dropped: true, tag: 'missing-citation' };
}
return {};
}
// Shared scanner + sanitizer helpers. Keep tiny — add only when duplicated 3+ times.
// 1-based line number of `idx` in a multi-line string.
export function lineOf(text, idx) {
return text.slice(0, idx).split('\n').length;
}
export function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// `slow_route:/api/products` → `/api/products`.
export function extractRoute(rec) {
if (typeof rec?.candidateRef !== 'string') return null;
const m = rec.candidateRef.match(/^[^:]+:(.+)$/);
return m ? m[1] : null;
}
Related skills
How it compares
Use vercel-optimize for metric-backed Vercel cost and route audits; use general Next.js performance guides when the app is not deployed on Vercel or lacks production traffic signals.
FAQ
What prerequisites does vercel-optimize require?
vercel-optimize requires Node.js 20+, an authenticated Vercel CLI session, a linked project directory via vercel link, and Vercel CLI v53+ exposing vercel metrics, vercel usage, vercel contract, and vercel api commands.
Which frameworks does vercel-optimize fully support?
vercel-optimize provides strongest route mapping for Next.js App Router and Pages Router, plus supported SvelteKit and Nuxt audits. Astro support is limited; Hono, Remix, and unknown frameworks trigger a user prompt before limited platform-only audits.
How does vercel-optimize decide what code to inspect?
vercel-optimize runs collect-signals.mjs and gate-investigations.mjs first, producing signals.json and gate.json. Only metric-backed candidates in toLaunch get briefs; default budget selects 6 code-scope candidates before deep-dive and verification scripts run.