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,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Collect raw sub-agent outputs into the recommendations.json array consumed by
|
|
3
|
+
// verify-and-regen. Sub-agent hosts often wrap JSON in prose or markdown fences,
|
|
4
|
+
// so extraction is permissive while candidateRef coverage stays strict.
|
|
5
|
+
|
|
6
|
+
import { readFile, readdir, stat, writeFile, mkdir } from 'node:fs/promises';
|
|
7
|
+
import { dirname, resolve, basename } from 'node:path';
|
|
8
|
+
|
|
9
|
+
const log = (...a) => console.error('[collect-sub-agent-outputs]', ...a);
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const args = parseArgs(process.argv.slice(2));
|
|
13
|
+
if (args.inputs.length === 0 && !args.manifestPath) {
|
|
14
|
+
console.error('usage: node scripts/collect-sub-agent-outputs.mjs [--manifest briefs/manifest.json] <output-file-or-dir...> [--out recommendations.json] [--strict]');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const manifest = args.manifestPath
|
|
19
|
+
? JSON.parse(await readFile(args.manifestPath, 'utf-8'))
|
|
20
|
+
: null;
|
|
21
|
+
const expected = manifest ? readExpectedBriefs(manifest) : [];
|
|
22
|
+
const preResolvedRecords = manifest ? readPreResolvedRecords(manifest) : [];
|
|
23
|
+
const files = args.inputs.length > 0 ? await collectInputFiles(args.inputs) : [];
|
|
24
|
+
const collected = [];
|
|
25
|
+
const summary = {
|
|
26
|
+
files: files.length,
|
|
27
|
+
kept: 0,
|
|
28
|
+
abstained: 0,
|
|
29
|
+
parseFailed: 0,
|
|
30
|
+
nonObject: 0,
|
|
31
|
+
missingCandidateRef: 0,
|
|
32
|
+
};
|
|
33
|
+
const errors = [];
|
|
34
|
+
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const raw = await readFile(file, 'utf-8');
|
|
37
|
+
const extracted = extractJsonValue(raw);
|
|
38
|
+
if (!extracted.ok) {
|
|
39
|
+
summary.parseFailed++;
|
|
40
|
+
const msg = `${file}: ${extracted.reason}`;
|
|
41
|
+
if (args.strict) errors.push(msg);
|
|
42
|
+
else log(`warn: ${msg}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const records = normalizeOutput(extracted.value);
|
|
47
|
+
if (records.length === 0) {
|
|
48
|
+
summary.nonObject++;
|
|
49
|
+
const msg = `${file}: JSON did not contain a recommendation or abstention object`;
|
|
50
|
+
if (args.strict) errors.push(msg);
|
|
51
|
+
else log(`warn: ${msg}`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const record of records) {
|
|
56
|
+
const candidateRef = record.candidateRef ?? inferCandidateRefFromFile(file, expected, records.length);
|
|
57
|
+
if (!candidateRef) {
|
|
58
|
+
summary.missingCandidateRef++;
|
|
59
|
+
errors.push(`${file}: output is missing candidateRef`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
collected.push({
|
|
63
|
+
sourcePath: file,
|
|
64
|
+
record: record.candidateRef ? record : { ...record, candidateRef },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let ordered = collected;
|
|
70
|
+
if (expected.length > 0) {
|
|
71
|
+
const byRef = new Map();
|
|
72
|
+
for (const item of collected) {
|
|
73
|
+
const ref = item.record.candidateRef;
|
|
74
|
+
if (!expected.some((b) => b.candidateRef === ref)) {
|
|
75
|
+
errors.push(`${item.sourcePath}: unknown candidateRef ${ref}`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (byRef.has(ref)) {
|
|
79
|
+
errors.push(`${item.sourcePath}: duplicate output for candidateRef ${ref}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
byRef.set(ref, item);
|
|
83
|
+
}
|
|
84
|
+
const missing = expected.filter((b) => !byRef.has(b.candidateRef));
|
|
85
|
+
for (const b of missing) errors.push(`missing output for candidateRef ${b.candidateRef}`);
|
|
86
|
+
ordered = expected.map((b) => byRef.get(b.candidateRef)).filter(Boolean);
|
|
87
|
+
} else {
|
|
88
|
+
ordered = collected.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const records = [...preResolvedRecords, ...ordered.map((item) => item.record)];
|
|
92
|
+
summary.kept = records.filter((r) => r?.abstain !== true).length;
|
|
93
|
+
summary.abstained = records.filter((r) => r?.abstain === true).length;
|
|
94
|
+
|
|
95
|
+
if (errors.length > 0) {
|
|
96
|
+
for (const e of errors) log(`error: ${e}`);
|
|
97
|
+
process.exit(2);
|
|
98
|
+
}
|
|
99
|
+
if (records.length === 0) {
|
|
100
|
+
log('error: no recommendation or abstention records collected');
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const serialized = JSON.stringify(records, null, 2) + '\n';
|
|
105
|
+
if (args.outPath) {
|
|
106
|
+
await mkdir(dirname(args.outPath), { recursive: true });
|
|
107
|
+
await writeFile(args.outPath, serialized, 'utf-8');
|
|
108
|
+
log(`wrote ${serialized.length}B → ${args.outPath}`);
|
|
109
|
+
} else {
|
|
110
|
+
process.stdout.write(serialized);
|
|
111
|
+
}
|
|
112
|
+
log(`done: ${summary.files} files, ${summary.kept} recommendation draft(s), ${summary.abstained} found no supported change, ${summary.parseFailed} parse failed, ${summary.nonObject} invalid output(s)`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseArgs(argv) {
|
|
116
|
+
const out = { inputs: [] };
|
|
117
|
+
for (let i = 0; i < argv.length; i++) {
|
|
118
|
+
const a = argv[i];
|
|
119
|
+
if (a === '--manifest') out.manifestPath = resolve(argv[++i]);
|
|
120
|
+
else if (a.startsWith('--manifest=')) out.manifestPath = resolve(a.slice('--manifest='.length));
|
|
121
|
+
else if (a === '--out') out.outPath = resolve(argv[++i]);
|
|
122
|
+
else if (a.startsWith('--out=')) out.outPath = resolve(a.slice('--out='.length));
|
|
123
|
+
else if (a === '--strict') out.strict = true;
|
|
124
|
+
else out.inputs.push(resolve(a));
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function collectInputFiles(paths) {
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const p of paths) {
|
|
132
|
+
const s = await stat(p);
|
|
133
|
+
if (s.isDirectory()) out.push(...await walkDir(p));
|
|
134
|
+
else if (s.isFile()) out.push(p);
|
|
135
|
+
}
|
|
136
|
+
return out.sort((a, b) => a.localeCompare(b));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function walkDir(dir) {
|
|
140
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
143
|
+
if (e.name.startsWith('.')) continue;
|
|
144
|
+
const p = resolve(dir, e.name);
|
|
145
|
+
if (e.isDirectory()) out.push(...await walkDir(p));
|
|
146
|
+
else if (e.isFile()) out.push(p);
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function readExpectedBriefs(manifest) {
|
|
152
|
+
if (!manifest || typeof manifest !== 'object' || !Array.isArray(manifest.briefs)) {
|
|
153
|
+
throw new TypeError('manifest must contain a briefs array');
|
|
154
|
+
}
|
|
155
|
+
return manifest.briefs.map((b, i) => {
|
|
156
|
+
if (!b?.candidateRef) throw new TypeError(`manifest.briefs[${i}].candidateRef is required`);
|
|
157
|
+
return {
|
|
158
|
+
group: b.group ?? null,
|
|
159
|
+
index: b.index ?? i,
|
|
160
|
+
candidateRef: b.candidateRef,
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function readPreResolvedRecords(manifest) {
|
|
166
|
+
if (!manifest || !Array.isArray(manifest.preResolvedRecords)) return [];
|
|
167
|
+
return manifest.preResolvedRecords.map((r, i) => {
|
|
168
|
+
if (!isRecordObject(r)) {
|
|
169
|
+
throw new TypeError(`manifest.preResolvedRecords[${i}] must be a recommendation or no-recommendation record`);
|
|
170
|
+
}
|
|
171
|
+
if (!r.candidateRef) {
|
|
172
|
+
throw new TypeError(`manifest.preResolvedRecords[${i}].candidateRef is required`);
|
|
173
|
+
}
|
|
174
|
+
return r;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function extractJsonValue(raw) {
|
|
179
|
+
for (const block of extractFenceBlocks(raw)) {
|
|
180
|
+
const parsed = tryParseJson(block);
|
|
181
|
+
if (parsed.ok) return parsed;
|
|
182
|
+
}
|
|
183
|
+
const full = tryParseJson(raw);
|
|
184
|
+
if (full.ok) return full;
|
|
185
|
+
for (const span of findBalancedJsonSpans(raw)) {
|
|
186
|
+
const parsed = tryParseJson(span);
|
|
187
|
+
if (parsed.ok) return parsed;
|
|
188
|
+
}
|
|
189
|
+
return { ok: false, reason: 'no valid JSON object or array found' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function extractFenceBlocks(raw) {
|
|
193
|
+
const out = [];
|
|
194
|
+
const re = /```(?:json|JSON)?\s*\n([\s\S]*?)```/g;
|
|
195
|
+
let m;
|
|
196
|
+
while ((m = re.exec(raw)) !== null) out.push(m[1].trim());
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function tryParseJson(raw) {
|
|
201
|
+
try {
|
|
202
|
+
return { ok: true, value: JSON.parse(raw.trim()) };
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return { ok: false, reason: err.message };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function findBalancedJsonSpans(raw) {
|
|
209
|
+
const spans = [];
|
|
210
|
+
for (let i = 0; i < raw.length; i++) {
|
|
211
|
+
const ch = raw[i];
|
|
212
|
+
if (ch !== '{' && ch !== '[') continue;
|
|
213
|
+
const closeFor = ch === '{' ? '}' : ']';
|
|
214
|
+
const stack = [closeFor];
|
|
215
|
+
let inString = false;
|
|
216
|
+
let escape = false;
|
|
217
|
+
for (let j = i + 1; j < raw.length; j++) {
|
|
218
|
+
const c = raw[j];
|
|
219
|
+
if (inString) {
|
|
220
|
+
if (escape) escape = false;
|
|
221
|
+
else if (c === '\\') escape = true;
|
|
222
|
+
else if (c === '"') inString = false;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (c === '"') {
|
|
226
|
+
inString = true;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (c === '{') stack.push('}');
|
|
230
|
+
else if (c === '[') stack.push(']');
|
|
231
|
+
else if (c === '}' || c === ']') {
|
|
232
|
+
if (stack.at(-1) !== c) break;
|
|
233
|
+
stack.pop();
|
|
234
|
+
if (stack.length === 0) {
|
|
235
|
+
spans.push(raw.slice(i, j + 1));
|
|
236
|
+
i = j;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return spans;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizeOutput(value) {
|
|
246
|
+
const unwrapped = unwrapEnvelope(value);
|
|
247
|
+
if (Array.isArray(unwrapped)) return unwrapped.filter(isRecordObject);
|
|
248
|
+
if (isRecordObject(unwrapped)) return [unwrapped];
|
|
249
|
+
if (unwrapped && typeof unwrapped === 'object') {
|
|
250
|
+
if (isRecordObject(unwrapped.recommendation)) return [unwrapped.recommendation];
|
|
251
|
+
if (Array.isArray(unwrapped.recommendations)) return unwrapped.recommendations.filter(isRecordObject);
|
|
252
|
+
}
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function unwrapEnvelope(value) {
|
|
257
|
+
let current = value;
|
|
258
|
+
for (let depth = 0; depth < 2; depth++) {
|
|
259
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) return current;
|
|
260
|
+
if (Array.isArray(current.recommendations) || current.recommendation) return current;
|
|
261
|
+
const keys = Object.keys(current);
|
|
262
|
+
const envelopeKey = ['data', 'result', 'insights'].find((k) => keys.length === 1 && k in current);
|
|
263
|
+
if (!envelopeKey) return current;
|
|
264
|
+
current = current[envelopeKey];
|
|
265
|
+
}
|
|
266
|
+
return current;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isRecordObject(value) {
|
|
270
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
271
|
+
if (value.abstain === true) return true;
|
|
272
|
+
return ['what', 'why', 'fix', 'bucket', 'affectedFiles', 'citations'].some((k) => k in value);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function inferCandidateRefFromFile(file, expected, recordCount) {
|
|
276
|
+
if (recordCount !== 1 || expected.length === 0) return null;
|
|
277
|
+
if (expected.length === 1) return expected[0].candidateRef;
|
|
278
|
+
const name = basename(file);
|
|
279
|
+
const matches = expected.filter((b) => {
|
|
280
|
+
if (!b.group && b.index == null) return false;
|
|
281
|
+
const group = escapeRegExp(String(b.group ?? ''));
|
|
282
|
+
const index = escapeRegExp(String(b.index));
|
|
283
|
+
return new RegExp(`(?:^|[^A-Za-z0-9])${group}[-_.]?${index}(?:[^A-Za-z0-9]|$)`).test(name);
|
|
284
|
+
});
|
|
285
|
+
return matches.length === 1 ? matches[0].candidateRef : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function escapeRegExp(value) {
|
|
289
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
main().catch((err) => {
|
|
293
|
+
console.error('[collect-sub-agent-outputs] FAILED:', err.message);
|
|
294
|
+
console.error(err.stack);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs AFTER gate-investigations.mjs and BEFORE any sub-agent reads source.
|
|
3
|
+
// Attaches per-candidate evidence.deepDive to gate.toLaunch + gate.platform.
|
|
4
|
+
// Byte-stable apart from totalWallMs; each CLI query is isolated.
|
|
5
|
+
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
import { queryMetric, readProjectJson, resolveCommandScope } from '../lib/vercel.mjs';
|
|
8
|
+
import { specsForCandidate, mergeIntoEvidence, SCANNER_KINDS, TIME_WINDOW } from '../lib/deep-dive.mjs';
|
|
9
|
+
|
|
10
|
+
const SCHEMA_VERSION = '1.0';
|
|
11
|
+
const log = (...a) => console.error('[deep-dive]', ...a);
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
// --cwd is load-bearing: the Vercel CLI resolves project/team from cwd's
|
|
15
|
+
// .vercel/project.json. Outside the project, metric queries silently hit the
|
|
16
|
+
// wrong team and look like "no traffic". We hard-fail on mismatch below.
|
|
17
|
+
const positional = [];
|
|
18
|
+
let explicitCwd = null;
|
|
19
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
20
|
+
const a = process.argv[i];
|
|
21
|
+
if (a === '--cwd' && i + 1 < process.argv.length) {
|
|
22
|
+
explicitCwd = process.argv[++i];
|
|
23
|
+
} else if (a.startsWith('--cwd=')) {
|
|
24
|
+
explicitCwd = a.slice('--cwd='.length);
|
|
25
|
+
} else {
|
|
26
|
+
positional.push(a);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const mergedPath = positional[0];
|
|
30
|
+
const gatePath = positional[1];
|
|
31
|
+
if (!mergedPath || !gatePath) {
|
|
32
|
+
console.error('usage: node scripts/deep-dive.mjs <merged.json> <gate.json> [--cwd <project-dir>]');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [merged, gate] = await Promise.all([
|
|
37
|
+
readFile(mergedPath, 'utf-8').then(JSON.parse),
|
|
38
|
+
readFile(gatePath, 'utf-8').then(JSON.parse),
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
if (explicitCwd) {
|
|
42
|
+
process.chdir(explicitCwd);
|
|
43
|
+
log(`cwd: ${process.cwd()} (via --cwd)`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const link = await readProjectJson(process.cwd());
|
|
47
|
+
if (!link) {
|
|
48
|
+
console.error(`[deep-dive] FATAL: cwd ${process.cwd()} has no .vercel/project.json or .vercel/repo.json.`);
|
|
49
|
+
console.error(' Re-run with --cwd <project-dir> pointing at the linked project, or cd into it first.');
|
|
50
|
+
console.error(' (The Vercel CLI resolves team/project from cwd; without a .vercel/ linkage every query returns empty rows for the wrong team.)');
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
if (merged.projectId && link.projectId !== merged.projectId) {
|
|
54
|
+
console.error('[deep-dive] FATAL: cwd .vercel/ links a different project than merged.json.');
|
|
55
|
+
console.error(' Re-run with --cwd <dir-linked-to-the-collected-project>.');
|
|
56
|
+
process.exit(2);
|
|
57
|
+
}
|
|
58
|
+
if (merged.orgId && link.orgId && link.orgId !== merged.orgId) {
|
|
59
|
+
console.error('[deep-dive] FATAL: cwd .vercel/ links the project to a different Vercel scope than signals.json.');
|
|
60
|
+
console.error(' Re-run with --cwd <dir-linked-to-the-collected-project>, or rerun collect-signals.mjs from the intended app directory.');
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
log(`cwd link OK (source ${link.source})`);
|
|
64
|
+
|
|
65
|
+
const commandScope = await resolveDeepDiveCommandScope(merged, link);
|
|
66
|
+
if (!commandScope.ok) {
|
|
67
|
+
console.error(`[deep-dive] FATAL: could not resolve a CLI-safe Vercel scope (${commandScope.detail ?? commandScope.error ?? 'unknown'}).`);
|
|
68
|
+
console.error(' Re-run collect-signals.mjs with the current skill, run `vercel switch <team>`, or re-link with `vercel link --yes --project <project> --team <team-slug>`.');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
if (typeof commandScope.cliScope === 'string' && /^(team|usr)_/.test(commandScope.cliScope)) {
|
|
72
|
+
console.error('[deep-dive] FATAL: commandScope.cliScope is a raw account ID, not a CLI-safe scope.');
|
|
73
|
+
console.error(' Re-run collect-signals.mjs with the current skill so deep-dive queries use the same team as the broad pass.');
|
|
74
|
+
process.exit(2);
|
|
75
|
+
}
|
|
76
|
+
const commandAccountId = commandScope.teamId ?? commandScope.userId ?? null;
|
|
77
|
+
if (commandAccountId && link.orgId && link.orgId !== commandAccountId) {
|
|
78
|
+
console.error('[deep-dive] FATAL: cwd .vercel/ links the project to a different Vercel scope than commandScope.');
|
|
79
|
+
console.error(' Re-run with --cwd <dir-linked-to-the-collected-project>, or rerun collect-signals.mjs from the intended app directory.');
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
const scope = commandScope.cliScope || undefined;
|
|
83
|
+
log(`command scope resolved (source=${commandScope.source}; scoped=${scope ? 'yes' : 'no'})`);
|
|
84
|
+
|
|
85
|
+
const toLaunch = Array.isArray(gate.toLaunch) ? gate.toLaunch : [];
|
|
86
|
+
const platform = Array.isArray(gate.platform) ? gate.platform : [];
|
|
87
|
+
|
|
88
|
+
log(`enriching ${toLaunch.length} toLaunch + ${platform.length} platform candidate(s) (window=${TIME_WINDOW})`);
|
|
89
|
+
|
|
90
|
+
const t0 = Date.now();
|
|
91
|
+
const errors = [];
|
|
92
|
+
|
|
93
|
+
// Flatten {candidate, spec}, fire all CLI calls in one Promise.all, re-group.
|
|
94
|
+
// Avoids per-candidate sequentiality.
|
|
95
|
+
const allCandidates = [...toLaunch.map((c, i) => ({ c, group: 'toLaunch', i })),
|
|
96
|
+
...platform.map((c, i) => ({ c, group: 'platform', i }))];
|
|
97
|
+
|
|
98
|
+
const flatJobs = [];
|
|
99
|
+
const skipNotes = new Map();
|
|
100
|
+
|
|
101
|
+
for (const entry of allCandidates) {
|
|
102
|
+
const specs = specsForCandidate(entry.c);
|
|
103
|
+
if (specs.length === 0) {
|
|
104
|
+
if (SCANNER_KINDS.has(entry.c.kind)) {
|
|
105
|
+
skipNotes.set(`${entry.group}:${entry.i}`, 'scanner-driven (no deep-dive needed)');
|
|
106
|
+
} else if (entry.c.kind === 'platform_fluid_compute') {
|
|
107
|
+
skipNotes.set(`${entry.group}:${entry.i}`, 'reused from broad pass (fnStartTypeByRoute)');
|
|
108
|
+
} else {
|
|
109
|
+
skipNotes.set(`${entry.group}:${entry.i}`, `no deep-dive spec for kind=${entry.c.kind}`);
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
for (const spec of specs) {
|
|
114
|
+
flatJobs.push({ entry, spec });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Cut CLI calls two ways: (1) extract per-route slices already collected in
|
|
119
|
+
// the broad pass; (2) dedupe identical queries across candidates (same route
|
|
120
|
+
// can fire multiple gates wanting the same metric).
|
|
121
|
+
let extractedFromBroadPass = 0;
|
|
122
|
+
let dedupedQueryHits = 0;
|
|
123
|
+
const broadPassResults = [];
|
|
124
|
+
const remainingJobs = [];
|
|
125
|
+
for (const job of flatJobs) {
|
|
126
|
+
const extracted = tryExtractFromBroadPass(job.spec, merged);
|
|
127
|
+
if (extracted) {
|
|
128
|
+
broadPassResults.push({ entry: job.entry, spec: job.spec, ok: true, ...extracted });
|
|
129
|
+
extractedFromBroadPass++;
|
|
130
|
+
} else {
|
|
131
|
+
remainingJobs.push(job);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// One CLI call per unique dedup key; jobs sharing a key share the result.
|
|
136
|
+
const queryGroups = new Map();
|
|
137
|
+
for (const job of remainingJobs) {
|
|
138
|
+
const key = queryKey(job.spec, scope);
|
|
139
|
+
if (!queryGroups.has(key)) {
|
|
140
|
+
queryGroups.set(key, { spec: job.spec, jobs: [] });
|
|
141
|
+
}
|
|
142
|
+
queryGroups.get(key).jobs.push(job);
|
|
143
|
+
}
|
|
144
|
+
dedupedQueryHits = remainingJobs.length - queryGroups.size;
|
|
145
|
+
|
|
146
|
+
const totalCliQueries = queryGroups.size;
|
|
147
|
+
log(`${flatJobs.length} specs total: ${extractedFromBroadPass} extracted from broad-pass, ${dedupedQueryHits} deduped, ${totalCliQueries} CLI queries to run`);
|
|
148
|
+
|
|
149
|
+
const groupResults = await Promise.all([...queryGroups.values()].map(async ({ spec, jobs }) => {
|
|
150
|
+
const r = await queryMetric(spec.metricId, {
|
|
151
|
+
aggregation: spec.aggregation,
|
|
152
|
+
groupBy: spec.groupBy,
|
|
153
|
+
filter: spec.filter,
|
|
154
|
+
since: spec.since,
|
|
155
|
+
limit: spec.limit,
|
|
156
|
+
scope,
|
|
157
|
+
});
|
|
158
|
+
return { spec, jobs, response: r };
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
const cliResults = [];
|
|
162
|
+
for (const { spec, jobs, response: r } of groupResults) {
|
|
163
|
+
if (!r.ok) {
|
|
164
|
+
for (const job of jobs) {
|
|
165
|
+
errors.push({
|
|
166
|
+
candidateGroup: job.entry.group,
|
|
167
|
+
candidateIndex: job.entry.i,
|
|
168
|
+
kind: job.entry.c.kind,
|
|
169
|
+
route: job.entry.c.route ?? job.entry.c.hostname ?? null,
|
|
170
|
+
specId: spec.id,
|
|
171
|
+
code: r.code,
|
|
172
|
+
});
|
|
173
|
+
cliResults.push({ entry: job.entry, spec, ok: false, error: r.code });
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const norm = normalizeResponse(r.data, spec);
|
|
178
|
+
for (const job of jobs) {
|
|
179
|
+
cliResults.push({ entry: job.entry, spec, ok: true, ...norm });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const results = [...broadPassResults, ...cliResults];
|
|
183
|
+
|
|
184
|
+
const wallMs = Date.now() - t0;
|
|
185
|
+
log(`done in ${wallMs}ms (${totalCliQueries} CLI queries, ${extractedFromBroadPass} extracted from broad-pass, ${dedupedQueryHits} deduped, ${errors.length} errors)`);
|
|
186
|
+
|
|
187
|
+
const byCandidate = new Map();
|
|
188
|
+
for (const res of results) {
|
|
189
|
+
const k = `${res.entry.group}:${res.entry.i}`;
|
|
190
|
+
if (!byCandidate.has(k)) byCandidate.set(k, []);
|
|
191
|
+
byCandidate.get(k).push(res);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function enrich(c, group, i) {
|
|
195
|
+
const k = `${group}:${i}`;
|
|
196
|
+
const note = skipNotes.get(k);
|
|
197
|
+
if (note) {
|
|
198
|
+
return {
|
|
199
|
+
...c,
|
|
200
|
+
evidence: {
|
|
201
|
+
...(c.evidence ?? {}),
|
|
202
|
+
deepDive: { note },
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const list = byCandidate.get(k) ?? [];
|
|
207
|
+
const merged = mergeIntoEvidence(list);
|
|
208
|
+
return {
|
|
209
|
+
...c,
|
|
210
|
+
evidence: {
|
|
211
|
+
...(c.evidence ?? {}),
|
|
212
|
+
deepDive: merged,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const enrichedToLaunch = toLaunch.map((c, i) => enrich(c, 'toLaunch', i));
|
|
218
|
+
const enrichedPlatform = platform.map((c, i) => enrich(c, 'platform', i));
|
|
219
|
+
|
|
220
|
+
const out = {
|
|
221
|
+
schemaVersion: SCHEMA_VERSION,
|
|
222
|
+
appliedAt: new Date().toISOString(),
|
|
223
|
+
candidatesEnriched: toLaunch.length + platform.length,
|
|
224
|
+
specsTotal: flatJobs.length,
|
|
225
|
+
queriesRun: totalCliQueries,
|
|
226
|
+
extractedFromBroadPass,
|
|
227
|
+
dedupedQueryHits,
|
|
228
|
+
totalWallMs: wallMs,
|
|
229
|
+
errors,
|
|
230
|
+
toLaunch: enrichedToLaunch,
|
|
231
|
+
platform: enrichedPlatform,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function resolveDeepDiveCommandScope(merged, link) {
|
|
238
|
+
const linkedOrgId = merged.orgId ?? link.orgId ?? null;
|
|
239
|
+
if (merged.commandScope?.ok && (merged.commandScope.cliScope || !linkedOrgId)) {
|
|
240
|
+
return merged.commandScope;
|
|
241
|
+
}
|
|
242
|
+
if (merged.commandScope && merged.commandScope.ok === false) return merged.commandScope;
|
|
243
|
+
|
|
244
|
+
return await resolveCommandScope({
|
|
245
|
+
projectId: merged.projectId ?? link.projectId ?? null,
|
|
246
|
+
orgId: merged.orgId ?? link.orgId ?? null,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Reduce CLI response to {value} or {rows:[{value,...dims}]}. The per-metric
|
|
251
|
+
// underscore field (e.g. vercel_function_invocation_count_sum) gets renamed
|
|
252
|
+
// to `value` for compactness.
|
|
253
|
+
function normalizeResponse(data, spec) {
|
|
254
|
+
if (!data || !Array.isArray(data.summary)) return { value: null };
|
|
255
|
+
const field = `${spec.metricId.replace(/\./g, '_')}_${spec.aggregation}`;
|
|
256
|
+
if (spec.groupBy.length === 0) {
|
|
257
|
+
const first = data.summary[0];
|
|
258
|
+
if (!first) return { value: null };
|
|
259
|
+
const v = first[field];
|
|
260
|
+
return { value: typeof v === 'number' ? round4(v) : null };
|
|
261
|
+
}
|
|
262
|
+
const rows = data.summary.map((row) => {
|
|
263
|
+
const out = { value: typeof row[field] === 'number' ? round4(row[field]) : null };
|
|
264
|
+
for (const dim of spec.groupBy) {
|
|
265
|
+
if (row[dim] !== undefined) out[dim] = row[dim];
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
});
|
|
269
|
+
return { rows };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function round4(n) {
|
|
273
|
+
if (!Number.isFinite(n)) return n;
|
|
274
|
+
return Math.round(n * 10000) / 10000;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Skip the CLI call when broad-pass already collected the same metric grouped
|
|
278
|
+
// by [route, dim]. Returns {rows} on hit, null on miss. Cuts rate-limit pressure
|
|
279
|
+
// for per-route slice specs (startTypeSplit, cacheBreakdown, methodDistribution).
|
|
280
|
+
function tryExtractFromBroadPass(spec, merged) {
|
|
281
|
+
const eq = spec.broadPassEquivalent;
|
|
282
|
+
if (!eq) return null;
|
|
283
|
+
const broadRows = merged?.metrics?.[eq.key]?.rows;
|
|
284
|
+
if (!Array.isArray(broadRows)) return null;
|
|
285
|
+
const rows = [];
|
|
286
|
+
for (const row of broadRows) {
|
|
287
|
+
if (row.route !== eq.routeFilter) continue;
|
|
288
|
+
const out = { value: typeof row.value === 'number' ? row.value : null };
|
|
289
|
+
for (const dim of (eq.projectDims ?? [])) {
|
|
290
|
+
if (row[dim] !== undefined) out[dim] = row[dim];
|
|
291
|
+
}
|
|
292
|
+
rows.push(out);
|
|
293
|
+
}
|
|
294
|
+
// Zero rows ≠ "no data" — broad-pass row limit may have truncated the route.
|
|
295
|
+
// Fall through to CLI so the caller gets a definitive answer.
|
|
296
|
+
if (rows.length === 0) return null;
|
|
297
|
+
return { rows };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Two specs sharing this key answer the same question — one CLI call serves both.
|
|
301
|
+
// Must include everything that affects the CLI's arg list.
|
|
302
|
+
function queryKey(spec, scope) {
|
|
303
|
+
const groupBy = [...(spec.groupBy ?? [])].sort();
|
|
304
|
+
return JSON.stringify({
|
|
305
|
+
metricId: spec.metricId,
|
|
306
|
+
aggregation: spec.aggregation,
|
|
307
|
+
groupBy,
|
|
308
|
+
filter: spec.filter ?? null,
|
|
309
|
+
since: spec.since ?? null,
|
|
310
|
+
limit: spec.limit ?? null,
|
|
311
|
+
scope: scope ?? null,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
main().catch((err) => {
|
|
316
|
+
console.error('[deep-dive] FAILED:', err.message);
|
|
317
|
+
console.error(err.stack);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
});
|