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,166 @@
1
+ #!/usr/bin/env node
2
+ // Pure-JS deterministic gate. Reads merged signals.json, emits
3
+ // {toLaunch, platform, gated}. Same input → byte-identical output (modulo
4
+ // appliedAt). Sort keys are stable and explicit — never change without
5
+ // a co-located golden-output test update.
6
+
7
+ import { readFile } from 'node:fs/promises';
8
+ import { gates, DEFAULT_MAX_CODE_CANDIDATES, GATE_VERSION } from '../lib/gates/index.mjs';
9
+ import { applyAuthDisqualifier } from '../lib/auth-route.mjs';
10
+ import { dedupeCandidates } from '../lib/route-normalize.mjs';
11
+ import { validateCandidates } from '../lib/gates/contract.mjs';
12
+ import { applyHardGates } from '../lib/gates/hard-gates.mjs';
13
+ import { selectLaunchCandidates } from '../lib/gates/select-candidates.mjs';
14
+ import { routePathMatchScore } from '../lib/investigation-brief.mjs';
15
+
16
+ const SCHEMA_VERSION = '1.1';
17
+
18
+ async function main() {
19
+ const args = parseArgs(process.argv.slice(2));
20
+ if (!args.signalsPath) {
21
+ console.error('usage: node scripts/gate-investigations.mjs <signals.json> [--max-candidates N|all]');
22
+ console.error(' VERCEL_OPTIMIZE_MAX_CANDIDATES env var supported (same values)');
23
+ process.exit(1);
24
+ }
25
+ const budget = resolveBudget(args);
26
+ const signals = JSON.parse(await readFile(args.signalsPath, 'utf-8'));
27
+
28
+ const allSeeds = gates.flatMap((g) => {
29
+ try {
30
+ return g.gate(signals) ?? [];
31
+ } catch (err) {
32
+ console.error(`[gate-investigations] gate ${g.metadata?.id} threw: ${err.message}`);
33
+ return [];
34
+ }
35
+ });
36
+
37
+ const validSeeds = validateCandidates(allSeeds, { source: 'gate-output' });
38
+ const annotated = validSeeds.map(applyAuthDisqualifier);
39
+ const sorted = annotated.slice().sort(stableCompare);
40
+
41
+ // Next.js 16 segment-tree metric paths surface the same source file under
42
+ // many encoded labels (city variants, _tree/_index siblings, base64 flag
43
+ // prefixes). Without dedup the budget gets shredded ~4-10x per page.
44
+ const { deduped, dropped } = dedupeCandidates(sorted);
45
+ const displayAnnotated = deduped.map((candidate) => attachDisplayRoute(candidate, signals));
46
+ const hardGateResult = applyHardGates(displayAnnotated, signals);
47
+ const gateable = hardGateResult.allowed;
48
+
49
+ // Account-scope candidates don't compete with code-scope for the budget.
50
+ const codeScoped = gateable.filter((c) => !c.disqualified && c.scope !== 'account');
51
+ const platformScoped = gateable.filter((c) => !c.disqualified && c.scope === 'account');
52
+
53
+ const selection = selectLaunchCandidates(codeScoped, budget, {
54
+ diversify: args.budgetSource === 'default',
55
+ });
56
+ const toLaunch = selection.selected;
57
+ const skippedByBudget = selection.skipped;
58
+ const budgetLabel = budget === Infinity ? 'unlimited (all)' : String(budget);
59
+ const gated = [
60
+ ...gateable
61
+ .filter((c) => c.disqualified)
62
+ .map((c) => ({ ...c, gatedReason: c.disqualifyReason ?? 'disqualified' })),
63
+ ...hardGateResult.gated,
64
+ ...skippedByBudget.map((c) => ({
65
+ ...c,
66
+ gatedReason: `skippedByBudget (max-candidates=${budgetLabel}; raise with --max-candidates N or =all)`,
67
+ })),
68
+ ...dropped.map((d) => ({
69
+ ...d.candidate,
70
+ gatedReason: `coveredBy (${d.mergedInto}) — ${d.reason}`,
71
+ })),
72
+ ];
73
+
74
+ process.stdout.write(JSON.stringify({
75
+ schemaVersion: SCHEMA_VERSION,
76
+ gateVersion: GATE_VERSION,
77
+ appliedAt: new Date().toISOString(),
78
+ budget: {
79
+ maxCandidates: budget === Infinity ? 'all' : budget,
80
+ source: args.budgetSource,
81
+ selection: selection.selectionMode,
82
+ },
83
+ toLaunch,
84
+ platform: platformScoped,
85
+ gated,
86
+ gateMetadata: gates.map((g) => ({
87
+ id: g.metadata?.id,
88
+ threshold: g.metadata?.threshold,
89
+ billingDimension: g.metadata?.billingDimension,
90
+ sourceCitation: g.metadata?.sourceCitation,
91
+ })),
92
+ }, null, 2) + '\n');
93
+ }
94
+
95
+ function parseArgs(argv) {
96
+ const out = { positional: [] };
97
+ for (let i = 0; i < argv.length; i++) {
98
+ const a = argv[i];
99
+ if (a === '--max-candidates') out.maxCandidatesArg = argv[++i];
100
+ else if (a.startsWith('--max-candidates=')) out.maxCandidatesArg = a.slice('--max-candidates='.length);
101
+ else out.positional.push(a);
102
+ }
103
+ out.signalsPath = out.positional[0];
104
+ return out;
105
+ }
106
+
107
+ function resolveBudget(args) {
108
+ const raw = args.maxCandidatesArg ?? process.env.VERCEL_OPTIMIZE_MAX_CANDIDATES;
109
+ if (raw == null || raw === '') {
110
+ args.budgetSource = 'default';
111
+ return DEFAULT_MAX_CODE_CANDIDATES;
112
+ }
113
+ const trimmed = String(raw).trim().toLowerCase();
114
+ if (trimmed === 'all' || trimmed === 'unlimited' || trimmed === '-1') {
115
+ args.budgetSource = args.maxCandidatesArg != null ? 'flag' : 'env';
116
+ return Infinity;
117
+ }
118
+ const n = Number(trimmed);
119
+ if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
120
+ console.error(`[gate-investigations] bad budget value '${raw}'; expected positive integer or 'all'`);
121
+ process.exit(2);
122
+ }
123
+ args.budgetSource = args.maxCandidatesArg != null ? 'flag' : 'env';
124
+ return n;
125
+ }
126
+
127
+ // Total ordering: priority desc, kind asc, route asc. Underpins byte-identical output.
128
+ function stableCompare(a, b) {
129
+ const pa = a.priority ?? 0;
130
+ const pb = b.priority ?? 0;
131
+ if (pa !== pb) return pb - pa;
132
+ const ka = String(a.kind ?? '');
133
+ const kb = String(b.kind ?? '');
134
+ if (ka !== kb) return ka.localeCompare(kb);
135
+ const ra = String(a.route ?? a.hostname ?? '');
136
+ const rb = String(b.route ?? b.hostname ?? '');
137
+ return ra.localeCompare(rb);
138
+ }
139
+
140
+ function attachDisplayRoute(candidate, signals) {
141
+ if (!candidate || candidate.scope !== 'route' || typeof candidate.route !== 'string') return candidate;
142
+ if (!candidate.route.includes('[*]')) return candidate;
143
+
144
+ const routes = (signals.codebase?.routes ?? [])
145
+ .map((route) => route?.routePath)
146
+ .filter((routePath) => typeof routePath === 'string' && routePath.length > 0);
147
+ if (routes.length === 0) return candidate;
148
+
149
+ let bestRoute = null;
150
+ let bestScore = 0;
151
+ for (const routePath of routes) {
152
+ const score = routePathMatchScore(routePath, candidate.route);
153
+ if (score > bestScore) {
154
+ bestRoute = routePath;
155
+ bestScore = score;
156
+ }
157
+ }
158
+
159
+ if (!bestRoute || bestScore <= 0 || bestRoute === candidate.route) return candidate;
160
+ return { ...candidate, displayRoute: bestRoute };
161
+ }
162
+
163
+ main().catch((err) => {
164
+ console.error('[gate-investigations] FAILED:', err.message);
165
+ process.exit(1);
166
+ });
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ // Deterministically combines Vercel metric collection with the local codebase
3
+ // scan. Keeps the merged artifact shape stable: collect-signals output at the
4
+ // top level, scan-codebase output under `codebase`.
5
+
6
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
7
+ import { realpathSync } from 'node:fs';
8
+ import { dirname } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { routePathMatchScore } from '../lib/investigation-brief.mjs';
11
+ import { canonicalizeRoute } from '../lib/route-normalize.mjs';
12
+
13
+ const log = (...args) => console.error('[merge-signals]', ...args);
14
+
15
+ async function main() {
16
+ const args = parseArgs(process.argv.slice(2));
17
+ if (!args.signalsPath || !args.codebasePath) {
18
+ console.error('usage: node scripts/merge-signals.mjs <signals.json> <codebase.json> [--out merged.json] [--force]');
19
+ process.exit(1);
20
+ }
21
+
22
+ const [signals, codebase] = await Promise.all([
23
+ readJson(args.signalsPath, 'signals'),
24
+ readJson(args.codebasePath, 'codebase scan'),
25
+ ]);
26
+
27
+ const merged = mergeSignals(signals, codebase);
28
+ const body = JSON.stringify(merged, null, 2) + '\n';
29
+ if (args.outPath) {
30
+ await writeOutput(args.outPath, body, { force: args.force });
31
+ log(`wrote ${args.outPath}`);
32
+ } else {
33
+ process.stdout.write(body);
34
+ }
35
+ }
36
+
37
+ export function mergeSignals(signals, codebase) {
38
+ assertObject(signals, 'signals');
39
+ assertObject(codebase, 'codebase scan');
40
+
41
+ if (!signals.schemaVersion) {
42
+ throw new Error('signals.json is missing schemaVersion; pass collect-signals output as the first file.');
43
+ }
44
+ if (!Array.isArray(codebase.routes) || !Array.isArray(codebase.findings) || !codebase.stack) {
45
+ throw new Error('codebase.json must be scan-codebase output with stack, routes[], and findings[].');
46
+ }
47
+
48
+ return {
49
+ ...signals,
50
+ codebase: annotateCodebaseScan(signals, codebase),
51
+ };
52
+ }
53
+
54
+ export function annotateCodebaseScan(signals, codebase) {
55
+ const index = buildRouteMetricIndex(signals);
56
+ return {
57
+ ...codebase,
58
+ findings: (codebase.findings ?? []).map((finding) => annotateFinding(finding, index)),
59
+ };
60
+ }
61
+
62
+ function annotateFinding(finding, index) {
63
+ if (!finding || typeof finding !== 'object') return finding;
64
+ if (finding.trafficIndependent) return finding;
65
+ if (!finding.route) return { ...finding, o11ySignal: 'NO-ROUTE-MAPPING' };
66
+
67
+ const summary = bestRouteSummary(finding.route, index);
68
+ if (!summary || !hasTraffic(summary)) return { ...finding, o11ySignal: 'COLD-PATH' };
69
+ return { ...finding, o11ySignal: formatRouteSignal(summary) };
70
+ }
71
+
72
+ function buildRouteMetricIndex(signals) {
73
+ const out = new Map();
74
+ const ensure = (route) => {
75
+ const canonical = canonicalizeRoute(route);
76
+ const existing = out.get(canonical) ?? { route: canonical };
77
+ out.set(canonical, existing);
78
+ return existing;
79
+ };
80
+
81
+ for (const row of rows(signals, 'fnStatusByRoute')) {
82
+ if (!row.route) continue;
83
+ const summary = ensure(row.route);
84
+ summary.functionRuns = (summary.functionRuns ?? 0) + numeric(row.value);
85
+ }
86
+ for (const row of rows(signals, 'fnDurationP95ByRoute')) {
87
+ if (!row.route) continue;
88
+ ensure(row.route).p95Ms = numeric(row.value);
89
+ }
90
+ for (const row of rows(signals, 'requestsByRouteCache')) {
91
+ if (!row.route) continue;
92
+ const summary = ensure(row.route);
93
+ const count = numeric(row.value);
94
+ summary.requests = (summary.requests ?? 0) + count;
95
+ if (String(row.cache_result).toUpperCase() === 'HIT') {
96
+ summary.cacheHits = (summary.cacheHits ?? 0) + count;
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+
102
+ function rows(signals, metricId) {
103
+ const rows = signals?.metrics?.[metricId]?.rows;
104
+ return Array.isArray(rows) ? rows : [];
105
+ }
106
+
107
+ function numeric(value) {
108
+ const n = Number(value);
109
+ return Number.isFinite(n) ? n : 0;
110
+ }
111
+
112
+ function bestRouteSummary(route, index) {
113
+ const canonical = canonicalizeRoute(route);
114
+ const exact = index.get(canonical);
115
+ if (exact) return exact;
116
+
117
+ let best = null;
118
+ for (const summary of index.values()) {
119
+ const score = routePathMatchScore(canonical, summary.route);
120
+ if (score <= 0) continue;
121
+ if (!best || score > best.score) best = { score, summary };
122
+ }
123
+ return best?.summary ?? null;
124
+ }
125
+
126
+ function hasTraffic(summary) {
127
+ return (summary.functionRuns ?? 0) > 0 || (summary.requests ?? 0) > 0;
128
+ }
129
+
130
+ function formatRouteSignal(summary) {
131
+ const parts = [];
132
+ if ((summary.functionRuns ?? 0) > 0) parts.push(`inv=${Math.round(summary.functionRuns)}`);
133
+ else if ((summary.requests ?? 0) > 0) parts.push(`requests=${Math.round(summary.requests)}`);
134
+ if ((summary.p95Ms ?? 0) > 0) parts.push(`p95=${Math.round(summary.p95Ms)}ms`);
135
+ if ((summary.requests ?? 0) > 0 && summary.cacheHits != null) {
136
+ const hitRate = Math.round((summary.cacheHits / summary.requests) * 100);
137
+ parts.push(`cache=${hitRate}%`);
138
+ }
139
+ return parts.join(',') || 'COLD-PATH';
140
+ }
141
+
142
+ function parseArgs(argv) {
143
+ const out = { positional: [], force: false };
144
+ for (let i = 0; i < argv.length; i++) {
145
+ const a = argv[i];
146
+ if (a === '--out') out.outPath = argv[++i];
147
+ else if (a.startsWith('--out=')) out.outPath = a.slice('--out='.length);
148
+ else if (a === '--force') out.force = true;
149
+ else out.positional.push(a);
150
+ }
151
+ out.signalsPath = out.positional[0];
152
+ out.codebasePath = out.positional[1];
153
+ return out;
154
+ }
155
+
156
+ async function readJson(path, label) {
157
+ try {
158
+ return JSON.parse(await readFile(path, 'utf-8'));
159
+ } catch (err) {
160
+ throw new Error(`Could not read ${label} JSON at ${path}: ${err.message}`);
161
+ }
162
+ }
163
+
164
+ function assertObject(value, label) {
165
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
166
+ throw new Error(`${label} must be a JSON object.`);
167
+ }
168
+ }
169
+
170
+ async function writeOutput(path, body, { force }) {
171
+ if (!force && await exists(path)) {
172
+ throw new Error(`output file already exists: ${path}. Use a fresh run directory or pass --force to overwrite.`);
173
+ }
174
+ await mkdir(dirname(path), { recursive: true });
175
+ await writeFile(path, body);
176
+ }
177
+
178
+ async function exists(path) {
179
+ try {
180
+ await access(path);
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
188
+ main().catch((err) => {
189
+ console.error('[merge-signals] FAILED:', err.message);
190
+ process.exit(1);
191
+ });
192
+ }
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ // Emits the ENTIRE prompt a sub-agent sees for one candidate (candidate +
3
+ // deep-dive evidence + filtered citations + playbook + protocol + output
4
+ // schema). --list emits a manifest the orchestrator uses to decide fan-out
5
+ // vs serial. Brief → stdout, status → stderr.
6
+
7
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
8
+ import { dirname, join, resolve } from 'node:path';
9
+ import {
10
+ buildBrief,
11
+ inferPlaybook,
12
+ inferFrameworkPlaybook,
13
+ resolveFiles,
14
+ citationSubset,
15
+ } from '../lib/investigation-brief.mjs';
16
+ import { supportTopicSubset } from '../lib/support-topics.mjs';
17
+ import { candidateRefFor } from '../lib/reconcile-candidates.mjs';
18
+ import { formatCandidateLabel } from '../lib/display-labels.mjs';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ const HERE = dirname(fileURLToPath(import.meta.url));
22
+ const PLAYBOOKS_DIR = join(HERE, '..', 'references', 'playbooks');
23
+
24
+ const log = (...a) => console.error('[prepare-brief]', ...a);
25
+
26
+ async function main() {
27
+ const args = parseArgs(process.argv.slice(2));
28
+ if (!args.mergedPath || !args.investigationPath) {
29
+ console.error('usage: node scripts/prepare-investigation-brief.mjs <merged.json> <investigation.json> [--index N] [--group toLaunch|platform] [--out FILE]');
30
+ console.error(' or: node scripts/prepare-investigation-brief.mjs <merged.json> <investigation.json> --list');
31
+ process.exit(1);
32
+ }
33
+
34
+ const [merged, investigation] = await Promise.all([
35
+ readFile(args.mergedPath, 'utf-8').then(JSON.parse),
36
+ readFile(args.investigationPath, 'utf-8').then(JSON.parse),
37
+ ]);
38
+
39
+ if (args.list) {
40
+ const manifest = buildManifest(merged, investigation);
41
+ process.stdout.write(JSON.stringify(manifest, null, 2) + '\n');
42
+ return;
43
+ }
44
+
45
+ const group = args.group ?? 'toLaunch';
46
+ const index = args.index ?? 0;
47
+ const pool = Array.isArray(investigation[group]) ? investigation[group] : [];
48
+ if (index < 0 || index >= pool.length) {
49
+ console.error(`[prepare-brief] FATAL: ${group}[${index}] out of range (${group} has ${pool.length} entries)`);
50
+ process.exit(2);
51
+ }
52
+ let candidate = pool[index];
53
+
54
+ // Scan output may live at merged.codebase (older shape) or merged.signals.codebase
55
+ // (current shape, after the jq merge nests it under signals). Resolve either.
56
+ const codebase = pickCodebase(merged);
57
+ const signals = {
58
+ ...merged,
59
+ codebase,
60
+ };
61
+ const files = resolveFiles(candidate, signals);
62
+ candidate = {
63
+ ...candidate,
64
+ candidateRef: candidate.candidateRef ?? candidateRefFor(candidate, files),
65
+ };
66
+ const playbookId = inferPlaybook(signals);
67
+ const playbookBody = playbookId ? await tryReadPlaybook(playbookId) : null;
68
+ const frameworkPlaybookId = inferFrameworkPlaybook(signals);
69
+ const frameworkPlaybookBody = frameworkPlaybookId ? await tryReadPlaybook(frameworkPlaybookId) : null;
70
+
71
+ const stack = signals.stack ?? signals.codebase?.stack ?? {};
72
+ const framework = stack.framework ?? 'unknown';
73
+ const version = stack.frameworkVersion ?? 'unknown';
74
+ const citations = await citationSubset(candidate.kind, framework, version);
75
+ const supportTopics = await supportTopicSubset({
76
+ candidate,
77
+ signals,
78
+ framework,
79
+ version,
80
+ profile: playbookId,
81
+ frameworkPlaybookId,
82
+ });
83
+
84
+ const brief = buildBrief({
85
+ candidate,
86
+ candidateIndex: index,
87
+ candidateGroup: group,
88
+ files,
89
+ signals,
90
+ citations,
91
+ playbookId,
92
+ playbookBody,
93
+ frameworkPlaybookId,
94
+ frameworkPlaybookBody,
95
+ supportTopics,
96
+ generatedAt: args.deterministic ? null : new Date().toISOString(),
97
+ });
98
+
99
+ if (args.outPath) {
100
+ await mkdir(dirname(args.outPath), { recursive: true });
101
+ await writeBriefFile(args.outPath, brief, { force: args.force });
102
+ log(`wrote ${brief.length}B → ${args.outPath}`);
103
+ } else {
104
+ process.stdout.write(brief + '\n');
105
+ }
106
+ }
107
+
108
+ function buildManifest(merged, investigation) {
109
+ const out = [];
110
+ const groups = ['toLaunch', 'platform'];
111
+ for (const group of groups) {
112
+ const pool = Array.isArray(investigation[group]) ? investigation[group] : [];
113
+ pool.forEach((c, i) => {
114
+ const files = resolveFiles(c, { ...merged, codebase: pickCodebase(merged) });
115
+ const candidateRef = c.candidateRef ?? candidateRefFor(c, files);
116
+ out.push({
117
+ group,
118
+ index: i,
119
+ kind: c.kind,
120
+ route: c.route ?? c.hostname ?? null,
121
+ scope: c.scope ?? null,
122
+ priority: c.priority ?? null,
123
+ confidence: c.confidence ?? null,
124
+ o11ySignal: c.o11ySignal ?? null,
125
+ files,
126
+ candidateRef,
127
+ label: formatCandidateLabel({ ...c, files }),
128
+ });
129
+ });
130
+ }
131
+ return {
132
+ schemaVersion: '1.0',
133
+ totalBriefs: out.length,
134
+ toLaunchCount: out.filter((b) => b.group === 'toLaunch').length,
135
+ platformCount: out.filter((b) => b.group === 'platform').length,
136
+ preResolvedRecords: Array.isArray(investigation.preResolvedRecords)
137
+ ? investigation.preResolvedRecords
138
+ : [],
139
+ fanoutPlan: buildFanoutPlan(out),
140
+ briefs: out,
141
+ };
142
+ }
143
+
144
+ function buildFanoutPlan(briefs) {
145
+ const groups = new Map();
146
+ for (const brief of briefs) {
147
+ const key = candidateFamilyKey(brief);
148
+ const existing = groups.get(key) ?? {
149
+ familyKey: key,
150
+ label: brief.label,
151
+ kind: brief.kind,
152
+ primaryBrief: { group: brief.group, index: brief.index, candidateRef: brief.candidateRef },
153
+ relatedBriefs: [],
154
+ };
155
+ if (existing.primaryBrief.candidateRef !== brief.candidateRef) {
156
+ existing.relatedBriefs.push({ group: brief.group, index: brief.index, candidateRef: brief.candidateRef });
157
+ }
158
+ groups.set(key, existing);
159
+ }
160
+ return {
161
+ totalFamilies: groups.size,
162
+ families: [...groups.values()].map((g) => ({
163
+ ...g,
164
+ totalBriefs: 1 + g.relatedBriefs.length,
165
+ })),
166
+ };
167
+ }
168
+
169
+ function candidateFamilyKey(brief) {
170
+ const file = Array.isArray(brief.files) && brief.files.length > 0 ? brief.files[0] : null;
171
+ const target = file ?? brief.route ?? brief.scope ?? '<account>';
172
+ return `${brief.kind ?? 'unknown'}:${target}`;
173
+ }
174
+
175
+ // Prefer merged.codebase, fall back to merged.signals.codebase, then empty.
176
+ // Also accepts a fully-shaped scan doc directly (used in tests).
177
+ function pickCodebase(merged) {
178
+ if (!merged || typeof merged !== 'object') return {};
179
+ if (merged.codebase && typeof merged.codebase === 'object' && (merged.codebase.routes || merged.codebase.findings)) {
180
+ return merged.codebase;
181
+ }
182
+ if (merged.signals?.codebase && typeof merged.signals.codebase === 'object') {
183
+ return merged.signals.codebase;
184
+ }
185
+ return {};
186
+ }
187
+
188
+ async function tryReadPlaybook(id) {
189
+ try {
190
+ return await readFile(join(PLAYBOOKS_DIR, `${id}.md`), 'utf-8');
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+
196
+ function parseArgs(argv) {
197
+ const out = { positional: [] };
198
+ for (let i = 0; i < argv.length; i++) {
199
+ const a = argv[i];
200
+ if (a === '--index') out.index = Number(argv[++i]);
201
+ else if (a.startsWith('--index=')) out.index = Number(a.slice('--index='.length));
202
+ else if (a === '--group') out.group = argv[++i];
203
+ else if (a.startsWith('--group=')) out.group = a.slice('--group='.length);
204
+ else if (a === '--out') out.outPath = resolve(argv[++i]);
205
+ else if (a.startsWith('--out=')) out.outPath = resolve(a.slice('--out='.length));
206
+ else if (a === '--list') out.list = true;
207
+ else if (a === '--deterministic') out.deterministic = true;
208
+ else if (a === '--force') out.force = true;
209
+ else out.positional.push(a);
210
+ }
211
+ out.mergedPath = out.positional[0];
212
+ out.investigationPath = out.positional[1];
213
+ return out;
214
+ }
215
+
216
+ async function writeBriefFile(outPath, brief, { force = false } = {}) {
217
+ try {
218
+ await writeFile(outPath, brief + '\n', { encoding: 'utf-8', flag: force ? 'w' : 'wx' });
219
+ } catch (err) {
220
+ if (err?.code === 'EEXIST') {
221
+ throw new Error(`output file already exists: ${outPath}. Use a fresh run directory or pass --force to overwrite.`);
222
+ }
223
+ throw err;
224
+ }
225
+ }
226
+
227
+ main().catch((err) => {
228
+ console.error('[prepare-brief] FAILED:', err.message);
229
+ console.error(err.stack);
230
+ process.exit(1);
231
+ });
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic reconciliation between deep-dive and investigator fan-out.
3
+ // Reads investigation-evidence.json, removes candidates whose follow-up metric
4
+ // evidence already disproves/reframes the gate hypothesis, and emits the same
5
+ // shape with preResolvedRecords for the final report.
6
+
7
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
8
+ import { dirname, resolve } from 'node:path';
9
+ import { reconcileInvestigation } from '../lib/reconcile-candidates.mjs';
10
+
11
+ const log = (...a) => console.error('[reconcile-candidates]', ...a);
12
+
13
+ async function main() {
14
+ const args = parseArgs(process.argv.slice(2));
15
+ if (!args.investigationPath) {
16
+ console.error('usage: node scripts/reconcile-candidates.mjs <investigation-evidence.json> [--gate gate.json] [--out reconciled-investigation.json]');
17
+ process.exit(1);
18
+ }
19
+
20
+ const [investigation, gate] = await Promise.all([
21
+ readFile(args.investigationPath, 'utf-8').then(JSON.parse),
22
+ args.gatePath ? readFile(args.gatePath, 'utf-8').then(JSON.parse) : null,
23
+ ]);
24
+
25
+ const reconciled = reconcileInvestigation(investigation, { gate });
26
+ const serialized = JSON.stringify({
27
+ ...reconciled,
28
+ reconciledAt: args.noTimestamp ? null : new Date().toISOString(),
29
+ }, null, 2) + '\n';
30
+
31
+ if (args.outPath) {
32
+ await mkdir(dirname(args.outPath), { recursive: true });
33
+ await writeFile(args.outPath, serialized, 'utf-8');
34
+ log(`wrote ${serialized.length}B -> ${args.outPath}`);
35
+ } else {
36
+ process.stdout.write(serialized);
37
+ }
38
+
39
+ const dropped = reconciled.reconciliation?.droppedBeforeInvestigation ?? 0;
40
+ if (dropped > 0) log(`dropped ${dropped} candidate(s) before investigation`);
41
+ }
42
+
43
+ function parseArgs(argv) {
44
+ const out = { positional: [] };
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const a = argv[i];
47
+ if (a === '--gate') out.gatePath = resolve(argv[++i]);
48
+ else if (a.startsWith('--gate=')) out.gatePath = resolve(a.slice('--gate='.length));
49
+ else if (a === '--out') out.outPath = resolve(argv[++i]);
50
+ else if (a.startsWith('--out=')) out.outPath = resolve(a.slice('--out='.length));
51
+ else if (a === '--no-timestamp') out.noTimestamp = true;
52
+ else out.positional.push(a);
53
+ }
54
+ out.investigationPath = out.positional[0] ? resolve(out.positional[0]) : null;
55
+ return out;
56
+ }
57
+
58
+ main().catch((err) => {
59
+ console.error('[reconcile-candidates] FAILED:', err.message);
60
+ console.error(err.stack);
61
+ process.exit(1);
62
+ });