opencode-skills-collection 3.0.35 → 3.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +1 -1
  238. package/skills_index.json +312 -0
@@ -0,0 +1,106 @@
1
+ // Detects time/randomness primitives that destabilize `'use cache'` cache keys,
2
+ // which manifests as ISR write amplification when the cached output embeds a timestamp
3
+ // that changes per request.
4
+ //
5
+ // Triggers when a file contains the `'use cache'` directive AND uses `new Date(`,
6
+ // `Date.now(`, or `Math.random(` outside client-only hooks (useEffect / useCallback /
7
+ // useMemo). Replacing module-scope `new Date().getFullYear()` with a build-time
8
+ // `buildYear` constant, and removing dates passed as `'use cache'` function
9
+ // arguments, prevents repeated writes when the rendered output is otherwise stable.
10
+
11
+ import { lineOf } from '../util.mjs';
12
+
13
+ export const metadata = {
14
+ id: 'use-cache-date-stamp',
15
+ title: "new Date() / Date.now() / Math.random() inside a 'use cache' file",
16
+ severity: 'high',
17
+ billingDimension: 'isr',
18
+ trafficIndependent: false,
19
+ description:
20
+ "`'use cache'` memoizes by argument identity AND prerender output. A timestamp baked into the cached output (`new Date().getFullYear()` in a footer, `Date.now()` in a payload field) forces a fresh ISR write on every regeneration even when the underlying data is unchanged. Random values have the same failure mode.",
21
+ fix:
22
+ "Replace module-scope `new Date()` with a build-time constant (`const buildYear = new Date().getFullYear()`) or move per-request timestamps into a client component inside `useEffect`. Do not pass dates as arguments to `'use cache'` functions — they invalidate the cache every call.",
23
+ citations: [
24
+ 'https://nextjs.org/docs/app/api-reference/directives/use-cache',
25
+ 'https://nextjs.org/docs/app/api-reference/functions/cacheLife',
26
+ ],
27
+ excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**', '**/*.test.*', '**/*.spec.*'],
28
+ includeGlobs: [
29
+ '**/page.{ts,tsx,js,jsx}',
30
+ '**/layout.{ts,tsx,js,jsx}',
31
+ '**/route.{ts,tsx,js,jsx}',
32
+ '**/lib/**/*.{ts,tsx,js,jsx}',
33
+ '**/app/**/*.{ts,tsx,js,jsx}',
34
+ '**/components/**/*.{ts,tsx,js,jsx}',
35
+ ],
36
+ };
37
+
38
+ const USE_CACHE_RE = /^[\t ]*['"]use cache['"]/m;
39
+ const SUSPECT_RE = /\b(new Date\(|Date\.now\(|Math\.random\()/g;
40
+ // Client-only hooks that don't affect server-side cache keys.
41
+ const CLIENT_HOOK_RE = /\b(useEffect|useCallback|useMemo|useLayoutEffect)\s*\(/g;
42
+
43
+ export function scan({ files }) {
44
+ const out = [];
45
+ for (const { path, content } of files) {
46
+ if (!USE_CACHE_RE.test(content)) continue;
47
+
48
+ const clientHookRanges = collectRanges(content, CLIENT_HOOK_RE);
49
+ let match;
50
+ SUSPECT_RE.lastIndex = 0;
51
+ while ((match = SUSPECT_RE.exec(content)) !== null) {
52
+ if (isInsideAnyRange(match.index, clientHookRanges)) continue;
53
+ out.push({
54
+ pattern: metadata.id,
55
+ file: path,
56
+ line: lineOf(content, match.index),
57
+ evidence: match[0],
58
+ trafficIndependent: metadata.trafficIndependent,
59
+ subtype: classifySubtype(content, match.index),
60
+ });
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+
66
+ function collectRanges(content, hookRe) {
67
+ const ranges = [];
68
+ hookRe.lastIndex = 0;
69
+ let m;
70
+ while ((m = hookRe.exec(content)) !== null) {
71
+ const open = content.indexOf('(', m.index);
72
+ if (open < 0) continue;
73
+ const close = findMatchingParen(content, open);
74
+ if (close < 0) continue;
75
+ ranges.push([open, close]);
76
+ }
77
+ return ranges;
78
+ }
79
+
80
+ function findMatchingParen(content, openIdx) {
81
+ let depth = 0;
82
+ for (let i = openIdx; i < content.length; i++) {
83
+ const c = content[i];
84
+ if (c === '(') depth++;
85
+ else if (c === ')') {
86
+ depth--;
87
+ if (depth === 0) return i;
88
+ }
89
+ }
90
+ return -1;
91
+ }
92
+
93
+ function isInsideAnyRange(idx, ranges) {
94
+ for (const [a, b] of ranges) {
95
+ if (idx >= a && idx <= b) return true;
96
+ }
97
+ return false;
98
+ }
99
+
100
+ // `module-scope` if the suspect appears before the first function/class declaration.
101
+ // `in-cache-fn` otherwise (likely inside a render or helper function body).
102
+ function classifySubtype(content, idx) {
103
+ const head = content.slice(0, idx);
104
+ if (!/\bfunction\b|\bclass\b|=>\s*\{/.test(head)) return 'module-scope';
105
+ return 'in-cache-fn';
106
+ }
@@ -0,0 +1,355 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { dirname, join, basename } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { gates } from './gates/index.mjs';
5
+ import { SCANNER_GATES } from './gates/scanner-driven.mjs';
6
+ import {
7
+ loadLibrary,
8
+ lookupSkillRule,
9
+ lookupUrl,
10
+ matchesFrameworkVersion,
11
+ } from './citations.mjs';
12
+
13
+ const HERE = dirname(fileURLToPath(import.meta.url));
14
+ const TOPICS_DIR = join(HERE, '..', 'references', 'support-topics');
15
+
16
+ export const SUPPORT_TOPIC_LIMIT = 3;
17
+ export const SUPPORT_TOPIC_TOTAL_CHAR_LIMIT = 2400;
18
+ const DEFAULT_MAX_BRIEF_CHARS = 900;
19
+
20
+ export const KNOWN_CANDIDATE_KINDS = new Set([
21
+ ...gates
22
+ .map((g) => g.metadata?.id)
23
+ .filter((id) => id && id !== 'scanner-driven'),
24
+ ...SCANNER_GATES.map((g) => g.id),
25
+ ]);
26
+
27
+ export async function supportTopicSubset({
28
+ candidate,
29
+ signals = {},
30
+ framework,
31
+ version,
32
+ profile,
33
+ frameworkPlaybookId,
34
+ maxTopics = SUPPORT_TOPIC_LIMIT,
35
+ maxChars = SUPPORT_TOPIC_TOTAL_CHAR_LIMIT,
36
+ } = {}) {
37
+ const stack = signals?.stack ?? signals?.codebase?.stack ?? {};
38
+ const fw = framework ?? stack.framework ?? 'unknown';
39
+ const fwVersion = version ?? stack.frameworkVersion ?? 'unknown';
40
+ const candidates = await loadSupportTopics();
41
+ const selected = [];
42
+ let usedChars = 0;
43
+
44
+ const sorted = candidates
45
+ .filter((t) => t.status === 'active')
46
+ .filter((t) => matchesCandidateKind(t, candidate?.kind))
47
+ .filter((t) => matchesFrameworks(t.frameworks, fw, fwVersion))
48
+ .filter((t) => matchesOptionalList(t.profiles, profile))
49
+ .filter((t) => matchesOptionalList(t.frameworkPlaybooks, frameworkPlaybookId))
50
+ .filter((t) => matchesRouter(t.routers, stack))
51
+ .filter((t) => matchesCandidateMetrics(t.metrics, candidate))
52
+ .filter((t) => matchesCandidateRoutePatterns(t.routePatterns, candidate))
53
+ .filter((t) => matchesScannerPatterns(t.scannerPatterns, candidate))
54
+ .sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id));
55
+
56
+ for (const topic of sorted) {
57
+ if (!await topicCitationsApply(topic, candidate?.kind, fw, fwVersion)) continue;
58
+ if (selected.length >= maxTopics) break;
59
+ const renderedChars = topic.title.length + topic.body.length + topic.id.length + 20;
60
+ if (selected.length > 0 && usedChars + renderedChars > maxChars) continue;
61
+ selected.push(topic);
62
+ usedChars += renderedChars;
63
+ }
64
+
65
+ return selected;
66
+ }
67
+
68
+ export async function loadSupportTopics({ includeDraft = false } = {}) {
69
+ let names = [];
70
+ try {
71
+ names = await readdir(TOPICS_DIR);
72
+ } catch (err) {
73
+ if (err?.code === 'ENOENT') return [];
74
+ throw err;
75
+ }
76
+
77
+ const topics = [];
78
+ for (const name of names.sort()) {
79
+ if (!name.endsWith('.md') || name === 'README.md') continue;
80
+ const path = join(TOPICS_DIR, name);
81
+ const raw = await readFile(path, 'utf-8');
82
+ const topic = parseSupportTopic(raw, path);
83
+ if (includeDraft || topic.status === 'active') topics.push(topic);
84
+ }
85
+ return topics.sort((a, b) => a.id.localeCompare(b.id));
86
+ }
87
+
88
+ export async function validateSupportTopics() {
89
+ const topics = await loadSupportTopics({ includeDraft: true });
90
+ const errors = [];
91
+ const seen = new Set();
92
+ for (const topic of topics) {
93
+ errors.push(...await validateSupportTopic(topic));
94
+ if (seen.has(topic.id)) errors.push(`${topic.path}: duplicate topic id "${topic.id}"`);
95
+ seen.add(topic.id);
96
+ }
97
+ return { ok: errors.length === 0, errors, topics };
98
+ }
99
+
100
+ export function renderSupportTopics(topics = []) {
101
+ if (!Array.isArray(topics) || topics.length === 0) return [];
102
+ const lines = [];
103
+ lines.push('## Support topics (investigation guardrails)');
104
+ lines.push('');
105
+ lines.push('These are deterministic, candidate-scoped hints selected from `references/support-topics/`. They do not create recommendations. Use them only to decide what evidence to check, what to rule out, and when to abstain.');
106
+ lines.push('');
107
+ for (const topic of topics) {
108
+ lines.push(`### ${topic.title} (\`${topic.id}\`)`);
109
+ lines.push('');
110
+ lines.push(topic.body.trim());
111
+ lines.push('');
112
+ }
113
+ return lines;
114
+ }
115
+
116
+ export function parseSupportTopic(raw, path = '<memory>') {
117
+ const { frontmatter, body } = splitFrontmatter(raw, path);
118
+ const metadata = parseFrontmatter(frontmatter, path);
119
+ return normalizeTopic({ ...metadata, body: body.trim(), path });
120
+ }
121
+
122
+ function splitFrontmatter(raw, path) {
123
+ const text = String(raw ?? '');
124
+ if (!text.startsWith('---\n')) {
125
+ throw new Error(`${path}: support topic must start with --- frontmatter`);
126
+ }
127
+ const end = text.indexOf('\n---\n', 4);
128
+ if (end === -1) {
129
+ throw new Error(`${path}: support topic frontmatter must end with ---`);
130
+ }
131
+ return {
132
+ frontmatter: text.slice(4, end),
133
+ body: text.slice(end + '\n---\n'.length),
134
+ };
135
+ }
136
+
137
+ function parseFrontmatter(src, path) {
138
+ const out = {};
139
+ for (const rawLine of src.split('\n')) {
140
+ const line = rawLine.trim();
141
+ if (!line || line.startsWith('#')) continue;
142
+ const m = line.match(/^([A-Za-z][A-Za-z0-9]*):\s*(.*)$/);
143
+ if (!m) throw new Error(`${path}: unsupported frontmatter line "${rawLine}"`);
144
+ const [, key, value] = m;
145
+ out[key] = parseFrontmatterValue(value, path, key);
146
+ }
147
+ return out;
148
+ }
149
+
150
+ function parseFrontmatterValue(value, path, key) {
151
+ if (value.startsWith('[')) {
152
+ try {
153
+ const parsed = JSON.parse(value);
154
+ if (!Array.isArray(parsed)) throw new Error('not an array');
155
+ return parsed;
156
+ } catch (err) {
157
+ throw new Error(`${path}: ${key} must use strict JSON array syntax (${err.message})`);
158
+ }
159
+ }
160
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
161
+ if (value === 'true') return true;
162
+ if (value === 'false') return false;
163
+ if (value === 'null') return null;
164
+ const quoted = value.match(/^"(.*)"$/) ?? value.match(/^'(.*)'$/);
165
+ return quoted ? quoted[1] : value;
166
+ }
167
+
168
+ function normalizeTopic(topic) {
169
+ const maxBriefChars = Number.isFinite(topic.maxBriefChars)
170
+ ? topic.maxBriefChars
171
+ : DEFAULT_MAX_BRIEF_CHARS;
172
+ return {
173
+ id: topic.id,
174
+ title: topic.title,
175
+ status: topic.status,
176
+ candidateKinds: toStringArray(topic.candidateKinds),
177
+ frameworks: toStringArray(topic.frameworks),
178
+ profiles: toStringArray(topic.profiles),
179
+ frameworkPlaybooks: toStringArray(topic.frameworkPlaybooks),
180
+ routers: toStringArray(topic.routers),
181
+ metrics: toStringArray(topic.metrics),
182
+ routePatterns: toStringArray(topic.routePatterns),
183
+ scannerPatterns: toStringArray(topic.scannerPatterns),
184
+ billingDimensions: toStringArray(topic.billingDimensions),
185
+ citations: toStringArray(topic.citations),
186
+ priority: Number(topic.priority),
187
+ maxBriefChars,
188
+ body: topic.body,
189
+ path: topic.path,
190
+ };
191
+ }
192
+
193
+ async function validateSupportTopic(topic) {
194
+ const errors = [];
195
+ const label = topic.path ?? topic.id ?? '<topic>';
196
+ const fileId = basename(label).replace(/\.md$/, '');
197
+
198
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(topic.id ?? '')) {
199
+ errors.push(`${label}: id must be kebab-case`);
200
+ }
201
+ if (fileId !== topic.id) errors.push(`${label}: filename must match id`);
202
+ if (!nonEmptyString(topic.title)) errors.push(`${label}: title is required`);
203
+ if (!['active', 'draft', 'deprecated'].includes(topic.status)) {
204
+ errors.push(`${label}: status must be active, draft, or deprecated`);
205
+ }
206
+ if (!Number.isFinite(topic.priority)) errors.push(`${label}: priority must be a number`);
207
+ if (!Number.isFinite(topic.maxBriefChars) || topic.maxBriefChars < 200 || topic.maxBriefChars > 1400) {
208
+ errors.push(`${label}: maxBriefChars must be between 200 and 1400`);
209
+ }
210
+ if (!nonEmptyArray(topic.candidateKinds)) {
211
+ errors.push(`${label}: candidateKinds must be a non-empty array`);
212
+ } else {
213
+ for (const kind of topic.candidateKinds) {
214
+ if (kind !== '*' && !KNOWN_CANDIDATE_KINDS.has(kind)) {
215
+ errors.push(`${label}: unknown candidate kind "${kind}"`);
216
+ }
217
+ }
218
+ }
219
+ if (!nonEmptyArray(topic.frameworks)) {
220
+ errors.push(`${label}: frameworks must be a non-empty array`);
221
+ } else {
222
+ for (const fw of topic.frameworks) {
223
+ if (fw !== '*' && !/^[\w-]+@/.test(fw)) {
224
+ errors.push(`${label}: framework "${fw}" must be "*" or "framework@range"`);
225
+ }
226
+ }
227
+ }
228
+ if (!nonEmptyArray(topic.citations)) {
229
+ errors.push(`${label}: citations must be a non-empty array`);
230
+ } else {
231
+ for (const citation of topic.citations) {
232
+ if (!await knownCitation(citation)) {
233
+ errors.push(`${label}: unknown citation "${citation}"`);
234
+ }
235
+ }
236
+ }
237
+ for (const pattern of topic.routePatterns) {
238
+ try {
239
+ new RegExp(pattern);
240
+ } catch (err) {
241
+ errors.push(`${label}: invalid routePatterns regex "${pattern}" (${err.message})`);
242
+ }
243
+ }
244
+ for (const heading of [
245
+ '## Investigation Brief',
246
+ '## Evidence To Check',
247
+ '## Do Not Recommend When',
248
+ '## Verification',
249
+ ]) {
250
+ if (!topic.body.includes(heading)) errors.push(`${label}: missing heading "${heading}"`);
251
+ }
252
+ if (topic.body.length > topic.maxBriefChars) {
253
+ errors.push(`${label}: body length ${topic.body.length} exceeds maxBriefChars ${topic.maxBriefChars}`);
254
+ }
255
+ if (/https?:\/\//.test(topic.body)) {
256
+ errors.push(`${label}: put URLs in frontmatter citations, not body text`);
257
+ }
258
+ if (/\/Users\/|(?:^|[\s`"'])apps\/[^/\s`"']+\/|[A-Za-z0-9_-]+\.ts:\d+/.test(topic.body)) {
259
+ errors.push(`${label}: body leaks internal implementation details`);
260
+ }
261
+ return errors;
262
+ }
263
+
264
+ function matchesCandidateKind(topic, candidateKind) {
265
+ if (!candidateKind) return false;
266
+ return topic.candidateKinds.includes('*') || topic.candidateKinds.includes(candidateKind);
267
+ }
268
+
269
+ function matchesFrameworks(frameworks, framework, version) {
270
+ return frameworks.some((pattern) =>
271
+ pattern === '*' || matchesFrameworkVersion(pattern, framework, version)
272
+ );
273
+ }
274
+
275
+ function matchesOptionalList(values, actual) {
276
+ if (!Array.isArray(values) || values.length === 0) return true;
277
+ return values.includes('*') || (actual != null && values.includes(actual));
278
+ }
279
+
280
+ function matchesRouter(routers, stack) {
281
+ if (!Array.isArray(routers) || routers.length === 0) return true;
282
+ if (routers.includes('*')) return true;
283
+ return (routers.includes('app') && stack?.hasAppRouter)
284
+ || (routers.includes('pages') && stack?.hasPagesRouter);
285
+ }
286
+
287
+ function matchesCandidateMetrics(metrics, candidate) {
288
+ if (!Array.isArray(metrics) || metrics.length === 0) return true;
289
+ if (metrics.includes('*')) return true;
290
+ const observed = new Set([
291
+ candidate?.evidence?.metric,
292
+ ...(candidate?.evidence?.issues ?? []).map((i) => i?.metric),
293
+ ].filter(Boolean).map((m) => String(m).toUpperCase()));
294
+ return metrics.some((m) => observed.has(String(m).toUpperCase()));
295
+ }
296
+
297
+ function matchesCandidateRoutePatterns(patterns, candidate) {
298
+ if (!Array.isArray(patterns) || patterns.length === 0) return true;
299
+ if (patterns.includes('*')) return true;
300
+ const route = candidate?.route ?? candidate?.path;
301
+ if (typeof route !== 'string' || route.length === 0) return false;
302
+ return patterns.some((p) => new RegExp(p).test(route));
303
+ }
304
+
305
+ function matchesScannerPatterns(patterns, candidate) {
306
+ if (!Array.isArray(patterns) || patterns.length === 0) return true;
307
+ const observed = new Set([
308
+ ...(candidate?.evidence?.patterns ?? []),
309
+ ...(candidate?.evidence?.deepDive?.patterns ?? []),
310
+ ].filter(Boolean));
311
+ if (observed.size === 0) return false;
312
+ return patterns.some((p) => observed.has(p));
313
+ }
314
+
315
+ function topicCitationsApply(topic, candidateKind, framework, version) {
316
+ if (!candidateKind) return false;
317
+ return topic.citations.every((citation) =>
318
+ citationApplies(citation, candidateKind, framework, version)
319
+ );
320
+ }
321
+
322
+ async function citationApplies(citation, candidateKind, framework, version) {
323
+ const lib = await loadLibrary();
324
+ const rule = lib.ruleSkillRefs.find((r) => `${r.skill}:${r.rule}` === citation);
325
+ if (rule) {
326
+ return rule.applicableFrameworks.includes('*')
327
+ || rule.applicableFrameworks.some((p) => matchesFrameworkVersion(p, framework, version));
328
+ }
329
+
330
+ const url = lib.urls.find((u) => u.url === citation);
331
+ if (!url) return false;
332
+ const kindOk = !Array.isArray(url.appliesTo)
333
+ || url.appliesTo.length === 0
334
+ || url.appliesTo.includes(candidateKind);
335
+ const versionOk = url.applicableFrameworks.includes('*')
336
+ || url.applicableFrameworks.some((p) => matchesFrameworkVersion(p, framework, version));
337
+ return kindOk && versionOk;
338
+ }
339
+
340
+ async function knownCitation(citation) {
341
+ return Boolean(await lookupUrl(citation) || await lookupSkillRule(citation));
342
+ }
343
+
344
+ function toStringArray(value) {
345
+ if (!Array.isArray(value)) return [];
346
+ return value.filter((v) => typeof v === 'string' && v.length > 0);
347
+ }
348
+
349
+ function nonEmptyArray(value) {
350
+ return Array.isArray(value) && value.length > 0;
351
+ }
352
+
353
+ function nonEmptyString(value) {
354
+ return typeof value === 'string' && value.trim().length > 0;
355
+ }