opencode-skills-collection 3.0.35 → 3.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +15 -1
- package/bundled-skills/accesslint-audit/SKILL.md +115 -0
- package/bundled-skills/accesslint-diff/SKILL.md +81 -0
- package/bundled-skills/accesslint-scan/SKILL.md +47 -0
- package/bundled-skills/composition-patterns/SKILL.md +87 -0
- package/bundled-skills/composition-patterns/rules/_sections.md +29 -0
- package/bundled-skills/composition-patterns/rules/_template.md +24 -0
- package/bundled-skills/composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/bundled-skills/composition-patterns/rules/architecture-compound-components.md +112 -0
- package/bundled-skills/composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/bundled-skills/composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/bundled-skills/composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/bundled-skills/composition-patterns/rules/state-context-interface.md +191 -0
- package/bundled-skills/composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/bundled-skills/composition-patterns/rules/state-lift-state.md +125 -0
- package/bundled-skills/debugging-toolkit/SKILL.md +35 -0
- package/bundled-skills/deploy-to-vercel/SKILL.md +304 -0
- package/bundled-skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
- package/bundled-skills/deploy-to-vercel/resources/deploy.sh +301 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/backups/README-2026-06-02.md +687 -0
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +4 -4
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +245 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/plugins.md +21 -13
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +95 -0
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/polis-protocol/SKILL.md +93 -0
- package/bundled-skills/python-development/SKILL.md +35 -0
- package/bundled-skills/radix-ui-design-system/SKILL.md +2 -2
- package/bundled-skills/react-native-skills/SKILL.md +120 -0
- package/bundled-skills/react-native-skills/rules/_sections.md +86 -0
- package/bundled-skills/react-native-skills/rules/_template.md +28 -0
- package/bundled-skills/react-native-skills/rules/animation-derived-value.md +53 -0
- package/bundled-skills/react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/bundled-skills/react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/bundled-skills/react-native-skills/rules/design-system-compound-components.md +66 -0
- package/bundled-skills/react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/bundled-skills/react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/bundled-skills/react-native-skills/rules/js-hoist-intl.md +61 -0
- package/bundled-skills/react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/bundled-skills/react-native-skills/rules/list-performance-function-references.md +132 -0
- package/bundled-skills/react-native-skills/rules/list-performance-images.md +53 -0
- package/bundled-skills/react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-types.md +104 -0
- package/bundled-skills/react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/bundled-skills/react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/bundled-skills/react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/bundled-skills/react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/bundled-skills/react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/bundled-skills/react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/bundled-skills/react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/bundled-skills/react-native-skills/rules/react-state-fallback.md +56 -0
- package/bundled-skills/react-native-skills/rules/react-state-minimize.md +65 -0
- package/bundled-skills/react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/bundled-skills/react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/bundled-skills/react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/bundled-skills/react-native-skills/rules/state-ground-truth.md +80 -0
- package/bundled-skills/react-native-skills/rules/ui-expo-image.md +66 -0
- package/bundled-skills/react-native-skills/rules/ui-image-gallery.md +104 -0
- package/bundled-skills/react-native-skills/rules/ui-measure-views.md +78 -0
- package/bundled-skills/react-native-skills/rules/ui-menus.md +174 -0
- package/bundled-skills/react-native-skills/rules/ui-native-modals.md +77 -0
- package/bundled-skills/react-native-skills/rules/ui-pressable.md +61 -0
- package/bundled-skills/react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/bundled-skills/react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/bundled-skills/react-native-skills/rules/ui-styling.md +87 -0
- package/bundled-skills/skill-issue/SKILL.md +73 -0
- package/bundled-skills/tdd-workflows/SKILL.md +35 -0
- package/bundled-skills/vercel-cli-with-tokens/SKILL.md +361 -0
- package/bundled-skills/vercel-optimize/CONTRIBUTING.md +41 -0
- package/bundled-skills/vercel-optimize/SKILL.md +331 -0
- package/bundled-skills/vercel-optimize/lib/auth-route.mjs +23 -0
- package/bundled-skills/vercel-optimize/lib/budget-summary.mjs +182 -0
- package/bundled-skills/vercel-optimize/lib/citations.mjs +139 -0
- package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +143 -0
- package/bundled-skills/vercel-optimize/lib/dedup-recs.mjs +325 -0
- package/bundled-skills/vercel-optimize/lib/deep-dive.mjs +350 -0
- package/bundled-skills/vercel-optimize/lib/display-labels.mjs +185 -0
- package/bundled-skills/vercel-optimize/lib/extract-claims.mjs +550 -0
- package/bundled-skills/vercel-optimize/lib/framework-support.mjs +67 -0
- package/bundled-skills/vercel-optimize/lib/gates/build-minutes-fanout.mjs +69 -0
- package/bundled-skills/vercel-optimize/lib/gates/cold-start.mjs +66 -0
- package/bundled-skills/vercel-optimize/lib/gates/contract.mjs +79 -0
- package/bundled-skills/vercel-optimize/lib/gates/cwv-poor.mjs +87 -0
- package/bundled-skills/vercel-optimize/lib/gates/external-api-slow.mjs +55 -0
- package/bundled-skills/vercel-optimize/lib/gates/hard-gates.mjs +73 -0
- package/bundled-skills/vercel-optimize/lib/gates/index.mjs +45 -0
- package/bundled-skills/vercel-optimize/lib/gates/isr-overrevalidation.mjs +62 -0
- package/bundled-skills/vercel-optimize/lib/gates/middleware-heavy.mjs +51 -0
- package/bundled-skills/vercel-optimize/lib/gates/observability-events-attribution.mjs +56 -0
- package/bundled-skills/vercel-optimize/lib/gates/platform-bot-protection.mjs +115 -0
- package/bundled-skills/vercel-optimize/lib/gates/platform-fluid-compute.mjs +83 -0
- package/bundled-skills/vercel-optimize/lib/gates/region-misconfig.mjs +64 -0
- package/bundled-skills/vercel-optimize/lib/gates/route-errors.mjs +80 -0
- package/bundled-skills/vercel-optimize/lib/gates/scanner-driven.mjs +122 -0
- package/bundled-skills/vercel-optimize/lib/gates/select-candidates.mjs +134 -0
- package/bundled-skills/vercel-optimize/lib/gates/slow-route.mjs +88 -0
- package/bundled-skills/vercel-optimize/lib/gates/types.d.ts +38 -0
- package/bundled-skills/vercel-optimize/lib/gates/uncached-route.mjs +93 -0
- package/bundled-skills/vercel-optimize/lib/gates/usage-spike-triage.mjs +121 -0
- package/bundled-skills/vercel-optimize/lib/grade-recommendation.mjs +155 -0
- package/bundled-skills/vercel-optimize/lib/impact-label.mjs +126 -0
- package/bundled-skills/vercel-optimize/lib/impact-magnitude.mjs +60 -0
- package/bundled-skills/vercel-optimize/lib/investigation-brief.mjs +610 -0
- package/bundled-skills/vercel-optimize/lib/observation-safety.mjs +174 -0
- package/bundled-skills/vercel-optimize/lib/project-facts.mjs +99 -0
- package/bundled-skills/vercel-optimize/lib/queries.mjs +315 -0
- package/bundled-skills/vercel-optimize/lib/reconcile-candidates.mjs +372 -0
- package/bundled-skills/vercel-optimize/lib/render-report.mjs +955 -0
- package/bundled-skills/vercel-optimize/lib/repo-root.mjs +86 -0
- package/bundled-skills/vercel-optimize/lib/route-normalize.mjs +220 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs +30 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/count-correct.mjs +52 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/function-duration-invocations.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/index.mjs +79 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/middleware-conflict.mjs +36 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/missing-citation.mjs +16 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/pre-release.mjs +74 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/rate-limit.mjs +67 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/undeclared-dep.mjs +78 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs +37 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/window-units.mjs +32 -0
- package/bundled-skills/vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs +109 -0
- package/bundled-skills/vercel-optimize/lib/scanners/edge-heavy-import.mjs +94 -0
- package/bundled-skills/vercel-optimize/lib/scanners/force-dynamic.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/headers-in-page.mjs +44 -0
- package/bundled-skills/vercel-optimize/lib/scanners/index.mjs +35 -0
- package/bundled-skills/vercel-optimize/lib/scanners/large-static-asset.mjs +92 -0
- package/bundled-skills/vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs +55 -0
- package/bundled-skills/vercel-optimize/lib/scanners/missing-cache-headers.mjs +90 -0
- package/bundled-skills/vercel-optimize/lib/scanners/prisma-include-tree.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/region-pin-in-config.mjs +88 -0
- package/bundled-skills/vercel-optimize/lib/scanners/source-maps-production.mjs +36 -0
- package/bundled-skills/vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs +43 -0
- package/bundled-skills/vercel-optimize/lib/scanners/turbo-force-bypass.mjs +129 -0
- package/bundled-skills/vercel-optimize/lib/scanners/unoptimized-image.mjs +113 -0
- package/bundled-skills/vercel-optimize/lib/scanners/use-cache-date-stamp.mjs +106 -0
- package/bundled-skills/vercel-optimize/lib/support-topics.mjs +355 -0
- package/bundled-skills/vercel-optimize/lib/throttle.mjs +273 -0
- package/bundled-skills/vercel-optimize/lib/util.mjs +17 -0
- package/bundled-skills/vercel-optimize/lib/vercel.mjs +784 -0
- package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +1296 -0
- package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +521 -0
- package/bundled-skills/vercel-optimize/references/candidates.md +176 -0
- package/bundled-skills/vercel-optimize/references/data-collection.md +218 -0
- package/bundled-skills/vercel-optimize/references/docs-library.json +683 -0
- package/bundled-skills/vercel-optimize/references/doctrine.md +105 -0
- package/bundled-skills/vercel-optimize/references/observability-plus.md +108 -0
- package/bundled-skills/vercel-optimize/references/playbooks/README.md +53 -0
- package/bundled-skills/vercel-optimize/references/playbooks/ai-application.md +32 -0
- package/bundled-skills/vercel-optimize/references/playbooks/api-service.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/content-site.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/ecommerce.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/marketing.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/saas.md +31 -0
- package/bundled-skills/vercel-optimize/references/playbooks/sveltekit.md +75 -0
- package/bundled-skills/vercel-optimize/references/recommendations.md +203 -0
- package/bundled-skills/vercel-optimize/references/scanner-patterns.md +251 -0
- package/bundled-skills/vercel-optimize/references/scoring.md +205 -0
- package/bundled-skills/vercel-optimize/references/support-topics/README.md +46 -0
- package/bundled-skills/vercel-optimize/references/support-topics/astro-edge-middleware-scope.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/astro-output-mode-and-isr.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/auth-preserving-parallelization.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/bot-protection-product-guardrails.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cdn-cache-auth-safety.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cold-start-initialization-bundle.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/database-egress-pooling-region.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/dynamic-rendering-traps.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path-platform.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/fast-data-transfer-payloads.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/fluid-compute-caveats.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-duration-io-and-after.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-invocation-reduction.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/image-optimization-cost-control.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/isr-revalidation-static-generation.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-font-cls-self-hosting.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-script-third-party-strategy.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/not-found-catchall-request-waste.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/observability-events-cost-attribution.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/post-response-work-waituntil.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/route-error-durable-offload.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/route-error-runtime-limits.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/runtime-cache-reusable-data.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/usage-spike-triage.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/workflow-resumable-stream-routes.md +23 -0
- package/bundled-skills/vercel-optimize/references/verification.md +102 -0
- package/bundled-skills/vercel-optimize/references/voice.md +76 -0
- package/bundled-skills/vercel-optimize/scripts/budget-summary.mjs +56 -0
- package/bundled-skills/vercel-optimize/scripts/build-docs.mjs +74 -0
- package/bundled-skills/vercel-optimize/scripts/check-citations.mjs +81 -0
- package/bundled-skills/vercel-optimize/scripts/check-docs-fresh.mjs +93 -0
- package/bundled-skills/vercel-optimize/scripts/collect-signals.mjs +576 -0
- package/bundled-skills/vercel-optimize/scripts/collect-sub-agent-outputs.mjs +296 -0
- package/bundled-skills/vercel-optimize/scripts/deep-dive.mjs +319 -0
- package/bundled-skills/vercel-optimize/scripts/gate-investigations.mjs +166 -0
- package/bundled-skills/vercel-optimize/scripts/merge-signals.mjs +192 -0
- package/bundled-skills/vercel-optimize/scripts/prepare-investigation-brief.mjs +231 -0
- package/bundled-skills/vercel-optimize/scripts/reconcile-candidates.mjs +62 -0
- package/bundled-skills/vercel-optimize/scripts/render-report.mjs +437 -0
- package/bundled-skills/vercel-optimize/scripts/scan-codebase.mjs +313 -0
- package/bundled-skills/vercel-optimize/scripts/verify-and-regen.mjs +346 -0
- package/bundled-skills/vercel-optimize/scripts/verify-finding.mjs +19 -0
- package/bundled-skills/vercel-react-view-transitions/SKILL.md +327 -0
- package/bundled-skills/vercel-react-view-transitions/references/css-recipes.md +242 -0
- package/bundled-skills/vercel-react-view-transitions/references/implementation.md +182 -0
- package/bundled-skills/vercel-react-view-transitions/references/nextjs.md +176 -0
- package/bundled-skills/vercel-react-view-transitions/references/patterns.md +262 -0
- package/package.json +1 -1
- package/skills_index.json +312 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Recommendations
|
|
2
|
+
|
|
3
|
+
How recommendations are shaped, written, sanitized, and graded.
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Schema](#schema)
|
|
8
|
+
- [Writing rules](#writing-rules)
|
|
9
|
+
- [The 12 sanitizers](#the-12-sanitizers)
|
|
10
|
+
- [Envelope-unwrap recovery](#envelope-unwrap-recovery)
|
|
11
|
+
- [Grading rubric](#grading-rubric)
|
|
12
|
+
- [Next.js version awareness](#nextjs-version-awareness)
|
|
13
|
+
|
|
14
|
+
## Schema
|
|
15
|
+
|
|
16
|
+
Every recommendation is a JSON object matching this TypeScript shape:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
interface Recommendation {
|
|
20
|
+
// Customer-facing
|
|
21
|
+
what: string; // 1 line, lead with impact. Max 80 chars when feasible.
|
|
22
|
+
why: string; // 1-2 sentences. Root cause. Cites codebase findings + counts.
|
|
23
|
+
fix: string; // Step-by-step. Includes before/after code fences. Specific enough to implement.
|
|
24
|
+
bucket: 'cost' | 'performance' | 'reliability';
|
|
25
|
+
effort: 'low' | 'medium' | 'high';
|
|
26
|
+
affectedFiles: string[]; // Verified file paths, from candidate.files
|
|
27
|
+
currentBehavior: string; // What the code does now (with snippet)
|
|
28
|
+
desiredBehavior: string; // Target state (with snippet)
|
|
29
|
+
risk?: string; // Optional: e.g., "Removing force-dynamic may serve stale data on /admin"
|
|
30
|
+
verify: string; // How to confirm the fix worked. e.g., "Re-run `vercel metrics …` and watch p95"
|
|
31
|
+
|
|
32
|
+
// Impact (computed from impact-magnitude.mjs in Step 4)
|
|
33
|
+
impactLabel: {
|
|
34
|
+
performance?: string; // PRECISE: "Reduce /api/products p95 from 850ms toward ~250-400ms"
|
|
35
|
+
costMagnitude?: 'negligible' | 'small' | 'medium' | 'large' | 'very-large';
|
|
36
|
+
costPhrase?: string; // "hundreds of dollars per month at current traffic"
|
|
37
|
+
billingDimension?: string;
|
|
38
|
+
fractionReduced?: number;
|
|
39
|
+
};
|
|
40
|
+
impactTier: 'high' | 'medium' | 'low';
|
|
41
|
+
billingDimension?: 'edge-requests' | 'function-duration' | 'image-optimization' | 'isr-reads' | 'isr-writes' | 'bandwidth' | 'data-cache-reads' | 'cron-invocations' | string;
|
|
42
|
+
|
|
43
|
+
// Grounding
|
|
44
|
+
citations: string[]; // From references/docs-library.json allow-list. Required: ≥1 entry.
|
|
45
|
+
candidateRef?: string; // The gate candidate this rec traces to (e.g., "uncached_route:/api/products")
|
|
46
|
+
findingRefs?: string[]; // File:line markers from verifiedFindings.json
|
|
47
|
+
appliesAlsoTo?: Array<{ // Added by dedup when matching recs collapse into one customer-facing item.
|
|
48
|
+
candidateRef?: string;
|
|
49
|
+
affectedFiles?: string[];
|
|
50
|
+
o11ySignal?: string;
|
|
51
|
+
what?: string;
|
|
52
|
+
}>;
|
|
53
|
+
corroborationCount?: number; // Number of matching verified recs folded into this item, including itself.
|
|
54
|
+
|
|
55
|
+
// Verifier output (computed in Step 3.6)
|
|
56
|
+
verification?: {
|
|
57
|
+
passRate: number;
|
|
58
|
+
failed: Array<{ type: string; text: string; reason: string }>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Sanitizer audit trail (computed in Step 3.4)
|
|
62
|
+
sanitizerTrail?: string[]; // ["$-strip:2", "version-mismatch:next@15+:1", ...]
|
|
63
|
+
needsReview?: boolean; // Set when a sanitizer caught a hazard
|
|
64
|
+
|
|
65
|
+
// Grading (Step 3.5)
|
|
66
|
+
quality: {
|
|
67
|
+
specificity: number; // 0-1
|
|
68
|
+
actionability: number;
|
|
69
|
+
grounding: number;
|
|
70
|
+
evidence: number;
|
|
71
|
+
overall: number;
|
|
72
|
+
grade: 'Excellent' | 'Good' | 'Fair' | 'Poor';
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Writing rules
|
|
78
|
+
|
|
79
|
+
The recommender prompt explicitly tells the agent to follow these rules. Sanitizers enforce them after generation.
|
|
80
|
+
|
|
81
|
+
**Voice and tone** are governed by [`references/voice.md`](./voice.md). Read it before writing recommendation prose; it keeps reports direct, metric-grounded, and free of internal process terms.
|
|
82
|
+
|
|
83
|
+
### Lead with impact
|
|
84
|
+
|
|
85
|
+
The `what` field opens with the verb + the change, not the framing. Compare:
|
|
86
|
+
|
|
87
|
+
- ❌ "Consider enabling caching on the /api/products route" (filler before substance)
|
|
88
|
+
- ✅ "Add Cache-Control with s-maxage to /api/products" (verb-first, scope-explicit)
|
|
89
|
+
|
|
90
|
+
### Cite codebase findings with line numbers
|
|
91
|
+
|
|
92
|
+
The `why` must reference a verified finding from `verifiedFindings.json`:
|
|
93
|
+
|
|
94
|
+
- ❌ "The route is uncached" (could apply anywhere)
|
|
95
|
+
- ✅ "src/app/api/products/route.ts:22 returns Response without Cache-Control; observability shows 0% cache hit on 1.2M invocations/mo"
|
|
96
|
+
|
|
97
|
+
### No $ literals in customer fields
|
|
98
|
+
|
|
99
|
+
The user-mandated rule. `what`/`why`/`fix`/`impact`/`currentBehavior`/`desiredBehavior` must not contain `$N` money literals. Use magnitude framing from `impact-magnitude.mjs`.
|
|
100
|
+
|
|
101
|
+
- ❌ "Save $340/mo by adding s-maxage"
|
|
102
|
+
- ✅ "Hundreds of dollars per month at current traffic"
|
|
103
|
+
- ✅ (precise performance) "Move 1.2M monthly invocations to the CDN; expect p95 to drop from 850ms toward ~50ms on cache hits"
|
|
104
|
+
|
|
105
|
+
The `$-strip` sanitizer enforces this at output time, but the prompt should also instruct the LLM not to emit dollar literals in the first place.
|
|
106
|
+
|
|
107
|
+
### Before/after code fences required
|
|
108
|
+
|
|
109
|
+
`currentBehavior` shows the offending snippet. `desiredBehavior` shows the target. Language-tagged code fences. Keep both under ~20 lines.
|
|
110
|
+
|
|
111
|
+
### Cite at least one URL from the library
|
|
112
|
+
|
|
113
|
+
`citations[]` must contain at least one entry from `references/docs-library.json`. The `missing-citation` sanitizer drops uncited recs. The `unknown-citation` and `version-mismatch` sanitizers strip invalid citations.
|
|
114
|
+
|
|
115
|
+
### Match the user's framework version
|
|
116
|
+
|
|
117
|
+
Don't recommend `'use cache'` (Next 15+) to a Next 13 user. The recommender prompt receives only the citation subset valid for the user's stack — but the LLM can still hallucinate. The `version-mismatch` sanitizer catches stragglers.
|
|
118
|
+
|
|
119
|
+
## The 12 sanitizers
|
|
120
|
+
|
|
121
|
+
Each sanitizer records its action in `rec.sanitizerTrail` when it mutates a field. Tag format: `tag:detail`. Tags are lexically stable — downstream consumers grep them.
|
|
122
|
+
|
|
123
|
+
| # | Sanitizer | Trigger | Action | Trail tag |
|
|
124
|
+
|---|---|---|---|---|
|
|
125
|
+
| 1 | `$-strip` | Money-literal regex in customer field | Replace with "the billed cost" | `$-strip:N` |
|
|
126
|
+
| 2 | `vercel-directive-strip` | `stale-if-error` / `proxy-revalidate` in cache-control | Strip directive (Vercel CDN doesn't honor) | `vercel-directive-strip:directive` |
|
|
127
|
+
| 3 | `rate-limit` | Concurrency × delay > known provider rate limit | Prepend caveat, set needsReview | `rate-limit:provider:prescribed/limit` |
|
|
128
|
+
| 4 | `pre-release` | Fix enables `-rc`/`-beta`/`-canary` feature | Append "requires pre-release version" caveat | `pre-release:pkg@version` |
|
|
129
|
+
| 5 | `middleware-conflict` | Rec targets route covered by middleware matcher | Append "Middleware {matcher} may intercept" caveat | `middleware-conflict:matcher` |
|
|
130
|
+
| 6 | `undeclared-dep` | Fix imports a package not in package.json | Prepend "Add dependency first: npm i {pkg}" | `undeclared-dep:pkg` |
|
|
131
|
+
| 7 | `count-correct` | Cited count > verified count, ground-truth known | Rewrite to "~N" with verified count | `count-correct:token:cited→actual` |
|
|
132
|
+
| 8 | `count-strip` | Cited count > verified count, no ground truth | Rewrite to "a number of" | `count-strip:token` |
|
|
133
|
+
| 9 | `rendering-mode-mislabel` | Rec blames ISR/SSR on a static page | Append warning, set needsReview | `rendering-mode-mislabel` |
|
|
134
|
+
| 10 | `unknown-citation` | URL not in `references/docs-library.json` | Strip URL, set needsReview if all stripped | `unknown-citation:url` |
|
|
135
|
+
| 11 | `version-mismatch` | URL's `applicableFrameworks` doesn't match stack | Strip URL, set needsReview if all stripped | `version-mismatch:url` |
|
|
136
|
+
| 12 | `missing-citation` | `citations.length === 0` after other sanitizers | DROP rec entirely | (rec not emitted; counted at end) |
|
|
137
|
+
|
|
138
|
+
The sanitizer order matters: dollar-strip runs first (cheap, deterministic), then content sanitizers, then citation sanitizers last. This guarantees citation count is computed against the final state.
|
|
139
|
+
|
|
140
|
+
The `recordSanitizer(rec, tag)` helper is the single entry point — sanitizers MUST call it before mutating fields. Otherwise the audit trail rots.
|
|
141
|
+
|
|
142
|
+
### Provider rate limits
|
|
143
|
+
|
|
144
|
+
Used by sanitizer #3. These provider limits are public contract values:
|
|
145
|
+
|
|
146
|
+
| Provider | Limit | Doc URL |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| Notion | 3 rps | https://developers.notion.com/reference/request-limits |
|
|
149
|
+
| OpenAI | 30 rps | https://platform.openai.com/docs/guides/rate-limits |
|
|
150
|
+
| Stripe | 100 rps | https://docs.stripe.com/rate-limits |
|
|
151
|
+
| Anthropic | 10 rps | https://docs.anthropic.com/en/api/rate-limits |
|
|
152
|
+
|
|
153
|
+
Tiers/plans differ; these are first-tier defaults. The sanitizer prepends a caveat if the rec prescribes higher concurrency.
|
|
154
|
+
|
|
155
|
+
## Envelope-unwrap recovery
|
|
156
|
+
|
|
157
|
+
Not a sanitizer — a recovery step. LLMs occasionally wrap their JSON output in an envelope:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{ "data": { "recommendations": [...] } }
|
|
161
|
+
{ "result": { "recommendations": [...] } }
|
|
162
|
+
{ "insights": { "recommendations": [...] } }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`attemptManualRecovery` peels one wrapping layer before schema validation. Increments `hygieneCounters.envelopeUnwraps`. Logs the unwrap to the run log.
|
|
166
|
+
|
|
167
|
+
This is the only "creative" parsing the skill does. Anything else that fails schema validation is rejected.
|
|
168
|
+
|
|
169
|
+
## Grading rubric
|
|
170
|
+
|
|
171
|
+
Each rec is scored on four axes, 0-1 each. Average → grade:
|
|
172
|
+
|
|
173
|
+
| Axis | What it measures | Strong (1.0) signal |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| Specificity | Concrete files, line numbers, code snippets | Triple-backtick code fence OR inline code ≥10 chars + verified file path |
|
|
176
|
+
| Actionability | Clear "do this then that" steps | Numbered steps; verbs present in each step; no "consider"/"might" |
|
|
177
|
+
| Grounding | Claims trace to findings or metric data | `sourceIndex` matches a finding OR rec has affectedFiles + code fences (presumed evidence) |
|
|
178
|
+
| Evidence | Numeric, observed claims | Count words (errors, queries, invocations) + units (% / ms / s / K / M) |
|
|
179
|
+
|
|
180
|
+
Grade thresholds:
|
|
181
|
+
- `Excellent` ≥ 0.85
|
|
182
|
+
- `Good` 0.70 – 0.85
|
|
183
|
+
- `Fair` 0.55 – 0.70
|
|
184
|
+
- `Poor` < 0.55 → dropped at quality floor in Step 4
|
|
185
|
+
|
|
186
|
+
## Next.js version awareness
|
|
187
|
+
|
|
188
|
+
The recommender's citation library is filtered by `signals.json.stack.framework@frameworkVersion`. The agent should still self-check the version when picking which APIs to recommend:
|
|
189
|
+
|
|
190
|
+
| Feature | Available | Notes |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| App Router | Next ≥ 13.0 | Default since 14 |
|
|
193
|
+
| `generateStaticParams` | Next ≥ 13.0 | Replaces getStaticPaths for App Router |
|
|
194
|
+
| Fetch `next: { revalidate }` | Next ≥ 13.0 | Note: default fetch caching flipped in Next 15 |
|
|
195
|
+
| `unstable_cache` | Next 14-15 | Replaced by 'use cache' in 16 |
|
|
196
|
+
| `'use cache'` directive | Next ≥ 15.0 | Persistent cache primitive |
|
|
197
|
+
| `cacheLife()`, `cacheTag()` | Next ≥ 15.0 | Pairs with 'use cache' |
|
|
198
|
+
| `after()` | Next ≥ 15.0 | Non-blocking post-response work |
|
|
199
|
+
| Partial Prerendering | Next ≥ 15.0 | Stable target later — verify per release |
|
|
200
|
+
| `revalidateTag` / `revalidatePath` | Next ≥ 13.4 | Tag-based on-demand invalidation |
|
|
201
|
+
| `cookies()` / `headers()` async | Next ≥ 15.0 | Async pattern in 15+ |
|
|
202
|
+
|
|
203
|
+
The skill's curated citation library encodes these constraints via `applicableFrameworks`. If a contributor adds a new Next.js feature URL, they MUST set the right semver range in `references/docs-library.json`.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<!-- THIS FILE IS GENERATED by scripts/build-docs.mjs. Do not edit by hand. -->
|
|
2
|
+
<!-- To change scanner descriptions, edit lib/scanners/*.mjs metadata exports. -->
|
|
3
|
+
<!-- To change gate thresholds, edit lib/gates/*.mjs metadata exports. -->
|
|
4
|
+
|
|
5
|
+
# Scanner patterns
|
|
6
|
+
|
|
7
|
+
AST/grep-style scanners run in parallel with metric-driven investigation. They find known anti-patterns. Findings on cold-path or unmappable files are dropped unless the scanner declares `trafficIndependent: true`.
|
|
8
|
+
|
|
9
|
+
Total scanners: 15.
|
|
10
|
+
|
|
11
|
+
## Patterns
|
|
12
|
+
|
|
13
|
+
### `cache-components-suspense-dedupe` — 'use cache' with multiple Suspense boundaries on the same data
|
|
14
|
+
|
|
15
|
+
- **Severity**: medium
|
|
16
|
+
- **Billing dimension**: function-duration
|
|
17
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
18
|
+
|
|
19
|
+
**Description.** Default `'use cache'` does not dedupe identical calls across separate `<Suspense>` boundaries on the same render. Each boundary re-invokes the cached function, multiplying function-duration cost and inflating ISR write churn when the output is large.
|
|
20
|
+
|
|
21
|
+
**Fix.** Hoist the promise to the page level (`const dataPromise = fetchData()` at the top, passed down to each Suspense child) OR move the shared fetch into a `'use cache: remote'` data-access layer so cross-request and cross-boundary dedupe applies.
|
|
22
|
+
|
|
23
|
+
**Citations:**
|
|
24
|
+
- `https://nextjs.org/docs/app/api-reference/directives/use-cache`
|
|
25
|
+
- `https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents`
|
|
26
|
+
- `https://nextjs.org/docs/app/guides/migrating-to-cache-components`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### `edge-heavy-import` — Heavy / node-only import inside edge-runtime file
|
|
31
|
+
|
|
32
|
+
- **Severity**: high
|
|
33
|
+
- **Billing dimension**: function-duration
|
|
34
|
+
- **Traffic-independent**: yes (cold-path findings survive the doctrine drop)
|
|
35
|
+
|
|
36
|
+
**Description.** Edge runtime is a constrained sandbox with no node: builtins and a much smaller cold-start budget than Node functions. Heavy SDKs (sharp, @aws-sdk/*, @prisma/client, pg, puppeteer) either fail at deploy or inflate cold-start latency. Move the import to a Node runtime function, or replace with an edge-compatible alternative (e.g., neon-driver instead of pg).
|
|
37
|
+
|
|
38
|
+
**Fix.** Either (a) drop the `export const runtime = 'edge'` so the route runs on Node (default in 2026), or (b) replace the heavy import with an edge-compatible alternative. For DB: use @neondatabase/serverless or @planetscale/database instead of pg/mysql2. For image: do the work in a Node route handler. For auth signing: use jose (Web Crypto) instead of jsonwebtoken.
|
|
39
|
+
|
|
40
|
+
**Citations:**
|
|
41
|
+
- `https://vercel.com/docs/functions/runtimes/edge-runtime`
|
|
42
|
+
- `https://vercel.com/docs/fluid-compute`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### `force-dynamic` — export const dynamic = 'force-dynamic'
|
|
47
|
+
|
|
48
|
+
- **Severity**: medium
|
|
49
|
+
- **Billing dimension**: function-duration
|
|
50
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
51
|
+
|
|
52
|
+
**Description.** force-dynamic disables static + ISR rendering. The route runs the function on every request. Sometimes necessary (cookies, headers, real-time data), often a habit that costs function-duration and edge-requests at scale.
|
|
53
|
+
|
|
54
|
+
**Fix.** Audit the route. If dynamic behavior comes from cookies()/headers()/searchParams, force-dynamic may be redundant — Next infers dynamic automatically. Consider revalidate / 'use cache' / generateStaticParams if any portion can be pre-rendered.
|
|
55
|
+
|
|
56
|
+
**Citations:**
|
|
57
|
+
- `https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config`
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### `headers-in-page` — Dynamic API call forcing dynamic rendering
|
|
62
|
+
|
|
63
|
+
- **Severity**: medium
|
|
64
|
+
- **Billing dimension**: function-duration
|
|
65
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
66
|
+
|
|
67
|
+
**Description.** headers(), cookies(), and draftMode() are dynamic APIs. Reading them in a page/layout makes the entire segment dynamic — no ISR, no static generation, and a function invocation on every request.
|
|
68
|
+
|
|
69
|
+
**Fix.** Move the dynamic API call into a child Server Component that lives inside a Suspense boundary. The parent can stay static; only the leaf re-renders dynamically.
|
|
70
|
+
|
|
71
|
+
**Citations:**
|
|
72
|
+
- `https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config`
|
|
73
|
+
- `https://nextjs.org/docs/app/building-your-application/caching`
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `large-static-asset` — Large file in public/
|
|
78
|
+
|
|
79
|
+
- **Severity**: medium
|
|
80
|
+
- **Billing dimension**: bandwidth
|
|
81
|
+
- **Traffic-independent**: yes (cold-path findings survive the doctrine drop)
|
|
82
|
+
|
|
83
|
+
**Description.** Static assets in `public/` over 500 KB ship as-is from the CDN. Whether the cost is meaningful depends on traffic, but the candidate is binary — the file is either needed at that size or it can be optimized (compressed image, video transcode, or moved off the critical path).
|
|
84
|
+
|
|
85
|
+
**Fix.** Verify the asset is reachable on the customer-facing hot path. Then choose: (a) compress (convert PNG → AVIF/WebP; transcode MP4 to lower bitrate); (b) host externally (Vercel Blob, S3, or a media CDN with per-asset signed URLs); (c) lazy-load (defer to client-side fetch instead of bundling into initial HTML).
|
|
86
|
+
|
|
87
|
+
**Citations:**
|
|
88
|
+
- `https://vercel.com/docs/manage-cdn-usage`
|
|
89
|
+
- `https://vercel.com/docs/image-optimization`
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `max-age-without-s-maxage` — Cache-Control: max-age without s-maxage
|
|
94
|
+
|
|
95
|
+
- **Severity**: medium
|
|
96
|
+
- **Billing dimension**: edge-requests
|
|
97
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
98
|
+
|
|
99
|
+
**Description.** max-age caches in the browser; s-maxage caches at the CDN. Without s-maxage, every uncached visitor request invokes the function. Adding s-maxage often cuts function invocations by 80%+ on read-heavy routes.
|
|
100
|
+
|
|
101
|
+
**Fix.** Add s-maxage to the Cache-Control header. Example: Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=86400. Pair with explicit cache-bust strategy if content can change.
|
|
102
|
+
|
|
103
|
+
**Citations:**
|
|
104
|
+
- `https://vercel.com/docs/caching/cdn-cache`
|
|
105
|
+
- `https://vercel.com/docs/caching/cache-control-headers`
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### `middleware-broad-matcher` — Middleware matcher missing or too broad
|
|
110
|
+
|
|
111
|
+
- **Severity**: high
|
|
112
|
+
- **Billing dimension**: edge-requests
|
|
113
|
+
- **Traffic-independent**: yes (cold-path findings survive the doctrine drop)
|
|
114
|
+
|
|
115
|
+
**Description.** middleware.ts without a config.matcher (or matcher: ["/(.*)"]) runs on every request including _next/static, _next/image, favicon.ico, and image asset fetches. Edge-request cost scales accordingly.
|
|
116
|
+
|
|
117
|
+
**Fix.** Scope the matcher to actual application paths. Example: matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"]
|
|
118
|
+
|
|
119
|
+
**Citations:**
|
|
120
|
+
- `https://nextjs.org/docs/app/building-your-application/routing/middleware`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `missing-cache-headers` — Cacheable route or fetch with no caching (Cache-Control absent or no-store)
|
|
125
|
+
|
|
126
|
+
- **Severity**: medium
|
|
127
|
+
- **Billing dimension**: edge-requests
|
|
128
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
129
|
+
|
|
130
|
+
**Description.** Two antipatterns: (a) GET handlers without explicit Cache-Control headers serve uncached; (b) fetch() calls with cache:"no-store" or next:{revalidate:0} opt out of caching even on cacheable upstream data. For non-auth routes / fetches, both are leaving cache hits on the floor.
|
|
131
|
+
|
|
132
|
+
**Fix.** For GET handlers: return a Response with Cache-Control: public, s-maxage=<seconds>, stale-while-revalidate=<window>. For fetch(): drop cache:"no-store" (use { next: { revalidate: <seconds> } } in Next.js) so the response is cached by the framework + CDN.
|
|
133
|
+
|
|
134
|
+
**Citations:**
|
|
135
|
+
- `https://vercel.com/docs/caching/cdn-cache`
|
|
136
|
+
- `https://vercel.com/docs/caching/cache-control-headers`
|
|
137
|
+
- `https://nextjs.org/docs/app/building-your-application/caching`
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### `prisma-include-tree-bloat` — Deep Prisma include tree (3+ levels)
|
|
142
|
+
|
|
143
|
+
- **Severity**: high
|
|
144
|
+
- **Billing dimension**: function-duration
|
|
145
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
146
|
+
|
|
147
|
+
**Description.** Nested .include({ x: { include: { y: { include: { z: ... } } } } }) makes Prisma issue a single huge join that scales O(N*M*K). Function duration explodes, memory spikes, often causes timeouts.
|
|
148
|
+
|
|
149
|
+
**Fix.** Replace with explicit .findMany() calls or scoped .include() of only what the consumer reads. Consider Prisma.select() to project specific fields. For lists, batch with DataLoader patterns.
|
|
150
|
+
|
|
151
|
+
**Citations:**
|
|
152
|
+
- `vercel-react-best-practices:server-parallel-fetching`
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### `region-pin-in-config` — Function region pinned in config
|
|
157
|
+
|
|
158
|
+
- **Severity**: low
|
|
159
|
+
- **Billing dimension**: function-duration
|
|
160
|
+
- **Traffic-independent**: yes (cold-path findings survive the doctrine drop)
|
|
161
|
+
|
|
162
|
+
**Description.** vercel.json `regions` or per-route `preferredRegion` is set. If the pinned region is far from the dominant user geo (or far from a data source) p95 TTFB suffers. This scanner provides the configured-region signal so the region-misconfig gate can recommend an audit.
|
|
163
|
+
|
|
164
|
+
**Fix.** Audit the pinned region against traffic geography (Speed Insights or Web Analytics by country) and data-source location. Consider multi-region if data lives in a fixed location and users are global; consider relocating if users are concentrated in one geography.
|
|
165
|
+
|
|
166
|
+
**Citations:**
|
|
167
|
+
- `https://vercel.com/docs/functions/configuring-functions/region`
|
|
168
|
+
- `https://vercel.com/docs/functions/configuring-functions/region`
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### `source-maps-production` — Source maps enabled in production
|
|
173
|
+
|
|
174
|
+
- **Severity**: low
|
|
175
|
+
- **Billing dimension**: edge-requests
|
|
176
|
+
- **Traffic-independent**: yes (cold-path findings survive the doctrine drop)
|
|
177
|
+
|
|
178
|
+
**Description.** productionBrowserSourceMaps: true ships .map files in the production bundle, increasing transfer size 30-100% per visitor. Useful for error reporting via Sentry; not useful for users.
|
|
179
|
+
|
|
180
|
+
**Fix.** Keep source maps generation but exclude them from the public bundle. Upload to your error tracker via build-time CI step; do not serve them with the deployment.
|
|
181
|
+
|
|
182
|
+
**Citations:**
|
|
183
|
+
- `https://nextjs.org/docs/messages/improper-devtool`
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### `sveltekit-prerender-missing` — SvelteKit page without explicit prerender / ISR config
|
|
188
|
+
|
|
189
|
+
- **Severity**: low
|
|
190
|
+
- **Billing dimension**: function-duration
|
|
191
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
192
|
+
|
|
193
|
+
**Description.** SvelteKit page or +page.server.ts is missing an explicit `prerender`, `ssr`, or adapter `config.isr` declaration. Default is per-request function execution — investigate whether the route could be prerendered or ISR-cached.
|
|
194
|
+
|
|
195
|
+
**Fix.** If the page is static (no per-user / per-request data), add `export const prerender = true` in +page.ts or +page.server.ts. If the data refreshes on a schedule, prefer adapter-vercel's ISR option via `export const config = { isr: { expiration: 60 } }`.
|
|
196
|
+
|
|
197
|
+
**Citations:**
|
|
198
|
+
- `https://kit.svelte.dev/docs/page-options`
|
|
199
|
+
- `https://kit.svelte.dev/docs/adapter-vercel`
|
|
200
|
+
- `https://vercel.com/docs/incremental-static-regeneration`
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### `turbo-force-bypass` — Turborepo cache bypass on a monorepo
|
|
205
|
+
|
|
206
|
+
- **Severity**: high
|
|
207
|
+
- **Billing dimension**: build
|
|
208
|
+
- **Traffic-independent**: yes (cold-path findings survive the doctrine drop)
|
|
209
|
+
|
|
210
|
+
**Description.** Turborepo's per-task cache can be bypassed by an explicit force flag, a `cache: false` config, or missing build-skip configuration. Every commit can rebuild unchanged work; Build Minutes climb with project count.
|
|
211
|
+
|
|
212
|
+
**Fix.** Remove `TURBO_FORCE=true` from build env/scripts unless intentional. Set `tasks.build.cache: true` in `turbo.json` (or remove the override), and include generated outputs in Turbo's cache contract. Prefer Vercel's skip-unaffected monorepo behavior when available; use `ignoreCommand` only when that setting cannot cover the project.
|
|
213
|
+
|
|
214
|
+
**Citations:**
|
|
215
|
+
- `https://vercel.com/docs/monorepos`
|
|
216
|
+
- `https://vercel.com/docs/builds`
|
|
217
|
+
- `https://turborepo.dev/docs/crafting-your-repository/caching`
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### `unoptimized-image` — Image optimization gap (raw <img>, global flag, missing sizes, or SVG mis-routed)
|
|
222
|
+
|
|
223
|
+
- **Severity**: high
|
|
224
|
+
- **Billing dimension**: image-optimization
|
|
225
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
226
|
+
|
|
227
|
+
**Description.** Four shapes of image-cost waste: raw <img> tags bypass the framework Image component; `images.unoptimized: true` disables Vercel image optimization globally; <Image fill> without `sizes` forces serving the largest source variant; <Image src=".svg"> without `unoptimized` routes vector data through the raster pipeline.
|
|
228
|
+
|
|
229
|
+
**Fix.** For raw <img>: switch to next/image, enhanced-img (SvelteKit), <Image /> (Astro), or NuxtImg. For global unoptimized:true: remove the flag unless the project is hosted outside Vercel. For fill without sizes: add `sizes="(max-width: 768px) 100vw, 50vw"` or whatever matches your layout. For SVG: add `unoptimized` so the raw SVG ships instead of rastering it.
|
|
230
|
+
|
|
231
|
+
**Citations:**
|
|
232
|
+
- `https://nextjs.org/docs/app/api-reference/components/image`
|
|
233
|
+
- `https://vercel.com/docs/image-optimization`
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### `use-cache-date-stamp` — new Date() / Date.now() / Math.random() inside a 'use cache' file
|
|
238
|
+
|
|
239
|
+
- **Severity**: high
|
|
240
|
+
- **Billing dimension**: isr
|
|
241
|
+
- **Traffic-independent**: no (cold-path findings get dropped)
|
|
242
|
+
|
|
243
|
+
**Description.** `'use cache'` memoizes by argument identity AND prerender output. A timestamp baked into the cached output (`new Date().getFullYear()` in a footer, `Date.now()` in a payload field) forces a fresh ISR write on every regeneration even when the underlying data is unchanged. Random values have the same failure mode.
|
|
244
|
+
|
|
245
|
+
**Fix.** Replace module-scope `new Date()` with a build-time constant (`const buildYear = new Date().getFullYear()`) or move per-request timestamps into a client component inside `useEffect`. Do not pass dates as arguments to `'use cache'` functions — they invalidate the cache every call.
|
|
246
|
+
|
|
247
|
+
**Citations:**
|
|
248
|
+
- `https://nextjs.org/docs/app/api-reference/directives/use-cache`
|
|
249
|
+
- `https://nextjs.org/docs/app/api-reference/functions/cacheLife`
|
|
250
|
+
|
|
251
|
+
---
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
## Step 4 — Score and report
|
|
2
|
+
|
|
3
|
+
This reference covers everything that happens after recommendations are drafted: quality floor, impact framing, sort order, the customer-facing report template, and the playbook selection matrix.
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Quality floor and prune rules](#quality-floor-and-prune-rules)
|
|
8
|
+
- [Impact framing — the magnitude rule](#impact-framing--the-magnitude-rule)
|
|
9
|
+
- [`impactLabel` schema](#impactlabel-schema)
|
|
10
|
+
- [Sort order and platform-rec cap](#sort-order-and-platform-rec-cap)
|
|
11
|
+
- [The customer-facing report template](#the-customer-facing-report-template)
|
|
12
|
+
- [Playbook selection matrix](#playbook-selection-matrix)
|
|
13
|
+
|
|
14
|
+
## Quality floor and prune rules
|
|
15
|
+
|
|
16
|
+
| Rule | Value | Why |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Drop recommendations with `quality.overall < 0.55` | Hard cutoff (raised from 0.4 in May 2026 audit) | Bad-grade recs erode trust faster than they help. 0.55 matches the Poor/Fair grade boundary; recs below this are "Poor" and shouldn't ship. |
|
|
19
|
+
| Prune cap on findings | 30% of input | Stops the pruner from wiping the report when LLM merit-grades are noisy |
|
|
20
|
+
| Platform-rec cap | 3 | Account-level recs (Fluid, Bot Protection, Speed Insights) only have room for the top three |
|
|
21
|
+
| Quick-wins definition | `effort === 'low' AND priority > 40` | Surfaces fixes the user can ship in a single PR |
|
|
22
|
+
| Savings floor (internal ranking only) | $5/mo equivalent | Below this, even a "high" tier impact translates to "negligible" magnitude |
|
|
23
|
+
|
|
24
|
+
## Impact framing — the magnitude rule
|
|
25
|
+
|
|
26
|
+
**Performance: be precise.** Use observed numbers. Example:
|
|
27
|
+
|
|
28
|
+
> "Reduce /api/products p95 from 850ms toward ~250-400ms; cache hit would lift from 0% toward ~60% based on similar cached routes."
|
|
29
|
+
|
|
30
|
+
Performance numbers come from `signals.json.metrics.*` — they're observed, not extrapolated. Cite the exact route + metric value.
|
|
31
|
+
|
|
32
|
+
**Dollar cost: never precise.** Use MAGNITUDE BUCKETS via `lib/impact-magnitude.mjs`'s `impactMagnitude({currentCost, impactTier})`:
|
|
33
|
+
|
|
34
|
+
| Estimated reduction (USD) | Magnitude | Customer-facing phrase |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| < $5 | `negligible` | "small cost impact at current traffic" |
|
|
37
|
+
| $5 – $50 | `small` | "low-tens of dollars per month at current traffic" |
|
|
38
|
+
| $50 – $500 | `medium` | "hundreds of dollars per month at current traffic" |
|
|
39
|
+
| $500 – $5,000 | `large` | "low-thousands of dollars per month at current traffic" |
|
|
40
|
+
| > $5,000 | `very-large` | "thousands+ of dollars per month at current traffic" |
|
|
41
|
+
|
|
42
|
+
Reduction is computed as `currentCost × fraction` where `fraction = {high: 0.4, medium: 0.2, low: 0.1}[impactTier]`. The fraction is intentionally conservative — we'd rather under-promise than mislead.
|
|
43
|
+
|
|
44
|
+
### Discountable vs non-discountable SKUs
|
|
45
|
+
|
|
46
|
+
When the project is on a Flex Commit and the report frames savings against contract burndown, segment spend before phrasing. Field doctrine (May 2026): the Flex discount slider applies only to a subset of SKUs.
|
|
47
|
+
|
|
48
|
+
| Discountable (slider applies) | Non-discountable (raw rate) |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Seats | Build CPU Minutes |
|
|
51
|
+
| Edge Requests | Fluid Active CPU |
|
|
52
|
+
| Fast Data Transfer | Fluid Provisioned Memory |
|
|
53
|
+
| Fast Origin Transfer | Raw Flex top-up |
|
|
54
|
+
| Image Optimization | |
|
|
55
|
+
| ISR Reads / Writes | |
|
|
56
|
+
| Observability Events | |
|
|
57
|
+
|
|
58
|
+
A recommendation that targets a non-discountable SKU should never frame savings as a percentage of contract; frame as absolute magnitude only. Conversely, a discountable-SKU recommendation may surface "applies to contract burndown" in the magnitude phrase.
|
|
59
|
+
|
|
60
|
+
**Why magnitudes:**
|
|
61
|
+
|
|
62
|
+
- Traffic varies. A "20% reduction in edge requests" is exact at today's traffic and meaningless next quarter.
|
|
63
|
+
- Pricing changes. Vercel's billing rates move; precise dollar projections rot.
|
|
64
|
+
- The user is smart. They'd rather see "hundreds of dollars per month" with a real metric backing it than `$340/mo` with a hand-wave behind it.
|
|
65
|
+
- The `$-strip` sanitizer enforces this at output time. Any `$N` literal that slips into customer-facing fields is replaced with "the billed cost" before rendering.
|
|
66
|
+
|
|
67
|
+
## `impactLabel` schema
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
type ImpactLabel = {
|
|
71
|
+
// PRECISE: performance recs
|
|
72
|
+
performance?: string;
|
|
73
|
+
// MAGNITUDE: cost recs
|
|
74
|
+
costMagnitude?: 'negligible' | 'small' | 'medium' | 'large' | 'very-large';
|
|
75
|
+
costPhrase?: string;
|
|
76
|
+
billingDimension?: string; // 'Edge Requests' | 'Function Duration' | ...
|
|
77
|
+
fractionReduced?: number; // 0.2 = ~20% — internal only, NOT rendered
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Cost recs render `costPhrase`. Performance recs render `performance`. Reliability recs frame impact as observed error/timeout reduction (e.g., "Cuts 5xx rate from 0.4% to <0.1% based on current traffic").
|
|
82
|
+
|
|
83
|
+
When a rec spans buckets — e.g., a caching fix that reduces both cost AND latency — render both lines.
|
|
84
|
+
|
|
85
|
+
## Sort order and platform-rec cap
|
|
86
|
+
|
|
87
|
+
Internal sort key (never rendered): `priority = currentDimensionCost × fractionReduced × confidence`.
|
|
88
|
+
|
|
89
|
+
The list of recs the customer sees is sorted by this priority. The platform recommendations section is capped at 3, sorted the same way.
|
|
90
|
+
|
|
91
|
+
## The customer-facing report template
|
|
92
|
+
|
|
93
|
+
The agent renders this as the final output of Step 4. The shape is fixed; the content comes from the merged signals + verified recommendations + the `gated[]` list from Step 2.
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
# Vercel Optimization Report — {projectName}
|
|
97
|
+
|
|
98
|
+
**Stack**: {framework}@{frameworkVersion} | {router} | {orm}
|
|
99
|
+
**Plan**: {plan.plan} ({plan.reason})
|
|
100
|
+
**Period**: {usage.period.from} → {usage.period.to}
|
|
101
|
+
**Observability**: {observability status}
|
|
102
|
+
|
|
103
|
+
## Cost breakdown
|
|
104
|
+
|
|
105
|
+
| Service | Usage | Billed Cost |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| (non-zero rows from usage.services, sorted by billedCost desc) |
|
|
108
|
+
|
|
109
|
+
Total billed: {usage.totals.billedCost} (we render the precise current cost — we just don't project future precise savings)
|
|
110
|
+
|
|
111
|
+
Omit zero-cost service rows from the table at the same cent precision shown to customers. If every row has `$0.00` billed cost but `effectiveCost` / USD `pricingQuantity` is non-zero, explain that net billed cost is `$0.00` after included credits or allotments and show the effective usage cost table instead. If both billed and effective costs are `$0.00`, replace the table with a concise note that `vercel usage` returned a billing payload but every reported service cost was `$0.00` for the window.
|
|
112
|
+
|
|
113
|
+
If `vercel usage` was queried and unavailable, the cost breakdown is replaced by an observability-derived cost ranking from `metrics.fnGbHrByRoute` + `metrics.fnCpuMsByRoute` + `metrics.fdtByRoute` when those metrics exist. These don't translate directly to dollars — they show *which routes consume the billable units* so the user knows what to attack first. If `usageError` is `NOT_COLLECTED_OBSERVABILITY_BLOCKED` or another `NOT_COLLECTED_*` value, say usage was not collected; do not describe it as a billing-plan or Costs-feature finding.
|
|
114
|
+
|
|
115
|
+
Render Observability status from the actual collection state:
|
|
116
|
+
- `Observability Plus enabled — per-route metrics included` when `observabilityPlusUsable=true`.
|
|
117
|
+
- `Per-route metrics unavailable — audit paused before metric-backed route ranking` when an Observability Plus blocker stopped the run before billing/scanner collection.
|
|
118
|
+
- `Per-route metrics unavailable — analysis based on billing + scanner findings` when the user accepted a limited audit and billing/scanner signals were collected.
|
|
119
|
+
- `Per-route metrics unavailable — limited analysis based on scanner findings` when the user accepted a limited audit but billing usage was queried and unavailable.
|
|
120
|
+
- `Not checked — audit paused at unsupported-framework preflight` when framework support stopped the run before the Observability Plus check.
|
|
121
|
+
- `Not enabled — analysis based on billing + scanner findings` only for legacy/limited reports where Observability Plus is known false and billing/scanner signals exist.
|
|
122
|
+
|
|
123
|
+
## Highest-impact recommendations
|
|
124
|
+
|
|
125
|
+
For each high-priority rec, in order:
|
|
126
|
+
1. **{route or file}** — {o11ySignal}
|
|
127
|
+
1. **{readable candidate label}** — {readable metric labels}
|
|
128
|
+
- **What to do**: {rec.what}
|
|
129
|
+
- **Impact**: {impactLabel.performance ?? impactLabel.costPhrase}
|
|
130
|
+
- **Effort**: {rec.effort}
|
|
131
|
+
- **Citations**: {rec.citations}
|
|
132
|
+
|
|
133
|
+
## Recommendations
|
|
134
|
+
|
|
135
|
+
### High impact
|
|
136
|
+
|
|
137
|
+
| # | Bucket | What | Impact | Effort | Citations |
|
|
138
|
+
|---|---|---|---|---|---|
|
|
139
|
+
|
|
140
|
+
### Medium impact
|
|
141
|
+
### Low impact
|
|
142
|
+
|
|
143
|
+
## Platform recommendations
|
|
144
|
+
|
|
145
|
+
(account-level recs from gate, capped at 3)
|
|
146
|
+
|
|
147
|
+
## Observations from investigation
|
|
148
|
+
|
|
149
|
+
Non-recommendation findings from reconciliation or investigation: deployment regressions, route-error storms, metric mismatches, and other real signals that should not become speculative performance recommendations.
|
|
150
|
+
|
|
151
|
+
Observations must not contain implementation-grade actions. If the suggested action says to enable, add, wrap, apply, move, configure, challenge, deny, or otherwise change code or project settings, the renderer must hold it back until it passes the ready-to-apply recommendation evidence bar. Customer-visible observations can ask for narrower evidence collection: inspect logs, compare deployments, check headers, or confirm cacheability.
|
|
152
|
+
|
|
153
|
+
## Investigated, no change recommended
|
|
154
|
+
|
|
155
|
+
Candidates that were checked but did not produce a supported recommendation. Use plain reasons; do not use "abstain" in customer-facing copy.
|
|
156
|
+
|
|
157
|
+
| Candidate | Why no recommendation shipped |
|
|
158
|
+
|---|---|
|
|
159
|
+
| Slow route on /docs | Detailed metrics did not support a code change |
|
|
160
|
+
|
|
161
|
+
## Not investigated in this run
|
|
162
|
+
|
|
163
|
+
This section earns the user's trust. For every metric signal we considered but didn't act on, group by candidate type and reason:
|
|
164
|
+
|
|
165
|
+
| Candidate type | Why not investigated | Targets | Count |
|
|
166
|
+
|---|---|---|---:|
|
|
167
|
+
| Low cache-hit route | hitRate 0.65 above threshold | /api/orders | 1 |
|
|
168
|
+
| Slow route | left for a larger run | /api/docs<br>/api/learn | 2 |
|
|
169
|
+
|
|
170
|
+
## Strengths
|
|
171
|
+
|
|
172
|
+
(what the project is doing right — caching is healthy on routes X/Y/Z; Fluid Compute is enabled; etc.)
|
|
173
|
+
|
|
174
|
+
## Data gaps
|
|
175
|
+
|
|
176
|
+
(what we couldn't measure — Observability Plus disabled means no per-route latency, etc.)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Common data gaps to call out when the underlying metric returned empty rows. If the metric query failed (`ok=false`), say the metric was not usable with the code; do not convert failed queries into "no measurements" or "not used" claims.
|
|
180
|
+
|
|
181
|
+
- **Core Web Vitals empty.** The Speed Insights metric returned no measurements for the 14-day window. The `cwv_poor` gate stayed dormant; no claims about LCP/INP/CLS are made.
|
|
182
|
+
- **ISR empty.** Project doesn't use Incremental Static Regeneration. The `isr_overrevalidation` gate stayed dormant.
|
|
183
|
+
- **Middleware empty.** No `middleware.ts` (or matcher excludes all observed traffic). The `middleware_heavy` gate stayed dormant.
|
|
184
|
+
- **Image transformations empty.** No `next/image` usage or no images served in the window.
|
|
185
|
+
- **BotID checks empty.** BotID is disabled — see the `platform_bot_protection` recommendation for the toggle.
|
|
186
|
+
- **Cold-start data near-zero.** Fluid Compute may already be enabled, or the project's traffic pattern keeps warm instances available; the `cold_start` gate evaluates the data but emits no candidate.
|
|
187
|
+
|
|
188
|
+
The "Not investigated in this run" section is critical. It comes directly from `gate.json` produced by the gate. It tells the user we considered everything; we didn't just pick the easy targets.
|
|
189
|
+
|
|
190
|
+
## Playbook selection matrix
|
|
191
|
+
|
|
192
|
+
The recommender selects 0-2 playbooks based on the project's `stack.applicationProfile` (inferred from frameworks + deps) and the top billing dimensions.
|
|
193
|
+
|
|
194
|
+
| Application profile | Likely top dimensions | Apply playbooks |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `ai-application` (AI SDK, AI Gateway, Sandbox usage) | AI Gateway, Sandbox Active Compute, Function Duration | `playbooks/ai-application.md` |
|
|
197
|
+
| `ecommerce` (Stripe, Shopify, cart components) | Edge Requests, Function Duration | `playbooks/ecommerce.md` |
|
|
198
|
+
| `saas` (auth, dashboards, multi-tenant) | Function Duration, Bandwidth | `playbooks/saas.md` |
|
|
199
|
+
| `api-service` (mostly API routes, no UI) | Function Duration, Edge Requests | `playbooks/api-service.md` |
|
|
200
|
+
| `content-site` (blog, docs, mostly static) | Edge Requests, Image Optimization | `playbooks/content-site.md` |
|
|
201
|
+
| `marketing` (landing pages, A/B tests) | Edge Requests, ISR Reads | `playbooks/marketing.md` |
|
|
202
|
+
|
|
203
|
+
`ai-application` is checked first in `inferPlaybook()` — an AI-heavy SaaS or AI commerce app shares the AI playbook's billing shape (AI Gateway dominant) and gotchas, not the dashboard or cart-checkout patterns.
|
|
204
|
+
|
|
205
|
+
Playbooks shape phrasing and ordering of recommendations. They never invent claims — every rec must still trace back to verified findings.
|