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.
Files changed (238) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +15 -1
  2. package/bundled-skills/accesslint-audit/SKILL.md +115 -0
  3. package/bundled-skills/accesslint-diff/SKILL.md +81 -0
  4. package/bundled-skills/accesslint-scan/SKILL.md +47 -0
  5. package/bundled-skills/composition-patterns/SKILL.md +87 -0
  6. package/bundled-skills/composition-patterns/rules/_sections.md +29 -0
  7. package/bundled-skills/composition-patterns/rules/_template.md +24 -0
  8. package/bundled-skills/composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
  9. package/bundled-skills/composition-patterns/rules/architecture-compound-components.md +112 -0
  10. package/bundled-skills/composition-patterns/rules/patterns-children-over-render-props.md +87 -0
  11. package/bundled-skills/composition-patterns/rules/patterns-explicit-variants.md +100 -0
  12. package/bundled-skills/composition-patterns/rules/react19-no-forwardref.md +42 -0
  13. package/bundled-skills/composition-patterns/rules/state-context-interface.md +191 -0
  14. package/bundled-skills/composition-patterns/rules/state-decouple-implementation.md +113 -0
  15. package/bundled-skills/composition-patterns/rules/state-lift-state.md +125 -0
  16. package/bundled-skills/debugging-toolkit/SKILL.md +35 -0
  17. package/bundled-skills/deploy-to-vercel/SKILL.md +304 -0
  18. package/bundled-skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
  19. package/bundled-skills/deploy-to-vercel/resources/deploy.sh +301 -0
  20. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  21. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  22. package/bundled-skills/docs/maintainers/backups/README-2026-06-02.md +687 -0
  23. package/bundled-skills/docs/maintainers/repo-growth-seo.md +4 -4
  24. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  25. package/bundled-skills/docs/users/bundles.md +245 -1
  26. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  27. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  28. package/bundled-skills/docs/users/getting-started.md +1 -1
  29. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  30. package/bundled-skills/docs/users/plugins.md +21 -13
  31. package/bundled-skills/docs/users/specialized-plugin-roadmap.md +95 -0
  32. package/bundled-skills/docs/users/usage.md +4 -4
  33. package/bundled-skills/docs/users/visual-guide.md +4 -4
  34. package/bundled-skills/polis-protocol/SKILL.md +93 -0
  35. package/bundled-skills/python-development/SKILL.md +35 -0
  36. package/bundled-skills/radix-ui-design-system/SKILL.md +2 -2
  37. package/bundled-skills/react-native-skills/SKILL.md +120 -0
  38. package/bundled-skills/react-native-skills/rules/_sections.md +86 -0
  39. package/bundled-skills/react-native-skills/rules/_template.md +28 -0
  40. package/bundled-skills/react-native-skills/rules/animation-derived-value.md +53 -0
  41. package/bundled-skills/react-native-skills/rules/animation-gesture-detector-press.md +95 -0
  42. package/bundled-skills/react-native-skills/rules/animation-gpu-properties.md +65 -0
  43. package/bundled-skills/react-native-skills/rules/design-system-compound-components.md +66 -0
  44. package/bundled-skills/react-native-skills/rules/fonts-config-plugin.md +71 -0
  45. package/bundled-skills/react-native-skills/rules/imports-design-system-folder.md +68 -0
  46. package/bundled-skills/react-native-skills/rules/js-hoist-intl.md +61 -0
  47. package/bundled-skills/react-native-skills/rules/list-performance-callbacks.md +44 -0
  48. package/bundled-skills/react-native-skills/rules/list-performance-function-references.md +132 -0
  49. package/bundled-skills/react-native-skills/rules/list-performance-images.md +53 -0
  50. package/bundled-skills/react-native-skills/rules/list-performance-inline-objects.md +97 -0
  51. package/bundled-skills/react-native-skills/rules/list-performance-item-expensive.md +94 -0
  52. package/bundled-skills/react-native-skills/rules/list-performance-item-memo.md +82 -0
  53. package/bundled-skills/react-native-skills/rules/list-performance-item-types.md +104 -0
  54. package/bundled-skills/react-native-skills/rules/list-performance-virtualize.md +67 -0
  55. package/bundled-skills/react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
  56. package/bundled-skills/react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
  57. package/bundled-skills/react-native-skills/rules/navigation-native-navigators.md +188 -0
  58. package/bundled-skills/react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
  59. package/bundled-skills/react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
  60. package/bundled-skills/react-native-skills/rules/react-state-dispatcher.md +91 -0
  61. package/bundled-skills/react-native-skills/rules/react-state-fallback.md +56 -0
  62. package/bundled-skills/react-native-skills/rules/react-state-minimize.md +65 -0
  63. package/bundled-skills/react-native-skills/rules/rendering-no-falsy-and.md +74 -0
  64. package/bundled-skills/react-native-skills/rules/rendering-text-in-text-component.md +36 -0
  65. package/bundled-skills/react-native-skills/rules/scroll-position-no-state.md +82 -0
  66. package/bundled-skills/react-native-skills/rules/state-ground-truth.md +80 -0
  67. package/bundled-skills/react-native-skills/rules/ui-expo-image.md +66 -0
  68. package/bundled-skills/react-native-skills/rules/ui-image-gallery.md +104 -0
  69. package/bundled-skills/react-native-skills/rules/ui-measure-views.md +78 -0
  70. package/bundled-skills/react-native-skills/rules/ui-menus.md +174 -0
  71. package/bundled-skills/react-native-skills/rules/ui-native-modals.md +77 -0
  72. package/bundled-skills/react-native-skills/rules/ui-pressable.md +61 -0
  73. package/bundled-skills/react-native-skills/rules/ui-safe-area-scroll.md +65 -0
  74. package/bundled-skills/react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
  75. package/bundled-skills/react-native-skills/rules/ui-styling.md +87 -0
  76. package/bundled-skills/skill-issue/SKILL.md +73 -0
  77. package/bundled-skills/tdd-workflows/SKILL.md +35 -0
  78. package/bundled-skills/vercel-cli-with-tokens/SKILL.md +361 -0
  79. package/bundled-skills/vercel-optimize/CONTRIBUTING.md +41 -0
  80. package/bundled-skills/vercel-optimize/SKILL.md +331 -0
  81. package/bundled-skills/vercel-optimize/lib/auth-route.mjs +23 -0
  82. package/bundled-skills/vercel-optimize/lib/budget-summary.mjs +182 -0
  83. package/bundled-skills/vercel-optimize/lib/citations.mjs +139 -0
  84. package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +143 -0
  85. package/bundled-skills/vercel-optimize/lib/dedup-recs.mjs +325 -0
  86. package/bundled-skills/vercel-optimize/lib/deep-dive.mjs +350 -0
  87. package/bundled-skills/vercel-optimize/lib/display-labels.mjs +185 -0
  88. package/bundled-skills/vercel-optimize/lib/extract-claims.mjs +550 -0
  89. package/bundled-skills/vercel-optimize/lib/framework-support.mjs +67 -0
  90. package/bundled-skills/vercel-optimize/lib/gates/build-minutes-fanout.mjs +69 -0
  91. package/bundled-skills/vercel-optimize/lib/gates/cold-start.mjs +66 -0
  92. package/bundled-skills/vercel-optimize/lib/gates/contract.mjs +79 -0
  93. package/bundled-skills/vercel-optimize/lib/gates/cwv-poor.mjs +87 -0
  94. package/bundled-skills/vercel-optimize/lib/gates/external-api-slow.mjs +55 -0
  95. package/bundled-skills/vercel-optimize/lib/gates/hard-gates.mjs +73 -0
  96. package/bundled-skills/vercel-optimize/lib/gates/index.mjs +45 -0
  97. package/bundled-skills/vercel-optimize/lib/gates/isr-overrevalidation.mjs +62 -0
  98. package/bundled-skills/vercel-optimize/lib/gates/middleware-heavy.mjs +51 -0
  99. package/bundled-skills/vercel-optimize/lib/gates/observability-events-attribution.mjs +56 -0
  100. package/bundled-skills/vercel-optimize/lib/gates/platform-bot-protection.mjs +115 -0
  101. package/bundled-skills/vercel-optimize/lib/gates/platform-fluid-compute.mjs +83 -0
  102. package/bundled-skills/vercel-optimize/lib/gates/region-misconfig.mjs +64 -0
  103. package/bundled-skills/vercel-optimize/lib/gates/route-errors.mjs +80 -0
  104. package/bundled-skills/vercel-optimize/lib/gates/scanner-driven.mjs +122 -0
  105. package/bundled-skills/vercel-optimize/lib/gates/select-candidates.mjs +134 -0
  106. package/bundled-skills/vercel-optimize/lib/gates/slow-route.mjs +88 -0
  107. package/bundled-skills/vercel-optimize/lib/gates/types.d.ts +38 -0
  108. package/bundled-skills/vercel-optimize/lib/gates/uncached-route.mjs +93 -0
  109. package/bundled-skills/vercel-optimize/lib/gates/usage-spike-triage.mjs +121 -0
  110. package/bundled-skills/vercel-optimize/lib/grade-recommendation.mjs +155 -0
  111. package/bundled-skills/vercel-optimize/lib/impact-label.mjs +126 -0
  112. package/bundled-skills/vercel-optimize/lib/impact-magnitude.mjs +60 -0
  113. package/bundled-skills/vercel-optimize/lib/investigation-brief.mjs +610 -0
  114. package/bundled-skills/vercel-optimize/lib/observation-safety.mjs +174 -0
  115. package/bundled-skills/vercel-optimize/lib/project-facts.mjs +99 -0
  116. package/bundled-skills/vercel-optimize/lib/queries.mjs +315 -0
  117. package/bundled-skills/vercel-optimize/lib/reconcile-candidates.mjs +372 -0
  118. package/bundled-skills/vercel-optimize/lib/render-report.mjs +955 -0
  119. package/bundled-skills/vercel-optimize/lib/repo-root.mjs +86 -0
  120. package/bundled-skills/vercel-optimize/lib/route-normalize.mjs +220 -0
  121. package/bundled-skills/vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs +38 -0
  122. package/bundled-skills/vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs +30 -0
  123. package/bundled-skills/vercel-optimize/lib/sanitizers/count-correct.mjs +52 -0
  124. package/bundled-skills/vercel-optimize/lib/sanitizers/function-duration-invocations.mjs +38 -0
  125. package/bundled-skills/vercel-optimize/lib/sanitizers/index.mjs +79 -0
  126. package/bundled-skills/vercel-optimize/lib/sanitizers/middleware-conflict.mjs +36 -0
  127. package/bundled-skills/vercel-optimize/lib/sanitizers/missing-citation.mjs +16 -0
  128. package/bundled-skills/vercel-optimize/lib/sanitizers/pre-release.mjs +74 -0
  129. package/bundled-skills/vercel-optimize/lib/sanitizers/rate-limit.mjs +67 -0
  130. package/bundled-skills/vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs +38 -0
  131. package/bundled-skills/vercel-optimize/lib/sanitizers/undeclared-dep.mjs +78 -0
  132. package/bundled-skills/vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs +37 -0
  133. package/bundled-skills/vercel-optimize/lib/sanitizers/window-units.mjs +32 -0
  134. package/bundled-skills/vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs +109 -0
  135. package/bundled-skills/vercel-optimize/lib/scanners/edge-heavy-import.mjs +94 -0
  136. package/bundled-skills/vercel-optimize/lib/scanners/force-dynamic.mjs +42 -0
  137. package/bundled-skills/vercel-optimize/lib/scanners/headers-in-page.mjs +44 -0
  138. package/bundled-skills/vercel-optimize/lib/scanners/index.mjs +35 -0
  139. package/bundled-skills/vercel-optimize/lib/scanners/large-static-asset.mjs +92 -0
  140. package/bundled-skills/vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs +42 -0
  141. package/bundled-skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs +55 -0
  142. package/bundled-skills/vercel-optimize/lib/scanners/missing-cache-headers.mjs +90 -0
  143. package/bundled-skills/vercel-optimize/lib/scanners/prisma-include-tree.mjs +42 -0
  144. package/bundled-skills/vercel-optimize/lib/scanners/region-pin-in-config.mjs +88 -0
  145. package/bundled-skills/vercel-optimize/lib/scanners/source-maps-production.mjs +36 -0
  146. package/bundled-skills/vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs +43 -0
  147. package/bundled-skills/vercel-optimize/lib/scanners/turbo-force-bypass.mjs +129 -0
  148. package/bundled-skills/vercel-optimize/lib/scanners/unoptimized-image.mjs +113 -0
  149. package/bundled-skills/vercel-optimize/lib/scanners/use-cache-date-stamp.mjs +106 -0
  150. package/bundled-skills/vercel-optimize/lib/support-topics.mjs +355 -0
  151. package/bundled-skills/vercel-optimize/lib/throttle.mjs +273 -0
  152. package/bundled-skills/vercel-optimize/lib/util.mjs +17 -0
  153. package/bundled-skills/vercel-optimize/lib/vercel.mjs +784 -0
  154. package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +1296 -0
  155. package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +521 -0
  156. package/bundled-skills/vercel-optimize/references/candidates.md +176 -0
  157. package/bundled-skills/vercel-optimize/references/data-collection.md +218 -0
  158. package/bundled-skills/vercel-optimize/references/docs-library.json +683 -0
  159. package/bundled-skills/vercel-optimize/references/doctrine.md +105 -0
  160. package/bundled-skills/vercel-optimize/references/observability-plus.md +108 -0
  161. package/bundled-skills/vercel-optimize/references/playbooks/README.md +53 -0
  162. package/bundled-skills/vercel-optimize/references/playbooks/ai-application.md +32 -0
  163. package/bundled-skills/vercel-optimize/references/playbooks/api-service.md +30 -0
  164. package/bundled-skills/vercel-optimize/references/playbooks/content-site.md +30 -0
  165. package/bundled-skills/vercel-optimize/references/playbooks/ecommerce.md +30 -0
  166. package/bundled-skills/vercel-optimize/references/playbooks/marketing.md +30 -0
  167. package/bundled-skills/vercel-optimize/references/playbooks/saas.md +31 -0
  168. package/bundled-skills/vercel-optimize/references/playbooks/sveltekit.md +75 -0
  169. package/bundled-skills/vercel-optimize/references/recommendations.md +203 -0
  170. package/bundled-skills/vercel-optimize/references/scanner-patterns.md +251 -0
  171. package/bundled-skills/vercel-optimize/references/scoring.md +205 -0
  172. package/bundled-skills/vercel-optimize/references/support-topics/README.md +46 -0
  173. package/bundled-skills/vercel-optimize/references/support-topics/astro-edge-middleware-scope.md +22 -0
  174. package/bundled-skills/vercel-optimize/references/support-topics/astro-output-mode-and-isr.md +22 -0
  175. package/bundled-skills/vercel-optimize/references/support-topics/auth-preserving-parallelization.md +22 -0
  176. package/bundled-skills/vercel-optimize/references/support-topics/bot-protection-product-guardrails.md +22 -0
  177. package/bundled-skills/vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md +23 -0
  178. package/bundled-skills/vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md +22 -0
  179. package/bundled-skills/vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md +23 -0
  180. package/bundled-skills/vercel-optimize/references/support-topics/cdn-cache-auth-safety.md +22 -0
  181. package/bundled-skills/vercel-optimize/references/support-topics/cold-start-initialization-bundle.md +22 -0
  182. package/bundled-skills/vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md +22 -0
  183. package/bundled-skills/vercel-optimize/references/support-topics/database-egress-pooling-region.md +22 -0
  184. package/bundled-skills/vercel-optimize/references/support-topics/dynamic-rendering-traps.md +22 -0
  185. package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path-platform.md +22 -0
  186. package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path.md +22 -0
  187. package/bundled-skills/vercel-optimize/references/support-topics/fast-data-transfer-payloads.md +22 -0
  188. package/bundled-skills/vercel-optimize/references/support-topics/fluid-compute-caveats.md +22 -0
  189. package/bundled-skills/vercel-optimize/references/support-topics/function-duration-io-and-after.md +22 -0
  190. package/bundled-skills/vercel-optimize/references/support-topics/function-invocation-reduction.md +22 -0
  191. package/bundled-skills/vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md +23 -0
  192. package/bundled-skills/vercel-optimize/references/support-topics/image-optimization-cost-control.md +22 -0
  193. package/bundled-skills/vercel-optimize/references/support-topics/isr-revalidation-static-generation.md +22 -0
  194. package/bundled-skills/vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md +22 -0
  195. package/bundled-skills/vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md +22 -0
  196. package/bundled-skills/vercel-optimize/references/support-topics/next-font-cls-self-hosting.md +23 -0
  197. package/bundled-skills/vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md +23 -0
  198. package/bundled-skills/vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md +23 -0
  199. package/bundled-skills/vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md +22 -0
  200. package/bundled-skills/vercel-optimize/references/support-topics/next-script-third-party-strategy.md +23 -0
  201. package/bundled-skills/vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md +22 -0
  202. package/bundled-skills/vercel-optimize/references/support-topics/not-found-catchall-request-waste.md +23 -0
  203. package/bundled-skills/vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md +22 -0
  204. package/bundled-skills/vercel-optimize/references/support-topics/observability-events-cost-attribution.md +22 -0
  205. package/bundled-skills/vercel-optimize/references/support-topics/post-response-work-waituntil.md +22 -0
  206. package/bundled-skills/vercel-optimize/references/support-topics/route-error-durable-offload.md +22 -0
  207. package/bundled-skills/vercel-optimize/references/support-topics/route-error-runtime-limits.md +22 -0
  208. package/bundled-skills/vercel-optimize/references/support-topics/runtime-cache-reusable-data.md +22 -0
  209. package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md +22 -0
  210. package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md +22 -0
  211. package/bundled-skills/vercel-optimize/references/support-topics/usage-spike-triage.md +22 -0
  212. package/bundled-skills/vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md +23 -0
  213. package/bundled-skills/vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md +22 -0
  214. package/bundled-skills/vercel-optimize/references/support-topics/workflow-resumable-stream-routes.md +23 -0
  215. package/bundled-skills/vercel-optimize/references/verification.md +102 -0
  216. package/bundled-skills/vercel-optimize/references/voice.md +76 -0
  217. package/bundled-skills/vercel-optimize/scripts/budget-summary.mjs +56 -0
  218. package/bundled-skills/vercel-optimize/scripts/build-docs.mjs +74 -0
  219. package/bundled-skills/vercel-optimize/scripts/check-citations.mjs +81 -0
  220. package/bundled-skills/vercel-optimize/scripts/check-docs-fresh.mjs +93 -0
  221. package/bundled-skills/vercel-optimize/scripts/collect-signals.mjs +576 -0
  222. package/bundled-skills/vercel-optimize/scripts/collect-sub-agent-outputs.mjs +296 -0
  223. package/bundled-skills/vercel-optimize/scripts/deep-dive.mjs +319 -0
  224. package/bundled-skills/vercel-optimize/scripts/gate-investigations.mjs +166 -0
  225. package/bundled-skills/vercel-optimize/scripts/merge-signals.mjs +192 -0
  226. package/bundled-skills/vercel-optimize/scripts/prepare-investigation-brief.mjs +231 -0
  227. package/bundled-skills/vercel-optimize/scripts/reconcile-candidates.mjs +62 -0
  228. package/bundled-skills/vercel-optimize/scripts/render-report.mjs +437 -0
  229. package/bundled-skills/vercel-optimize/scripts/scan-codebase.mjs +313 -0
  230. package/bundled-skills/vercel-optimize/scripts/verify-and-regen.mjs +346 -0
  231. package/bundled-skills/vercel-optimize/scripts/verify-finding.mjs +19 -0
  232. package/bundled-skills/vercel-react-view-transitions/SKILL.md +327 -0
  233. package/bundled-skills/vercel-react-view-transitions/references/css-recipes.md +242 -0
  234. package/bundled-skills/vercel-react-view-transitions/references/implementation.md +182 -0
  235. package/bundled-skills/vercel-react-view-transitions/references/nextjs.md +176 -0
  236. package/bundled-skills/vercel-react-view-transitions/references/patterns.md +262 -0
  237. package/package.json +2 -2
  238. package/skills_index.json +312 -0
@@ -0,0 +1,521 @@
1
+ // Resolve workspace-package imports to actual source files. Sub-agents need this when the route file is a thin shell that re-exports from a workspace package.
2
+ //
3
+ // Bounded expansion keeps the brief allowlist small: package export resolution, pure-barrel
4
+ // traversal, and suffix fan-out for likely data-loading modules. This stays string-based
5
+ // and falls through ("couldn't resolve") on shapes that need a full TS resolver.
6
+
7
+ import { readFile, readdir, stat } from 'node:fs/promises';
8
+ import { dirname, join, resolve as pathResolve } from 'node:path';
9
+
10
+ const DEFAULT_RESOLVE_OPTIONS = {
11
+ pureBarrelDepth: 3,
12
+ suffixFanoutDepth: 2,
13
+ perSpecifierCap: 3,
14
+ };
15
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
16
+ const EXTENSIONS = ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
17
+ const INDEX_FILES = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.mjs'];
18
+ const SUFFIX_FANOUT_RE = /(^|\/)(content|data|loader|fetch|service|metadata|actions)\.tsx?$/;
19
+ const EXPORT_FORWARD_RE = /export\s+(?:type\s+)?(?:\*|\*\s+as\s+[A-Za-z_$][\w$]*|\{[^}]*\})\s+from\s+['"][^'"\n]+['"]\s*;?/gs;
20
+
21
+ export async function detectMonorepoRoot(startDir) {
22
+ let dir = pathResolve(startDir);
23
+ for (let depth = 0; depth < 15; depth++) {
24
+ if (await fileExists(join(dir, 'pnpm-workspace.yaml'))) return dir;
25
+ const pkg = await tryReadJson(join(dir, 'package.json'));
26
+ if (pkg && (Array.isArray(pkg.workspaces) || Array.isArray(pkg.workspaces?.packages))) {
27
+ return dir;
28
+ }
29
+ const parent = dirname(dir);
30
+ if (parent === dir) return null;
31
+ dir = parent;
32
+ }
33
+ return null;
34
+ }
35
+
36
+ // Zero-dependency — pnpm-workspace.yaml shape is predictable; not pulling in js-yaml.
37
+ export async function readWorkspaceGlobs(monorepoRoot) {
38
+ const pnpmPath = join(monorepoRoot, 'pnpm-workspace.yaml');
39
+ if (await fileExists(pnpmPath)) {
40
+ const text = await readFile(pnpmPath, 'utf-8');
41
+ return parsePnpmWorkspaceYaml(text);
42
+ }
43
+ const pkg = await tryReadJson(join(monorepoRoot, 'package.json'));
44
+ if (Array.isArray(pkg?.workspaces)) return pkg.workspaces;
45
+ if (Array.isArray(pkg?.workspaces?.packages)) return pkg.workspaces.packages;
46
+ return [];
47
+ }
48
+
49
+ // Handles `packages:` block with `- glob` entries. Not full YAML grammar.
50
+ export function parsePnpmWorkspaceYaml(text) {
51
+ const out = [];
52
+ let inPackages = false;
53
+ for (const rawLine of text.split('\n')) {
54
+ const line = rawLine.replace(/#.*$/, '').trimEnd();
55
+ if (!line.trim()) continue;
56
+ if (/^packages\s*:/.test(line)) { inPackages = true; continue; }
57
+ if (!inPackages) continue;
58
+ if (!/^\s/.test(line)) { inPackages = false; continue; }
59
+ const m = line.match(/^\s*-\s+['"]?([^'"\s]+)['"]?\s*$/);
60
+ if (m) out.push(m[1]);
61
+ }
62
+ return out;
63
+ }
64
+
65
+ export async function listWorkspacePackages(monorepoRoot) {
66
+ const globs = await readWorkspaceGlobs(monorepoRoot);
67
+ const dirs = new Set();
68
+ for (const g of globs) {
69
+ const expanded = await expandWorkspaceGlob(monorepoRoot, g);
70
+ for (const d of expanded) dirs.add(d);
71
+ }
72
+ const out = [];
73
+ for (const dir of dirs) {
74
+ const pkg = await tryReadJson(join(dir, 'package.json'));
75
+ if (pkg?.name) out.push({ name: pkg.name, dir, pkg });
76
+ }
77
+ return out.sort((a, b) => a.name.localeCompare(b.name));
78
+ }
79
+
80
+ // Handles workspace-shape globs only. `**` collapses to one level — npm/pnpm don't document deep `**`.
81
+ async function expandWorkspaceGlob(root, glob) {
82
+ const parts = glob.replace(/\\/g, '/').split('/');
83
+ return await expandParts(root, parts);
84
+ }
85
+
86
+ async function expandParts(currentDir, parts) {
87
+ if (parts.length === 0) return [currentDir];
88
+ const [head, ...rest] = parts;
89
+ if (head === '' || head === '.') return await expandParts(currentDir, rest);
90
+ if (head === '*' || head === '**') {
91
+ let entries = [];
92
+ try {
93
+ entries = await readdir(currentDir, { withFileTypes: true });
94
+ } catch { return []; }
95
+ const childDirs = entries.filter((e) => e.isDirectory()).map((e) => join(currentDir, e.name));
96
+ const out = [];
97
+ for (const d of childDirs) {
98
+ const more = await expandParts(d, rest);
99
+ out.push(...more);
100
+ }
101
+ return out;
102
+ }
103
+ const next = join(currentDir, head);
104
+ try {
105
+ const s = await stat(next);
106
+ if (!s.isDirectory()) return [];
107
+ } catch {
108
+ return [];
109
+ }
110
+ return await expandParts(next, rest);
111
+ }
112
+
113
+ export function buildResolver(packages) {
114
+ const byName = new Map();
115
+ for (const p of packages) {
116
+ byName.set(p.name, buildPackageLookup(p));
117
+ }
118
+ return function resolveSpecifier(specifier) {
119
+ if (typeof specifier !== 'string' || !specifier.length) return null;
120
+ // Longest-name match first so `@vercel/foo-bar` wins over `@vercel/foo`.
121
+ const candidates = [...byName.keys()]
122
+ .filter((name) => specifier === name || specifier.startsWith(name + '/'))
123
+ .sort((a, b) => b.length - a.length);
124
+ if (candidates.length === 0) return null;
125
+ const pkgName = candidates[0];
126
+ const subpath = specifier === pkgName ? '.' : './' + specifier.slice(pkgName.length + 1);
127
+ const lookup = byName.get(pkgName);
128
+ return lookup.resolveSubpath(subpath);
129
+ };
130
+ }
131
+
132
+ // Node spec: pattern key has exactly one `*`; target may have one or zero.
133
+ function buildPackageLookup(p) {
134
+ const exact = new Map();
135
+ const wildcards = [];
136
+ const exports = p.pkg.exports;
137
+ if (exports && typeof exports === 'object' && !Array.isArray(exports)) {
138
+ for (const [key, value] of Object.entries(exports)) {
139
+ const target = pickConditionalTarget(value);
140
+ if (typeof target !== 'string') continue;
141
+ if (key.includes('*')) {
142
+ const keyStarIdx = key.indexOf('*');
143
+ if (keyStarIdx !== key.lastIndexOf('*')) continue;
144
+ wildcards.push({
145
+ keyPrefix: key.slice(0, keyStarIdx),
146
+ keySuffix: key.slice(keyStarIdx + 1),
147
+ valueTemplate: target,
148
+ });
149
+ } else {
150
+ exact.set(key, target);
151
+ }
152
+ }
153
+ }
154
+ return {
155
+ resolveSubpath(subpath) {
156
+ const exactHit = exact.get(subpath);
157
+ if (exactHit) return joinPackagePath(p.dir, exactHit);
158
+ for (const w of wildcards) {
159
+ if (subpath.startsWith(w.keyPrefix) && subpath.endsWith(w.keySuffix)) {
160
+ const star = subpath.slice(w.keyPrefix.length, subpath.length - w.keySuffix.length);
161
+ if (!star) continue;
162
+ const target = w.valueTemplate.replace('*', star);
163
+ return joinPackagePath(p.dir, target);
164
+ }
165
+ }
166
+ // Unsafe to guess when no exports declared.
167
+ if (exact.size === 0 && wildcards.length === 0 && subpath !== '.') {
168
+ return null;
169
+ }
170
+ return null;
171
+ },
172
+ };
173
+ }
174
+
175
+ // Condition order matches what Next.js / Vite / esbuild would resolve.
176
+ function pickConditionalTarget(value) {
177
+ if (typeof value === 'string') return value;
178
+ if (Array.isArray(value)) {
179
+ for (const item of value) {
180
+ const target = pickConditionalTarget(item);
181
+ if (typeof target === 'string') return target;
182
+ }
183
+ return null;
184
+ }
185
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
186
+ for (const cond of ['default', 'import', 'node', 'browser', 'require', 'types']) {
187
+ const v = value[cond];
188
+ if (typeof v === 'string') return v;
189
+ }
190
+ return null;
191
+ }
192
+
193
+ export async function resolveWorkspaceImports(sourceFilePath, resolver, options = {}) {
194
+ let text;
195
+ try {
196
+ text = await readFile(sourceFilePath, 'utf-8');
197
+ } catch {
198
+ return [];
199
+ }
200
+ const opts = { ...DEFAULT_RESOLVE_OPTIONS, ...options };
201
+ const refs = extractModuleReferences(text);
202
+ const out = [];
203
+ const seen = new Set();
204
+ for (const ref of refs) {
205
+ const resolved = await resolveModuleSpecifier(sourceFilePath, ref.specifier, resolver);
206
+ if (!resolved) continue;
207
+ const expanded = await expandResolvedSpecifier(resolved, ref.importedNames, resolver, opts);
208
+ for (const file of expanded) {
209
+ if (seen.has(file)) continue;
210
+ seen.add(file);
211
+ out.push(file);
212
+ }
213
+ }
214
+ return out;
215
+ }
216
+
217
+ // Skips CommonJS `require('foo')` and template-literal dynamic imports (statically unresolvable).
218
+ export function extractImportSpecifiers(text) {
219
+ return [...new Set(extractModuleReferences(text).map((ref) => ref.specifier))];
220
+ }
221
+
222
+ function joinPackagePath(packageDir, relativeTarget) {
223
+ return join(packageDir, relativeTarget.replace(/^\.\//, ''));
224
+ }
225
+
226
+ async function expandResolvedSpecifier(startFile, importedNames, resolver, opts) {
227
+ const out = [];
228
+ const seen = new Set();
229
+ const barrelVisited = new Set();
230
+ const fanoutVisited = new Set();
231
+
232
+ const add = (file) => {
233
+ if (seen.has(file)) return false;
234
+ if (out.length > 0 && out.length - 1 >= opts.perSpecifierCap) return false;
235
+ seen.add(file);
236
+ out.push(file);
237
+ return true;
238
+ };
239
+
240
+ add(startFile);
241
+ await expandPureBarrel(startFile, importedNames, 0);
242
+ const fanoutSeeds = out.slice();
243
+ for (const file of fanoutSeeds) {
244
+ await expandSuffixFanout(file, 0);
245
+ }
246
+ return out;
247
+
248
+ async function expandPureBarrel(file, requestedNames, depth) {
249
+ if (depth >= opts.pureBarrelDepth) return;
250
+ if (barrelVisited.has(file)) return;
251
+ barrelVisited.add(file);
252
+ const text = await tryReadText(file);
253
+ if (text == null || !isPureBarrel(text)) return;
254
+ const refs = await selectRelevantForwards(file, extractExportForwardRefs(text), requestedNames, resolver);
255
+ for (const { ref, next } of refs) {
256
+ if (!add(next)) return;
257
+ await expandPureBarrel(next, requestedNamesForForward(ref, requestedNames), depth + 1);
258
+ }
259
+ }
260
+
261
+ async function expandSuffixFanout(file, depth) {
262
+ if (depth >= opts.suffixFanoutDepth) return;
263
+ if (!isSuffixFanoutFile(file)) return;
264
+ const visitKey = `${file}:${depth}`;
265
+ if (fanoutVisited.has(visitKey)) return;
266
+ fanoutVisited.add(visitKey);
267
+ const text = await tryReadText(file);
268
+ if (text == null) return;
269
+ for (const ref of extractModuleReferences(text)) {
270
+ const next = await resolveModuleSpecifier(file, ref.specifier, resolver);
271
+ if (!next) continue;
272
+ if (!add(next)) return;
273
+ if (isSuffixFanoutFile(next)) await expandSuffixFanout(next, depth + 1);
274
+ }
275
+ }
276
+ }
277
+
278
+ async function selectRelevantForwards(fromFile, refs, requestedNames, resolver) {
279
+ const resolved = [];
280
+ for (const [index, ref] of refs.entries()) {
281
+ const next = await resolveModuleSpecifier(fromFile, ref.specifier, resolver);
282
+ if (!next) continue;
283
+ let score = requestedNames && requestedNames.size > 0
284
+ ? forwardRelevanceScore(ref, requestedNames, refs.length)
285
+ : 1;
286
+ if (requestedNames && requestedNames.size > 0 && await fileExportsAnyName(next, requestedNames)) {
287
+ score = Math.max(score, 75);
288
+ }
289
+ resolved.push({ ref, next, index, score });
290
+ }
291
+ if (!requestedNames || requestedNames.size === 0) return resolved;
292
+ const ranked = resolved
293
+ .filter((x) => x.score > 0)
294
+ .sort((a, b) => b.score - a.score || a.index - b.index);
295
+ return ranked.length > 0 ? ranked : resolved;
296
+ }
297
+
298
+ function forwardRelevanceScore(ref, requestedNames, siblingCount) {
299
+ if (!requestedNames || requestedNames.size === 0) return 1;
300
+ if (ref.exportedNames) {
301
+ for (const name of requestedNames) {
302
+ if (ref.exportedNames.has(name)) return 100;
303
+ }
304
+ }
305
+ if (specifierMatchesNames(ref.specifier, requestedNames)) return 50;
306
+ return siblingCount === 1 ? 1 : 0;
307
+ }
308
+
309
+ function requestedNamesForForward(ref, requestedNames) {
310
+ if (!requestedNames || requestedNames.size === 0) return null;
311
+ if (ref.star) return requestedNames;
312
+ const out = new Set();
313
+ for (const name of requestedNames) {
314
+ const source = ref.sourceNamesByExported?.get(name);
315
+ if (source) out.add(source);
316
+ }
317
+ return out.size > 0 ? out : requestedNames;
318
+ }
319
+
320
+ async function resolveModuleSpecifier(fromFile, specifier, resolver) {
321
+ const raw = specifier.startsWith('.')
322
+ ? join(dirname(fromFile), specifier)
323
+ : resolver(specifier);
324
+ if (!raw) return null;
325
+ return await resolveExistingPath(raw);
326
+ }
327
+
328
+ async function resolveExistingPath(basePath) {
329
+ for (const ext of EXTENSIONS) {
330
+ const candidate = ext === '' ? basePath : basePath + ext;
331
+ if (!isSourcePath(candidate)) continue;
332
+ if (await isFile(candidate)) return candidate;
333
+ }
334
+ for (const indexFile of INDEX_FILES) {
335
+ const candidate = join(basePath, indexFile);
336
+ if (await isFile(candidate)) return candidate;
337
+ }
338
+ return null;
339
+ }
340
+
341
+ function extractModuleReferences(text) {
342
+ return [
343
+ ...extractImportReferences(text),
344
+ ...extractExportForwardRefs(text).map((ref) => ({
345
+ specifier: ref.specifier,
346
+ importedNames: ref.star ? null : ref.exportedNames,
347
+ })),
348
+ ...extractDynamicImportReferences(text),
349
+ ];
350
+ }
351
+
352
+ function extractImportReferences(text) {
353
+ const out = [];
354
+ const fromRe = /import\s+(?:type\s+)?([\s\S]*?)\s+from\s+['"]([^'"\n]+)['"]/g;
355
+ let m;
356
+ while ((m = fromRe.exec(text)) !== null) {
357
+ out.push({ specifier: m[2], importedNames: parseImportNames(m[1]) });
358
+ }
359
+ const sideEffectRe = /import\s+['"]([^'"\n]+)['"]/g;
360
+ while ((m = sideEffectRe.exec(text)) !== null) {
361
+ out.push({ specifier: m[1], importedNames: null });
362
+ }
363
+ return out;
364
+ }
365
+
366
+ function extractDynamicImportReferences(text) {
367
+ const out = [];
368
+ const re = /import\s*\(\s*['"]([^'"\n]+)['"]\s*\)/g;
369
+ let m;
370
+ while ((m = re.exec(text)) !== null) {
371
+ out.push({ specifier: m[1], importedNames: null });
372
+ }
373
+ return out;
374
+ }
375
+
376
+ function extractExportForwardRefs(text) {
377
+ const out = [];
378
+ const re = /export\s+(?:type\s+)?(\*|\*\s+as\s+[A-Za-z_$][\w$]*|\{[^}]*\})\s+from\s+['"]([^'"\n]+)['"]\s*;?/g;
379
+ let m;
380
+ while ((m = re.exec(text)) !== null) {
381
+ const clause = m[1].trim();
382
+ const star = clause.startsWith('*');
383
+ const names = star ? null : parseExportNames(clause);
384
+ out.push({
385
+ specifier: m[2],
386
+ star,
387
+ exportedNames: names?.exportedNames ?? null,
388
+ sourceNamesByExported: names?.sourceNamesByExported ?? null,
389
+ });
390
+ }
391
+ return out;
392
+ }
393
+
394
+ function parseImportNames(clause) {
395
+ const names = new Set();
396
+ const trimmed = clause.trim();
397
+ if (!trimmed) return null;
398
+ const named = /\{([^}]+)\}/s.exec(trimmed);
399
+ if (named) {
400
+ for (const part of splitImportList(named[1])) {
401
+ const cleaned = part.replace(/^type\s+/, '').trim();
402
+ if (!cleaned) continue;
403
+ const [source] = cleaned.split(/\s+as\s+/i);
404
+ if (source?.trim()) names.add(source.trim());
405
+ }
406
+ }
407
+ const withoutNamed = trimmed.replace(/\{[^}]*\}/s, '').replace(/,\s*$/, '').trim();
408
+ if (withoutNamed && !withoutNamed.startsWith('*')) names.add('default');
409
+ return names.size > 0 ? names : null;
410
+ }
411
+
412
+ function parseExportNames(clause) {
413
+ const body = clause.replace(/^\{|\}$/g, '');
414
+ const exportedNames = new Set();
415
+ const sourceNamesByExported = new Map();
416
+ for (const part of splitImportList(body)) {
417
+ const cleaned = part.replace(/^type\s+/, '').trim();
418
+ if (!cleaned) continue;
419
+ const [sourceRaw, exportedRaw] = cleaned.split(/\s+as\s+/i);
420
+ const source = sourceRaw.trim();
421
+ const exported = (exportedRaw ?? sourceRaw).trim();
422
+ if (!source || !exported) continue;
423
+ exportedNames.add(exported);
424
+ sourceNamesByExported.set(exported, source);
425
+ }
426
+ return { exportedNames, sourceNamesByExported };
427
+ }
428
+
429
+ function splitImportList(value) {
430
+ return value.split(',').map((part) => part.trim()).filter(Boolean);
431
+ }
432
+
433
+ function isPureBarrel(text) {
434
+ const refs = extractExportForwardRefs(text);
435
+ if (refs.length === 0) return false;
436
+ const withoutComments = text
437
+ .replace(/\/\*[\s\S]*?\*\//g, '')
438
+ .replace(/^\s*\/\/.*$/gm, '');
439
+ return withoutComments.replace(EXPORT_FORWARD_RE, '').trim() === '';
440
+ }
441
+
442
+ function specifierMatchesNames(specifier, names) {
443
+ const normalizedSpecifier = normalizeName(specifier.split('/').at(-1) ?? specifier);
444
+ for (const name of names) {
445
+ const normalizedName = normalizeName(name);
446
+ if (normalizedSpecifier === normalizedName || normalizedSpecifier.endsWith(normalizedName)) {
447
+ return true;
448
+ }
449
+ }
450
+ return false;
451
+ }
452
+
453
+ function normalizeName(value) {
454
+ return String(value ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
455
+ }
456
+
457
+ function isSuffixFanoutFile(file) {
458
+ return SUFFIX_FANOUT_RE.test(file.replace(/\\/g, '/'));
459
+ }
460
+
461
+ async function fileExportsAnyName(file, names) {
462
+ const text = await tryReadText(file);
463
+ if (text == null) return false;
464
+ for (const name of names) {
465
+ if (textExportsName(text, name)) return true;
466
+ }
467
+ return false;
468
+ }
469
+
470
+ function textExportsName(text, name) {
471
+ const escaped = escapeRegExp(name);
472
+ const declaration = new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var|class|interface|type)\\s+${escaped}\\b`);
473
+ if (declaration.test(text)) return true;
474
+ const listRe = /export\s+\{([^}]+)\}(?!\s+from\b)/gs;
475
+ let m;
476
+ while ((m = listRe.exec(text)) !== null) {
477
+ const names = parseExportNames(`{${m[1]}}`).exportedNames;
478
+ if (names.has(name)) return true;
479
+ }
480
+ return false;
481
+ }
482
+
483
+ function isSourcePath(path) {
484
+ const match = /\.([A-Za-z0-9]+)$/.exec(path);
485
+ if (!match) return true;
486
+ return SOURCE_EXTENSIONS.has('.' + match[1]);
487
+ }
488
+
489
+ function escapeRegExp(value) {
490
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
491
+ }
492
+
493
+ async function tryReadText(path) {
494
+ try {
495
+ return await readFile(path, 'utf-8');
496
+ } catch {
497
+ return null;
498
+ }
499
+ }
500
+
501
+ async function fileExists(p) {
502
+ try { await stat(p); return true; } catch { return false; }
503
+ }
504
+
505
+ async function isFile(p) {
506
+ try {
507
+ const s = await stat(p);
508
+ return s.isFile();
509
+ } catch {
510
+ return false;
511
+ }
512
+ }
513
+
514
+ async function tryReadJson(path) {
515
+ try {
516
+ const text = await readFile(path, 'utf-8');
517
+ return JSON.parse(text);
518
+ } catch {
519
+ return null;
520
+ }
521
+ }
@@ -0,0 +1,176 @@
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
+ # Candidate gates
6
+
7
+ The deterministic threshold expressions that turn observability signals into investigation candidates. Pure JS, no LLM. Thresholds live in `lib/gates/*.mjs`.
8
+
9
+ Total gates: 15. Budget cap: `MAX_CODE_CANDIDATES = 6`. Gate version: `1.8.0`.
10
+
11
+ ## Gates
12
+
13
+ ### `build_minutes_fanout`
14
+
15
+ - **Threshold**: `Build Minutes share > 0.15 OR turbo-force-bypass finding present`
16
+ - **Billing dimension**: build
17
+ - **Scope**: account
18
+ - **Source citation**: `vercel-optimize gate threshold`
19
+
20
+ 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.
21
+
22
+ ---
23
+
24
+ ### `cold_start`
25
+
26
+ - **Threshold**: `coldPct > 0.4 AND total >= 1000`
27
+ - **Billing dimension**: function-duration
28
+ - **Scope**: route
29
+ - **Source citation**: `vercel-optimize gate threshold`
30
+
31
+ Routes where > 40% of invocations are cold-start, at meaningful traffic (>=1,000 total invocations in window). Cold starts add 200-800ms per request and break the perceived latency budget on cache-miss paths. The 40% threshold is where cold-rate becomes a real signal vs Poisson noise on serverless. Sourced from vercel.function_invocation.count grouped by function_start_type.
32
+
33
+ ---
34
+
35
+ ### `cwv_poor`
36
+
37
+ - **Threshold**: `LCP p75>2500 OR INP p75>200 OR CLS p75>0.1, AND speed_insights count > 50`
38
+ - **Billing dimension**: speed-insights
39
+ - **Scope**: route
40
+ - **Source citation**: `https://web.dev/articles/vitals`
41
+
42
+ Routes where Core Web Vitals fall into Google's "Poor" band on real-user traffic. LCP > 2500ms, INP > 200ms, or CLS > 0.1 each hurt SEO and conversion. Surfaces one candidate per (route, metric) pair to keep recommendations focused.
43
+
44
+ ---
45
+
46
+ ### `external_api_slow`
47
+
48
+ - **Threshold**: `p75Ms > 2000 AND callCount >= 500`
49
+ - **Billing dimension**: function-duration
50
+ - **Scope**: route
51
+ - **Source citation**: `vercel-optimize gate threshold`
52
+
53
+ External API hostnames with p75 latency above 2 seconds AND at least 500 calls in the window. External API latency is a primary driver of function duration cost when the upstream is on a hot path; a single slow stale call isn't worth recommending against.
54
+
55
+ ---
56
+
57
+ ### `isr_overrevalidation`
58
+
59
+ - **Threshold**: `writes/reads > 0.5 AND writes > 100`
60
+ - **Billing dimension**: isr
61
+ - **Scope**: route
62
+ - **Source citation**: `https://vercel.com/docs/incremental-static-regeneration`
63
+
64
+ ISR routes with > 1 write per 2 reads. The revalidate interval is too aggressive relative to read traffic — many reads pay to regenerate. Investigate whether the page can tolerate a longer revalidate window or on-demand revalidation via revalidateTag.
65
+
66
+ ---
67
+
68
+ ### `middleware_heavy`
69
+
70
+ - **Threshold**: `middlewareInv/totalInv > 0.5 AND middlewareInv > 1000`
71
+ - **Billing dimension**: edge-requests
72
+ - **Scope**: account
73
+ - **Source citation**: `https://nextjs.org/docs/app/building-your-application/routing/middleware`
74
+
75
+ Middleware invocations cover > 50% of total requests at non-trivial volume. The matcher is probably broader than necessary; narrow it to the paths that actually need auth/rewrites/headers.
76
+
77
+ ---
78
+
79
+ ### `observability_events_attribution`
80
+
81
+ - **Threshold**: `observabilityEventsShare > 0.20 (critical at > 0.30)`
82
+ - **Billing dimension**: observability-events
83
+ - **Scope**: account
84
+ - **Source citation**: `vercel-optimize gate threshold`
85
+
86
+ Observability Events line item exceeds 20% of total billed cost. High share usually traces to low cache hit rate, middleware-heavy traffic, or unconstrained custom-span cardinality. No sampling lever exists for Observability Plus; reduce upstream invocations instead.
87
+
88
+ ---
89
+
90
+ ### `platform_bot_protection`
91
+
92
+ - **Threshold**: `botIdEnabled=false AND (botPct >= 0.05 OR edge_cost >= $25/window OR requests >= 14k/14d)`
93
+ - **Billing dimension**: edge-requests
94
+ - **Scope**: account
95
+ - **Source citation**: `vercel-optimize gate threshold`
96
+
97
+ When BotID is disabled AND there is evidence (observed bot bandwidth share, edge cost, or substantial request volume) that bot traffic is non-trivial. Bot traffic inflates edge request counts without delivering user value; staged bot protection can reduce waste on bot-heavy projects. Skipped on quiet projects with no bot evidence — the recommendation would be noise.
98
+
99
+ ---
100
+
101
+ ### `platform_fluid_compute`
102
+
103
+ - **Threshold**: `fluid=false AND (any cold_start signal OR any route with p95>1000ms AND inv>1000)`
104
+ - **Billing dimension**: function-duration
105
+ - **Scope**: account
106
+ - **Source citation**: `vercel-optimize gate threshold`
107
+
108
+ When Fluid Compute is disabled on a project that shows cold-start pressure (high cold-start rate) or sustained slow function p95 on hot routes. Fluid Compute reduces cold starts via instance reuse — recommend turning it on at the project level rather than per-route.
109
+
110
+ ---
111
+
112
+ ### `region_misconfig`
113
+
114
+ - **Threshold**: `single-region pin found AND routes.length > 20 (scanner-only branch)`
115
+ - **Billing dimension**: function-duration
116
+ - **Scope**: account
117
+ - **Source citation**: `vercel-optimize gate threshold`
118
+
119
+ A single function region is pinned in `vercel.json` or per-route `preferredRegion`. Without per-region TTFB data (data gap), the gate can't quantify the geographic latency cost — but a single-region pin on a project with 20+ routes is worth auditing against Speed Insights traffic geo.
120
+
121
+ ---
122
+
123
+ ### `route_errors`
124
+
125
+ - **Threshold**: `count > 250 OR (totalRequests >= 1000 AND errorRate > 0.01)`
126
+ - **Billing dimension**: function-duration
127
+ - **Scope**: route
128
+ - **Source citation**: `vercel-optimize gate threshold`
129
+
130
+ Routes producing > 250 5xx errors over the window, or with > 1% error rate on at least 1,000 total requests. Errored function invocations still bill at full duration; high error rates also poison user experience.
131
+
132
+ ---
133
+
134
+ ### `scanner-driven`
135
+
136
+ - **Threshold**: `per-kind: scanner matches.length >= threshold`
137
+ - **Billing dimension**: mixed
138
+ - **Scope**: mixed
139
+ - **Source citation**: `vercel-optimize gate threshold`
140
+
141
+ Configured kinds emitted from scanner output. Each requires a minimum match count to avoid noise. Findings on cold-path or unmappable files are dropped unless the underlying scanner is trafficIndependent.
142
+
143
+ ---
144
+
145
+ ### `slow_route`
146
+
147
+ - **Threshold**: `(p95 > 500 AND inv >= 1400) OR (p95 > 1500 AND inv >= 250); disqualified when 5xx rate > 50%; Vercel Workflow runtime endpoints are hard-gated`
148
+ - **Billing dimension**: function-duration
149
+ - **Scope**: route
150
+ - **Source citation**: `vercel-optimize gate threshold`
151
+
152
+ Routes with p95 function duration above 500ms at meaningful traffic (>=1,400 invocations in window), OR catastrophically slow routes (>1500ms p95 at any volume >=250). High duration drives both function-duration cost and user-perceived latency. Investigate sequential awaits, slow external APIs, missing caching, N+1 patterns. Routes with >50% 5xx rate are disqualified — those are reliability problems, not performance tuning targets, and surface via route_errors instead. Vercel Workflow runtime endpoints (`/.well-known/workflow/v1/*`) are hard-gated before launch because long-running step/flow requests are expected orchestration, not app-route bottlenecks.
153
+
154
+ ---
155
+
156
+ ### `uncached_route`
157
+
158
+ - **Threshold**: `requests > 500 AND hitRate < 0.5 AND getShare > 0.2 (missing getShare is gated)`
159
+ - **Billing dimension**: edge-requests
160
+ - **Scope**: route
161
+ - **Source citation**: `vercel-optimize gate threshold`
162
+
163
+ Routes serving > 500 requests/period at < 50% cache hit AND at least 20% GET traffic. Each uncached GET request reaches the function, costing edge requests + function duration. Routes that are mostly POST/PUT/DELETE (Server Actions, mutations) are skipped — 0% cache is correct behavior there. Routes with missing method-share data are gated instead of launched. Auth-gated routes are disqualified separately.
164
+
165
+ ---
166
+
167
+ ### `usage_spike_triage`
168
+
169
+ - **Threshold**: `any-day total > 2x mean OR any-day SKU > 3x SKU mean`
170
+ - **Billing dimension**: mixed
171
+ - **Scope**: account
172
+ - **Source citation**: `vercel-optimize gate threshold`
173
+
174
+ A single day in the billing window deviates sharply from the window baseline. Triage branches: bot or AI crawler spike, viral moment, pricing-model migration (legacy SKU → new), code regression. Without daily-granularity data, this gate stays dormant.
175
+
176
+ ---