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.
- package/bundled-skills/.antigravity-install-manifest.json +16 -1
- package/bundled-skills/accesslint-audit/SKILL.md +115 -0
- package/bundled-skills/accesslint-diff/SKILL.md +81 -0
- package/bundled-skills/accesslint-scan/SKILL.md +47 -0
- package/bundled-skills/composition-patterns/SKILL.md +87 -0
- package/bundled-skills/composition-patterns/rules/_sections.md +29 -0
- package/bundled-skills/composition-patterns/rules/_template.md +24 -0
- package/bundled-skills/composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/bundled-skills/composition-patterns/rules/architecture-compound-components.md +112 -0
- package/bundled-skills/composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/bundled-skills/composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/bundled-skills/composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/bundled-skills/composition-patterns/rules/state-context-interface.md +191 -0
- package/bundled-skills/composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/bundled-skills/composition-patterns/rules/state-lift-state.md +125 -0
- package/bundled-skills/debugging-toolkit/SKILL.md +35 -0
- package/bundled-skills/deploy-to-vercel/SKILL.md +304 -0
- package/bundled-skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
- package/bundled-skills/deploy-to-vercel/resources/deploy.sh +301 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/backups/README-2026-06-02.md +687 -0
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +4 -4
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +245 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +3 -3
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/plugins.md +21 -13
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +95 -0
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/mmx-cli/SKILL.md +5 -2
- package/bundled-skills/nextjs-seo-indexing/SKILL.md +3 -3
- package/bundled-skills/polis-protocol/SKILL.md +93 -0
- package/bundled-skills/python-development/SKILL.md +35 -0
- package/bundled-skills/radix-ui-design-system/SKILL.md +2 -2
- package/bundled-skills/react-native-skills/SKILL.md +120 -0
- package/bundled-skills/react-native-skills/rules/_sections.md +86 -0
- package/bundled-skills/react-native-skills/rules/_template.md +28 -0
- package/bundled-skills/react-native-skills/rules/animation-derived-value.md +53 -0
- package/bundled-skills/react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/bundled-skills/react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/bundled-skills/react-native-skills/rules/design-system-compound-components.md +66 -0
- package/bundled-skills/react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/bundled-skills/react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/bundled-skills/react-native-skills/rules/js-hoist-intl.md +61 -0
- package/bundled-skills/react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/bundled-skills/react-native-skills/rules/list-performance-function-references.md +132 -0
- package/bundled-skills/react-native-skills/rules/list-performance-images.md +53 -0
- package/bundled-skills/react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/bundled-skills/react-native-skills/rules/list-performance-item-types.md +104 -0
- package/bundled-skills/react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/bundled-skills/react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/bundled-skills/react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/bundled-skills/react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/bundled-skills/react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/bundled-skills/react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/bundled-skills/react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/bundled-skills/react-native-skills/rules/react-state-fallback.md +56 -0
- package/bundled-skills/react-native-skills/rules/react-state-minimize.md +65 -0
- package/bundled-skills/react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/bundled-skills/react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/bundled-skills/react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/bundled-skills/react-native-skills/rules/state-ground-truth.md +80 -0
- package/bundled-skills/react-native-skills/rules/ui-expo-image.md +66 -0
- package/bundled-skills/react-native-skills/rules/ui-image-gallery.md +104 -0
- package/bundled-skills/react-native-skills/rules/ui-measure-views.md +78 -0
- package/bundled-skills/react-native-skills/rules/ui-menus.md +174 -0
- package/bundled-skills/react-native-skills/rules/ui-native-modals.md +77 -0
- package/bundled-skills/react-native-skills/rules/ui-pressable.md +61 -0
- package/bundled-skills/react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/bundled-skills/react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/bundled-skills/react-native-skills/rules/ui-styling.md +87 -0
- package/bundled-skills/schema-markup-generator/SKILL.md +1 -1
- package/bundled-skills/skill-issue/SKILL.md +73 -0
- package/bundled-skills/social-metadata-hardening/SKILL.md +4 -3
- package/bundled-skills/social-post-writer-seo/SKILL.md +19 -0
- package/bundled-skills/tdd-workflows/SKILL.md +35 -0
- package/bundled-skills/user-thoughts/SKILL.md +236 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/README.ai.md +13 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/define.ini +3 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/README.ai.md +25 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/backlog.md +19 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/dev-stack.md +7 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/general.md +7 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/plans.md +7 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/rules.md +7 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/ui/details.md +7 -0
- package/bundled-skills/user-thoughts/assets/Runtime-Template/mdbase/details/ui/outline.md +7 -0
- package/bundled-skills/user-thoughts/references/commands.md +54 -0
- package/bundled-skills/user-thoughts/references/edge-cases.md +84 -0
- package/bundled-skills/user-thoughts/references/safety.md +65 -0
- package/bundled-skills/user-thoughts/references/sortin.md +76 -0
- package/bundled-skills/user-thoughts/scripts/common.py +62 -0
- package/bundled-skills/user-thoughts/scripts/ignore_ops.py +125 -0
- package/bundled-skills/user-thoughts/scripts/init.py +63 -0
- package/bundled-skills/user-thoughts/scripts/show_mdbase.py +93 -0
- package/bundled-skills/user-thoughts/scripts/show_raw.py +42 -0
- package/bundled-skills/user-thoughts/scripts/sortin.py +211 -0
- package/bundled-skills/user-thoughts/scripts/status.py +56 -0
- package/bundled-skills/user-thoughts/scripts/toggle.py +68 -0
- package/bundled-skills/user-thoughts/scripts/write_raw.py +106 -0
- package/bundled-skills/vercel-cli-with-tokens/SKILL.md +361 -0
- package/bundled-skills/vercel-optimize/CONTRIBUTING.md +41 -0
- package/bundled-skills/vercel-optimize/SKILL.md +331 -0
- package/bundled-skills/vercel-optimize/lib/auth-route.mjs +23 -0
- package/bundled-skills/vercel-optimize/lib/budget-summary.mjs +182 -0
- package/bundled-skills/vercel-optimize/lib/citations.mjs +139 -0
- package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +143 -0
- package/bundled-skills/vercel-optimize/lib/dedup-recs.mjs +325 -0
- package/bundled-skills/vercel-optimize/lib/deep-dive.mjs +350 -0
- package/bundled-skills/vercel-optimize/lib/display-labels.mjs +185 -0
- package/bundled-skills/vercel-optimize/lib/extract-claims.mjs +550 -0
- package/bundled-skills/vercel-optimize/lib/framework-support.mjs +67 -0
- package/bundled-skills/vercel-optimize/lib/gates/build-minutes-fanout.mjs +69 -0
- package/bundled-skills/vercel-optimize/lib/gates/cold-start.mjs +66 -0
- package/bundled-skills/vercel-optimize/lib/gates/contract.mjs +79 -0
- package/bundled-skills/vercel-optimize/lib/gates/cwv-poor.mjs +87 -0
- package/bundled-skills/vercel-optimize/lib/gates/external-api-slow.mjs +55 -0
- package/bundled-skills/vercel-optimize/lib/gates/hard-gates.mjs +73 -0
- package/bundled-skills/vercel-optimize/lib/gates/index.mjs +45 -0
- package/bundled-skills/vercel-optimize/lib/gates/isr-overrevalidation.mjs +62 -0
- package/bundled-skills/vercel-optimize/lib/gates/middleware-heavy.mjs +51 -0
- package/bundled-skills/vercel-optimize/lib/gates/observability-events-attribution.mjs +56 -0
- package/bundled-skills/vercel-optimize/lib/gates/platform-bot-protection.mjs +115 -0
- package/bundled-skills/vercel-optimize/lib/gates/platform-fluid-compute.mjs +83 -0
- package/bundled-skills/vercel-optimize/lib/gates/region-misconfig.mjs +64 -0
- package/bundled-skills/vercel-optimize/lib/gates/route-errors.mjs +80 -0
- package/bundled-skills/vercel-optimize/lib/gates/scanner-driven.mjs +122 -0
- package/bundled-skills/vercel-optimize/lib/gates/select-candidates.mjs +134 -0
- package/bundled-skills/vercel-optimize/lib/gates/slow-route.mjs +88 -0
- package/bundled-skills/vercel-optimize/lib/gates/types.d.ts +38 -0
- package/bundled-skills/vercel-optimize/lib/gates/uncached-route.mjs +93 -0
- package/bundled-skills/vercel-optimize/lib/gates/usage-spike-triage.mjs +121 -0
- package/bundled-skills/vercel-optimize/lib/grade-recommendation.mjs +155 -0
- package/bundled-skills/vercel-optimize/lib/impact-label.mjs +126 -0
- package/bundled-skills/vercel-optimize/lib/impact-magnitude.mjs +60 -0
- package/bundled-skills/vercel-optimize/lib/investigation-brief.mjs +610 -0
- package/bundled-skills/vercel-optimize/lib/observation-safety.mjs +174 -0
- package/bundled-skills/vercel-optimize/lib/project-facts.mjs +99 -0
- package/bundled-skills/vercel-optimize/lib/queries.mjs +315 -0
- package/bundled-skills/vercel-optimize/lib/reconcile-candidates.mjs +372 -0
- package/bundled-skills/vercel-optimize/lib/render-report.mjs +955 -0
- package/bundled-skills/vercel-optimize/lib/repo-root.mjs +86 -0
- package/bundled-skills/vercel-optimize/lib/route-normalize.mjs +220 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs +30 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/count-correct.mjs +52 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/function-duration-invocations.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/index.mjs +79 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/middleware-conflict.mjs +36 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/missing-citation.mjs +16 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/pre-release.mjs +74 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/rate-limit.mjs +67 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs +38 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/undeclared-dep.mjs +78 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs +37 -0
- package/bundled-skills/vercel-optimize/lib/sanitizers/window-units.mjs +32 -0
- package/bundled-skills/vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs +109 -0
- package/bundled-skills/vercel-optimize/lib/scanners/edge-heavy-import.mjs +94 -0
- package/bundled-skills/vercel-optimize/lib/scanners/force-dynamic.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/headers-in-page.mjs +44 -0
- package/bundled-skills/vercel-optimize/lib/scanners/index.mjs +35 -0
- package/bundled-skills/vercel-optimize/lib/scanners/large-static-asset.mjs +92 -0
- package/bundled-skills/vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs +55 -0
- package/bundled-skills/vercel-optimize/lib/scanners/missing-cache-headers.mjs +90 -0
- package/bundled-skills/vercel-optimize/lib/scanners/prisma-include-tree.mjs +42 -0
- package/bundled-skills/vercel-optimize/lib/scanners/region-pin-in-config.mjs +88 -0
- package/bundled-skills/vercel-optimize/lib/scanners/source-maps-production.mjs +36 -0
- package/bundled-skills/vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs +43 -0
- package/bundled-skills/vercel-optimize/lib/scanners/turbo-force-bypass.mjs +129 -0
- package/bundled-skills/vercel-optimize/lib/scanners/unoptimized-image.mjs +113 -0
- package/bundled-skills/vercel-optimize/lib/scanners/use-cache-date-stamp.mjs +106 -0
- package/bundled-skills/vercel-optimize/lib/support-topics.mjs +355 -0
- package/bundled-skills/vercel-optimize/lib/throttle.mjs +273 -0
- package/bundled-skills/vercel-optimize/lib/util.mjs +17 -0
- package/bundled-skills/vercel-optimize/lib/vercel.mjs +784 -0
- package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +1296 -0
- package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +521 -0
- package/bundled-skills/vercel-optimize/references/candidates.md +176 -0
- package/bundled-skills/vercel-optimize/references/data-collection.md +218 -0
- package/bundled-skills/vercel-optimize/references/docs-library.json +683 -0
- package/bundled-skills/vercel-optimize/references/doctrine.md +105 -0
- package/bundled-skills/vercel-optimize/references/observability-plus.md +108 -0
- package/bundled-skills/vercel-optimize/references/playbooks/README.md +53 -0
- package/bundled-skills/vercel-optimize/references/playbooks/ai-application.md +32 -0
- package/bundled-skills/vercel-optimize/references/playbooks/api-service.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/content-site.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/ecommerce.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/marketing.md +30 -0
- package/bundled-skills/vercel-optimize/references/playbooks/saas.md +31 -0
- package/bundled-skills/vercel-optimize/references/playbooks/sveltekit.md +75 -0
- package/bundled-skills/vercel-optimize/references/recommendations.md +203 -0
- package/bundled-skills/vercel-optimize/references/scanner-patterns.md +251 -0
- package/bundled-skills/vercel-optimize/references/scoring.md +205 -0
- package/bundled-skills/vercel-optimize/references/support-topics/README.md +46 -0
- package/bundled-skills/vercel-optimize/references/support-topics/astro-edge-middleware-scope.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/astro-output-mode-and-isr.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/auth-preserving-parallelization.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/bot-protection-product-guardrails.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cdn-cache-auth-safety.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/cold-start-initialization-bundle.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/database-egress-pooling-region.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/dynamic-rendering-traps.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path-platform.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/fast-data-transfer-payloads.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/fluid-compute-caveats.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-duration-io-and-after.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-invocation-reduction.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/image-optimization-cost-control.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/isr-revalidation-static-generation.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-font-cls-self-hosting.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/next-script-third-party-strategy.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/not-found-catchall-request-waste.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/observability-events-cost-attribution.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/post-response-work-waituntil.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/route-error-durable-offload.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/route-error-runtime-limits.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/runtime-cache-reusable-data.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/usage-spike-triage.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md +23 -0
- package/bundled-skills/vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md +22 -0
- package/bundled-skills/vercel-optimize/references/support-topics/workflow-resumable-stream-routes.md +23 -0
- package/bundled-skills/vercel-optimize/references/verification.md +102 -0
- package/bundled-skills/vercel-optimize/references/voice.md +76 -0
- package/bundled-skills/vercel-optimize/scripts/budget-summary.mjs +56 -0
- package/bundled-skills/vercel-optimize/scripts/build-docs.mjs +74 -0
- package/bundled-skills/vercel-optimize/scripts/check-citations.mjs +81 -0
- package/bundled-skills/vercel-optimize/scripts/check-docs-fresh.mjs +93 -0
- package/bundled-skills/vercel-optimize/scripts/collect-signals.mjs +576 -0
- package/bundled-skills/vercel-optimize/scripts/collect-sub-agent-outputs.mjs +296 -0
- package/bundled-skills/vercel-optimize/scripts/deep-dive.mjs +319 -0
- package/bundled-skills/vercel-optimize/scripts/gate-investigations.mjs +166 -0
- package/bundled-skills/vercel-optimize/scripts/merge-signals.mjs +192 -0
- package/bundled-skills/vercel-optimize/scripts/prepare-investigation-brief.mjs +231 -0
- package/bundled-skills/vercel-optimize/scripts/reconcile-candidates.mjs +62 -0
- package/bundled-skills/vercel-optimize/scripts/render-report.mjs +437 -0
- package/bundled-skills/vercel-optimize/scripts/scan-codebase.mjs +313 -0
- package/bundled-skills/vercel-optimize/scripts/verify-and-regen.mjs +346 -0
- package/bundled-skills/vercel-optimize/scripts/verify-finding.mjs +19 -0
- package/bundled-skills/vercel-react-view-transitions/SKILL.md +327 -0
- package/bundled-skills/vercel-react-view-transitions/references/css-recipes.md +242 -0
- package/bundled-skills/vercel-react-view-transitions/references/implementation.md +182 -0
- package/bundled-skills/vercel-react-view-transitions/references/nextjs.md +176 -0
- package/bundled-skills/vercel-react-view-transitions/references/patterns.md +262 -0
- package/bundled-skills/vibe-code-cleanup/SKILL.md +4 -4
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +3 -2
- package/package.json +1 -1
- package/skills_index.json +338 -4
|
@@ -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
|
+
});
|