opencode-skills-collection 3.0.35 → 3.0.37
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 +2 -2
- package/skills_index.json +312 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
// Extract mechanically-verifiable claims from a rec without parsing LLM prose. High precision, not recall.
|
|
2
|
+
|
|
3
|
+
export function extractClaims(rec, ctx = {}) {
|
|
4
|
+
const claims = [];
|
|
5
|
+
const repoRoot = ctx.repoRoot;
|
|
6
|
+
const projectRootDirectory = normalizeProjectRootDirectory(ctx.projectRootDirectory);
|
|
7
|
+
const framework = ctx.framework;
|
|
8
|
+
const frameworkVersion = ctx.version;
|
|
9
|
+
const cacheComponents = ctx.cacheComponents;
|
|
10
|
+
const signals = ctx.signals;
|
|
11
|
+
const projectFacts = Array.isArray(ctx.projectFacts) ? ctx.projectFacts : [];
|
|
12
|
+
|
|
13
|
+
// One synthetic claim asserts rec doesn't contradict any already-on project fact (Fluid, in-function concurrency, …).
|
|
14
|
+
if (projectFacts.length > 0) {
|
|
15
|
+
claims.push({
|
|
16
|
+
type: 'does_not_contradict_project_config',
|
|
17
|
+
rec,
|
|
18
|
+
projectFacts,
|
|
19
|
+
sourceField: 'projectFacts',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const cite of asArray(rec.citations)) {
|
|
24
|
+
// Skill-rule refs are filtered upstream — skip version check.
|
|
25
|
+
if (/^[\w-]+:[\w-]+$/.test(cite)) {
|
|
26
|
+
claims.push({ type: 'citation_in_library', url: cite, sourceField: 'citations' });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
claims.push({ type: 'citation_in_library', url: cite, sourceField: 'citations' });
|
|
30
|
+
if (framework && frameworkVersion) {
|
|
31
|
+
claims.push({
|
|
32
|
+
type: 'citation_applies_to_version',
|
|
33
|
+
url: cite,
|
|
34
|
+
framework,
|
|
35
|
+
frameworkVersion,
|
|
36
|
+
sourceField: 'citations',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const f of asArray(rec.affectedFiles)) {
|
|
42
|
+
claims.push({ type: 'file_exists', file: f, repoRoot, projectRootDirectory, sourceField: 'affectedFiles' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// findingRefs lack a pattern, so we only check file existence.
|
|
46
|
+
for (const ref of asArray(rec.findingRefs)) {
|
|
47
|
+
const m = String(ref).match(/^(.+?):\d+$/);
|
|
48
|
+
if (m && !claims.some((c) => c.type === 'file_exists' && c.file === m[1])) {
|
|
49
|
+
claims.push({ type: 'file_exists', file: m[1], repoRoot, projectRootDirectory, sourceField: 'findingRefs' });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const cacheFiles = cacheRecommendationFiles(rec);
|
|
54
|
+
if (isCacheCandidate(rec)) {
|
|
55
|
+
claims.push({
|
|
56
|
+
type: 'cache_policy_positive_or_no_ready_rec',
|
|
57
|
+
rec,
|
|
58
|
+
sourceField: 'cache-policy',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (cacheFiles.length > 0) {
|
|
62
|
+
claims.push({
|
|
63
|
+
type: 'cache_vary_matches_dynamic_inputs',
|
|
64
|
+
rec,
|
|
65
|
+
files: cacheFiles,
|
|
66
|
+
repoRoot,
|
|
67
|
+
projectRootDirectory,
|
|
68
|
+
sourceField: 'cache-safety',
|
|
69
|
+
});
|
|
70
|
+
if (mentionsVaryHeader(rec)) {
|
|
71
|
+
claims.push({
|
|
72
|
+
type: 'cache_vary_cardinality_safe',
|
|
73
|
+
rec,
|
|
74
|
+
sourceField: 'cache-vary-cardinality',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
claims.push({
|
|
78
|
+
type: 'cache_rec_not_error_dominated_or_acknowledged',
|
|
79
|
+
rec,
|
|
80
|
+
signals,
|
|
81
|
+
sourceField: 'cache-error-safety',
|
|
82
|
+
});
|
|
83
|
+
claims.push({
|
|
84
|
+
type: 'cache_control_header_syntax',
|
|
85
|
+
rec,
|
|
86
|
+
sourceField: 'cache-header-syntax',
|
|
87
|
+
});
|
|
88
|
+
claims.push({
|
|
89
|
+
type: 'cache_control_headers_citation',
|
|
90
|
+
rec,
|
|
91
|
+
sourceField: 'cache-header-citation',
|
|
92
|
+
});
|
|
93
|
+
if (mentionsCachedNotFoundOr404(rec)) {
|
|
94
|
+
claims.push({
|
|
95
|
+
type: 'cache_404_long_ttl_safety',
|
|
96
|
+
rec,
|
|
97
|
+
sourceField: 'cache-404-safety',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (mentionsNextCachedNotFound(rec)) {
|
|
103
|
+
claims.push({
|
|
104
|
+
type: 'next_cached_not_found_causal_support',
|
|
105
|
+
rec,
|
|
106
|
+
framework,
|
|
107
|
+
frameworkVersion,
|
|
108
|
+
sourceField: 'next-cache-not-found',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (mentionsNextStableCacheApi(rec)) {
|
|
113
|
+
claims.push({
|
|
114
|
+
type: 'next_stable_cache_api_for_version',
|
|
115
|
+
rec,
|
|
116
|
+
framework,
|
|
117
|
+
frameworkVersion,
|
|
118
|
+
sourceField: 'next-cache-api-version',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (mentionsNext16RuntimeCacheApiMismatch(rec)) {
|
|
123
|
+
claims.push({
|
|
124
|
+
type: 'next_runtime_cache_api_for_version',
|
|
125
|
+
rec,
|
|
126
|
+
framework,
|
|
127
|
+
frameworkVersion,
|
|
128
|
+
sourceField: 'next-runtime-cache-api-version',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (mentionsRuntimeCacheWhenCacheComponents(rec)) {
|
|
133
|
+
claims.push({
|
|
134
|
+
type: 'next_cache_components_runtime_cache_preference',
|
|
135
|
+
rec,
|
|
136
|
+
framework,
|
|
137
|
+
frameworkVersion,
|
|
138
|
+
cacheComponents,
|
|
139
|
+
sourceField: 'next-cache-components-runtime-cache-preference',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (mentionsMultipleCacheLifeCalls(rec)) {
|
|
144
|
+
claims.push({
|
|
145
|
+
type: 'next_cache_life_single_execution',
|
|
146
|
+
rec,
|
|
147
|
+
framework,
|
|
148
|
+
frameworkVersion,
|
|
149
|
+
sourceField: 'next-cache-life-single-execution',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (mentionsCacheLifetimeChange(rec)) {
|
|
154
|
+
claims.push({
|
|
155
|
+
type: 'next_cache_lifetime_freshness_supported',
|
|
156
|
+
rec,
|
|
157
|
+
files: recommendationFiles(rec),
|
|
158
|
+
repoRoot,
|
|
159
|
+
projectRootDirectory,
|
|
160
|
+
sourceField: 'next-cache-lifetime-freshness',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (mentionsNextCacheComponentsStaticShellTarget(rec)) {
|
|
165
|
+
claims.push({
|
|
166
|
+
type: 'next_cache_components_route_chain_file',
|
|
167
|
+
rec,
|
|
168
|
+
framework,
|
|
169
|
+
frameworkVersion,
|
|
170
|
+
cacheComponents,
|
|
171
|
+
signals,
|
|
172
|
+
sourceField: 'next-cache-components-route-chain',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (mentionsCacheLifeCdnHeaderClaim(rec)) {
|
|
177
|
+
claims.push({
|
|
178
|
+
type: 'next_cache_life_cdn_header_semantics',
|
|
179
|
+
rec,
|
|
180
|
+
framework,
|
|
181
|
+
frameworkVersion,
|
|
182
|
+
sourceField: 'next-cache-life-cdn-header-semantics',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (mentionsImageResponseHeaders(rec)) {
|
|
187
|
+
claims.push({
|
|
188
|
+
type: 'image_response_headers_citation',
|
|
189
|
+
rec,
|
|
190
|
+
framework,
|
|
191
|
+
frameworkVersion,
|
|
192
|
+
sourceField: 'image-response-headers',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (mentionsNextImagePriorityRecommendation(rec)) {
|
|
197
|
+
claims.push({
|
|
198
|
+
type: 'next_image_priority_api_for_version',
|
|
199
|
+
rec,
|
|
200
|
+
framework,
|
|
201
|
+
frameworkVersion,
|
|
202
|
+
sourceField: 'next-image-priority-api',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (mentionsNextCacheComponentsRouteSegmentConfig(rec)) {
|
|
207
|
+
claims.push({
|
|
208
|
+
type: 'next_cache_components_route_segment_config',
|
|
209
|
+
rec,
|
|
210
|
+
framework,
|
|
211
|
+
frameworkVersion,
|
|
212
|
+
cacheComponents,
|
|
213
|
+
sourceField: 'next-route-segment-config',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (mentionsRouteLevelRevalidate(rec)) {
|
|
218
|
+
claims.push({
|
|
219
|
+
type: 'next_route_revalidate_static_prereq',
|
|
220
|
+
rec,
|
|
221
|
+
framework,
|
|
222
|
+
frameworkVersion,
|
|
223
|
+
cacheComponents,
|
|
224
|
+
repoRoot,
|
|
225
|
+
projectRootDirectory,
|
|
226
|
+
sourceField: 'next-route-revalidate-static-prereq',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (mentionsExistingCacheTagInvalidation(rec)) {
|
|
231
|
+
claims.push({
|
|
232
|
+
type: 'next_cache_tag_invalidation_supported',
|
|
233
|
+
rec,
|
|
234
|
+
repoRoot,
|
|
235
|
+
projectRootDirectory,
|
|
236
|
+
sourceField: 'next-cache-tag-invalidation',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (mentionsUnsafeImmutableDynamicRoute(rec)) {
|
|
241
|
+
claims.push({
|
|
242
|
+
type: 'immutable_dynamic_route_safety',
|
|
243
|
+
rec,
|
|
244
|
+
sourceField: 'immutable-dynamic-route',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (mentionsAuthSensitiveParallelization(rec)) {
|
|
249
|
+
claims.push({
|
|
250
|
+
type: 'auth_guard_parallelization_safety',
|
|
251
|
+
rec,
|
|
252
|
+
sourceField: 'auth-parallelization',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (mentionsParallelizationImpactOverclaim(rec)) {
|
|
257
|
+
claims.push({
|
|
258
|
+
type: 'parallelization_impact_not_overclaimed',
|
|
259
|
+
rec,
|
|
260
|
+
sourceField: 'parallelization-impact',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (mentionsCpuBoundParallelization(rec)) {
|
|
265
|
+
claims.push({
|
|
266
|
+
type: 'parallelization_not_cpu_bound_work',
|
|
267
|
+
rec,
|
|
268
|
+
sourceField: 'parallelization-cpu-bound',
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (mentionsRuntimeErrorCause(rec)) {
|
|
273
|
+
claims.push({
|
|
274
|
+
type: 'runtime_error_cause_supported',
|
|
275
|
+
rec,
|
|
276
|
+
sourceField: 'runtime-error-cause',
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (mentionsCatchToNotFound(rec)) {
|
|
281
|
+
claims.push({
|
|
282
|
+
type: 'route_error_not_found_status_and_scope',
|
|
283
|
+
rec,
|
|
284
|
+
sourceField: 'route-error-catch-safety',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (mentionsIgnoredBuildStepRecommendation(rec)) {
|
|
289
|
+
claims.push({
|
|
290
|
+
type: 'vercel_ignore_command_project_state',
|
|
291
|
+
rec,
|
|
292
|
+
signals,
|
|
293
|
+
sourceField: 'ignored-build-step-state',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (mentionsTurboBuildCacheRecommendation(rec)) {
|
|
298
|
+
claims.push({
|
|
299
|
+
type: 'turbo_build_cache_safety',
|
|
300
|
+
rec,
|
|
301
|
+
files: recommendationFiles(rec),
|
|
302
|
+
repoRoot,
|
|
303
|
+
projectRootDirectory,
|
|
304
|
+
framework,
|
|
305
|
+
sourceField: 'turbo-build-cache-safety',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const c of asArray(rec.verifiableClaims)) {
|
|
310
|
+
if (c && typeof c === 'object' && typeof c.type === 'string') {
|
|
311
|
+
claims.push({
|
|
312
|
+
...c,
|
|
313
|
+
repoRoot: c.repoRoot ?? repoRoot,
|
|
314
|
+
projectRootDirectory: c.projectRootDirectory ?? projectRootDirectory,
|
|
315
|
+
sourceField: 'verifiableClaims',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return claims;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeProjectRootDirectory(value) {
|
|
324
|
+
if (typeof value !== 'string' || value.trim() === '') return null;
|
|
325
|
+
return value.replace(/\\/g, '/').replace(/^\.\/+/, '').replace(/\/+$/, '');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function cacheRecommendationFiles(rec) {
|
|
329
|
+
if (!recommendsSharedCache(rec)) return [];
|
|
330
|
+
return recommendationFiles(rec);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isCacheCandidate(rec) {
|
|
334
|
+
return /^(?:uncached_route|cache_header_gap):/.test(String(rec?.candidateRef ?? ''));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function recommendationFiles(rec) {
|
|
338
|
+
const files = [
|
|
339
|
+
...asArray(rec.affectedFiles),
|
|
340
|
+
...asArray(rec.findingRefs)
|
|
341
|
+
.map((ref) => String(ref).match(/^(.+?):\d+$/)?.[1])
|
|
342
|
+
.filter(Boolean),
|
|
343
|
+
];
|
|
344
|
+
return Array.from(new Set(files));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function recommendsSharedCache(rec) {
|
|
348
|
+
const haystack = [
|
|
349
|
+
rec?.what,
|
|
350
|
+
rec?.why,
|
|
351
|
+
rec?.fix,
|
|
352
|
+
rec?.desiredBehavior,
|
|
353
|
+
rec?.verify,
|
|
354
|
+
].filter(Boolean).join('\n');
|
|
355
|
+
return /\b(?:s-maxage|CDN-Cache-Control|Vercel-CDN-Cache-Control|Cache-Control)\b/i.test(haystack);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function mentionsVaryHeader(rec) {
|
|
359
|
+
return /\bVary\b/i.test(recText(rec));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function mentionsNextCachedNotFound(rec) {
|
|
363
|
+
const haystack = recText(rec);
|
|
364
|
+
return /\bnotFound\b/.test(haystack) &&
|
|
365
|
+
/['"`]use cache['"`]|\buse cache\b/i.test(haystack) &&
|
|
366
|
+
/\b(?:500|5xx|error rate|errors?)\b/i.test(haystack);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function mentionsNextStableCacheApi(rec) {
|
|
370
|
+
const haystack = recText(rec);
|
|
371
|
+
return /\bunstable_(?:cacheLife|cacheTag)\b/.test(haystack) ||
|
|
372
|
+
/\brevalidateTag\s*\([^)]*['"`][^'"`]+['"`]\s*\)/.test(haystack);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function mentionsNext16RuntimeCacheApiMismatch(rec) {
|
|
376
|
+
const haystack = recText(rec);
|
|
377
|
+
const citations = asArray(rec?.citations).join('\n');
|
|
378
|
+
return /\bunstable_cache\b/.test(haystack) &&
|
|
379
|
+
(/\bRuntime Cache\b/i.test(haystack) || /vercel\.com\/docs\/caching\/runtime-cache/i.test(citations));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function mentionsRuntimeCacheWhenCacheComponents(rec) {
|
|
383
|
+
const haystack = recText(rec);
|
|
384
|
+
const citations = asArray(rec?.citations).join('\n');
|
|
385
|
+
return /\b(?:Runtime Cache|@vercel\/functions|getCache\s*\(|setCache\s*\()\b/i.test(haystack) ||
|
|
386
|
+
/vercel\.com\/docs\/caching\/runtime-cache/i.test(citations);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function mentionsMultipleCacheLifeCalls(rec) {
|
|
390
|
+
const haystack = recText(rec);
|
|
391
|
+
const matches = haystack.match(/\bcacheLife\s*\(/g) ?? [];
|
|
392
|
+
return matches.length > 1;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function mentionsCacheLifetimeChange(rec) {
|
|
396
|
+
return /\bcacheLife\s*\(/.test(recText(rec));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function mentionsCacheLifeCdnHeaderClaim(rec) {
|
|
400
|
+
const haystack = recText(rec);
|
|
401
|
+
if (!/\bcacheLife\b/.test(haystack)) return false;
|
|
402
|
+
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) ||
|
|
403
|
+
/\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) ||
|
|
404
|
+
/\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);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function mentionsNextCacheComponentsStaticShellTarget(rec) {
|
|
408
|
+
const haystack = recText(rec);
|
|
409
|
+
if (!/\b(?:cacheComponents|Cache Components|cacheLife|cacheTag|['"`]use cache['"`]|use cache|static shell|pre[- ]?render|prerender)\b/i.test(haystack)) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
const files = [
|
|
413
|
+
...asArray(rec?.affectedFiles),
|
|
414
|
+
...asArray(rec?.findingRefs).map((ref) => String(ref).match(/^(.+?):\d+$/)?.[1]).filter(Boolean),
|
|
415
|
+
];
|
|
416
|
+
return files.some((file) => /(^|\/)layout\.(?:tsx?|jsx?)$/.test(String(file)));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function mentionsImageResponseHeaders(rec) {
|
|
420
|
+
const haystack = recText(rec);
|
|
421
|
+
return /\bImageResponse\b/.test(haystack) &&
|
|
422
|
+
/\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);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function mentionsNextImagePriorityRecommendation(rec) {
|
|
426
|
+
const haystack = recText(rec);
|
|
427
|
+
if (!/\b(?:next\/image|<Image\b|Image component|image)\b/i.test(haystack)) return false;
|
|
428
|
+
if (!/\bpriority\b/i.test(haystack)) return false;
|
|
429
|
+
if (/\b(?:deprecated|replace|remove|avoid)\b[^.\n]{0,120}\bpriority\b/i.test(haystack) ||
|
|
430
|
+
/\bpriority\b[^.\n]{0,120}\b(?:deprecated|replace|remove|avoid)\b/i.test(haystack)) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
return /\b(?:set|add|use|enable|mark|make|turn on|with)\b[^.\n]{0,120}\bpriority\b/i.test(haystack) ||
|
|
434
|
+
/<Image\b[^>]*\bpriority(?:\s|=|>)/i.test(haystack);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function mentionsNextCacheComponentsRouteSegmentConfig(rec) {
|
|
438
|
+
const haystack = recText(rec);
|
|
439
|
+
return /\b(?:export\s+const\s+)?(?:dynamicParams|fetchCache)\s*=/.test(haystack) ||
|
|
440
|
+
/\bexport\s+const\s+(?:dynamic|revalidate)\b/.test(haystack) ||
|
|
441
|
+
/\b(?:set|add|configure|use)\s+[^.\n]{0,80}\b(?:dynamicParams|fetchCache)\b/i.test(haystack) ||
|
|
442
|
+
/\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) ||
|
|
443
|
+
/\b(?:revalidate|dynamic|fetchCache)\b[^.\n]{0,80}\broute segment (?:config|export)\b/i.test(haystack);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function mentionsRouteLevelRevalidate(rec) {
|
|
447
|
+
const haystack = recText(rec);
|
|
448
|
+
return /\bexport\s+const\s+revalidate\b/.test(haystack) ||
|
|
449
|
+
/\broute[- ]level\s+revalidate\b/i.test(haystack) ||
|
|
450
|
+
/\brevalidate\s*(?:=|:)\s*\d+\b[^.\n]{0,120}\b(?:page|layout|route segment|segment export)\b/i.test(haystack);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function mentionsExistingCacheTagInvalidation(rec) {
|
|
454
|
+
const haystack = recText(rec);
|
|
455
|
+
if (!/\bcacheTag\s*\(/.test(haystack)) return false;
|
|
456
|
+
if (!/\b(?:revalidateTag|updateTag|invalidate|invalidation|revalidation|webhook|CMS|content-sync|content sync|publish|deploy)\b/i.test(haystack)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
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) ||
|
|
460
|
+
/\b(?:invalidation|revalidation)\s+is\s+already\b/i.test(haystack) ||
|
|
461
|
+
/\balready\s+event-driven\b/i.test(haystack);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function mentionsUnsafeImmutableDynamicRoute(rec) {
|
|
465
|
+
const haystack = recText(rec);
|
|
466
|
+
if (!/\bimmutable\b/i.test(haystack)) return false;
|
|
467
|
+
const files = [
|
|
468
|
+
...asArray(rec?.affectedFiles),
|
|
469
|
+
...asArray(rec?.findingRefs).map((ref) => String(ref).match(/^(.+?):\d+$/)?.[1]).filter(Boolean),
|
|
470
|
+
];
|
|
471
|
+
const routeHandler = files.some((file) => /(?:^|\/)route\.[cm]?[jt]sx?$/.test(String(file)));
|
|
472
|
+
const apiRoute = /^cache_header_gap:\/api\//.test(String(rec?.candidateRef ?? ''));
|
|
473
|
+
return routeHandler || apiRoute;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function mentionsAuthSensitiveParallelization(rec) {
|
|
477
|
+
const haystack = recText(rec);
|
|
478
|
+
if (!/\b(?:parallelize|Promise\.all|run concurrently|start .* early)\b/i.test(haystack)) return false;
|
|
479
|
+
if (!/\b(?:auth|authorize|authorization|ownership|owns|owner|private|session|permission|access)\b/i.test(haystack)) return false;
|
|
480
|
+
return /\b(?:private|secret|token|registrant|account|user|ticket|payment|session)\w*\b/i.test(haystack);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function mentionsParallelizationImpactOverclaim(rec) {
|
|
484
|
+
const haystack = recText(rec);
|
|
485
|
+
if (!/\b(?:parallelize|Promise\.all|run concurrently|start .* early)\b/i.test(haystack)) return false;
|
|
486
|
+
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);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function mentionsCpuBoundParallelization(rec) {
|
|
490
|
+
const haystack = recText(rec);
|
|
491
|
+
if (!/\b(?:parallelize|Promise\.all|run concurrently|start .* early)\b/i.test(haystack)) return false;
|
|
492
|
+
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);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function mentionsCachedNotFoundOr404(rec) {
|
|
496
|
+
const haystack = recText(rec);
|
|
497
|
+
if (!/\b(?:s-maxage|CDN-Cache-Control|Vercel-CDN-Cache-Control|Cache-Control)\b/i.test(haystack)) return false;
|
|
498
|
+
return /\b(?:404|not[- ]found|notFound|not found branch|not-found branch)\b/i.test(haystack);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function mentionsRuntimeErrorCause(rec) {
|
|
502
|
+
if (!/^route_errors:/.test(String(rec?.candidateRef ?? ''))) return false;
|
|
503
|
+
const haystack = recText(rec);
|
|
504
|
+
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);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function mentionsCatchToNotFound(rec) {
|
|
508
|
+
if (!/^route_errors:/.test(String(rec?.candidateRef ?? ''))) return false;
|
|
509
|
+
const haystack = recText(rec);
|
|
510
|
+
return /\bcatch\b/i.test(haystack) &&
|
|
511
|
+
/\b(?:404|not[- ]found|not found|notFound)\b/i.test(haystack);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function mentionsIgnoredBuildStepRecommendation(rec) {
|
|
515
|
+
const haystack = recText(rec);
|
|
516
|
+
return /\b(?:Ignored Build Step|ignoreCommand|turbo-ignore|skip unaffected|unaffected projects?)\b/i.test(haystack) &&
|
|
517
|
+
/\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);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function mentionsTurboBuildCacheRecommendation(rec) {
|
|
521
|
+
const haystack = recText(rec);
|
|
522
|
+
if (!/\b(?:Turbo|Turborepo|turbo\.json|tasks\.build|build cache|build caching)\b/i.test(haystack)) return false;
|
|
523
|
+
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) ||
|
|
524
|
+
/\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);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function recText(rec) {
|
|
528
|
+
return [
|
|
529
|
+
rec?.what,
|
|
530
|
+
rec?.why,
|
|
531
|
+
rec?.fix,
|
|
532
|
+
rec?.currentBehavior,
|
|
533
|
+
rec?.desiredBehavior,
|
|
534
|
+
rec?.verify,
|
|
535
|
+
].filter(Boolean).join('\n');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function asArray(v) {
|
|
539
|
+
return Array.isArray(v) ? v : [];
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function summarizeClaimResults(results) {
|
|
543
|
+
const counts = { verified: 0, failed: 0, unsupported: 0, unverifiable: 0 };
|
|
544
|
+
for (const r of results) {
|
|
545
|
+
if (r?.disposition && counts[r.disposition] !== undefined) counts[r.disposition]++;
|
|
546
|
+
}
|
|
547
|
+
const verifiable = counts.verified + counts.failed;
|
|
548
|
+
const passRate = verifiable > 0 ? counts.verified / verifiable : 1;
|
|
549
|
+
return { ...counts, verifiable, passRate, total: results.length };
|
|
550
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const CORE_SUPPORTED_FRAMEWORKS = ['next', 'sveltekit', 'nuxt'];
|
|
2
|
+
export const LIMITED_FRAMEWORKS = ['astro'];
|
|
3
|
+
|
|
4
|
+
const LABELS = {
|
|
5
|
+
next: 'Next.js',
|
|
6
|
+
sveltekit: 'SvelteKit',
|
|
7
|
+
nuxt: 'Nuxt',
|
|
8
|
+
astro: 'Astro',
|
|
9
|
+
hono: 'Hono',
|
|
10
|
+
remix: 'Remix',
|
|
11
|
+
unknown: 'unknown framework',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function frameworkLabel(framework) {
|
|
15
|
+
return LABELS[normalizeFramework(framework)] ?? String(framework ?? 'unknown');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function classifyFrameworkSupport(stack = {}) {
|
|
19
|
+
const framework = normalizeFramework(stack.framework);
|
|
20
|
+
const label = frameworkLabel(framework);
|
|
21
|
+
const supportedLabels = CORE_SUPPORTED_FRAMEWORKS.map(frameworkLabel);
|
|
22
|
+
const limitedLabels = LIMITED_FRAMEWORKS.map(frameworkLabel);
|
|
23
|
+
|
|
24
|
+
if (CORE_SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
status: 'supported',
|
|
28
|
+
blocker: null,
|
|
29
|
+
framework,
|
|
30
|
+
label,
|
|
31
|
+
supportedFrameworks: supportedLabels,
|
|
32
|
+
limitedFrameworks: limitedLabels,
|
|
33
|
+
detail: `${label} is supported for metric-backed route-to-file investigations.`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (LIMITED_FRAMEWORKS.includes(framework)) {
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
status: 'limited',
|
|
41
|
+
blocker: null,
|
|
42
|
+
framework,
|
|
43
|
+
label,
|
|
44
|
+
supportedFrameworks: supportedLabels,
|
|
45
|
+
limitedFrameworks: limitedLabels,
|
|
46
|
+
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.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
status: 'unsupported',
|
|
53
|
+
blocker: 'unsupported_framework',
|
|
54
|
+
framework,
|
|
55
|
+
label,
|
|
56
|
+
supportedFrameworks: supportedLabels,
|
|
57
|
+
limitedFrameworks: limitedLabels,
|
|
58
|
+
detail: `${label} is not supported for metric-backed route-to-file investigations. Supported frameworks: ${supportedLabels.join(', ')}. Limited support: ${limitedLabels.join(', ')}.`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeFramework(value) {
|
|
63
|
+
const raw = String(value ?? 'unknown').trim().toLowerCase();
|
|
64
|
+
if (raw === 'nextjs' || raw === 'next.js') return 'next';
|
|
65
|
+
if (raw === 'svelte' || raw === 'svelte-kit') return 'sveltekit';
|
|
66
|
+
return raw || 'unknown';
|
|
67
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Build Minutes climb on monorepos when Turborepo cache is bypassed or every project rebuilds on every commit.
|
|
2
|
+
// Threshold: Build Minutes line > 15% of total bill OR scanner emits any turbo-force-bypass finding (even at lower share).
|
|
3
|
+
// Account-scoped because the lever is project-settings (Ignored Build Step, Elastic Build Machines), not code.
|
|
4
|
+
export const metadata = {
|
|
5
|
+
id: 'build_minutes_fanout',
|
|
6
|
+
threshold: 'Build Minutes share > 0.15 OR turbo-force-bypass finding present',
|
|
7
|
+
billingDimension: 'build',
|
|
8
|
+
scope: 'account',
|
|
9
|
+
sourceCitation: 'vercel-optimize gate threshold',
|
|
10
|
+
description:
|
|
11
|
+
'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.',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const BUILD_RE = /^Build (CPU )?Minutes$/i;
|
|
15
|
+
const SCANNER_PATTERN = 'turbo-force-bypass';
|
|
16
|
+
const SHARE_FLOOR = 0.15;
|
|
17
|
+
|
|
18
|
+
export function gate(signals) {
|
|
19
|
+
const services = signals?.usage?.services;
|
|
20
|
+
const total = Array.isArray(services)
|
|
21
|
+
? services.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0)
|
|
22
|
+
: 0;
|
|
23
|
+
const buildBilled = Array.isArray(services)
|
|
24
|
+
? services
|
|
25
|
+
.filter((s) => BUILD_RE.test(String(s?.name ?? '')))
|
|
26
|
+
.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0)
|
|
27
|
+
: 0;
|
|
28
|
+
|
|
29
|
+
const buildShare = total > 0 ? buildBilled / total : 0;
|
|
30
|
+
|
|
31
|
+
const findings = (signals?.codebase?.findings ?? []).filter((f) => f.pattern === SCANNER_PATTERN);
|
|
32
|
+
|
|
33
|
+
if (buildShare <= SHARE_FLOOR && findings.length === 0) return [];
|
|
34
|
+
|
|
35
|
+
const subtypes = unique(findings.map((f) => f.subtype).filter(Boolean));
|
|
36
|
+
const sampleFiles = unique(findings.map((f) => f.file).filter(Boolean)).slice(0, 4);
|
|
37
|
+
|
|
38
|
+
const reason = findings.length > 0
|
|
39
|
+
? (buildShare > SHARE_FLOOR
|
|
40
|
+
? 'Build Minutes share is high and Turborepo cache bypass detected in repo'
|
|
41
|
+
: 'Turborepo cache bypass detected in repo')
|
|
42
|
+
: 'Build Minutes line exceeds 15% of total billed cost';
|
|
43
|
+
|
|
44
|
+
return [{
|
|
45
|
+
kind: metadata.id,
|
|
46
|
+
scope: 'account',
|
|
47
|
+
files: sampleFiles,
|
|
48
|
+
priority: findings.length > 0 ? 65 : 50,
|
|
49
|
+
confidence: findings.length > 0 ? 0.86 : 0.74,
|
|
50
|
+
o11ySignal: `build_minutes_share=${(buildShare * 100).toFixed(0)}% scanner_findings=${findings.length}`,
|
|
51
|
+
reason,
|
|
52
|
+
question: findings.length > 0
|
|
53
|
+
? `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?`
|
|
54
|
+
: '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?',
|
|
55
|
+
evidence: {
|
|
56
|
+
metric: 'usage.services',
|
|
57
|
+
buildBilled,
|
|
58
|
+
totalBilled: total,
|
|
59
|
+
buildShare,
|
|
60
|
+
scannerFindings: findings.length,
|
|
61
|
+
scannerSubtypes: subtypes,
|
|
62
|
+
sampleFiles,
|
|
63
|
+
},
|
|
64
|
+
}];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unique(values) {
|
|
68
|
+
return [...new Set(values)];
|
|
69
|
+
}
|