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,313 @@
1
+ #!/usr/bin/env node
2
+ // Walks the repo, runs every scanner in lib/scanners/, emits findings + routes
3
+ // + stack as JSON. Output is merged into signals.codebase.*. New scanners drop
4
+ // into lib/scanners/ + the barrel; this file is closed for modification.
5
+
6
+ import { readdir, readFile } from 'node:fs/promises';
7
+ import { join, relative } from 'node:path';
8
+ import { scanners } from '../lib/scanners/index.mjs';
9
+ import { detectStack } from '../lib/vercel.mjs';
10
+ import {
11
+ detectMonorepoRoot,
12
+ listWorkspacePackages,
13
+ buildResolver,
14
+ resolveWorkspaceImports,
15
+ } from '../lib/workspace-resolver.mjs';
16
+
17
+ const SCHEMA_VERSION = '1.0';
18
+ const SKIP_DIRS = new Set(['node_modules', '.next', '.vercel', 'dist', 'build', '.git', 'coverage', '.turbo', '__tests__', 'cypress']);
19
+ const SKIP_FILE_PATTERNS = [/\.test\./, /\.spec\./, /\.d\.ts$/];
20
+
21
+ async function main() {
22
+ const rootDir = process.argv[2] || process.cwd();
23
+ process.stderr.write(`[scan-codebase] scanning ${rootDir}\n`);
24
+
25
+ const [stack, files, routes] = await Promise.all([
26
+ detectStack(rootDir),
27
+ collectFiles(rootDir),
28
+ enumerateRoutes(rootDir),
29
+ ]);
30
+
31
+ // In a monorepo, route files often re-export from workspace packages. Without
32
+ // resolving those, sub-agents abstain because the workspace path is outside
33
+ // their read scope.
34
+ const monorepoRoot = await detectMonorepoRoot(rootDir);
35
+ let workspacePackages = [];
36
+ let resolver = () => null;
37
+ if (monorepoRoot) {
38
+ workspacePackages = await listWorkspacePackages(monorepoRoot);
39
+ resolver = buildResolver(workspacePackages);
40
+ process.stderr.write(`[scan-codebase] monorepo root: ${monorepoRoot} (${workspacePackages.length} workspace packages)\n`);
41
+ }
42
+ await enrichRoutesWithWorkspaceImports(routes, rootDir, resolver, monorepoRoot);
43
+
44
+ process.stderr.write(`[scan-codebase] ${files.length} files, ${routes.length} routes, ${scanners.length} scanners\n`);
45
+
46
+ const findings = [];
47
+ for (const scanner of scanners) {
48
+ try {
49
+ const applicable = filterApplicable(files, scanner.metadata);
50
+ // Scanners may be sync or async (large-static-asset does fs.stat walks).
51
+ const found = await scanner.scan({ files: applicable, rootDir, routes, stack });
52
+ for (const f of (found ?? [])) {
53
+ findings.push({
54
+ ...f,
55
+ route: mapFileToRoute(f.file, routes),
56
+ });
57
+ }
58
+ } catch (err) {
59
+ process.stderr.write(`[scan-codebase] scanner ${scanner.metadata?.id} threw: ${err.message}\n`);
60
+ }
61
+ }
62
+
63
+ findings.sort((a, b) =>
64
+ a.file.localeCompare(b.file)
65
+ || (a.line ?? 0) - (b.line ?? 0)
66
+ || a.pattern.localeCompare(b.pattern)
67
+ );
68
+
69
+ process.stdout.write(JSON.stringify({
70
+ schemaVersion: SCHEMA_VERSION,
71
+ scannedAt: new Date().toISOString(),
72
+ rootDir,
73
+ monorepoRoot: monorepoRoot ?? null,
74
+ workspacePackages: workspacePackages.map((p) => ({ name: p.name, dir: relative(monorepoRoot ?? rootDir, p.dir) })),
75
+ stack,
76
+ routes,
77
+ findings,
78
+ scannerMetadata: scanners.map((s) => ({
79
+ id: s.metadata.id,
80
+ title: s.metadata.title,
81
+ severity: s.metadata.severity,
82
+ billingDimension: s.metadata.billingDimension,
83
+ trafficIndependent: s.metadata.trafficIndependent,
84
+ })),
85
+ }, null, 2) + '\n');
86
+
87
+ process.stderr.write(`[scan-codebase] ${findings.length} finding(s)\n`);
88
+ }
89
+
90
+ // Record workspace-package imports per route so the brief allowlists them and
91
+ // sub-agents can investigate the real source rather than abstaining on a thin
92
+ // re-export shell. Capped to keep the brief focused (source order ≈ import order,
93
+ // so the primary view component usually leads).
94
+ const WORKSPACE_IMPORT_LIMIT_PER_ROUTE = 12;
95
+ async function enrichRoutesWithWorkspaceImports(routes, scanRootDir, resolver, monorepoRoot) {
96
+ if (!monorepoRoot) return;
97
+ for (const r of routes) {
98
+ if (!r?.file) continue;
99
+ const abs = join(scanRootDir, r.file);
100
+ const resolved = await resolveWorkspaceImports(abs, resolver, {
101
+ pureBarrelDepth: 3,
102
+ suffixFanoutDepth: 2,
103
+ perSpecifierCap: 3,
104
+ });
105
+ if (resolved.length === 0) continue;
106
+ // Paths must be relative to the monorepo root so they align between signals + verifier.
107
+ r.workspaceImports = resolved
108
+ .slice(0, WORKSPACE_IMPORT_LIMIT_PER_ROUTE)
109
+ .map((abs) => relative(monorepoRoot, abs));
110
+ }
111
+ }
112
+
113
+ async function collectFiles(root) {
114
+ const entries = await readdir(root, { recursive: true, withFileTypes: true });
115
+ const out = [];
116
+ for (const e of entries) {
117
+ if (!e.isFile()) continue;
118
+ const segments = (e.parentPath ?? e.path ?? root).split('/');
119
+ if (segments.some((s) => SKIP_DIRS.has(s))) continue;
120
+ if (SKIP_FILE_PATTERNS.some((re) => re.test(e.name))) continue;
121
+ if (!/\.(tsx?|jsx?|mjs|cjs|html|svelte|astro|vue|json)$/.test(e.name)) continue;
122
+
123
+ const full = join(e.parentPath ?? e.path ?? root, e.name);
124
+ try {
125
+ const content = await readFile(full, 'utf-8');
126
+ if (content.length > 500_000) continue;
127
+ out.push({ path: relative(root, full), content });
128
+ } catch {}
129
+ }
130
+ return out;
131
+ }
132
+
133
+ function filterApplicable(files, meta) {
134
+ const incl = meta.includeGlobs ?? ['**/*'];
135
+ return files.filter((f) => incl.some((g) => globMatch(g, f.path)));
136
+ }
137
+
138
+ // Tiny glob → regex. Supports **, *, and {a,b} alternation.
139
+ function globMatch(pattern, path) {
140
+ const re = new RegExp(
141
+ '^' +
142
+ pattern
143
+ .replace(/[.+^$()|[\]\\]/g, '\\$&')
144
+ .replace(/\{([^}]+)\}/g, (_, inner) => '(' + inner.split(',').join('|') + ')')
145
+ .replace(/\*\*/g, '__GLOBSTAR__')
146
+ .replace(/\*/g, '[^/]*')
147
+ .replace(/__GLOBSTAR__/g, '.*')
148
+ + '$'
149
+ );
150
+ return re.test(path);
151
+ }
152
+
153
+ async function enumerateRoutes(root) {
154
+ const entries = await readdir(root, { recursive: true, withFileTypes: true });
155
+ const routes = [];
156
+ for (const e of entries) {
157
+ if (!e.isFile()) continue;
158
+ const segments = (e.parentPath ?? e.path ?? root).split('/');
159
+ if (segments.some((s) => SKIP_DIRS.has(s))) continue;
160
+
161
+ const full = join(e.parentPath ?? e.path ?? root, e.name);
162
+ const rel = relative(root, full);
163
+
164
+ // App Router: route groups ((name)), parallel routes (@slot), private folders
165
+ // (_name), and the top-level page.tsx (no path segment) all need explicit handling.
166
+ let m = rel.match(/^(?:src\/)?app\/(.*)\/(page|route|layout)\.(tsx?|jsx?)$/);
167
+ if (!m) {
168
+ const top = rel.match(/^(?:src\/)?app\/(page|route|layout)\.(tsx?|jsx?)$/);
169
+ if (top) {
170
+ routes.push({
171
+ routePath: '/',
172
+ file: rel,
173
+ type: routeEntryType(top[1]),
174
+ });
175
+ continue;
176
+ }
177
+ }
178
+ if (m) {
179
+ const stripped = m[1]
180
+ .split('/')
181
+ .filter((seg) => !/^\([^)]+\)$/.test(seg) && !/^@/.test(seg) && !/^_/.test(seg))
182
+ .join('/')
183
+ .replace(/^\/+|\/+$/g, '');
184
+ const routePath = stripped === '' ? '/' : `/${stripped}`;
185
+ routes.push({
186
+ routePath,
187
+ file: rel,
188
+ type: routeEntryType(m[2]),
189
+ });
190
+ continue;
191
+ }
192
+
193
+ // Astro endpoint filenames commonly include the response extension
194
+ // (`feed.xml.ts`, `robots.txt.ts`). Handle these before the generic
195
+ // `src/pages` rule, which otherwise treats them as page components.
196
+ m = rel.match(/^src\/pages\/(.*\.(?:xml|json|txt|rss|atom|svg|png|jpg|jpeg|webp))\.(tsx?|jsx?|mjs|cjs)$/);
197
+ if (m) {
198
+ const name = normalizeRouteFileStem(m[1]);
199
+ routes.push({
200
+ routePath: name === '' ? '/' : '/' + name,
201
+ file: rel,
202
+ type: 'route',
203
+ });
204
+ continue;
205
+ }
206
+
207
+ m = rel.match(/^(?:src\/)?pages\/(.*)\.(tsx?|jsx?)$/);
208
+ if (m) {
209
+ const name = m[1].replace(/\/index$/, '').replace(/^index$/, '');
210
+ const isApi = /^api\//.test(name);
211
+ routes.push({
212
+ routePath: name === '' ? '/' : '/' + name,
213
+ file: rel,
214
+ type: isApi ? 'route' : 'page',
215
+ });
216
+ continue;
217
+ }
218
+
219
+ // Nuxt 3/4 pages. Dynamic segments use the same bracket shape as metrics
220
+ // (`[id]`, `[...slug]`), so keep them intact for route matching.
221
+ m = rel.match(/^(?:app\/)?pages\/(.*)\.vue$/);
222
+ if (m) {
223
+ const name = normalizeRouteFileStem(m[1]);
224
+ routes.push({
225
+ routePath: name === '' ? '/' : '/' + name,
226
+ file: rel,
227
+ type: 'page',
228
+ });
229
+ continue;
230
+ }
231
+
232
+ // Nuxt server routes: server/api/foo.get.ts -> /api/foo,
233
+ // server/routes/rss.xml.ts -> /rss.xml.
234
+ m = rel.match(/^server\/(api|routes)\/(.*)\.(tsx?|jsx?|mjs|cjs)$/);
235
+ if (m) {
236
+ const base = m[1] === 'api' ? 'api/' : '';
237
+ const name = normalizeRouteFileStem(`${base}${m[2]}`);
238
+ routes.push({
239
+ routePath: name === '' ? '/' : '/' + name,
240
+ file: rel,
241
+ type: 'route',
242
+ });
243
+ continue;
244
+ }
245
+
246
+ // Astro pages and endpoints. This is limited framework support, but route
247
+ // mapping still improves reports when Vercel metrics use user-facing paths.
248
+ m = rel.match(/^src\/pages\/(.*)\.(astro|tsx?|jsx?|mjs|cjs)$/);
249
+ if (m) {
250
+ const name = normalizeRouteFileStem(m[1]);
251
+ routes.push({
252
+ routePath: name === '' ? '/' : '/' + name,
253
+ file: rel,
254
+ type: m[2] === 'astro' ? 'page' : 'route',
255
+ });
256
+ continue;
257
+ }
258
+
259
+ // SvelteKit: +page.svelte = page, +page.server.{ts,js} pairs with it (treat
260
+ // as page), +server.{ts,js} = API route, +layout.* = ancestor layout context.
261
+ // Route groups (auth) stripped like Next; dynamic segments [slug]/[...rest]/[[opt]] preserved.
262
+ m = rel.match(/^src\/routes\/(.*)\/\+(page\.svelte|page\.server\.(?:ts|js)|server\.(?:ts|js)|layout\.svelte|layout\.server\.(?:ts|js))$/);
263
+ if (m || /^src\/routes\/\+(page\.svelte|page\.server\.(?:ts|js)|server\.(?:ts|js)|layout\.svelte|layout\.server\.(?:ts|js))$/.test(rel)) {
264
+ const fileTypeMatch = rel.match(/\+(page\.svelte|page\.server\.(?:ts|js)|server\.(?:ts|js)|layout\.svelte|layout\.server\.(?:ts|js))$/);
265
+ const fileType = fileTypeMatch?.[1] ?? '';
266
+ const segs = (m?.[1] ?? '').split('/').filter(Boolean)
267
+ .filter((seg) => !/^\([^)]+\)$/.test(seg));
268
+ const routePath = segs.length === 0 ? '/' : '/' + segs.join('/');
269
+ const type = fileType.startsWith('server') ? 'route' : fileType.startsWith('layout') ? 'layout' : 'page';
270
+ // When +page.svelte AND +page.server.ts both exist, +page.svelte wins ownership.
271
+ const existing = type === 'layout' ? null : routes.find((r) => r.routePath === routePath && r.type !== 'layout');
272
+ if (existing) {
273
+ if (fileType === 'page.svelte' && existing.type === 'page') {
274
+ existing.file = rel;
275
+ }
276
+ continue;
277
+ }
278
+ routes.push({ routePath, file: rel, type });
279
+ continue;
280
+ }
281
+ }
282
+ return routes.sort((a, b) =>
283
+ a.routePath.localeCompare(b.routePath)
284
+ || routeTypeOrder(a.type) - routeTypeOrder(b.type)
285
+ || a.file.localeCompare(b.file)
286
+ );
287
+ }
288
+
289
+ function routeEntryType(name) {
290
+ return name === 'route' ? 'route' : name === 'layout' ? 'layout' : 'page';
291
+ }
292
+
293
+ function normalizeRouteFileStem(stem) {
294
+ return String(stem ?? '')
295
+ .replace(/\/index$/, '')
296
+ .replace(/^index$/, '')
297
+ .replace(/\.(?:get|post|put|patch|delete|options|head)$/, '')
298
+ .replace(/^\/+|\/+$/g, '');
299
+ }
300
+
301
+ function routeTypeOrder(type) {
302
+ return type === 'page' ? 0 : type === 'route' ? 1 : type === 'layout' ? 2 : 3;
303
+ }
304
+
305
+ function mapFileToRoute(filePath, routes) {
306
+ const r = routes.find((rt) => rt.file === filePath);
307
+ return r?.routePath ?? null;
308
+ }
309
+
310
+ main().catch((err) => {
311
+ process.stderr.write(`[scan-codebase] FAILED: ${err.message}\n`);
312
+ process.exit(1);
313
+ });
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env node
2
+ // Verify → grade → emit regenPlan. Does NOT spawn sub-agents — the
3
+ // orchestrator reads regenPlan, re-spawns one sub-agent per targeted candidate
4
+ // with topFailures injected, then re-runs this script. Thresholds are tuned
5
+ // below (REGEN_*, QUALITY_FLOOR) — read those constants for the live values.
6
+
7
+ import { readFile, writeFile } from 'node:fs/promises';
8
+ import { dirname, resolve } from 'node:path';
9
+ import { mkdir } from 'node:fs/promises';
10
+ import { verifyClaim } from '../lib/verify-claim.mjs';
11
+ import { extractClaims, summarizeClaimResults } from '../lib/extract-claims.mjs';
12
+ import { gradeRecommendation, applyQualityFloor } from '../lib/grade-recommendation.mjs';
13
+ import { deriveProjectFacts } from '../lib/project-facts.mjs';
14
+ import { resolveRepoRoot } from '../lib/repo-root.mjs';
15
+ import { applySanitizers } from '../lib/sanitizers/index.mjs';
16
+
17
+ const SCHEMA_VERSION = '1.0';
18
+ const REGEN_PASS_RATE_THRESHOLD = 0.8;
19
+ // 1/1 failed is as broken as 1/5; below 2 claims is below the noise floor.
20
+ const REGEN_MIN_CLAIMS = 2;
21
+ // The Poor/Fair grade boundary — Poor recs erode trust faster than recall helps.
22
+ const QUALITY_FLOOR = 0.55;
23
+
24
+ const log = (...a) => console.error('[verify-and-regen]', ...a);
25
+
26
+ async function main() {
27
+ const args = parseArgs(process.argv.slice(2));
28
+ if (!args.recsPath) {
29
+ console.error('usage: node scripts/verify-and-regen.mjs <recommendations.json> [--signals merged.json] [--repo-root DIR] [--out FILE]');
30
+ process.exit(1);
31
+ }
32
+
33
+ const recs = JSON.parse(await readFile(args.recsPath, 'utf-8'));
34
+ if (!Array.isArray(recs)) {
35
+ console.error('[verify-and-regen] FATAL: recommendations.json must be an array of rec objects');
36
+ process.exit(2);
37
+ }
38
+
39
+ let framework, version, cacheComponents, knownFindings = [], projectFacts = [], signals = null;
40
+ if (args.signalsPath) {
41
+ signals = JSON.parse(await readFile(args.signalsPath, 'utf-8'));
42
+ const stack = signals.stack ?? signals.codebase?.stack ?? {};
43
+ framework = stack.framework;
44
+ version = stack.frameworkVersion;
45
+ cacheComponents = stack.cacheComponents;
46
+ knownFindings = (signals.codebase?.findings ?? signals.findings ?? [])
47
+ .filter((f) => f.file && (f.line != null))
48
+ .map((f) => ({ file: f.file, line: f.line }));
49
+ projectFacts = deriveProjectFacts(signals);
50
+ if (projectFacts.length > 0) {
51
+ log(`project facts in play: ${projectFacts.map((f) => f.id).join(', ')}`);
52
+ }
53
+ }
54
+
55
+ // Repo-root priority: (1) signals.project.rootDirectory from Vercel API
56
+ // (authoritative — returns "apps/<name>" so cwd can be back-mapped without
57
+ // filesystem probing), (2) supplied --repo-root, (3) walk-up from cwd.
58
+ const rootResult = await resolveRepoRoot(recs, args.repoRoot, process.cwd(), signals);
59
+ const repoRoot = rootResult.root;
60
+ if (rootResult.source === 'api') {
61
+ log(`repo-root from Vercel API: '${repoRoot}' (rootDirectory='${rootResult.apiOffset}')`);
62
+ } else if (rootResult.source === 'auto-detected') {
63
+ log(`repo-root auto-detected: '${repoRoot}' (probe: ${rootResult.probe})`);
64
+ } else if (rootResult.source === 'corrected') {
65
+ log(`repo-root auto-corrected: '${args.repoRoot}' → '${repoRoot}' (sub-agent paths resolve there)`);
66
+ }
67
+ log(`verifying ${recs.length} rec(s) — framework=${framework ?? '?'}@${version ?? '?'} repoRoot=${repoRoot}`);
68
+
69
+ // knownFindings MUST combine scanner findings + sub-agent's verified
70
+ // findingRefs — scanner-only grounding would miss every metric-gate rec.
71
+ // Abstentions are first-class outputs ({abstain:true, candidateRef, reason})
72
+ // and MUST NOT be graded; the abstention IS the answer.
73
+ const recsGraded = [];
74
+ const abstentions = [];
75
+ const observations = [];
76
+ const sanitizerDropped = [];
77
+ for (let i = 0; i < recs.length; i++) {
78
+ const rec = recs[i];
79
+
80
+ if (rec?.abstain === true) {
81
+ abstentions.push({
82
+ index: i,
83
+ candidateRef: rec.candidateRef ?? null,
84
+ reason: rec.reason ?? '(no reason recorded)',
85
+ });
86
+ // Observation: real non-perf signal worth surfacing (regression, error storm).
87
+ if (rec.observation && typeof rec.observation === 'object' && rec.observation.summary) {
88
+ observations.push({
89
+ index: i,
90
+ candidateRef: rec.candidateRef ?? null,
91
+ summary: String(rec.observation.summary),
92
+ evidence: rec.observation.evidence ?? null,
93
+ suggestedAction: rec.observation.suggestedAction ?? null,
94
+ kind: rec.observation.kind ?? 'other',
95
+ });
96
+ }
97
+ continue;
98
+ }
99
+
100
+ const baseClaimCtx = {
101
+ framework,
102
+ version,
103
+ repoRoot,
104
+ projectFacts,
105
+ projectRootDirectory: signals?.project?.rootDirectory ?? null,
106
+ cacheComponents,
107
+ signals,
108
+ };
109
+ const initialClaims = extractClaims(rec, baseClaimCtx);
110
+ const initialVerifyResults = await Promise.all(initialClaims.map((c) => verifyClaim(c)));
111
+ const initialClaimsWithResults = initialVerifyResults.map((r, j) => ({
112
+ ...r,
113
+ type: initialClaims[j]?.type,
114
+ claimType: initialClaims[j]?.type,
115
+ claim: initialClaims[j],
116
+ }));
117
+ const sanitizerResult = await applySanitizers(rec, {
118
+ framework,
119
+ version,
120
+ signals,
121
+ verifyResults: initialClaimsWithResults,
122
+ });
123
+ if (!sanitizerResult.kept) {
124
+ sanitizerDropped.push({
125
+ index: i,
126
+ candidateRef: rec.candidateRef ?? null,
127
+ what: rec.what ?? null,
128
+ reason: sanitizerResult.dropReason ?? 'automated-check',
129
+ });
130
+ continue;
131
+ }
132
+
133
+ const sanitizedRec = sanitizerResult.rec;
134
+ const claims = extractClaims(sanitizedRec, baseClaimCtx);
135
+ const verifyResults = await Promise.all(claims.map((c) => verifyClaim(c)));
136
+ const verification = summarizeClaimResults(verifyResults);
137
+
138
+ // A findingRef whose file_exists claim verified counts as grounding evidence.
139
+ const verifiedRefs = [];
140
+ for (let j = 0; j < claims.length; j++) {
141
+ const c = claims[j];
142
+ const r = verifyResults[j];
143
+ if (r?.disposition !== 'verified') continue;
144
+ if (c.sourceField === 'findingRefs' && c.type === 'file_exists') {
145
+ const ref = (rec.findingRefs ?? []).find((x) => String(x).startsWith(c.file + ':'));
146
+ if (ref) {
147
+ const m = String(ref).match(/^(.+?):(\d+)$/);
148
+ if (m) verifiedRefs.push({ file: m[1], line: Number(m[2]) });
149
+ }
150
+ }
151
+ }
152
+ const recKnownFindings = [...knownFindings, ...verifiedRefs];
153
+ const quality = gradeRecommendation(sanitizedRec, { knownFindings: recKnownFindings });
154
+
155
+ recsGraded.push({
156
+ index: i,
157
+ rec: { ...sanitizedRec, verification, verifyResults, quality },
158
+ claims,
159
+ verifyResults,
160
+ verification,
161
+ quality,
162
+ });
163
+ }
164
+
165
+ // Project-config contradictions are a HARD trigger: a "turn on Fluid" rec on
166
+ // a project where Fluid is already on passes 8/9 claims but is the wrong rec.
167
+ // passRate alone won't catch this.
168
+ const regenPlan = [];
169
+ for (const g of recsGraded) {
170
+ const { passRate, verifiable } = g.verification;
171
+ const claimsWithResults = g.verifyResults.map((r, j) => ({ ...r, claim: g.claims[j] }));
172
+ const contradictions = claimsWithResults.filter(
173
+ (r) => r.disposition === 'failed' && r.claim?.type === 'does_not_contradict_project_config'
174
+ );
175
+ const triggeredByPassRate = verifiable >= REGEN_MIN_CLAIMS && passRate < REGEN_PASS_RATE_THRESHOLD;
176
+ const cacheSafetyFailures = claimsWithResults.filter(
177
+ (r) => r.disposition === 'failed' && (
178
+ r.claim?.type === 'cache_vary_matches_dynamic_inputs' ||
179
+ r.claim?.type === 'cache_vary_cardinality_safe'
180
+ )
181
+ );
182
+ const semanticSafetyFailures = claimsWithResults.filter(
183
+ (r) => r.disposition === 'failed' && (
184
+ r.claim?.type === 'next_cached_not_found_causal_support' ||
185
+ r.claim?.type === 'next_stable_cache_api_for_version' ||
186
+ r.claim?.type === 'next_runtime_cache_api_for_version' ||
187
+ r.claim?.type === 'next_cache_life_single_execution' ||
188
+ r.claim?.type === 'next_cache_lifetime_freshness_supported' ||
189
+ r.claim?.type === 'next_cache_components_route_chain_file' ||
190
+ r.claim?.type === 'next_cache_life_cdn_header_semantics' ||
191
+ r.claim?.type === 'image_response_headers_citation' ||
192
+ r.claim?.type === 'next_image_priority_api_for_version' ||
193
+ r.claim?.type === 'next_cache_components_route_segment_config' ||
194
+ r.claim?.type === 'next_route_revalidate_static_prereq' ||
195
+ r.claim?.type === 'next_cache_tag_invalidation_supported' ||
196
+ r.claim?.type === 'cache_rec_not_error_dominated_or_acknowledged' ||
197
+ r.claim?.type === 'cache_control_header_syntax' ||
198
+ r.claim?.type === 'cache_control_headers_citation' ||
199
+ r.claim?.type === 'cache_404_long_ttl_safety' ||
200
+ r.claim?.type === 'route_error_not_found_status_and_scope' ||
201
+ r.claim?.type === 'immutable_dynamic_route_safety' ||
202
+ r.claim?.type === 'auth_guard_parallelization_safety' ||
203
+ r.claim?.type === 'parallelization_impact_not_overclaimed' ||
204
+ r.claim?.type === 'parallelization_not_cpu_bound_work' ||
205
+ r.claim?.type === 'runtime_error_cause_supported' ||
206
+ r.claim?.type === 'vercel_ignore_command_project_state'
207
+ )
208
+ );
209
+ const triggeredByContradiction = contradictions.length > 0;
210
+ const triggeredByCacheSafety = cacheSafetyFailures.length > 0;
211
+ const triggeredBySemanticSafety = semanticSafetyFailures.length > 0;
212
+ if (!triggeredByPassRate && !triggeredByContradiction && !triggeredByCacheSafety && !triggeredBySemanticSafety) continue;
213
+
214
+ const failures = claimsWithResults
215
+ .filter((r) => r.disposition === 'failed')
216
+ .slice(0, 5);
217
+ regenPlan.push({
218
+ index: g.index,
219
+ candidateRef: g.rec.candidateRef ?? null,
220
+ what: g.rec.what ?? null,
221
+ verifiableClaimCount: verifiable,
222
+ passRate,
223
+ regenTrigger: triggeredByContradiction
224
+ ? 'project_config_contradiction'
225
+ : triggeredByCacheSafety
226
+ ? 'cache_vary_safety'
227
+ : triggeredBySemanticSafety
228
+ ? 'semantic_safety'
229
+ : 'pass_rate_below_threshold',
230
+ topFailures: failures.map((f) => ({
231
+ claimType: f.claim?.type,
232
+ field: f.claim?.sourceField,
233
+ url: f.claim?.url,
234
+ file: f.claim?.file,
235
+ pattern: f.claim?.pattern,
236
+ reason: f.reason,
237
+ })),
238
+ regenBriefHint: triggeredByContradiction
239
+ ? 'Sub-agent recommended toggling on a project setting that is already enabled. Re-spawn with the project-config Strengths block highlighted; the rec must drop the contradictory step and keep only the actionable parts.'
240
+ : triggeredByCacheSafety
241
+ ? 'Sub-agent recommended CDN caching with unsafe or missing Vary behavior. Re-spawn with the cache safety failure highlighted; the rec must use a low-cardinality Vary header that matches the dynamic inputs, or abstain.'
242
+ : triggeredBySemanticSafety
243
+ ? 'Sub-agent made a framework-semantic claim that failed deterministic checks. Re-spawn with the failure highlighted; the rec must either add version-correct code/citations/runtime evidence or abstain.'
244
+ : 'Re-spawn the sub-agent with this rec\'s topFailures injected as feedback. Re-emit the rec only if regenPassRate >= originalPassRate AND citation count not gutted.',
245
+ });
246
+ }
247
+
248
+ const qualityCheck = applyQualityFloor(recsGraded.map((g) => g.rec), QUALITY_FLOOR);
249
+ const hardRegenIndexes = new Set(regenPlan.map((p) => p.index));
250
+ const qualityDroppedIndexes = new Set(
251
+ qualityCheck.dropped
252
+ .map((d) => recsGraded.findIndex((g) => g.rec === d.rec))
253
+ .filter((i) => i >= 0)
254
+ );
255
+ const needsReviewIndexes = new Set(
256
+ recsGraded
257
+ .filter((g) => g.rec.needsReview === true)
258
+ .map((g) => g.index)
259
+ );
260
+ const verifiedRecommendations = recsGraded
261
+ .filter((g) => !hardRegenIndexes.has(g.index) && !qualityDroppedIndexes.has(g.index) && !needsReviewIndexes.has(g.index))
262
+ .map((g) => g.rec);
263
+ const withheldRecommendations = recsGraded
264
+ .filter((g) => hardRegenIndexes.has(g.index) || qualityDroppedIndexes.has(g.index) || needsReviewIndexes.has(g.index))
265
+ .map((g) => ({
266
+ index: g.index,
267
+ candidateRef: g.rec.candidateRef ?? null,
268
+ what: g.rec.what ?? null,
269
+ reason: hardRegenIndexes.has(g.index)
270
+ ? (regenPlan.find((p) => p.index === g.index)?.regenTrigger ?? 'verification')
271
+ : qualityDroppedIndexes.has(g.index)
272
+ ? 'quality_floor'
273
+ : 'needs_review',
274
+ }));
275
+
276
+ const summary = {
277
+ totalRecs: recs.length,
278
+ abstentions: abstentions.length,
279
+ observations: observations.length,
280
+ sanitizerDropped: sanitizerDropped.length,
281
+ needsRegen: regenPlan.length,
282
+ qualityDropped: qualityCheck.dropped.length,
283
+ needsReview: needsReviewIndexes.size,
284
+ verifiedRecommendations: verifiedRecommendations.length,
285
+ withheldRecommendations: withheldRecommendations.length,
286
+ averagePassRate: recsGraded.length > 0
287
+ ? round4(recsGraded.reduce((s, g) => s + g.verification.passRate, 0) / recsGraded.length)
288
+ : null,
289
+ averageQuality: recsGraded.length > 0
290
+ ? round4(recsGraded.reduce((s, g) => s + g.quality.overall, 0) / recsGraded.length)
291
+ : null,
292
+ };
293
+
294
+ const output = {
295
+ schemaVersion: SCHEMA_VERSION,
296
+ summary,
297
+ recsGraded: recsGraded.map((g) => g.rec),
298
+ verifiedRecommendations,
299
+ renderableRecommendations: verifiedRecommendations,
300
+ withheldRecommendations,
301
+ abstentions,
302
+ observations,
303
+ sanitizerDropped,
304
+ regenPlan,
305
+ qualityDropped: qualityCheck.dropped.map((d) => ({
306
+ index: recsGraded.findIndex((g) => g.rec === d.rec),
307
+ candidateRef: d.rec.candidateRef ?? null,
308
+ quality: d.rec.quality,
309
+ reason: d.reason,
310
+ })),
311
+ };
312
+
313
+ const serialized = JSON.stringify(output, null, 2) + '\n';
314
+ if (args.outPath) {
315
+ await mkdir(dirname(args.outPath), { recursive: true });
316
+ await writeFile(args.outPath, serialized, 'utf-8');
317
+ log(`wrote ${serialized.length}B → ${args.outPath}`);
318
+ } else {
319
+ process.stdout.write(serialized);
320
+ }
321
+ log(`done: ${summary.totalRecs} records checked; ${summary.verifiedRecommendations} ready, ${summary.withheldRecommendations} held back, ${summary.abstentions} found no supported change, ${summary.sanitizerDropped} dropped by safety checks`);
322
+ }
323
+
324
+ function parseArgs(argv) {
325
+ const out = { positional: [] };
326
+ for (let i = 0; i < argv.length; i++) {
327
+ const a = argv[i];
328
+ if (a === '--signals') out.signalsPath = argv[++i];
329
+ else if (a.startsWith('--signals=')) out.signalsPath = a.slice('--signals='.length);
330
+ else if (a === '--repo-root') out.repoRoot = argv[++i];
331
+ else if (a.startsWith('--repo-root=')) out.repoRoot = a.slice('--repo-root='.length);
332
+ else if (a === '--out') out.outPath = resolve(argv[++i]);
333
+ else if (a.startsWith('--out=')) out.outPath = resolve(a.slice('--out='.length));
334
+ else out.positional.push(a);
335
+ }
336
+ out.recsPath = out.positional[0];
337
+ return out;
338
+ }
339
+
340
+ function round4(n) { return Math.round(n * 10000) / 10000; }
341
+
342
+ main().catch((err) => {
343
+ console.error('[verify-and-regen] FAILED:', err.message);
344
+ console.error(err.stack);
345
+ process.exit(1);
346
+ });
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // CLI shell around lib/verify-claim.mjs. argv[2] = JSON claim, stdout = result.
3
+ // Claim `type` enum: pattern_count | pattern_exists | pattern_absent | file_exists |
4
+ // code_snippet | repo_count | citation_in_library | citation_applies_to_version.
5
+
6
+ import { verifyClaim } from '../lib/verify-claim.mjs';
7
+
8
+ const SCHEMA_VERSION = '1.0';
9
+
10
+ async function main() {
11
+ const claim = JSON.parse(process.argv[2] || '{}');
12
+ const result = await verifyClaim(claim);
13
+ process.stdout.write(JSON.stringify({ schemaVersion: SCHEMA_VERSION, ...result }, null, 2) + '\n');
14
+ }
15
+
16
+ main().catch((err) => {
17
+ process.stderr.write(`[verify-finding] FAILED: ${err.message}\n`);
18
+ process.exit(1);
19
+ });