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,437 @@
1
+ #!/usr/bin/env node
2
+ // Final pipeline step. Emits customer-facing markdown from
3
+ // recommendations.json + gate.json + signals.json.
4
+
5
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
6
+ import { dirname, resolve } from 'node:path';
7
+ import { buildFinalReportMessage, renderReport } from '../lib/render-report.mjs';
8
+ import { dedupeRecommendations } from '../lib/dedup-recs.mjs';
9
+ import { canonicalizeRoute } from '../lib/route-normalize.mjs';
10
+ import { hasUnsupportedCacheLifeCdnText, splitCustomerSafeObservations } from '../lib/observation-safety.mjs';
11
+
12
+ const log = (...a) => console.error('[render-report]', ...a);
13
+ const HARD_REGEN_TRIGGERS = new Set([
14
+ 'project_config_contradiction',
15
+ 'cache_vary_safety',
16
+ 'semantic_safety',
17
+ ]);
18
+
19
+ async function main() {
20
+ const args = parseArgs(process.argv.slice(2));
21
+ if (!args.recsPath || !args.gatePath || !args.signalsPath) {
22
+ console.error('usage: node scripts/render-report.mjs <recommendations.json> <gate.json> <signals.json> [--project NAME] [--out FILE] [--message-out FILE] [--no-timestamp] [--debug-out FILE]');
23
+ process.exit(1);
24
+ }
25
+
26
+ const [recsRaw, gateRaw, signalsRaw] = await Promise.all([
27
+ readFile(args.recsPath, 'utf-8').then(JSON.parse),
28
+ readFile(args.gatePath, 'utf-8').then(JSON.parse),
29
+ readFile(args.signalsPath, 'utf-8').then(JSON.parse),
30
+ ]);
31
+
32
+ // Accept either a raw rec array OR the verify-and-regen wrapper
33
+ // {recsGraded, qualityDropped, ...}. Stale-rec defense: when verify-and-regen
34
+ // flagged a hard-safety issue but the orchestrator skipped re-spawn, the
35
+ // original rec is still in recsGraded. Filter it here so it can't ship; it
36
+ // surfaces in "Investigated, no change recommended" instead.
37
+ const hardRegenRefs = new Set(
38
+ Array.isArray(recsRaw.regenPlan)
39
+ ? recsRaw.regenPlan
40
+ .filter((p) => HARD_REGEN_TRIGGERS.has(p.regenTrigger))
41
+ .map((p) => p.candidateRef)
42
+ .filter(Boolean)
43
+ : []
44
+ );
45
+ const activeCandidates = [
46
+ ...(Array.isArray(gateRaw.toLaunch) ? gateRaw.toLaunch : []),
47
+ ...(Array.isArray(gateRaw.platform) ? gateRaw.platform : []),
48
+ ];
49
+ const enforceCurrentGate = !Array.isArray(recsRaw) && activeCandidates.length > 0;
50
+ const staleRecommendationDrops = [];
51
+ const wrapperRecommendations = Array.isArray(recsRaw.renderableRecommendations)
52
+ ? recsRaw.renderableRecommendations
53
+ : (recsRaw.recsGraded ?? []);
54
+ const needsReviewDrops = [];
55
+ const candidateRecommendations = Array.isArray(recsRaw)
56
+ ? recsRaw.filter((r) => r?.abstain !== true)
57
+ : wrapperRecommendations
58
+ .filter((r, i) => (r.quality?.overall ?? 0) >= 0.55)
59
+ .filter((r) => !hardRegenRefs.has(r.candidateRef));
60
+ const recommendationsRaw = candidateRecommendations
61
+ .filter((r) => {
62
+ if (r?.abstain === true || r?.needsReview !== true) return true;
63
+ needsReviewDrops.push({
64
+ candidateRef: r.candidateRef ?? null,
65
+ reason: 'This recommendation needs a manual safety review before it is ready to apply.',
66
+ });
67
+ return false;
68
+ })
69
+ .filter((r) => {
70
+ if (!enforceCurrentGate) return true;
71
+ if (recommendationMatchesActiveCandidate(r, activeCandidates)) return true;
72
+ staleRecommendationDrops.push({
73
+ candidateRef: r.candidateRef ?? null,
74
+ reason: 'This recommendation came from a candidate that is not in the current run output. Re-run from a clean run directory before applying it.',
75
+ });
76
+ return false;
77
+ });
78
+ const recommendations = dedupeRecommendations(recommendationsRaw);
79
+ const readyTargets = new Set(
80
+ recommendations
81
+ .map((r) => candidateTarget(r?.candidateRef))
82
+ .filter(Boolean)
83
+ );
84
+ const droppedContradictions = !Array.isArray(recsRaw)
85
+ ? (recsRaw.recsGraded ?? [])
86
+ .map((r, i) => ({ r, i }))
87
+ .filter(({ r }) => hardRegenRefs.has(r.candidateRef))
88
+ .map(({ r, i }) => ({
89
+ candidateRef: r.candidateRef ?? null,
90
+ reason: publicHardRegenReason(recsRaw.regenPlan?.find((p) => p.index === i || p.candidateRef === r.candidateRef)),
91
+ }))
92
+ : [];
93
+
94
+ const gated = Array.isArray(gateRaw.gated) ? gateRaw.gated : [];
95
+
96
+ // No-change findings are first-class investigation outputs ("the hypothesis didn't hold").
97
+ // Contradiction-dropped recs ride alongside them so customers see WHY a rec
98
+ // was held back instead of it silently disappearing.
99
+ const baseAbstentions = Array.isArray(recsRaw)
100
+ ? recsRaw.filter((r) => r?.abstain === true).map((r) => ({
101
+ candidateRef: r.candidateRef ?? null,
102
+ reason: publicNoChangeReason(r.reason ?? '(no reason recorded)'),
103
+ }))
104
+ : (recsRaw.abstentions ?? []).map((r) => ({
105
+ ...r,
106
+ reason: publicNoChangeReason(r.reason ?? '(no reason recorded)'),
107
+ }));
108
+ const publicBaseAbstentions = baseAbstentions.filter((r) => !readyTargets.has(candidateTarget(r?.candidateRef)));
109
+ // Observations: no-change findings carrying a structured non-perf finding
110
+ // (deployment regression, error storm, etc.).
111
+ const flattenedObservations = Array.isArray(recsRaw)
112
+ ? flattenObservations(recsRaw.filter((r) => r?.abstain === true))
113
+ : flattenObservations([
114
+ ...(Array.isArray(recsRaw.observations) ? recsRaw.observations : []),
115
+ ...(Array.isArray(recsRaw.abstentions) ? recsRaw.abstentions : []),
116
+ ]);
117
+ const { observations: safeObservations, heldBackObservations } = splitCustomerSafeObservations(flattenedObservations, baseAbstentions, signalsRaw);
118
+ const observations = suppressReadyCoveredObservations(safeObservations, recommendations);
119
+
120
+ const abstentions = [
121
+ ...publicBaseAbstentions,
122
+ ...droppedContradictions,
123
+ ...staleRecommendationDrops,
124
+ ...needsReviewDrops,
125
+ ...(Array.isArray(recsRaw.withheldRecommendations) ? recsRaw.withheldRecommendations.map((d) => ({
126
+ candidateRef: d.candidateRef ?? null,
127
+ reason: publicWithheldReason(d),
128
+ needsEvidence: true,
129
+ })) : []),
130
+ ...(Array.isArray(recsRaw.sanitizerDropped) ? recsRaw.sanitizerDropped.map((d) => ({
131
+ candidateRef: d.candidateRef ?? null,
132
+ reason: `This needs a closer review before it is safe to apply: ${d.reason ?? 'review required'}.`,
133
+ needsEvidence: true,
134
+ })) : []),
135
+ ...(Array.isArray(recsRaw.heldBackObservations) ? recsRaw.heldBackObservations.map((d) => ({
136
+ ...d,
137
+ needsEvidence: true,
138
+ })) : []),
139
+ ...heldBackObservations,
140
+ ];
141
+
142
+ // Full catalog lets the renderer recover o11ySignal + aliasRoutes that recs
143
+ // didn't propagate, and canonicalize segment-tree candidateRefs.
144
+ const allCandidates = [
145
+ ...activeCandidates,
146
+ ...gated,
147
+ ];
148
+
149
+ const md = renderReport({
150
+ recommendations,
151
+ gated,
152
+ abstentions,
153
+ observations,
154
+ signals: signalsRaw,
155
+ candidates: allCandidates,
156
+ opts: {
157
+ projectName: args.projectName,
158
+ generatedAt: args.noTimestamp ? null : new Date().toISOString(),
159
+ heldBackCount: (Number.isInteger(recsRaw.summary?.withheldRecommendations)
160
+ ? recsRaw.summary.withheldRecommendations
161
+ : (Array.isArray(recsRaw.regenPlan) ? recsRaw.regenPlan.length : 0) +
162
+ (Array.isArray(recsRaw.qualityDropped) ? recsRaw.qualityDropped.length : 0)) +
163
+ (Array.isArray(recsRaw.sanitizerDropped) ? recsRaw.sanitizerDropped.length : 0) +
164
+ (Array.isArray(recsRaw.heldBackObservations) ? recsRaw.heldBackObservations.length : 0) +
165
+ heldBackObservations.length,
166
+ noChangeCount: Number.isInteger(recsRaw.summary?.abstentions)
167
+ ? Math.min(recsRaw.summary.abstentions, publicBaseAbstentions.length)
168
+ : publicBaseAbstentions.length,
169
+ },
170
+ });
171
+
172
+ if (args.debugOutPath) {
173
+ const debugArtifact = buildDebugArtifact({
174
+ recsRaw,
175
+ recommendationsRaw,
176
+ recommendations,
177
+ gateRaw,
178
+ abstentions,
179
+ observations,
180
+ heldBackObservations,
181
+ staleRecommendationDrops,
182
+ droppedContradictions,
183
+ });
184
+ const serializedDebug = JSON.stringify(debugArtifact, null, 2) + '\n';
185
+ await mkdir(dirname(args.debugOutPath), { recursive: true });
186
+ await writeFile(args.debugOutPath, serializedDebug, 'utf-8');
187
+ log(`wrote debug ${serializedDebug.length}B → ${args.debugOutPath}`);
188
+ }
189
+
190
+ if (args.messageOutPath) {
191
+ const messageArtifact = buildFinalReportMessage({
192
+ reportPath: args.outPath ?? '(stdout)',
193
+ markdown: md,
194
+ recommendations,
195
+ signals: signalsRaw,
196
+ });
197
+ const serializedMessage = JSON.stringify(messageArtifact, null, 2) + '\n';
198
+ await mkdir(dirname(args.messageOutPath), { recursive: true });
199
+ await writeFile(args.messageOutPath, serializedMessage, 'utf-8');
200
+ log(`wrote final message ${serializedMessage.length}B → ${args.messageOutPath}`);
201
+ }
202
+
203
+ if (args.outPath) {
204
+ await mkdir(dirname(args.outPath), { recursive: true });
205
+ await writeFile(args.outPath, md + '\n', 'utf-8');
206
+ log(`wrote ${md.length}B → ${args.outPath}`);
207
+ } else {
208
+ process.stdout.write(md + '\n');
209
+ }
210
+ }
211
+
212
+ function parseArgs(argv) {
213
+ const out = { positional: [] };
214
+ for (let i = 0; i < argv.length; i++) {
215
+ const a = argv[i];
216
+ if (a === '--project') out.projectName = argv[++i];
217
+ else if (a.startsWith('--project=')) out.projectName = a.slice('--project='.length);
218
+ else if (a === '--out') out.outPath = resolve(argv[++i]);
219
+ else if (a.startsWith('--out=')) out.outPath = resolve(a.slice('--out='.length));
220
+ else if (a === '--message-out') out.messageOutPath = resolve(argv[++i]);
221
+ else if (a.startsWith('--message-out=')) out.messageOutPath = resolve(a.slice('--message-out='.length));
222
+ else if (a === '--no-timestamp') out.noTimestamp = true;
223
+ else if (a === '--debug-out') out.debugOutPath = resolve(argv[++i]);
224
+ else if (a.startsWith('--debug-out=')) out.debugOutPath = resolve(a.slice('--debug-out='.length));
225
+ else if (a === '--debug') {
226
+ console.error('[render-report] --debug no longer writes internal details into customer markdown; use --debug-out FILE');
227
+ }
228
+ else out.positional.push(a);
229
+ }
230
+ out.recsPath = out.positional[0];
231
+ out.gatePath = out.positional[1];
232
+ out.signalsPath = out.positional[2];
233
+ return out;
234
+ }
235
+
236
+ function publicWithheldReason(record) {
237
+ switch (record?.reason) {
238
+ case 'needs_review':
239
+ return 'Automated checks added a safety caveat, so this run kept the recommendation out of the ready-to-apply list.';
240
+ case 'quality_floor':
241
+ return 'The recommendation did not meet the evidence bar for this report.';
242
+ case 'project_config_contradiction':
243
+ case 'cache_vary_safety':
244
+ case 'semantic_safety':
245
+ return publicHardRegenReason({ regenTrigger: record.reason });
246
+ default:
247
+ return 'This recommendation needs stronger evidence before it is safe to apply.';
248
+ }
249
+ }
250
+
251
+ function publicHardRegenReason(plan) {
252
+ switch (plan?.regenTrigger) {
253
+ case 'project_config_contradiction':
254
+ return 'The recommendation tried to turn on a project setting that is already enabled. Re-run the investigation with refreshed project-config evidence.';
255
+ case 'cache_vary_safety':
256
+ return 'The recommendation added shared CDN caching to output that varies by request geography without the required Vary header. Re-run the investigation with the cache-safety failure in scope.';
257
+ case 'semantic_safety':
258
+ return 'This recommendation needs stronger framework evidence before it is safe to apply. Re-run the investigation with that evidence in scope.';
259
+ default:
260
+ return 'This recommendation needs stronger evidence before it is safe to apply. Re-run the investigation with those checks in scope.';
261
+ }
262
+ }
263
+
264
+ function recommendationMatchesActiveCandidate(rec, candidates) {
265
+ const ref = parseCandidateRef(rec?.candidateRef);
266
+ if (!ref) return true;
267
+ return candidates.some((candidate) => candidateMatchesRef(candidate, ref));
268
+ }
269
+
270
+ function parseCandidateRef(ref) {
271
+ if (typeof ref !== 'string' || ref.length === 0) return null;
272
+ const [kind, ...targetParts] = ref.split(':');
273
+ if (!kind) return null;
274
+ return { kind, target: targetParts.join(':') };
275
+ }
276
+
277
+ function candidateMatchesRef(candidate, ref) {
278
+ if (!candidate || candidate.kind !== ref.kind) return false;
279
+ if (candidate.scope === 'account' || ref.target === '<account>') return true;
280
+
281
+ const candidateTarget = candidate.route ?? candidate.hostname ?? candidate.file ?? candidate.target ?? null;
282
+ if (!candidateTarget || !ref.target) return false;
283
+
284
+ const a = String(candidateTarget);
285
+ const b = String(ref.target);
286
+ return a === b || canonicalizeRoute(a) === canonicalizeRoute(b);
287
+ }
288
+
289
+ function suppressReadyCoveredObservations(observations, recommendations = []) {
290
+ if (!Array.isArray(observations) || observations.length === 0) return [];
291
+ const readyFamiliesByTarget = new Map();
292
+ for (const rec of recommendations) {
293
+ const parsed = parseCandidateRef(rec?.candidateRef);
294
+ const target = candidateTarget(rec?.candidateRef);
295
+ const family = candidateFamily(parsed?.kind);
296
+ if (!target || !family) continue;
297
+ const set = readyFamiliesByTarget.get(target) ?? new Set();
298
+ set.add(family);
299
+ readyFamiliesByTarget.set(target, set);
300
+ }
301
+
302
+ return observations.filter((observation) => {
303
+ const parsed = parseCandidateRef(observation?.candidateRef);
304
+ const target = candidateTarget(observation?.candidateRef);
305
+ const family = candidateFamily(parsed?.kind);
306
+ if (!target || !family) return true;
307
+ return !readyFamiliesByTarget.get(target)?.has(family);
308
+ });
309
+ }
310
+
311
+ function candidateFamily(kind) {
312
+ switch (kind) {
313
+ case 'uncached_route':
314
+ case 'cache_header_gap':
315
+ case 'missing_cache_headers':
316
+ case 'max_age_without_s_maxage':
317
+ return 'cache';
318
+ case 'slow_route':
319
+ case 'cold_start':
320
+ case 'external_api_slow':
321
+ case 'cwv_poor':
322
+ return 'performance';
323
+ case 'route_errors':
324
+ return 'reliability';
325
+ case 'isr_overrevalidation':
326
+ return 'isr';
327
+ case 'middleware_heavy':
328
+ return 'middleware';
329
+ case 'build_minutes_fanout':
330
+ return 'build';
331
+ default:
332
+ return kind || null;
333
+ }
334
+ }
335
+
336
+ function candidateTarget(ref) {
337
+ if (typeof ref !== 'string') return null;
338
+ const idx = ref.indexOf(':');
339
+ if (idx === -1) return null;
340
+ return ref.slice(idx + 1);
341
+ }
342
+
343
+ function publicNoChangeReason(reason) {
344
+ if (hasUnsupportedCacheLifeCdnText(reason)) {
345
+ return 'This candidate overlapped a cache-lifetime draft that did not meet the framework evidence bar. No supported change shipped from this run.';
346
+ }
347
+ return reason;
348
+ }
349
+
350
+ function buildDebugArtifact({
351
+ recsRaw,
352
+ recommendationsRaw,
353
+ recommendations,
354
+ gateRaw,
355
+ abstentions = [],
356
+ observations = [],
357
+ heldBackObservations = [],
358
+ staleRecommendationDrops = [],
359
+ droppedContradictions = [],
360
+ }) {
361
+ const wrapper = Array.isArray(recsRaw) ? null : recsRaw;
362
+ const sourceRecords = Array.isArray(recsRaw)
363
+ ? recsRaw
364
+ : (recsRaw.recsGraded ?? []);
365
+ const summary = wrapper?.summary
366
+ ? {
367
+ ...wrapper.summary,
368
+ rawRecommendationCount: recommendationsRaw.length,
369
+ renderedRecommendationCount: recommendations.length,
370
+ }
371
+ : null;
372
+ return {
373
+ schemaVersion: '1.0',
374
+ summary,
375
+ regenPlan: wrapper?.regenPlan ?? [],
376
+ qualityDropped: wrapper?.qualityDropped ?? [],
377
+ withheldRecommendations: wrapper?.withheldRecommendations ?? [],
378
+ abstentions,
379
+ observations,
380
+ heldBackObservations,
381
+ staleRecommendationDrops,
382
+ droppedContradictions,
383
+ sanitizerDropped: wrapper?.sanitizerDropped ?? [],
384
+ renderedRecommendationCount: recommendations.length,
385
+ rawRecommendationCount: recommendationsRaw.length,
386
+ gateBudget: gateRaw?.budget ?? null,
387
+ recommendations: sourceRecords
388
+ .filter((record) => record && record.abstain !== true)
389
+ .map((record) => ({
390
+ candidateRef: record.candidateRef ?? null,
391
+ what: record.what ?? null,
392
+ verification: record.verification ?? null,
393
+ quality: record.quality ?? null,
394
+ passRate: record.passRate ?? record.verification?.passRate ?? null,
395
+ avgQuality: record.avgQuality ?? null,
396
+ needsReview: record.needsReview === true,
397
+ sanitizerTrail: Array.isArray(record.sanitizerTrail) ? record.sanitizerTrail : [],
398
+ })),
399
+ };
400
+ }
401
+
402
+ function flattenObservations(records) {
403
+ const out = [];
404
+ for (const record of records) {
405
+ if (!record || typeof record !== 'object') continue;
406
+ if (record.observation && typeof record.observation === 'object') {
407
+ out.push({
408
+ candidateRef: record.candidateRef ?? null,
409
+ summary: coerceOptionalString(record.observation.summary),
410
+ evidence: record.observation.evidence ?? null,
411
+ suggestedAction: record.observation.suggestedAction ?? null,
412
+ kind: record.observation.kind ?? 'other',
413
+ });
414
+ continue;
415
+ }
416
+ if ('summary' in record || 'evidence' in record || 'suggestedAction' in record || 'kind' in record) {
417
+ out.push({
418
+ candidateRef: record.candidateRef ?? null,
419
+ summary: coerceOptionalString(record.summary),
420
+ evidence: record.evidence ?? null,
421
+ suggestedAction: record.suggestedAction ?? null,
422
+ kind: record.kind ?? 'other',
423
+ });
424
+ }
425
+ }
426
+ return out;
427
+ }
428
+
429
+ function coerceOptionalString(value) {
430
+ return value == null ? value : String(value);
431
+ }
432
+
433
+ main().catch((err) => {
434
+ console.error('[render-report] FAILED:', err.message);
435
+ console.error(err.stack);
436
+ process.exit(1);
437
+ });