opencode-skills-collection 3.0.34 → 3.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +16 -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 +3 -3
  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/mmx-cli/SKILL.md +5 -2
  35. package/bundled-skills/nextjs-seo-indexing/SKILL.md +3 -3
  36. package/bundled-skills/polis-protocol/SKILL.md +93 -0
  37. package/bundled-skills/python-development/SKILL.md +35 -0
  38. package/bundled-skills/radix-ui-design-system/SKILL.md +2 -2
  39. package/bundled-skills/react-native-skills/SKILL.md +120 -0
  40. package/bundled-skills/react-native-skills/rules/_sections.md +86 -0
  41. package/bundled-skills/react-native-skills/rules/_template.md +28 -0
  42. package/bundled-skills/react-native-skills/rules/animation-derived-value.md +53 -0
  43. package/bundled-skills/react-native-skills/rules/animation-gesture-detector-press.md +95 -0
  44. package/bundled-skills/react-native-skills/rules/animation-gpu-properties.md +65 -0
  45. package/bundled-skills/react-native-skills/rules/design-system-compound-components.md +66 -0
  46. package/bundled-skills/react-native-skills/rules/fonts-config-plugin.md +71 -0
  47. package/bundled-skills/react-native-skills/rules/imports-design-system-folder.md +68 -0
  48. package/bundled-skills/react-native-skills/rules/js-hoist-intl.md +61 -0
  49. package/bundled-skills/react-native-skills/rules/list-performance-callbacks.md +44 -0
  50. package/bundled-skills/react-native-skills/rules/list-performance-function-references.md +132 -0
  51. package/bundled-skills/react-native-skills/rules/list-performance-images.md +53 -0
  52. package/bundled-skills/react-native-skills/rules/list-performance-inline-objects.md +97 -0
  53. package/bundled-skills/react-native-skills/rules/list-performance-item-expensive.md +94 -0
  54. package/bundled-skills/react-native-skills/rules/list-performance-item-memo.md +82 -0
  55. package/bundled-skills/react-native-skills/rules/list-performance-item-types.md +104 -0
  56. package/bundled-skills/react-native-skills/rules/list-performance-virtualize.md +67 -0
  57. package/bundled-skills/react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
  58. package/bundled-skills/react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
  59. package/bundled-skills/react-native-skills/rules/navigation-native-navigators.md +188 -0
  60. package/bundled-skills/react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
  61. package/bundled-skills/react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
  62. package/bundled-skills/react-native-skills/rules/react-state-dispatcher.md +91 -0
  63. package/bundled-skills/react-native-skills/rules/react-state-fallback.md +56 -0
  64. package/bundled-skills/react-native-skills/rules/react-state-minimize.md +65 -0
  65. package/bundled-skills/react-native-skills/rules/rendering-no-falsy-and.md +74 -0
  66. package/bundled-skills/react-native-skills/rules/rendering-text-in-text-component.md +36 -0
  67. package/bundled-skills/react-native-skills/rules/scroll-position-no-state.md +82 -0
  68. package/bundled-skills/react-native-skills/rules/state-ground-truth.md +80 -0
  69. package/bundled-skills/react-native-skills/rules/ui-expo-image.md +66 -0
  70. package/bundled-skills/react-native-skills/rules/ui-image-gallery.md +104 -0
  71. package/bundled-skills/react-native-skills/rules/ui-measure-views.md +78 -0
  72. package/bundled-skills/react-native-skills/rules/ui-menus.md +174 -0
  73. package/bundled-skills/react-native-skills/rules/ui-native-modals.md +77 -0
  74. package/bundled-skills/react-native-skills/rules/ui-pressable.md +61 -0
  75. package/bundled-skills/react-native-skills/rules/ui-safe-area-scroll.md +65 -0
  76. package/bundled-skills/react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
  77. package/bundled-skills/react-native-skills/rules/ui-styling.md +87 -0
  78. package/bundled-skills/schema-markup-generator/SKILL.md +1 -1
  79. package/bundled-skills/skill-issue/SKILL.md +73 -0
  80. package/bundled-skills/social-metadata-hardening/SKILL.md +4 -3
  81. package/bundled-skills/social-post-writer-seo/SKILL.md +19 -0
  82. package/bundled-skills/tdd-workflows/SKILL.md +35 -0
  83. package/bundled-skills/user-thoughts/SKILL.md +236 -0
  84. package/bundled-skills/user-thoughts/assets/Runtime-Template/README.ai.md +13 -0
  85. package/bundled-skills/user-thoughts/assets/Runtime-Template/define.ini +3 -0
  86. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/README.ai.md +25 -0
  87. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/backlog.md +19 -0
  88. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/dev-stack.md +7 -0
  89. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/general.md +7 -0
  90. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/plans.md +7 -0
  91. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/rules.md +7 -0
  92. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/ui/details.md +7 -0
  93. package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/ui/outline.md +7 -0
  94. package/bundled-skills/user-thoughts/references/commands.md +54 -0
  95. package/bundled-skills/user-thoughts/references/edge-cases.md +84 -0
  96. package/bundled-skills/user-thoughts/references/safety.md +65 -0
  97. package/bundled-skills/user-thoughts/references/sortin.md +76 -0
  98. package/bundled-skills/user-thoughts/scripts/common.py +62 -0
  99. package/bundled-skills/user-thoughts/scripts/ignore_ops.py +125 -0
  100. package/bundled-skills/user-thoughts/scripts/init.py +63 -0
  101. package/bundled-skills/user-thoughts/scripts/show_mdbase.py +93 -0
  102. package/bundled-skills/user-thoughts/scripts/show_raw.py +42 -0
  103. package/bundled-skills/user-thoughts/scripts/sortin.py +211 -0
  104. package/bundled-skills/user-thoughts/scripts/status.py +56 -0
  105. package/bundled-skills/user-thoughts/scripts/toggle.py +68 -0
  106. package/bundled-skills/user-thoughts/scripts/write_raw.py +106 -0
  107. package/bundled-skills/vercel-cli-with-tokens/SKILL.md +361 -0
  108. package/bundled-skills/vercel-optimize/CONTRIBUTING.md +41 -0
  109. package/bundled-skills/vercel-optimize/SKILL.md +331 -0
  110. package/bundled-skills/vercel-optimize/lib/auth-route.mjs +23 -0
  111. package/bundled-skills/vercel-optimize/lib/budget-summary.mjs +182 -0
  112. package/bundled-skills/vercel-optimize/lib/citations.mjs +139 -0
  113. package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +143 -0
  114. package/bundled-skills/vercel-optimize/lib/dedup-recs.mjs +325 -0
  115. package/bundled-skills/vercel-optimize/lib/deep-dive.mjs +350 -0
  116. package/bundled-skills/vercel-optimize/lib/display-labels.mjs +185 -0
  117. package/bundled-skills/vercel-optimize/lib/extract-claims.mjs +550 -0
  118. package/bundled-skills/vercel-optimize/lib/framework-support.mjs +67 -0
  119. package/bundled-skills/vercel-optimize/lib/gates/build-minutes-fanout.mjs +69 -0
  120. package/bundled-skills/vercel-optimize/lib/gates/cold-start.mjs +66 -0
  121. package/bundled-skills/vercel-optimize/lib/gates/contract.mjs +79 -0
  122. package/bundled-skills/vercel-optimize/lib/gates/cwv-poor.mjs +87 -0
  123. package/bundled-skills/vercel-optimize/lib/gates/external-api-slow.mjs +55 -0
  124. package/bundled-skills/vercel-optimize/lib/gates/hard-gates.mjs +73 -0
  125. package/bundled-skills/vercel-optimize/lib/gates/index.mjs +45 -0
  126. package/bundled-skills/vercel-optimize/lib/gates/isr-overrevalidation.mjs +62 -0
  127. package/bundled-skills/vercel-optimize/lib/gates/middleware-heavy.mjs +51 -0
  128. package/bundled-skills/vercel-optimize/lib/gates/observability-events-attribution.mjs +56 -0
  129. package/bundled-skills/vercel-optimize/lib/gates/platform-bot-protection.mjs +115 -0
  130. package/bundled-skills/vercel-optimize/lib/gates/platform-fluid-compute.mjs +83 -0
  131. package/bundled-skills/vercel-optimize/lib/gates/region-misconfig.mjs +64 -0
  132. package/bundled-skills/vercel-optimize/lib/gates/route-errors.mjs +80 -0
  133. package/bundled-skills/vercel-optimize/lib/gates/scanner-driven.mjs +122 -0
  134. package/bundled-skills/vercel-optimize/lib/gates/select-candidates.mjs +134 -0
  135. package/bundled-skills/vercel-optimize/lib/gates/slow-route.mjs +88 -0
  136. package/bundled-skills/vercel-optimize/lib/gates/types.d.ts +38 -0
  137. package/bundled-skills/vercel-optimize/lib/gates/uncached-route.mjs +93 -0
  138. package/bundled-skills/vercel-optimize/lib/gates/usage-spike-triage.mjs +121 -0
  139. package/bundled-skills/vercel-optimize/lib/grade-recommendation.mjs +155 -0
  140. package/bundled-skills/vercel-optimize/lib/impact-label.mjs +126 -0
  141. package/bundled-skills/vercel-optimize/lib/impact-magnitude.mjs +60 -0
  142. package/bundled-skills/vercel-optimize/lib/investigation-brief.mjs +610 -0
  143. package/bundled-skills/vercel-optimize/lib/observation-safety.mjs +174 -0
  144. package/bundled-skills/vercel-optimize/lib/project-facts.mjs +99 -0
  145. package/bundled-skills/vercel-optimize/lib/queries.mjs +315 -0
  146. package/bundled-skills/vercel-optimize/lib/reconcile-candidates.mjs +372 -0
  147. package/bundled-skills/vercel-optimize/lib/render-report.mjs +955 -0
  148. package/bundled-skills/vercel-optimize/lib/repo-root.mjs +86 -0
  149. package/bundled-skills/vercel-optimize/lib/route-normalize.mjs +220 -0
  150. package/bundled-skills/vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs +38 -0
  151. package/bundled-skills/vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs +30 -0
  152. package/bundled-skills/vercel-optimize/lib/sanitizers/count-correct.mjs +52 -0
  153. package/bundled-skills/vercel-optimize/lib/sanitizers/function-duration-invocations.mjs +38 -0
  154. package/bundled-skills/vercel-optimize/lib/sanitizers/index.mjs +79 -0
  155. package/bundled-skills/vercel-optimize/lib/sanitizers/middleware-conflict.mjs +36 -0
  156. package/bundled-skills/vercel-optimize/lib/sanitizers/missing-citation.mjs +16 -0
  157. package/bundled-skills/vercel-optimize/lib/sanitizers/pre-release.mjs +74 -0
  158. package/bundled-skills/vercel-optimize/lib/sanitizers/rate-limit.mjs +67 -0
  159. package/bundled-skills/vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs +38 -0
  160. package/bundled-skills/vercel-optimize/lib/sanitizers/undeclared-dep.mjs +78 -0
  161. package/bundled-skills/vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs +37 -0
  162. package/bundled-skills/vercel-optimize/lib/sanitizers/window-units.mjs +32 -0
  163. package/bundled-skills/vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs +109 -0
  164. package/bundled-skills/vercel-optimize/lib/scanners/edge-heavy-import.mjs +94 -0
  165. package/bundled-skills/vercel-optimize/lib/scanners/force-dynamic.mjs +42 -0
  166. package/bundled-skills/vercel-optimize/lib/scanners/headers-in-page.mjs +44 -0
  167. package/bundled-skills/vercel-optimize/lib/scanners/index.mjs +35 -0
  168. package/bundled-skills/vercel-optimize/lib/scanners/large-static-asset.mjs +92 -0
  169. package/bundled-skills/vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs +42 -0
  170. package/bundled-skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs +55 -0
  171. package/bundled-skills/vercel-optimize/lib/scanners/missing-cache-headers.mjs +90 -0
  172. package/bundled-skills/vercel-optimize/lib/scanners/prisma-include-tree.mjs +42 -0
  173. package/bundled-skills/vercel-optimize/lib/scanners/region-pin-in-config.mjs +88 -0
  174. package/bundled-skills/vercel-optimize/lib/scanners/source-maps-production.mjs +36 -0
  175. package/bundled-skills/vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs +43 -0
  176. package/bundled-skills/vercel-optimize/lib/scanners/turbo-force-bypass.mjs +129 -0
  177. package/bundled-skills/vercel-optimize/lib/scanners/unoptimized-image.mjs +113 -0
  178. package/bundled-skills/vercel-optimize/lib/scanners/use-cache-date-stamp.mjs +106 -0
  179. package/bundled-skills/vercel-optimize/lib/support-topics.mjs +355 -0
  180. package/bundled-skills/vercel-optimize/lib/throttle.mjs +273 -0
  181. package/bundled-skills/vercel-optimize/lib/util.mjs +17 -0
  182. package/bundled-skills/vercel-optimize/lib/vercel.mjs +784 -0
  183. package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +1296 -0
  184. package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +521 -0
  185. package/bundled-skills/vercel-optimize/references/candidates.md +176 -0
  186. package/bundled-skills/vercel-optimize/references/data-collection.md +218 -0
  187. package/bundled-skills/vercel-optimize/references/docs-library.json +683 -0
  188. package/bundled-skills/vercel-optimize/references/doctrine.md +105 -0
  189. package/bundled-skills/vercel-optimize/references/observability-plus.md +108 -0
  190. package/bundled-skills/vercel-optimize/references/playbooks/README.md +53 -0
  191. package/bundled-skills/vercel-optimize/references/playbooks/ai-application.md +32 -0
  192. package/bundled-skills/vercel-optimize/references/playbooks/api-service.md +30 -0
  193. package/bundled-skills/vercel-optimize/references/playbooks/content-site.md +30 -0
  194. package/bundled-skills/vercel-optimize/references/playbooks/ecommerce.md +30 -0
  195. package/bundled-skills/vercel-optimize/references/playbooks/marketing.md +30 -0
  196. package/bundled-skills/vercel-optimize/references/playbooks/saas.md +31 -0
  197. package/bundled-skills/vercel-optimize/references/playbooks/sveltekit.md +75 -0
  198. package/bundled-skills/vercel-optimize/references/recommendations.md +203 -0
  199. package/bundled-skills/vercel-optimize/references/scanner-patterns.md +251 -0
  200. package/bundled-skills/vercel-optimize/references/scoring.md +205 -0
  201. package/bundled-skills/vercel-optimize/references/support-topics/README.md +46 -0
  202. package/bundled-skills/vercel-optimize/references/support-topics/astro-edge-middleware-scope.md +22 -0
  203. package/bundled-skills/vercel-optimize/references/support-topics/astro-output-mode-and-isr.md +22 -0
  204. package/bundled-skills/vercel-optimize/references/support-topics/auth-preserving-parallelization.md +22 -0
  205. package/bundled-skills/vercel-optimize/references/support-topics/bot-protection-product-guardrails.md +22 -0
  206. package/bundled-skills/vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md +23 -0
  207. package/bundled-skills/vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md +22 -0
  208. package/bundled-skills/vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md +23 -0
  209. package/bundled-skills/vercel-optimize/references/support-topics/cdn-cache-auth-safety.md +22 -0
  210. package/bundled-skills/vercel-optimize/references/support-topics/cold-start-initialization-bundle.md +22 -0
  211. package/bundled-skills/vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md +22 -0
  212. package/bundled-skills/vercel-optimize/references/support-topics/database-egress-pooling-region.md +22 -0
  213. package/bundled-skills/vercel-optimize/references/support-topics/dynamic-rendering-traps.md +22 -0
  214. package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path-platform.md +22 -0
  215. package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path.md +22 -0
  216. package/bundled-skills/vercel-optimize/references/support-topics/fast-data-transfer-payloads.md +22 -0
  217. package/bundled-skills/vercel-optimize/references/support-topics/fluid-compute-caveats.md +22 -0
  218. package/bundled-skills/vercel-optimize/references/support-topics/function-duration-io-and-after.md +22 -0
  219. package/bundled-skills/vercel-optimize/references/support-topics/function-invocation-reduction.md +22 -0
  220. package/bundled-skills/vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md +23 -0
  221. package/bundled-skills/vercel-optimize/references/support-topics/image-optimization-cost-control.md +22 -0
  222. package/bundled-skills/vercel-optimize/references/support-topics/isr-revalidation-static-generation.md +22 -0
  223. package/bundled-skills/vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md +22 -0
  224. package/bundled-skills/vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md +22 -0
  225. package/bundled-skills/vercel-optimize/references/support-topics/next-font-cls-self-hosting.md +23 -0
  226. package/bundled-skills/vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md +23 -0
  227. package/bundled-skills/vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md +23 -0
  228. package/bundled-skills/vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md +22 -0
  229. package/bundled-skills/vercel-optimize/references/support-topics/next-script-third-party-strategy.md +23 -0
  230. package/bundled-skills/vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md +22 -0
  231. package/bundled-skills/vercel-optimize/references/support-topics/not-found-catchall-request-waste.md +23 -0
  232. package/bundled-skills/vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md +22 -0
  233. package/bundled-skills/vercel-optimize/references/support-topics/observability-events-cost-attribution.md +22 -0
  234. package/bundled-skills/vercel-optimize/references/support-topics/post-response-work-waituntil.md +22 -0
  235. package/bundled-skills/vercel-optimize/references/support-topics/route-error-durable-offload.md +22 -0
  236. package/bundled-skills/vercel-optimize/references/support-topics/route-error-runtime-limits.md +22 -0
  237. package/bundled-skills/vercel-optimize/references/support-topics/runtime-cache-reusable-data.md +22 -0
  238. package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md +22 -0
  239. package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md +22 -0
  240. package/bundled-skills/vercel-optimize/references/support-topics/usage-spike-triage.md +22 -0
  241. package/bundled-skills/vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md +23 -0
  242. package/bundled-skills/vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md +22 -0
  243. package/bundled-skills/vercel-optimize/references/support-topics/workflow-resumable-stream-routes.md +23 -0
  244. package/bundled-skills/vercel-optimize/references/verification.md +102 -0
  245. package/bundled-skills/vercel-optimize/references/voice.md +76 -0
  246. package/bundled-skills/vercel-optimize/scripts/budget-summary.mjs +56 -0
  247. package/bundled-skills/vercel-optimize/scripts/build-docs.mjs +74 -0
  248. package/bundled-skills/vercel-optimize/scripts/check-citations.mjs +81 -0
  249. package/bundled-skills/vercel-optimize/scripts/check-docs-fresh.mjs +93 -0
  250. package/bundled-skills/vercel-optimize/scripts/collect-signals.mjs +576 -0
  251. package/bundled-skills/vercel-optimize/scripts/collect-sub-agent-outputs.mjs +296 -0
  252. package/bundled-skills/vercel-optimize/scripts/deep-dive.mjs +319 -0
  253. package/bundled-skills/vercel-optimize/scripts/gate-investigations.mjs +166 -0
  254. package/bundled-skills/vercel-optimize/scripts/merge-signals.mjs +192 -0
  255. package/bundled-skills/vercel-optimize/scripts/prepare-investigation-brief.mjs +231 -0
  256. package/bundled-skills/vercel-optimize/scripts/reconcile-candidates.mjs +62 -0
  257. package/bundled-skills/vercel-optimize/scripts/render-report.mjs +437 -0
  258. package/bundled-skills/vercel-optimize/scripts/scan-codebase.mjs +313 -0
  259. package/bundled-skills/vercel-optimize/scripts/verify-and-regen.mjs +346 -0
  260. package/bundled-skills/vercel-optimize/scripts/verify-finding.mjs +19 -0
  261. package/bundled-skills/vercel-react-view-transitions/SKILL.md +327 -0
  262. package/bundled-skills/vercel-react-view-transitions/references/css-recipes.md +242 -0
  263. package/bundled-skills/vercel-react-view-transitions/references/implementation.md +182 -0
  264. package/bundled-skills/vercel-react-view-transitions/references/nextjs.md +176 -0
  265. package/bundled-skills/vercel-react-view-transitions/references/patterns.md +262 -0
  266. package/bundled-skills/vibe-code-cleanup/SKILL.md +4 -4
  267. package/bundled-skills/vibecode-production-qa-validator/SKILL.md +3 -2
  268. package/package.json +1 -1
  269. package/skills_index.json +338 -4
@@ -0,0 +1,42 @@
1
+ import { lineOf } from '../util.mjs';
2
+
3
+ export const metadata = {
4
+ id: 'max-age-without-s-maxage',
5
+ title: 'Cache-Control: max-age without s-maxage',
6
+ severity: 'medium',
7
+ billingDimension: 'edge-requests',
8
+ trafficIndependent: false,
9
+ description:
10
+ 'max-age caches in the browser; s-maxage caches at the CDN. Without s-maxage, every uncached visitor request invokes the function. Adding s-maxage often cuts function invocations by 80%+ on read-heavy routes.',
11
+ fix:
12
+ 'Add s-maxage to the Cache-Control header. Example: Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=86400. Pair with explicit cache-bust strategy if content can change.',
13
+ citations: [
14
+ 'https://vercel.com/docs/caching/cdn-cache',
15
+ 'https://vercel.com/docs/caching/cache-control-headers',
16
+ ],
17
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**', '*.config.*'],
18
+ includeGlobs: ['**/*.{ts,tsx,js,jsx,mjs}'],
19
+ };
20
+
21
+ const RE = /Cache-Control[^"'`]*?max-age\s*=\s*\d+/gi;
22
+
23
+ export function scan({ files }) {
24
+ const out = [];
25
+ for (const { path, content } of files) {
26
+ if (/\.test\.|\.spec\./.test(path)) continue;
27
+ let m;
28
+ RE.lastIndex = 0;
29
+ while ((m = RE.exec(content)) !== null) {
30
+ const hit = m[0];
31
+ if (/s-maxage/i.test(hit) || /CDN-Cache-Control/i.test(content.slice(Math.max(0, m.index - 100), m.index + hit.length + 100))) continue;
32
+ out.push({
33
+ pattern: metadata.id,
34
+ file: path,
35
+ line: lineOf(content, m.index),
36
+ evidence: hit.slice(0, 160),
37
+ trafficIndependent: metadata.trafficIndependent,
38
+ });
39
+ }
40
+ }
41
+ return out;
42
+ }
@@ -0,0 +1,55 @@
1
+ export const metadata = {
2
+ id: 'middleware-broad-matcher',
3
+ title: 'Middleware matcher missing or too broad',
4
+ severity: 'high',
5
+ billingDimension: 'edge-requests',
6
+ trafficIndependent: true,
7
+ description:
8
+ 'middleware.ts without a config.matcher (or matcher: ["/(.*)"]) runs on every request including _next/static, _next/image, favicon.ico, and image asset fetches. Edge-request cost scales accordingly.',
9
+ fix:
10
+ 'Scope the matcher to actual application paths. Example: matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"]',
11
+ citations: [
12
+ 'https://nextjs.org/docs/app/building-your-application/routing/middleware',
13
+ ],
14
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**'],
15
+ includeGlobs: ['middleware.{ts,js,mjs}', 'src/middleware.{ts,js,mjs}'],
16
+ };
17
+
18
+ export function scan({ files }) {
19
+ const out = [];
20
+ for (const { path, content } of files) {
21
+ if (!isApplicable(path)) continue;
22
+
23
+ const exportsMiddleware = /export\s+(default\s+)?(async\s+)?function\s+middleware/.test(content)
24
+ || /export\s+const\s+middleware\s*=/.test(content);
25
+ if (!exportsMiddleware) continue;
26
+
27
+ const configBlock = content.match(/export\s+const\s+config\s*=\s*\{([\s\S]*?)\}/);
28
+ const matcherStr = configBlock && configBlock[1].match(/matcher\s*:\s*([^,}]+)/);
29
+
30
+ let problem = null;
31
+ if (!configBlock || !matcherStr) {
32
+ problem = 'no config.matcher (runs on every request)';
33
+ } else {
34
+ const m = matcherStr[1];
35
+ if (/['"`]\s*\/\s*['"`]/.test(m) || /['"`]\/\(\.\*\)['"`]/.test(m)) {
36
+ problem = 'matcher = "/" or "/(.*)" (still covers everything)';
37
+ }
38
+ }
39
+
40
+ if (problem) {
41
+ out.push({
42
+ pattern: metadata.id,
43
+ file: path,
44
+ line: 1,
45
+ evidence: problem,
46
+ trafficIndependent: metadata.trafficIndependent,
47
+ });
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function isApplicable(path) {
54
+ return /(^|\/)middleware\.(ts|js|mjs)$/.test(path);
55
+ }
@@ -0,0 +1,90 @@
1
+ // Two checks emitted as `missing-cache-headers`:
2
+ // A. GET handler with no Cache-Control AND no auth signal.
3
+ // B. fetch() with `cache:'no-store'` / `next:{revalidate:0}` outside an
4
+ // auth window (~10 lines above, 5 below).
5
+
6
+ export const metadata = {
7
+ id: 'missing-cache-headers',
8
+ title: 'Cacheable route or fetch with no caching (Cache-Control absent or no-store)',
9
+ severity: 'medium',
10
+ billingDimension: 'edge-requests',
11
+ trafficIndependent: false,
12
+ description:
13
+ 'Two antipatterns: (a) GET handlers without explicit Cache-Control headers serve uncached; (b) fetch() calls with cache:"no-store" or next:{revalidate:0} opt out of caching even on cacheable upstream data. For non-auth routes / fetches, both are leaving cache hits on the floor.',
14
+ fix:
15
+ 'For GET handlers: return a Response with Cache-Control: public, s-maxage=<seconds>, stale-while-revalidate=<window>. For fetch(): drop cache:"no-store" (use { next: { revalidate: <seconds> } } in Next.js) so the response is cached by the framework + CDN.',
16
+ citations: [
17
+ 'https://vercel.com/docs/caching/cdn-cache',
18
+ 'https://vercel.com/docs/caching/cache-control-headers',
19
+ 'https://nextjs.org/docs/app/building-your-application/caching',
20
+ ],
21
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**', '**/*.test.*', '**/*.spec.*'],
22
+ // page/layout included: no-store fetches commonly hide in Server Components.
23
+ includeGlobs: [
24
+ '**/route.{ts,tsx,js,jsx}',
25
+ '**/api/**/*.{ts,tsx,js,jsx}',
26
+ '**/page.{ts,tsx,js,jsx}',
27
+ '**/layout.{ts,tsx,js,jsx}',
28
+ ],
29
+ };
30
+
31
+ // Covers NextAuth, Clerk, JWT, Bearer, plus Next dynamic-render APIs.
32
+ // FP on cacheable routes that read a session is acceptable — verifier decides.
33
+ const AUTH_RE = /\b(cookies\(\)|headers\(\)|getSession\(|getServerSession\(|currentUser\(|clerkClient|auth\(\)|verifyJWT|verifyToken|jwt\.verify|decode\(|Bearer\s|Authorization|supabase\.auth\.)/i;
34
+
35
+ const NO_STORE_RE = /cache\s*:\s*['"]no-store['"]/;
36
+ const REVALIDATE_ZERO_RE = /next\s*:\s*\{[^}]*revalidate\s*:\s*0\b/;
37
+
38
+ export function scan({ files }) {
39
+ const out = [];
40
+ for (const { path, content } of files) {
41
+ if (!isApplicable(path)) continue;
42
+
43
+ const hasGetHandler =
44
+ /export\s+(async\s+)?function\s+GET/.test(content)
45
+ || /export\s+const\s+GET\s*=/.test(content);
46
+ if (hasGetHandler) {
47
+ const hasCacheControl =
48
+ /Cache-Control/i.test(content)
49
+ || /CDN-Cache-Control/i.test(content)
50
+ || /export\s+const\s+revalidate\s*=/.test(content);
51
+ if (!hasCacheControl && !AUTH_RE.test(content)) {
52
+ out.push({
53
+ pattern: metadata.id,
54
+ subtype: 'get-handler-no-cache-control',
55
+ file: path,
56
+ line: 1,
57
+ evidence: 'GET handler with no Cache-Control / revalidate / auth signal',
58
+ trafficIndependent: metadata.trafficIndependent,
59
+ });
60
+ }
61
+ }
62
+
63
+ const lines = content.split('\n');
64
+ for (let i = 0; i < lines.length; i++) {
65
+ const line = lines[i];
66
+ const noStoreHit = NO_STORE_RE.test(line);
67
+ const revalidateZeroHit = REVALIDATE_ZERO_RE.test(line);
68
+ if (!noStoreHit && !revalidateZeroHit) continue;
69
+ const start = Math.max(0, i - 10);
70
+ const end = Math.min(lines.length, i + 5);
71
+ const window = lines.slice(start, end).join('\n');
72
+ if (AUTH_RE.test(window)) continue;
73
+ // Mutation verbs legitimately don't cache.
74
+ if (/method\s*:\s*['"](?:POST|PUT|PATCH|DELETE)['"]/i.test(window)) continue;
75
+ out.push({
76
+ pattern: metadata.id,
77
+ subtype: noStoreHit ? 'fetch-no-store' : 'fetch-revalidate-zero',
78
+ file: path,
79
+ line: i + 1,
80
+ evidence: line.trim().slice(0, 200),
81
+ trafficIndependent: metadata.trafficIndependent,
82
+ });
83
+ }
84
+ }
85
+ return out;
86
+ }
87
+
88
+ function isApplicable(path) {
89
+ return /\/(route|index|page|layout)\.(ts|tsx|js|jsx)$/.test(path) || /\/api\//.test(path);
90
+ }
@@ -0,0 +1,42 @@
1
+ import { lineOf } from '../util.mjs';
2
+
3
+ export const metadata = {
4
+ id: 'prisma-include-tree-bloat',
5
+ title: 'Deep Prisma include tree (3+ levels)',
6
+ severity: 'high',
7
+ billingDimension: 'function-duration',
8
+ trafficIndependent: false,
9
+ description:
10
+ 'Nested .include({ x: { include: { y: { include: { z: ... } } } } }) makes Prisma issue a single huge join that scales O(N*M*K). Function duration explodes, memory spikes, often causes timeouts.',
11
+ fix:
12
+ 'Replace with explicit .findMany() calls or scoped .include() of only what the consumer reads. Consider Prisma.select() to project specific fields. For lists, batch with DataLoader patterns.',
13
+ citations: [
14
+ 'vercel-react-best-practices:server-parallel-fetching',
15
+ ],
16
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**'],
17
+ includeGlobs: ['**/*.{ts,tsx,js,jsx}'],
18
+ };
19
+
20
+ // Catches 3+ nesting levels of `include:` within a single object literal.
21
+ const RE = /include\s*:\s*\{[\s\S]*?include\s*:\s*\{[\s\S]*?include\s*:/g;
22
+
23
+ export function scan({ files }) {
24
+ const out = [];
25
+ for (const { path, content } of files) {
26
+ if (/\.test\.|\.spec\./.test(path)) continue;
27
+ let m;
28
+ RE.lastIndex = 0;
29
+ while ((m = RE.exec(content)) !== null) {
30
+ out.push({
31
+ pattern: metadata.id,
32
+ file: path,
33
+ line: lineOf(content, m.index),
34
+ evidence: '3+ levels of nested Prisma .include()',
35
+ trafficIndependent: metadata.trafficIndependent,
36
+ });
37
+ // One finding per file is enough — agent investigates holistically.
38
+ break;
39
+ }
40
+ }
41
+ return out;
42
+ }
@@ -0,0 +1,88 @@
1
+ // Detects pinned function regions in vercel.json or per-route segment config.
2
+ // Provides the "configured region" signal for the region-misconfig gate when a TTFB
3
+ // breakdown by function_region isn't available (current state — see Phase 0 preflight
4
+ // in plans/wild-splashing-flamingo.md).
5
+ //
6
+ // Subtypes:
7
+ // vercel-json-single — vercel.json `regions: ["iad1"]` (single region, no failover)
8
+ // vercel-json-list — vercel.json `regions: [...]` (multi-region; informational)
9
+ // segment-preferred — `export const preferredRegion = 'iad1'` (or array)
10
+
11
+ import { lineOf } from '../util.mjs';
12
+
13
+ export const metadata = {
14
+ id: 'region-pin-in-config',
15
+ title: 'Function region pinned in config',
16
+ severity: 'low',
17
+ billingDimension: 'function-duration',
18
+ trafficIndependent: true,
19
+ description:
20
+ "vercel.json `regions` or per-route `preferredRegion` is set. If the pinned region is far from the dominant user geo (or far from a data source) p95 TTFB suffers. This scanner provides the configured-region signal so the region-misconfig gate can recommend an audit.",
21
+ fix:
22
+ "Audit the pinned region against traffic geography (Speed Insights or Web Analytics by country) and data-source location. Consider multi-region if data lives in a fixed location and users are global; consider relocating if users are concentrated in one geography.",
23
+ citations: [
24
+ 'https://vercel.com/docs/functions/configuring-functions/region',
25
+ 'https://vercel.com/docs/functions/configuring-functions/region',
26
+ ],
27
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**'],
28
+ includeGlobs: [
29
+ 'vercel.json',
30
+ '**/vercel.json',
31
+ '**/page.{ts,tsx,js,jsx}',
32
+ '**/route.{ts,tsx,js,jsx}',
33
+ '**/layout.{ts,tsx,js,jsx}',
34
+ ],
35
+ };
36
+
37
+ // Matches `regions: ["iad1"]`, `regions: ['iad1', 'sfo1']`, or `"regions": ["iad1"]`
38
+ const VERCEL_JSON_REGIONS_RE = /['"]?regions['"]?\s*:\s*\[([^\]]+)\]/;
39
+ // `export const preferredRegion = 'iad1'` OR `= ['iad1', 'sfo1']`
40
+ const PREFERRED_REGION_RE = /export\s+const\s+preferredRegion\s*=\s*(['"][^'"]+['"]|\[[^\]]+\])/;
41
+
42
+ export function scan({ files }) {
43
+ const out = [];
44
+ for (const { path, content } of files) {
45
+ const name = path.split('/').pop();
46
+
47
+ if (name === 'vercel.json') {
48
+ const m = VERCEL_JSON_REGIONS_RE.exec(content);
49
+ if (m) {
50
+ const regions = parseRegionList(m[1]);
51
+ out.push({
52
+ pattern: metadata.id,
53
+ file: path,
54
+ line: lineOf(content, m.index),
55
+ evidence: `vercel.json regions: [${regions.join(', ')}]`,
56
+ trafficIndependent: metadata.trafficIndependent,
57
+ subtype: regions.length === 1 ? 'vercel-json-single' : 'vercel-json-list',
58
+ regions,
59
+ });
60
+ }
61
+ continue;
62
+ }
63
+
64
+ // Segment config files (page.tsx, route.ts, layout.tsx).
65
+ const m = PREFERRED_REGION_RE.exec(content);
66
+ if (m) {
67
+ const raw = m[1];
68
+ const regions = raw.startsWith('[') ? parseRegionList(raw.slice(1, -1)) : [raw.replace(/['"]/g, '')];
69
+ out.push({
70
+ pattern: metadata.id,
71
+ file: path,
72
+ line: lineOf(content, m.index),
73
+ evidence: `preferredRegion = ${raw}`,
74
+ trafficIndependent: metadata.trafficIndependent,
75
+ subtype: 'segment-preferred',
76
+ regions,
77
+ });
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+
83
+ function parseRegionList(inner) {
84
+ return inner
85
+ .split(',')
86
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
87
+ .filter(Boolean);
88
+ }
@@ -0,0 +1,36 @@
1
+ import { lineOf } from '../util.mjs';
2
+
3
+ export const metadata = {
4
+ id: 'source-maps-production',
5
+ title: 'Source maps enabled in production',
6
+ severity: 'low',
7
+ billingDimension: 'edge-requests',
8
+ trafficIndependent: true,
9
+ description:
10
+ 'productionBrowserSourceMaps: true ships .map files in the production bundle, increasing transfer size 30-100% per visitor. Useful for error reporting via Sentry; not useful for users.',
11
+ fix:
12
+ 'Keep source maps generation but exclude them from the public bundle. Upload to your error tracker via build-time CI step; do not serve them with the deployment.',
13
+ citations: [
14
+ 'https://nextjs.org/docs/messages/improper-devtool',
15
+ ],
16
+ excludeGlobs: [],
17
+ includeGlobs: ['next.config.{js,mjs,ts}', 'svelte.config.{js,mjs,ts}'],
18
+ };
19
+
20
+ export function scan({ files }) {
21
+ const out = [];
22
+ for (const { path, content } of files) {
23
+ if (!/^next\.config\.(js|mjs|ts)$/.test(path.split('/').pop() ?? '')) continue;
24
+ const m = /productionBrowserSourceMaps\s*:\s*true/.exec(content);
25
+ if (m) {
26
+ out.push({
27
+ pattern: metadata.id,
28
+ file: path,
29
+ line: lineOf(content, m.index),
30
+ evidence: 'productionBrowserSourceMaps: true',
31
+ trafficIndependent: metadata.trafficIndependent,
32
+ });
33
+ }
34
+ }
35
+ return out;
36
+ }
@@ -0,0 +1,43 @@
1
+ // Flag SvelteKit pages that haven't declared prerender/ssr/config — they
2
+ // default to per-request function execution. Pages that have already opted
3
+ // in or out are skipped; the investigator agent decides actual staticness.
4
+
5
+ export const metadata = {
6
+ id: 'sveltekit-prerender-missing',
7
+ title: 'SvelteKit page without explicit prerender / ISR config',
8
+ severity: 'low',
9
+ billingDimension: 'function-duration',
10
+ trafficIndependent: false,
11
+ description:
12
+ 'SvelteKit page or +page.server.ts is missing an explicit `prerender`, `ssr`, or adapter `config.isr` declaration. Default is per-request function execution — investigate whether the route could be prerendered or ISR-cached.',
13
+ fix:
14
+ 'If the page is static (no per-user / per-request data), add `export const prerender = true` in +page.ts or +page.server.ts. If the data refreshes on a schedule, prefer adapter-vercel\'s ISR option via `export const config = { isr: { expiration: 60 } }`.',
15
+ citations: [
16
+ 'https://kit.svelte.dev/docs/page-options',
17
+ 'https://kit.svelte.dev/docs/adapter-vercel',
18
+ 'https://vercel.com/docs/incremental-static-regeneration',
19
+ ],
20
+ excludeGlobs: ['node_modules/**', '.svelte-kit/**', 'build/**', '__tests__/**'],
21
+ includeGlobs: ['src/routes/**/+page.svelte', 'src/routes/**/+page.server.{ts,js}', 'src/routes/+page.svelte', 'src/routes/+page.server.{ts,js}'],
22
+ };
23
+
24
+ const PRERENDER_RE = /export\s+const\s+prerender\b/;
25
+ const SSR_RE = /export\s+const\s+ssr\b/;
26
+ const CONFIG_RE = /export\s+const\s+config\s*=\s*\{[^}]*\b(isr|prerender|runtime|split|regions)\b/;
27
+
28
+ export function scan({ files }) {
29
+ const out = [];
30
+ for (const { path, content } of files) {
31
+ if (!path.includes('/routes/')) continue;
32
+ if (PRERENDER_RE.test(content) || SSR_RE.test(content) || CONFIG_RE.test(content)) continue;
33
+ out.push({
34
+ pattern: metadata.id,
35
+ file: path,
36
+ // Absence-finding — no specific line, placeholder 1.
37
+ line: 1,
38
+ evidence: 'No `prerender`, `ssr`, or `config = { isr | runtime | ... }` export found',
39
+ trafficIndependent: metadata.trafficIndependent,
40
+ });
41
+ }
42
+ return out;
43
+ }
@@ -0,0 +1,129 @@
1
+ // Detects Turborepo cache bypass patterns that cause every commit to rebuild every project,
2
+ // driving Build Minutes to dominate the bill on monorepos.
3
+ //
4
+ // Three signal subtypes:
5
+ // force-flag — `TURBO_FORCE=true` env var or `turbo run ... --force` in build script
6
+ // cache-disabled — `turbo.json` declares `"cache": false` for the build pipeline
7
+ // no-ignore-step — repo has turbo.json and no repo-declared ignoreCommand;
8
+ // verify Vercel's skip-unaffected project setting before recommending one
9
+ //
10
+ // This pattern has caused full-monorepo rebuilds on every commit. Build-skip
11
+ // settings and right-sized build machines can reduce Build Minutes when the
12
+ // project is rebuilding unchanged work.
13
+
14
+ export const metadata = {
15
+ id: 'turbo-force-bypass',
16
+ title: 'Turborepo cache bypass on a monorepo',
17
+ severity: 'high',
18
+ billingDimension: 'build',
19
+ trafficIndependent: true, // build-time, fires regardless of route traffic
20
+ description:
21
+ "Turborepo's per-task cache can be bypassed by an explicit force flag, a `cache: false` config, or missing build-skip configuration. Every commit can rebuild unchanged work; Build Minutes climb with project count.",
22
+ fix:
23
+ "Remove `TURBO_FORCE=true` from build env/scripts unless intentional. Set `tasks.build.cache: true` in `turbo.json` (or remove the override), and include generated outputs in Turbo's cache contract. Prefer Vercel's skip-unaffected monorepo behavior when available; use `ignoreCommand` only when that setting cannot cover the project.",
24
+ citations: [
25
+ 'https://vercel.com/docs/monorepos',
26
+ 'https://vercel.com/docs/builds',
27
+ 'https://turborepo.dev/docs/crafting-your-repository/caching',
28
+ ],
29
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**'],
30
+ includeGlobs: ['turbo.json', '**/turbo.json', 'package.json', '**/package.json', 'vercel.json', '**/vercel.json'],
31
+ };
32
+
33
+ const FORCE_ENV_RE = /TURBO_FORCE\s*=\s*(?:true|1)\b/;
34
+ const FORCE_FLAG_RE = /\bturbo\s+(?:run\s+)?[a-z:_-]+[^\n&|;]*\s--force\b/;
35
+
36
+ export function scan({ files }) {
37
+ const out = [];
38
+ let hasTurboJson = false;
39
+ let vercelJsonFile = null;
40
+ let vercelJsonContent = null;
41
+
42
+ for (const { path, content } of files) {
43
+ const name = path.split('/').pop();
44
+
45
+ if (name === 'turbo.json') {
46
+ hasTurboJson = true;
47
+ const buildCacheDisabled = detectBuildCacheDisabled(content);
48
+ if (buildCacheDisabled) {
49
+ out.push({
50
+ pattern: metadata.id,
51
+ file: path,
52
+ line: buildCacheDisabled.line,
53
+ evidence: 'turbo.json: tasks.build.cache = false',
54
+ trafficIndependent: metadata.trafficIndependent,
55
+ subtype: 'cache-disabled',
56
+ });
57
+ }
58
+ continue;
59
+ }
60
+
61
+ if (name === 'package.json') {
62
+ const scripts = safeScripts(content);
63
+ for (const [scriptName, body] of Object.entries(scripts)) {
64
+ if (FORCE_ENV_RE.test(body) || FORCE_FLAG_RE.test(body)) {
65
+ const line = lineOfMatch(content, body) ?? 1;
66
+ out.push({
67
+ pattern: metadata.id,
68
+ file: path,
69
+ line,
70
+ evidence: `package.json scripts.${scriptName}: ${truncate(body, 80)}`,
71
+ trafficIndependent: metadata.trafficIndependent,
72
+ subtype: 'force-flag',
73
+ });
74
+ }
75
+ }
76
+ continue;
77
+ }
78
+
79
+ if (name === 'vercel.json') {
80
+ vercelJsonFile = path;
81
+ vercelJsonContent = content;
82
+ }
83
+ }
84
+
85
+ // No-ignore-step: repo has turbo.json AND vercel.json lacks an ignoreCommand.
86
+ // This is an investigation prompt, not proof that the dashboard skip setting is off.
87
+ if (hasTurboJson && vercelJsonFile && !/"ignoreCommand"\s*:/.test(vercelJsonContent ?? '')) {
88
+ out.push({
89
+ pattern: metadata.id,
90
+ file: vercelJsonFile,
91
+ line: 1,
92
+ evidence: 'turbo repo without ignoreCommand in vercel.json; verify Vercel skip-unaffected setting',
93
+ trafficIndependent: metadata.trafficIndependent,
94
+ subtype: 'no-ignore-step',
95
+ });
96
+ }
97
+
98
+ return out;
99
+ }
100
+
101
+ function detectBuildCacheDisabled(content) {
102
+ // Tolerate JSONC comments and trailing commas — light scan, not full parse.
103
+ // Match `"build": { ... "cache": false ... }` within reasonable lookahead.
104
+ const buildTask = /"build"\s*:\s*\{([\s\S]{0,400}?)\}/.exec(content);
105
+ if (!buildTask) return null;
106
+ if (!/"cache"\s*:\s*false/.test(buildTask[1])) return null;
107
+ const lineNum = content.slice(0, buildTask.index).split('\n').length;
108
+ return { line: lineNum };
109
+ }
110
+
111
+ function safeScripts(content) {
112
+ try {
113
+ const parsed = JSON.parse(content);
114
+ return parsed?.scripts && typeof parsed.scripts === 'object' ? parsed.scripts : {};
115
+ } catch {
116
+ return {};
117
+ }
118
+ }
119
+
120
+ function lineOfMatch(haystack, needle) {
121
+ const idx = haystack.indexOf(needle);
122
+ if (idx < 0) return null;
123
+ return haystack.slice(0, idx).split('\n').length;
124
+ }
125
+
126
+ function truncate(s, n) {
127
+ if (typeof s !== 'string') return '';
128
+ return s.length > n ? s.slice(0, n - 1) + '…' : s;
129
+ }
@@ -0,0 +1,113 @@
1
+ // Four image-optimization checks emitted as `unoptimized-image` findings.
2
+ // `subtype` distinguishes raw-img / global-unoptimized / image-fill-no-sizes
3
+ // / image-svg-no-unoptimized so the recommender can frame each separately.
4
+
5
+ export const metadata = {
6
+ id: 'unoptimized-image',
7
+ title: 'Image optimization gap (raw <img>, global flag, missing sizes, or SVG mis-routed)',
8
+ severity: 'high',
9
+ billingDimension: 'image-optimization',
10
+ trafficIndependent: false,
11
+ description:
12
+ 'Four shapes of image-cost waste: raw <img> tags bypass the framework Image component; `images.unoptimized: true` disables Vercel image optimization globally; <Image fill> without `sizes` forces serving the largest source variant; <Image src=".svg"> without `unoptimized` routes vector data through the raster pipeline.',
13
+ fix:
14
+ 'For raw <img>: switch to next/image, enhanced-img (SvelteKit), <Image /> (Astro), or NuxtImg. For global unoptimized:true: remove the flag unless the project is hosted outside Vercel. For fill without sizes: add `sizes="(max-width: 768px) 100vw, 50vw"` or whatever matches your layout. For SVG: add `unoptimized` so the raw SVG ships instead of rastering it.',
15
+ citations: [
16
+ 'https://nextjs.org/docs/app/api-reference/components/image',
17
+ 'https://vercel.com/docs/image-optimization',
18
+ ],
19
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**', 'cypress/**', '*.test.*'],
20
+ includeGlobs: ['**/*.{tsx,jsx,html,svelte,astro,vue,js,mjs,ts}'],
21
+ };
22
+
23
+ const IMG_RE = /<img\s+[^>]*src\s*=\s*["'{`]/g;
24
+ const GLOBAL_UNOPT_RE = /images\s*:\s*\{[^}]*\bunoptimized\s*:\s*true/;
25
+ const IMAGE_TAG_RE = /<Image\b[^>]*?\/?>/g;
26
+ const NEXT_IMAGE_IMPORT_RE = /from\s+['"]next\/image['"]/;
27
+
28
+ export function scan({ files }) {
29
+ const out = [];
30
+ for (const { path, content } of files) {
31
+ if (isJsxLike(path)) {
32
+ let m;
33
+ IMG_RE.lastIndex = 0;
34
+ while ((m = IMG_RE.exec(content)) !== null) {
35
+ out.push({
36
+ pattern: metadata.id,
37
+ subtype: 'raw-img',
38
+ file: path,
39
+ line: lineOf(content, m.index),
40
+ evidence: snippet(content, m.index),
41
+ trafficIndependent: metadata.trafficIndependent,
42
+ });
43
+ }
44
+ }
45
+
46
+ if (isNextConfig(path)) {
47
+ const match = GLOBAL_UNOPT_RE.exec(content);
48
+ if (match) {
49
+ out.push({
50
+ pattern: metadata.id,
51
+ subtype: 'global-unoptimized',
52
+ file: path,
53
+ line: lineOf(content, match.index),
54
+ evidence: 'images: { unoptimized: true } — disables Vercel image optimization for the entire project',
55
+ // Config-level flag affects every image regardless of route.
56
+ trafficIndependent: true,
57
+ });
58
+ }
59
+ }
60
+
61
+ // Only fire if next/image is imported — otherwise `Image` is some
62
+ // other component.
63
+ if (isJsxLike(path) && NEXT_IMAGE_IMPORT_RE.test(content)) {
64
+ let m;
65
+ IMAGE_TAG_RE.lastIndex = 0;
66
+ while ((m = IMAGE_TAG_RE.exec(content)) !== null) {
67
+ const tag = m[0];
68
+ const hasFill = /\bfill\b/.test(tag);
69
+ const hasSizes = /\bsizes\s*=/.test(tag);
70
+ if (hasFill && !hasSizes) {
71
+ out.push({
72
+ pattern: metadata.id,
73
+ subtype: 'image-fill-no-sizes',
74
+ file: path,
75
+ line: lineOf(content, m.index),
76
+ evidence: tag.slice(0, 200),
77
+ trafficIndependent: metadata.trafficIndependent,
78
+ });
79
+ }
80
+ // Inline data: URLs never round-trip through the optimizer.
81
+ const srcMatch = /\bsrc\s*=\s*["']([^"']+)["']/.exec(tag);
82
+ if (srcMatch) {
83
+ const src = srcMatch[1];
84
+ if (/\.svg(\?|$)/i.test(src) && !src.startsWith('data:') && !/\bunoptimized\b/.test(tag)) {
85
+ out.push({
86
+ pattern: metadata.id,
87
+ subtype: 'image-svg-no-unoptimized',
88
+ file: path,
89
+ line: lineOf(content, m.index),
90
+ evidence: tag.slice(0, 200),
91
+ trafficIndependent: metadata.trafficIndependent,
92
+ });
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+
101
+ import { lineOf } from '../util.mjs';
102
+
103
+ function isJsxLike(path) {
104
+ return /\.(tsx|jsx|html|svelte|astro|vue)$/.test(path);
105
+ }
106
+ function isNextConfig(path) {
107
+ return /(?:^|\/)next\.config\.(js|mjs|ts|cjs)$/.test(path);
108
+ }
109
+ function snippet(text, idx) {
110
+ const start = text.lastIndexOf('\n', idx) + 1;
111
+ const end = text.indexOf('\n', idx);
112
+ return text.slice(start, end === -1 ? text.length : end).trim().slice(0, 160);
113
+ }