@1aboveio/skills 0.0.0-stage → 1.0.0
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/README.md +30 -2
- package/bin/cli.mjs +14 -0
- package/bin/select-core.mjs +172 -0
- package/package.json +146 -4
- package/skills/app-dev/airflow/LICENSE +3 -0
- package/skills/app-dev/airflow/SKILL.md +199 -0
- package/skills/app-dev/airflow/release-check.mjs +93 -0
- package/skills/app-dev/android-foundation/LICENSE +3 -0
- package/skills/app-dev/android-foundation/SKILL.md +192 -0
- package/skills/app-dev/android-foundation/assets/android-ci-job.yml +217 -0
- package/skills/app-dev/android-foundation/references/ci.md +87 -0
- package/skills/app-dev/android-foundation/references/gotchas.md +221 -0
- package/skills/app-dev/android-foundation/references/setup.md +164 -0
- package/skills/app-dev/android-foundation/release-check.mjs +93 -0
- package/skills/app-dev/android-foundation/scripts/device-run.sh +176 -0
- package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +172 -0
- package/skills/app-dev/app-debug/LICENSE +3 -0
- package/skills/app-dev/app-debug/SKILL.md +110 -0
- package/skills/app-dev/app-debug/references/common-errors.md +128 -0
- package/skills/app-dev/app-debug/release-check.mjs +93 -0
- package/skills/app-dev/better-auth/LICENSE +3 -0
- package/skills/app-dev/better-auth/SKILL.md +598 -0
- package/skills/app-dev/better-auth/references/feishu-api.md +270 -0
- package/skills/app-dev/better-auth/release-check.mjs +93 -0
- package/skills/app-dev/data-catalog/LICENSE +3 -0
- package/skills/app-dev/data-catalog/SKILL.md +204 -0
- package/skills/app-dev/data-catalog/agents/openai.yaml +3 -0
- package/skills/app-dev/data-catalog/references/data-catalog-conventions.md +75 -0
- package/skills/app-dev/data-catalog/references/field-definitions.md +116 -0
- package/skills/app-dev/data-catalog/references/format.md +121 -0
- package/skills/app-dev/data-catalog/release-check.mjs +93 -0
- package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +36 -0
- package/skills/app-dev/data-catalog/scripts/data_catalog.py +1392 -0
- package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +117 -0
- package/skills/app-dev/data-catalog/scripts/persist-conventions.mjs +17 -0
- package/skills/app-dev/data-catalog/scripts/test_data_catalog.py +571 -0
- package/skills/app-dev/ios-foundation/LICENSE +3 -0
- package/skills/app-dev/ios-foundation/SKILL.md +184 -0
- package/skills/app-dev/ios-foundation/references/setup.md +243 -0
- package/skills/app-dev/ios-foundation/release-check.mjs +93 -0
- package/skills/app-dev/monorepo/LICENSE +3 -0
- package/skills/app-dev/monorepo/SKILL.md +462 -0
- package/skills/app-dev/monorepo/release-check.mjs +93 -0
- package/skills/app-dev/nextjs-fullstack/LICENSE +3 -0
- package/skills/app-dev/nextjs-fullstack/SKILL.md +211 -0
- package/skills/app-dev/nextjs-fullstack/conventions.md +318 -0
- package/skills/app-dev/nextjs-fullstack/frontend-conventions.md +61 -0
- package/skills/app-dev/nextjs-fullstack/nextjs16.md +287 -0
- package/skills/app-dev/nextjs-fullstack/release-check.mjs +93 -0
- package/skills/app-dev/nextjs-fullstack/server-actions.md +409 -0
- package/skills/app-dev/prisma-setup/LICENSE +3 -0
- package/skills/app-dev/prisma-setup/SKILL.md +181 -0
- package/skills/app-dev/prisma-setup/nextjs.md +258 -0
- package/skills/app-dev/prisma-setup/release-check.mjs +93 -0
- package/skills/app-dev/prisma-setup/turborepo.md +301 -0
- package/skills/app-dev/pyspark/LICENSE +3 -0
- package/skills/app-dev/pyspark/SKILL.md +127 -0
- package/skills/app-dev/pyspark/assets/templates/etl.py +748 -0
- package/skills/app-dev/pyspark/assets/templates/utils/__init__.py +1 -0
- package/skills/app-dev/pyspark/assets/templates/utils/hudi_metadata.py +14 -0
- package/skills/app-dev/pyspark/references/diagnosis-and-profiling.md +169 -0
- package/skills/app-dev/pyspark/references/etl-contract.md +129 -0
- package/skills/app-dev/pyspark/references/production-validation.md +131 -0
- package/skills/app-dev/pyspark/references/reconciliation.md +38 -0
- package/skills/app-dev/pyspark/references/transformation-design.md +190 -0
- package/skills/app-dev/pyspark/references/velocity-feature-calculation.md +193 -0
- package/skills/app-dev/pyspark/release-check.mjs +93 -0
- package/skills/app-dev/pyspark/scripts/spark_eventlog_summary.py +223 -0
- package/skills/app-dev/python-backend/LICENSE +3 -0
- package/skills/app-dev/python-backend/SKILL.md +325 -0
- package/skills/app-dev/python-backend/assets/uv-install.sh +48 -0
- package/skills/app-dev/python-backend/references/uv-installs.md +83 -0
- package/skills/app-dev/python-backend/release-check.mjs +93 -0
- package/skills/app-dev/shadcn/LICENSE +3 -0
- package/skills/app-dev/shadcn/SKILL.md +120 -0
- package/skills/app-dev/shadcn/assets/shadcn-small.png +0 -0
- package/skills/app-dev/shadcn/assets/shadcn.png +0 -0
- package/skills/app-dev/shadcn/cli.md +411 -0
- package/skills/app-dev/shadcn/customization.md +224 -0
- package/skills/app-dev/shadcn/mcp.md +101 -0
- package/skills/app-dev/shadcn/release-check.mjs +93 -0
- package/skills/app-dev/shadcn/rules/base-vs-radix.md +323 -0
- package/skills/app-dev/shadcn/rules/component-selection.md +67 -0
- package/skills/app-dev/shadcn/rules/composition.md +195 -0
- package/skills/app-dev/shadcn/rules/data-table.md +201 -0
- package/skills/app-dev/shadcn/rules/forms.md +255 -0
- package/skills/app-dev/shadcn/rules/icons.md +103 -0
- package/skills/app-dev/shadcn/rules/styling.md +167 -0
- package/skills/app-dev/zod-v4/LICENSE +3 -0
- package/skills/app-dev/zod-v4/SKILL.md +286 -0
- package/skills/app-dev/zod-v4/release-check.mjs +93 -0
- package/skills/cicd/cloud-build/LICENSE +3 -0
- package/skills/cicd/cloud-build/SKILL.md +707 -0
- package/skills/cicd/cloud-build/release-check.mjs +93 -0
- package/skills/cicd/cloud-debug/LICENSE +3 -0
- package/skills/cicd/cloud-debug/SKILL.md +315 -0
- package/skills/cicd/cloud-debug/references/build-failures.md +238 -0
- package/skills/cicd/cloud-debug/references/deploy-failures.md +376 -0
- package/skills/cicd/cloud-debug/references/pipeline-failures.md +378 -0
- package/skills/cicd/cloud-debug/release-check.mjs +93 -0
- package/skills/cicd/cloud-deploy/LICENSE +3 -0
- package/skills/cicd/cloud-deploy/SKILL.md +230 -0
- package/skills/cicd/cloud-deploy/references/config-templates.md +257 -0
- package/skills/cicd/cloud-deploy/release-check.mjs +93 -0
- package/skills/cicd/docker/LICENSE +3 -0
- package/skills/cicd/docker/SKILL.md +151 -0
- package/skills/cicd/docker/release-check.mjs +93 -0
- package/skills/cicd/github-actions/LICENSE +3 -0
- package/skills/cicd/github-actions/SKILL.md +111 -0
- package/skills/cicd/github-actions/references/audit-profile-optimize.md +150 -0
- package/skills/cicd/github-actions/references/ci-case-studies.md +141 -0
- package/skills/cicd/github-actions/references/conditional-ci.md +214 -0
- package/skills/cicd/github-actions/references/debugging.md +13 -0
- package/skills/cicd/github-actions/references/pipeline.md +87 -0
- package/skills/cicd/github-actions/references/post-deploy-and-publishing.md +33 -0
- package/skills/cicd/github-actions/references/runners.md +253 -0
- package/skills/cicd/github-actions/references/security.md +29 -0
- package/skills/cicd/github-actions/references/speed-and-audit.md +115 -0
- package/skills/cicd/github-actions/references/two-pass-reading.md +92 -0
- package/skills/cicd/github-actions/release-check.mjs +93 -0
- package/skills/cicd/github-actions/scripts/ci-run-audit-core.mjs +612 -0
- package/skills/cicd/github-actions/scripts/ci-run-audit.mjs +4 -0
- package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +637 -0
- package/skills/cicd/github-flow/LICENSE +3 -0
- package/skills/cicd/github-flow/SKILL.md +123 -0
- package/skills/cicd/github-flow/references/ci-conventions.md +14 -0
- package/skills/cicd/github-flow/release-check.mjs +93 -0
- package/skills/cicd/github-flow/scripts/flow-core.mjs +105 -0
- package/skills/cicd/github-flow/scripts/flow.mjs +81 -0
- package/skills/cicd/github-flow/scripts/flow.test.mjs +114 -0
- package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +117 -0
- package/skills/cicd/github-flow/scripts/persist-conventions.mjs +49 -0
- package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +108 -0
- package/skills/cicd/google-cloud/LICENSE +3 -0
- package/skills/cicd/google-cloud/SKILL.md +119 -0
- package/skills/cicd/google-cloud/references/gcs.md +469 -0
- package/skills/cicd/google-cloud/references/iam.md +451 -0
- package/skills/cicd/google-cloud/references/project.md +349 -0
- package/skills/cicd/google-cloud/references/secrets.md +336 -0
- package/skills/cicd/google-cloud/references/vpc.md +312 -0
- package/skills/cicd/google-cloud/release-check.mjs +93 -0
- package/skills/cicd/google-cloud/scripts/create-sa.sh +36 -0
- package/skills/cicd/google-cloud/scripts/gcp-config.sh +31 -0
- package/skills/cicd/google-cloud/scripts/grant-iap.sh +41 -0
- package/skills/cicd/google-cloud/scripts/setup-secrets.sh +48 -0
- package/skills/cicd/mergify/LICENSE +3 -0
- package/skills/cicd/mergify/SKILL.md +102 -0
- package/skills/cicd/mergify/assets/templates/mergify.yml +215 -0
- package/skills/cicd/mergify/assets/templates/ruleset.json +46 -0
- package/skills/cicd/mergify/references/branch-protection.md +282 -0
- package/skills/cicd/mergify/references/configuration.md +274 -0
- package/skills/cicd/mergify/references/diagnosis.md +109 -0
- package/skills/cicd/mergify/references/stacks.md +84 -0
- package/skills/cicd/mergify/references/traps.md +119 -0
- package/skills/cicd/mergify/references/watch-contract.md +274 -0
- package/skills/cicd/mergify/release-check.mjs +93 -0
- package/skills/cicd/mergify/scripts/audit-core.mjs +208 -0
- package/skills/cicd/mergify/scripts/audit.mjs +4 -0
- package/skills/cicd/mergify/scripts/audit.test.mjs +201 -0
- package/skills/cicd/mergify/scripts/entrypoints.test.mjs +106 -0
- package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +30 -0
- package/skills/cicd/mergify/scripts/stack-push-core.mjs +258 -0
- package/skills/cicd/mergify/scripts/stack-push.mjs +4 -0
- package/skills/cicd/mergify/scripts/stack-push.test.mjs +233 -0
- package/skills/cicd/mergify/scripts/watch-pr-delivery-core.mjs +1065 -0
- package/skills/cicd/mergify/scripts/watch-pr-delivery.mjs +4 -0
- package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +1826 -0
- package/skills/cicd/podman/LICENSE +3 -0
- package/skills/cicd/podman/SKILL.md +71 -0
- package/skills/cicd/podman/agents/openai.yaml +4 -0
- package/skills/cicd/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
- package/skills/cicd/podman/assets/templates/podman-service-override.conf +3 -0
- package/skills/cicd/podman/references/compose-compatibility.md +70 -0
- package/skills/cicd/podman/references/networking-and-ports.md +74 -0
- package/skills/cicd/podman/references/rootless-services-and-sockets.md +156 -0
- package/skills/cicd/podman/references/troubleshooting.md +98 -0
- package/skills/cicd/podman/release-check.mjs +93 -0
- package/skills/content/article-review/LICENSE +3 -0
- package/skills/content/article-review/SKILL.md +66 -0
- package/skills/content/article-review/references/report-format.md +68 -0
- package/skills/content/article-review/references/template-guide.md +79 -0
- package/skills/content/article-review/references/template-wechat-payments.md +125 -0
- package/skills/content/article-review/release-check.mjs +93 -0
- package/skills/content/article-to-image/LICENSE +3 -0
- package/skills/content/article-to-image/SKILL.md +91 -0
- package/skills/content/article-to-image/references/formats.md +53 -0
- package/skills/content/article-to-image/release-check.mjs +93 -0
- package/skills/content/flowchart-svg/LICENSE +3 -0
- package/skills/content/flowchart-svg/SKILL.md +43 -0
- package/skills/content/flowchart-svg/release-check.mjs +93 -0
- package/skills/content/flowchart-svg/scripts/export_png.sh +8 -0
- package/skills/content/image-gen/LICENSE +3 -0
- package/skills/content/image-gen/SKILL.md +192 -0
- package/skills/content/image-gen/release-check.mjs +93 -0
- package/skills/content/image-gen/scripts/credentials.mjs +217 -0
- package/skills/content/image-gen/scripts/credentials.test.mjs +212 -0
- package/skills/content/image-gen/scripts/generate.mjs +240 -0
- package/skills/content/image-gen/scripts/options.mjs +293 -0
- package/skills/content/image-gen/scripts/options.test.mjs +245 -0
- package/skills/content/mermaid/LICENSE +3 -0
- package/skills/content/mermaid/SKILL.md +635 -0
- package/skills/content/mermaid/release-check.mjs +93 -0
- package/skills/content/wechat-article-writer/LICENSE +3 -0
- package/skills/content/wechat-article-writer/SKILL.md +92 -0
- package/skills/content/wechat-article-writer/references/style-selector.md +133 -0
- package/skills/content/wechat-article-writer/references/writing-rules.md +152 -0
- package/skills/content/wechat-article-writer/release-check.mjs +93 -0
- package/skills/content/wechat-article-writer/templates/wechat-comparison.md +52 -0
- package/skills/content/wechat-article-writer/templates/wechat-howto.md +51 -0
- package/skills/content/wechat-article-writer/templates/wechat-insight.md +50 -0
- package/skills/content/wechat-article-writer/templates/wechat-pitfall.md +52 -0
- package/skills/content/wechat-publisher/LICENSE +3 -0
- package/skills/content/wechat-publisher/SKILL.md +171 -0
- package/skills/content/wechat-publisher/references/api.md +105 -0
- package/skills/content/wechat-publisher/references/layout-rules.md +57 -0
- package/skills/content/wechat-publisher/references/wechat-safe-template.html +86 -0
- package/skills/content/wechat-publisher/release-check.mjs +93 -0
- package/skills/content/wechat-publisher/scripts/md_to_wechat_html.py +194 -0
- package/skills/content/wechat-publisher/scripts/publish.py +239 -0
- package/skills/content/wechat-search/LICENSE +3 -0
- package/skills/content/wechat-search/SKILL.md +211 -0
- package/skills/content/wechat-search/release-check.mjs +93 -0
- package/skills/content/wechat-search/scripts/start-chrome.sh +83 -0
- package/skills/content/xai-search/LICENSE +3 -0
- package/skills/content/xai-search/SKILL.md +79 -0
- package/skills/content/xai-search/release-check.mjs +93 -0
- package/skills/content/xai-search/scripts/xai_search.sh +187 -0
- package/skills/content/xai-search/tests/smoke_test.sh +36 -0
- package/skills/core/distill-writing/LICENSE +3 -0
- package/skills/core/distill-writing/SKILL.md +74 -0
- package/skills/core/distill-writing/references/extraction.md +95 -0
- package/skills/core/distill-writing/references/updating.md +46 -0
- package/skills/core/distill-writing/release-check.mjs +93 -0
- package/skills/core/doc-to-md/LICENSE +3 -0
- package/skills/core/doc-to-md/SKILL.md +78 -0
- package/skills/core/doc-to-md/release-check.mjs +93 -0
- package/skills/core/doc-to-md/scripts/convert.py +157 -0
- package/skills/core/editorial-review/LICENSE +3 -0
- package/skills/core/editorial-review/SKILL.md +79 -0
- package/skills/core/editorial-review/references/checklist.md +94 -0
- package/skills/core/editorial-review/release-check.mjs +93 -0
- package/skills/core/house-style/LICENSE +3 -0
- package/skills/core/house-style/SKILL.md +33 -0
- package/skills/core/house-style/references/defaults.md +66 -0
- package/skills/core/house-style/references/rules.md +267 -0
- package/skills/core/house-style/references/sources.md +27 -0
- package/skills/core/house-style/references/style-en.md +131 -0
- package/skills/core/house-style/references/style-zh.md +141 -0
- package/skills/core/house-style/references/terminology-zh.md +223 -0
- package/skills/core/house-style/release-check.mjs +93 -0
- package/skills/core/house-style/templates/brd.md +103 -0
- package/skills/core/house-style/templates/commercial-proposal.md +93 -0
- package/skills/core/house-style/templates/compliance-policy.md +109 -0
- package/skills/core/house-style/templates/gap-analysis.md +105 -0
- package/skills/core/house-style/templates/general.md +63 -0
- package/skills/core/house-style/templates/integration-guide.md +98 -0
- package/skills/core/house-style/templates/prd.md +81 -0
- package/skills/core/house-style/templates/risk-operations-procedure.md +94 -0
- package/skills/core/house-style/templates/technical-spec.md +96 -0
- package/skills/core/polish-cn/LICENSE +3 -0
- package/skills/core/polish-cn/SKILL.md +102 -0
- package/skills/core/polish-cn/references/brief.md +93 -0
- package/skills/core/polish-cn/references/models.md +76 -0
- package/skills/core/polish-cn/release-check.mjs +93 -0
- package/skills/core/polish-cn/scripts/polish-cn.test.mjs +244 -0
- package/skills/core/polish-cn/scripts/polish_blocks.py +489 -0
- package/skills/core/reg-parser/LICENSE +3 -0
- package/skills/core/reg-parser/SKILL.md +212 -0
- package/skills/core/reg-parser/references/checklist-schema.md +223 -0
- package/skills/core/reg-parser/references/checklists/hk-amlo-checklist.json +1306 -0
- package/skills/core/reg-parser/references/checklists/hk-amlo-checklist.md +469 -0
- package/skills/core/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.json +634 -0
- package/skills/core/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.md +233 -0
- package/skills/core/reg-parser/references/checklists/hk-mso-guideline-checklist.json +2194 -0
- package/skills/core/reg-parser/references/checklists/hk-mso-guideline-checklist.md +771 -0
- package/skills/core/reg-parser/references/checklists/kyc-aml-checklist.md +258 -0
- package/skills/core/reg-parser/references/checklists/visa-vars-checklist.md +1058 -0
- package/skills/core/reg-parser/references/mandates/hk-amlo-cap615.md +389 -0
- package/skills/core/reg-parser/references/mandates/visa-vars.md +205 -0
- package/skills/core/reg-parser/release-check.mjs +93 -0
- package/skills/core/reg-parser/scripts/compare_checklist.py +230 -0
- package/skills/core/reg-parser/scripts/dump_checklist.py +361 -0
- package/skills/core/reg-parser/scripts/preprocess-mandate.sh +187 -0
- package/skills/core/reg-parser/scripts/reg-parser.test.mjs +165 -0
- package/skills/core/reg-parser/scripts/test_quarantine.sh +105 -0
- package/skills/core/reg-parser/scripts/validate_checklist.py +563 -0
- package/skills/core/reg-parser/scripts/validate_mandate.py +273 -0
- package/skills/core/terminology/LICENSE +3 -0
- package/skills/core/terminology/SKILL.md +62 -0
- package/skills/core/terminology/references/format.md +50 -0
- package/skills/core/terminology/references/init.md +32 -0
- package/skills/core/terminology/release-check.mjs +93 -0
- package/skills/core/write-doc/LICENSE +3 -0
- package/skills/core/write-doc/SKILL.md +141 -0
- package/skills/core/write-doc/references/bilingual.md +60 -0
- package/skills/core/write-doc/release-check.mjs +93 -0
- package/skills/engineering/better-review/LICENSE +3 -0
- package/skills/engineering/better-review/SKILL.md +249 -0
- package/skills/engineering/better-review/agents/openai.yaml +9 -0
- package/skills/engineering/better-review/references/domain-hazards.md +53 -0
- package/skills/engineering/better-review/references/passes.md +147 -0
- package/skills/engineering/better-review/references/result.md +151 -0
- package/skills/engineering/better-review/release-check.mjs +93 -0
- package/skills/engineering/better-review/scripts/pr-context.mjs +212 -0
- package/skills/engineering/better-review/scripts/pr-context.test.mjs +168 -0
- package/skills/engineering/better-review/scripts/rereview-scope.mjs +503 -0
- package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +587 -0
- package/skills/engineering/better-review/scripts/scan-diff.mjs +365 -0
- package/skills/engineering/better-review/scripts/scan-diff.test.mjs +414 -0
- package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +96 -0
- package/skills/engineering/e2e-test/LICENSE +3 -0
- package/skills/engineering/e2e-test/SKILL.md +98 -0
- package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +24 -0
- package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-config.md +73 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +144 -0
- package/skills/engineering/e2e-test/references/ci-integration.md +25 -0
- package/skills/engineering/e2e-test/references/ci-playwright-container.md +290 -0
- package/skills/engineering/e2e-test/references/presentation-sweep.md +104 -0
- package/skills/engineering/e2e-test/references/reviewing.md +41 -0
- package/skills/engineering/e2e-test/references/route-manifest.md +51 -0
- package/skills/engineering/e2e-test/release-check.mjs +93 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +106 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +117 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +69 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +98 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +147 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +71 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +59 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +82 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +76 -0
- package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
- package/skills/engineering/engineering-runtime/LICENSE +3 -0
- package/skills/engineering/engineering-runtime/SKILL.md +11 -0
- package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/engineering-runtime/coherence/workflow.json +597 -0
- package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +68 -0
- package/skills/engineering/engineering-runtime/release-check.mjs +93 -0
- package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
- package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.mjs +187 -0
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +95 -0
- package/skills/engineering/engineering-runtime/scripts/main-module.mjs +79 -0
- package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +288 -0
- package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
- package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
- package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +96 -0
- package/skills/engineering/engineering-runtime/scripts/review-completion.mjs +56 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +595 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +189 -0
- package/skills/engineering/engineering-runtime/scripts/yaml-mapping.mjs +18 -0
- package/skills/engineering/ensure-coverage/LICENSE +3 -0
- package/skills/engineering/ensure-coverage/SKILL.md +129 -0
- package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +92 -0
- package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +85 -0
- package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
- package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
- package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
- package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +19 -0
- package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
- package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +57 -0
- package/skills/engineering/ensure-coverage/references/depth/test-smells.md +39 -0
- package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +44 -0
- package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +110 -0
- package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +35 -0
- package/skills/engineering/ensure-coverage/references/presentation/axis.md +64 -0
- package/skills/engineering/ensure-coverage/references/process/audit-mode.md +27 -0
- package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
- package/skills/engineering/ensure-coverage/references/process/test-strategy.md +56 -0
- package/skills/engineering/ensure-coverage/release-check.mjs +93 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +67 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +140 -0
- package/skills/engineering/ensure-coverage/scripts/README.md +99 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +182 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +96 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +105 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +47 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +137 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +86 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +674 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +447 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +552 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +282 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +289 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +119 -0
- package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
- package/skills/engineering/golden-path/LICENSE +3 -0
- package/skills/engineering/golden-path/SKILL.md +230 -0
- package/skills/engineering/golden-path/agents/openai.yaml +3 -0
- package/skills/engineering/golden-path/assets/golden-paths.example.json +57 -0
- package/skills/engineering/golden-path/references/contract.md +239 -0
- package/skills/engineering/golden-path/references/migration.md +111 -0
- package/skills/engineering/golden-path/release-check.mjs +93 -0
- package/skills/engineering/golden-path/scripts/golden-path-core.mjs +1779 -0
- package/skills/engineering/golden-path/scripts/golden-path.mjs +6 -0
- package/skills/engineering/golden-path/scripts/golden-path.test.mjs +1457 -0
- package/skills/engineering/harness-runtime/LICENSE +3 -0
- package/skills/engineering/harness-runtime/SKILL.md +19 -0
- package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
- package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
- package/skills/engineering/harness-runtime/contracts.md +15 -0
- package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
- package/skills/engineering/harness-runtime/discover-models.test.mjs +363 -0
- package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
- package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
- package/skills/engineering/harness-runtime/harness-runtime.test.mjs +967 -0
- package/skills/engineering/harness-runtime/index.mjs +767 -0
- package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +829 -0
- package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
- package/skills/engineering/harness-runtime/model-catalog.test.mjs +366 -0
- package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
- package/skills/engineering/harness-runtime/references/model-catalog-seed.json +408 -0
- package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
- package/skills/engineering/harness-runtime/release-check.mjs +93 -0
- package/skills/engineering/resolve-issues/LICENSE +3 -0
- package/skills/engineering/resolve-issues/SKILL.md +250 -0
- package/skills/engineering/resolve-issues/agents/openai.yaml +9 -0
- package/skills/engineering/resolve-issues/plugin-hooks.json +17 -0
- package/skills/engineering/resolve-issues/references/canary.md +28 -0
- package/skills/engineering/resolve-issues/references/ci.md +105 -0
- package/skills/engineering/resolve-issues/references/done.md +25 -0
- package/skills/engineering/resolve-issues/references/dropbox.md +90 -0
- package/skills/engineering/resolve-issues/references/expire.md +318 -0
- package/skills/engineering/resolve-issues/references/exploration.md +202 -0
- package/skills/engineering/resolve-issues/references/implementation.md +303 -0
- package/skills/engineering/resolve-issues/references/preflight.md +445 -0
- package/skills/engineering/resolve-issues/references/review.md +228 -0
- package/skills/engineering/resolve-issues/references/shared-modules.md +44 -0
- package/skills/engineering/resolve-issues/references/task-plan.md +264 -0
- package/skills/engineering/resolve-issues/release-check.mjs +93 -0
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.mjs +364 -0
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +280 -0
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/dropbox.mjs +473 -0
- package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +346 -0
- package/skills/engineering/resolve-issues/scripts/findings-reuse.mjs +98 -0
- package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +88 -0
- package/skills/engineering/resolve-issues/scripts/github-reviews.mjs +384 -0
- package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +264 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +135 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +79 -0
- package/skills/engineering/resolve-issues/scripts/issue-tree.mjs +162 -0
- package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +107 -0
- package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +14 -0
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +149 -0
- package/skills/engineering/resolve-issues/scripts/next-actions.mjs +856 -0
- package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +915 -0
- package/skills/engineering/resolve-issues/scripts/plan-sync.mjs +269 -0
- package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +156 -0
- package/skills/engineering/resolve-issues/scripts/preflight-models.mjs +736 -0
- package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +448 -0
- package/skills/engineering/resolve-issues/scripts/ready-check.mjs +185 -0
- package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +217 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +98 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +91 -0
- package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +509 -0
- package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +1060 -0
- package/skills/engineering/resolve-issues/scripts/size-check.mjs +492 -0
- package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +458 -0
- package/skills/engineering/resolve-release/LICENSE +3 -0
- package/skills/engineering/resolve-release/SKILL.md +215 -0
- package/skills/engineering/resolve-release/agents/openai.yaml +8 -0
- package/skills/engineering/resolve-release/flows/cloud-run-canary.md +13 -0
- package/skills/engineering/resolve-release/flows/cloud-run.md +7 -0
- package/skills/engineering/resolve-release/flows/root-tag-npm.md +13 -0
- package/skills/engineering/resolve-release/flows/workspace-npm.md +22 -0
- package/skills/engineering/resolve-release/references/canary.md +51 -0
- package/skills/engineering/resolve-release/references/candidate.md +41 -0
- package/skills/engineering/resolve-release/references/cut.md +131 -0
- package/skills/engineering/resolve-release/references/dark-launch.md +66 -0
- package/skills/engineering/resolve-release/references/flows.md +150 -0
- package/skills/engineering/resolve-release/references/preflight.md +154 -0
- package/skills/engineering/resolve-release/references/prepared-metadata.md +260 -0
- package/skills/engineering/resolve-release/references/promotion.md +82 -0
- package/skills/engineering/resolve-release/references/publish.md +232 -0
- package/skills/engineering/resolve-release/references/workspace-npm.md +103 -0
- package/skills/engineering/resolve-release/release-check.mjs +93 -0
- package/skills/engineering/resolve-release/scripts/apply.mjs +212 -0
- package/skills/engineering/resolve-release/scripts/apply.test.mjs +99 -0
- package/skills/engineering/resolve-release/scripts/dark-flags.mjs +134 -0
- package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +100 -0
- package/skills/engineering/resolve-release/scripts/flow.mjs +500 -0
- package/skills/engineering/resolve-release/scripts/flow.test.mjs +340 -0
- package/skills/engineering/resolve-release/scripts/green-head.mjs +231 -0
- package/skills/engineering/resolve-release/scripts/green-head.test.mjs +101 -0
- package/skills/engineering/resolve-release/scripts/npm-version-absence.mjs +138 -0
- package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +426 -0
- package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +659 -0
- package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +317 -0
- package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +291 -0
- package/skills/engineering/resolve-release/scripts/plan.mjs +806 -0
- package/skills/engineering/resolve-release/scripts/plan.test.mjs +428 -0
- package/skills/engineering/rush-issues/LICENSE +3 -0
- package/skills/engineering/rush-issues/SKILL.md +58 -0
- package/skills/engineering/rush-issues/agents/openai.yaml +8 -0
- package/skills/engineering/rush-issues/rehydrate.mjs +10 -0
- package/skills/engineering/rush-issues/release-check.mjs +93 -0
- package/skills/engineering/rush-release/LICENSE +3 -0
- package/skills/engineering/rush-release/SKILL.md +35 -0
- package/skills/engineering/rush-release/agents/openai.yaml +8 -0
- package/skills/engineering/rush-release/release-check.mjs +93 -0
- package/skills/engineering/test-quality/LICENSE +3 -0
- package/skills/engineering/test-quality/SKILL.md +79 -0
- package/skills/engineering/test-quality/references/audit.md +80 -0
- package/skills/engineering/test-quality/references/lint.md +24 -0
- package/skills/engineering/test-quality/references/principles.md +143 -0
- package/skills/engineering/test-quality/references/testing-conventions.md +9 -0
- package/skills/engineering/test-quality/release-check.mjs +93 -0
- package/skills/engineering/test-quality/scripts/lint-tests.mjs +574 -0
- package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +591 -0
- package/skills/engineering/test-quality/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +119 -0
- package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +117 -0
- package/skills/engineering/test-quality/scripts/persist-conventions.mjs +17 -0
- package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +104 -0
- package/skills/risk-compliance/compliance-audit/LICENSE +3 -0
- package/skills/risk-compliance/compliance-audit/SKILL.md +439 -0
- package/skills/risk-compliance/compliance-audit/references/findings-schema.md +293 -0
- package/skills/risk-compliance/compliance-audit/references/normalization-rules.md +45 -0
- package/skills/risk-compliance/compliance-audit/references/vars-checklist.md +1068 -0
- package/skills/risk-compliance/compliance-audit/references/vars-findings-schema.md +398 -0
- package/skills/risk-compliance/compliance-audit/release-check.mjs +93 -0
- package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +14 -0
- package/skills/risk-compliance/compliance-audit/scripts/export_tracker_xlsx.py +221 -0
- package/skills/risk-compliance/compliance-audit/scripts/reconcile_tracker.py +458 -0
- package/skills/risk-compliance/compliance-audit/scripts/test_export_tracker_xlsx.py +97 -0
- package/skills/risk-compliance/compliance-audit/scripts/test_validate_tracker.py +157 -0
- package/skills/risk-compliance/compliance-audit/scripts/validate_tracker.py +829 -0
- package/skills/risk-compliance/contract-compare/LICENSE +3 -0
- package/skills/risk-compliance/contract-compare/SKILL.md +199 -0
- package/skills/risk-compliance/contract-compare/references/comparison-dimensions.md +209 -0
- package/skills/risk-compliance/contract-compare/release-check.mjs +93 -0
- package/skills/risk-compliance/contract-review/LICENSE +3 -0
- package/skills/risk-compliance/contract-review/SKILL.md +240 -0
- package/skills/risk-compliance/contract-review/references/review-criteria.md +223 -0
- package/skills/risk-compliance/contract-review/release-check.mjs +93 -0
- package/skills/risk-compliance/doc-authenticity/LICENSE +3 -0
- package/skills/risk-compliance/doc-authenticity/SKILL.md +160 -0
- package/skills/risk-compliance/doc-authenticity/agents/openai.yaml +8 -0
- package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +12 -0
- package/skills/risk-compliance/doc-authenticity/references/agreement.md +66 -0
- package/skills/risk-compliance/doc-authenticity/references/bank-statement.md +150 -0
- package/skills/risk-compliance/doc-authenticity/references/forensic-signals.md +157 -0
- package/skills/risk-compliance/doc-authenticity/references/image-signals.md +114 -0
- package/skills/risk-compliance/doc-authenticity/references/invoice.md +66 -0
- package/skills/risk-compliance/doc-authenticity/references/issuer-profiles.md +86 -0
- package/skills/risk-compliance/doc-authenticity/references/verdict-and-report.md +95 -0
- package/skills/risk-compliance/doc-authenticity/references/verdict.schema.json +81 -0
- package/skills/risk-compliance/doc-authenticity/release-check.mjs +93 -0
- package/skills/risk-compliance/doc-authenticity/scripts/image_forensics.py +294 -0
- package/skills/risk-compliance/doc-authenticity/scripts/pdf_forensics.py +401 -0
- package/skills/risk-compliance/doc-authenticity/scripts/validate_codes.py +224 -0
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.py +308 -0
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +212 -0
- package/skills/risk-compliance/fraud-analysis/LICENSE +3 -0
- package/skills/risk-compliance/fraud-analysis/SKILL.md +114 -0
- package/skills/risk-compliance/fraud-analysis/references/archetypes/authorized-payment-scam.md +41 -0
- package/skills/risk-compliance/fraud-analysis/references/archetypes/first-party-fraud.md +44 -0
- package/skills/risk-compliance/fraud-analysis/references/archetypes/third-party-fraud.md +27 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/bank-transfer.md +24 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/card-payment.md +30 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/payment-collection.md +20 -0
- package/skills/risk-compliance/fraud-analysis/references/contexts/payout.md +20 -0
- package/skills/risk-compliance/fraud-analysis/references/feature-engineering.md +158 -0
- package/skills/risk-compliance/fraud-analysis/references/mechanisms/account-takeover.md +36 -0
- package/skills/risk-compliance/fraud-analysis/references/report-rationale.md +45 -0
- package/skills/risk-compliance/fraud-analysis/references/report-template.md +190 -0
- package/skills/risk-compliance/fraud-analysis/references/review-checklist.md +175 -0
- package/skills/risk-compliance/fraud-analysis/references/taxonomy.md +79 -0
- package/skills/risk-compliance/fraud-analysis/references/terminology.md +108 -0
- package/skills/risk-compliance/fraud-analysis/references/workflow.md +175 -0
- package/skills/risk-compliance/fraud-analysis/release-check.mjs +93 -0
- package/skills/risk-compliance/payment-analysis/LICENSE +3 -0
- package/skills/risk-compliance/payment-analysis/SKILL.md +180 -0
- package/skills/risk-compliance/payment-analysis/references/auth-rate-actions.md +30 -0
- package/skills/risk-compliance/payment-analysis/references/chargebacks.md +208 -0
- package/skills/risk-compliance/payment-analysis/references/chinese-polish.md +51 -0
- package/skills/risk-compliance/payment-analysis/references/event-layers.md +127 -0
- package/skills/risk-compliance/payment-analysis/references/fx.md +59 -0
- package/skills/risk-compliance/payment-analysis/references/journey.md +79 -0
- package/skills/risk-compliance/payment-analysis/references/metrics.md +67 -0
- package/skills/risk-compliance/payment-analysis/references/report-template.md +119 -0
- package/skills/risk-compliance/payment-analysis/references/terminology.md +95 -0
- package/skills/risk-compliance/payment-analysis/references/visualization.md +56 -0
- package/skills/risk-compliance/payment-analysis/release-check.mjs +93 -0
- package/skills/risk-compliance/report-writing/LICENSE +3 -0
- package/skills/risk-compliance/report-writing/SKILL.md +220 -0
- package/skills/risk-compliance/report-writing/references/audit-report-template.md +121 -0
- package/skills/risk-compliance/report-writing/references/external-assessment-template.md +167 -0
- package/skills/risk-compliance/report-writing/references/management-report-template.md +145 -0
- package/skills/risk-compliance/report-writing/references/vars-report-template.md +224 -0
- package/skills/risk-compliance/report-writing/release-check.mjs +93 -0
- package/skills/risk-compliance/similar-domain/LICENSE +3 -0
- package/skills/risk-compliance/similar-domain/SKILL.md +203 -0
- package/skills/risk-compliance/similar-domain/references/entities.md +67 -0
- package/skills/risk-compliance/similar-domain/references/fingerprints.md +134 -0
- package/skills/risk-compliance/similar-domain/references/pivots.md +82 -0
- package/skills/risk-compliance/similar-domain/references/report-template.md +93 -0
- package/skills/risk-compliance/similar-domain/references/scoring.md +130 -0
- package/skills/risk-compliance/similar-domain/references/terminology.md +41 -0
- package/skills/risk-compliance/similar-domain/references/workflow.md +85 -0
- package/skills/risk-compliance/similar-domain/release-check.mjs +93 -0
- package/skills/risk-compliance/tld-detection/LICENSE +3 -0
- package/skills/risk-compliance/tld-detection/SKILL.md +245 -0
- package/skills/risk-compliance/tld-detection/agents/openai.yaml +9 -0
- package/skills/risk-compliance/tld-detection/references/chinese-polish.md +55 -0
- package/skills/risk-compliance/tld-detection/references/clean.md +51 -0
- package/skills/risk-compliance/tld-detection/references/clustering.md +235 -0
- package/skills/risk-compliance/tld-detection/references/decision.md +189 -0
- package/skills/risk-compliance/tld-detection/references/fx-conversion.md +232 -0
- package/skills/risk-compliance/tld-detection/references/report-template.md +272 -0
- package/skills/risk-compliance/tld-detection/references/streams.md +141 -0
- package/skills/risk-compliance/tld-detection/references/terminology.md +95 -0
- package/skills/risk-compliance/tld-detection/references/workflow.md +95 -0
- package/skills/risk-compliance/tld-detection/release-check.mjs +93 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.py +390 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +14 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.py +650 -0
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +14 -0
- package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +134 -0
- package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +520 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Presentation route SWEEP — the runner that opens every manifest route in a real
|
|
2
|
+
// browser and asserts the presentation floor, spine first. This is the evidence run that
|
|
3
|
+
// catches a crashing route a green functional E2E suite sails past, because it
|
|
4
|
+
// drives the REAL render path (no first-party interception) over the WHOLE manifest
|
|
5
|
+
// instead of the handful of routes someone wrote a spec for.
|
|
6
|
+
//
|
|
7
|
+
// Implements the result shape in references/presentation-sweep.md. It stays separate
|
|
8
|
+
// from the functional/journey suite and is not a CI gate.
|
|
9
|
+
//
|
|
10
|
+
// Usage (reference; adapt the fixtures to the repo):
|
|
11
|
+
// E2E_AUTH_BYPASS=1 node presentation-sweep.mjs \
|
|
12
|
+
// --manifest e2e/route-manifest.json --base-url http://127.0.0.1:3000 \
|
|
13
|
+
// --out docs/tests/_generated/presentation-results.json
|
|
14
|
+
//
|
|
15
|
+
// Fixtures the repo supplies (see the contract): auth bypass, seeded data, and a
|
|
16
|
+
// param resolver for dynamic segments. Routes that cannot be rendered are reported
|
|
17
|
+
// `blocked`, never silently skipped or passed.
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs'
|
|
20
|
+
import path from 'node:path'
|
|
21
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
22
|
+
|
|
23
|
+
// ---- pure, unit-testable core (no browser) ----
|
|
24
|
+
|
|
25
|
+
// Expand a manifest into the concrete routes to visit. Dynamic segments ([id], :id)
|
|
26
|
+
// are resolved via `paramResolver(route) -> concreteUrl | null`. A dynamic route
|
|
27
|
+
// with no resolver answer becomes a `blocked` plan entry (cannot render → not pass).
|
|
28
|
+
export function planRoutes(manifest, { paramResolver = () => null, ignore = [] } = {}) {
|
|
29
|
+
const routes = (manifest?.routes || manifest?.surfaces || [])
|
|
30
|
+
.map((r) => (typeof r === 'string' ? { route: r } : r))
|
|
31
|
+
.filter((r) => r.route && (r.kind ? r.kind === 'route' : true))
|
|
32
|
+
.filter((r) => !ignore.some((ig) => r.route.includes(ig)))
|
|
33
|
+
const plan = []
|
|
34
|
+
for (const r of routes) {
|
|
35
|
+
const isDynamic = /[:\[]/.test(r.route)
|
|
36
|
+
if (!isDynamic) { plan.push({ route: r.route, url: r.route }); continue }
|
|
37
|
+
const concrete = paramResolver(r.route)
|
|
38
|
+
if (concrete) plan.push({ route: r.route, url: concrete })
|
|
39
|
+
else plan.push({ route: r.route, url: null, blocked: 'dynamic segment has no param resolver answer' })
|
|
40
|
+
}
|
|
41
|
+
return plan
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Reduce raw per-route outcomes into the contract's result rows + an exit verdict.
|
|
45
|
+
// `fail` and `blocked` fail the gate (exit non-zero); `pass` does not.
|
|
46
|
+
export function summarize(rawResults) {
|
|
47
|
+
const results = rawResults.flatMap((r) => r.levels.map((l) => ({
|
|
48
|
+
route: r.route, level: l.level, status: l.status, evidence: l.evidence || undefined,
|
|
49
|
+
})))
|
|
50
|
+
const failed = results.filter((r) => r.status === 'fail' || r.status === 'blocked')
|
|
51
|
+
return { viewportFloor: [375, 1280], results, ok: failed.length === 0, failedCount: failed.length }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseArgs(argv) {
|
|
55
|
+
const f = {}
|
|
56
|
+
for (let i = 2; i < argv.length; i++) {
|
|
57
|
+
const a = argv[i]
|
|
58
|
+
if (a.startsWith('--')) f[a.slice(2)] = argv[i + 1]?.startsWith('--') || i + 1 >= argv.length ? true : argv[++i]
|
|
59
|
+
}
|
|
60
|
+
return f
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---- browser orchestration (Playwright, lazy-imported) ----
|
|
64
|
+
|
|
65
|
+
// Run the sweep. `fixtures` lets the repo inject auth + param resolution:
|
|
66
|
+
// { applyAuth: async (context) => {}, paramResolver: (route) => url|null,
|
|
67
|
+
// extraFatal: [], ignoreRoutes: [], spatialIgnore: [] }
|
|
68
|
+
export async function runSweep({ manifestPath, baseURL, outPath, fixtures = {} }) {
|
|
69
|
+
const { chromium } = await import('@playwright/test')
|
|
70
|
+
const { watchRenderHealth, isFatalResponse } = await import('./presentation-render-health.mjs')
|
|
71
|
+
const { assertNoHorizontalOverflow } = await import('./presentation-checks.mjs')
|
|
72
|
+
|
|
73
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
74
|
+
const plan = planRoutes(manifest, { paramResolver: fixtures.paramResolver, ignore: fixtures.ignoreRoutes })
|
|
75
|
+
|
|
76
|
+
const browser = await chromium.launch()
|
|
77
|
+
const context = await browser.newContext()
|
|
78
|
+
if (fixtures.applyAuth) await fixtures.applyAuth(context)
|
|
79
|
+
|
|
80
|
+
const raw = []
|
|
81
|
+
for (const entry of plan) {
|
|
82
|
+
if (entry.blocked) {
|
|
83
|
+
raw.push({ route: entry.route, levels: [
|
|
84
|
+
{ level: 'presentation:render-health', status: 'blocked', evidence: entry.blocked },
|
|
85
|
+
{ level: 'presentation:spatial', status: 'blocked', evidence: entry.blocked },
|
|
86
|
+
] })
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
const page = await context.newPage()
|
|
90
|
+
const levels = []
|
|
91
|
+
const health = watchRenderHealth(page, { origin: baseURL, extraFatal: fixtures.extraFatal })
|
|
92
|
+
try {
|
|
93
|
+
const res = await page.goto(baseURL + entry.url, { waitUntil: 'domcontentloaded' })
|
|
94
|
+
await page.waitForLoadState('networkidle').catch(() => {})
|
|
95
|
+
// spine: render-health
|
|
96
|
+
const docFatal = res && isFatalResponse(res.status(), res.url(), { origin: baseURL })
|
|
97
|
+
const verdict = await health.evaluate()
|
|
98
|
+
if (docFatal || !verdict.healthy) {
|
|
99
|
+
const ev = docFatal ? `document ${res.status()}` : verdict.offenders.map((o) => `[${o.kind}] ${o.detail}`).join('; ')
|
|
100
|
+
levels.push({ level: 'presentation:render-health', status: 'fail', evidence: ev })
|
|
101
|
+
// layout is not assessable once the page failed to render
|
|
102
|
+
levels.push({ level: 'presentation:spatial', status: 'blocked', evidence: 'render-health failed; layout not assessable' })
|
|
103
|
+
} else {
|
|
104
|
+
levels.push({ level: 'presentation:render-health', status: 'pass' })
|
|
105
|
+
try {
|
|
106
|
+
await assertNoHorizontalOverflow(page, { ignore: fixtures.spatialIgnore || [] })
|
|
107
|
+
levels.push({ level: 'presentation:spatial', status: 'pass' })
|
|
108
|
+
} catch (e) {
|
|
109
|
+
levels.push({ level: 'presentation:spatial', status: 'fail', evidence: e.message })
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
levels.push({ level: 'presentation:render-health', status: 'fail', evidence: e.message })
|
|
114
|
+
} finally {
|
|
115
|
+
await page.close()
|
|
116
|
+
}
|
|
117
|
+
raw.push({ route: entry.route, levels })
|
|
118
|
+
}
|
|
119
|
+
await browser.close()
|
|
120
|
+
|
|
121
|
+
const summary = summarize(raw)
|
|
122
|
+
if (outPath) {
|
|
123
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
|
124
|
+
fs.writeFileSync(outPath, JSON.stringify(summary, null, 2))
|
|
125
|
+
}
|
|
126
|
+
return summary
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// CLI entry: load a repo-supplied fixtures module via --fixtures (optional).
|
|
130
|
+
async function main() {
|
|
131
|
+
const f = parseArgs(process.argv)
|
|
132
|
+
const manifestPath = f.manifest || 'e2e/route-manifest.json'
|
|
133
|
+
const baseURL = (f['base-url'] || 'http://127.0.0.1:3000').replace(/\/$/, '')
|
|
134
|
+
const outPath = f.out || 'docs/tests/_generated/presentation-results.json'
|
|
135
|
+
let fixtures = {}
|
|
136
|
+
if (f.fixtures) fixtures = (await import(path.resolve(f.fixtures))).default || {}
|
|
137
|
+
const summary = await runSweep({ manifestPath, baseURL, outPath, fixtures })
|
|
138
|
+
const failed = summary.results.filter((r) => r.status === 'fail' || r.status === 'blocked')
|
|
139
|
+
for (const r of failed) console.error(`✖ ${r.route} ${r.level} — ${r.status}: ${r.evidence || ''}`)
|
|
140
|
+
console.log(`presentation sweep: ${summary.results.length} checks, ${summary.failedCount} failing across ${summary.results.length ? new Set(summary.results.map((r) => r.route)).size : 0} routes`)
|
|
141
|
+
process.exit(summary.ok ? 0 : 1)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Run as CLI only when invoked directly (not when imported by tests).
|
|
145
|
+
if (isMainModule(import.meta.url)) {
|
|
146
|
+
main().catch((e) => { console.error(e); process.exit(1) })
|
|
147
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Unit tests for the sweep's pure core (planning + summarize) — no browser.
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import assert from 'node:assert/strict'
|
|
4
|
+
|
|
5
|
+
import { planRoutes, summarize } from './presentation-sweep.mjs'
|
|
6
|
+
|
|
7
|
+
test('static routes plan to themselves; route-kind filter applies', () => {
|
|
8
|
+
const plan = planRoutes({ surfaces: [
|
|
9
|
+
{ route: '/tasks', kind: 'route' },
|
|
10
|
+
{ route: '/api/tasks/list', kind: 'api' }, // not a browser route → excluded
|
|
11
|
+
{ route: '/dashboard', kind: 'route' },
|
|
12
|
+
] })
|
|
13
|
+
assert.deepEqual(plan, [
|
|
14
|
+
{ route: '/tasks', url: '/tasks' },
|
|
15
|
+
{ route: '/dashboard', url: '/dashboard' },
|
|
16
|
+
])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('dynamic route with a resolver becomes a concrete url', () => {
|
|
20
|
+
const plan = planRoutes(
|
|
21
|
+
{ routes: [{ route: '/tasks/[taskRunId]', kind: 'route' }] },
|
|
22
|
+
{ paramResolver: (r) => (r.includes('taskRunId') ? '/tasks/tr_seeded_1' : null) },
|
|
23
|
+
)
|
|
24
|
+
assert.deepEqual(plan, [{ route: '/tasks/[taskRunId]', url: '/tasks/tr_seeded_1' }])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('dynamic route with NO resolver answer is blocked, not skipped or passed', () => {
|
|
28
|
+
const plan = planRoutes({ routes: [{ route: '/tasks/[id]', kind: 'route' }] })
|
|
29
|
+
assert.equal(plan.length, 1)
|
|
30
|
+
assert.equal(plan[0].url, null)
|
|
31
|
+
assert.match(plan[0].blocked, /param resolver/)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('ignore list drops routes', () => {
|
|
35
|
+
const plan = planRoutes(
|
|
36
|
+
{ routes: [{ route: '/tasks', kind: 'route' }, { route: '/_debug/x', kind: 'route' }] },
|
|
37
|
+
{ ignore: ['/_debug'] },
|
|
38
|
+
)
|
|
39
|
+
assert.deepEqual(plan.map((p) => p.route), ['/tasks'])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('accepts a bare string manifest entry', () => {
|
|
43
|
+
const plan = planRoutes({ routes: ['/a', '/b'] })
|
|
44
|
+
assert.deepEqual(plan.map((p) => p.url), ['/a', '/b'])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('summarize: a render-health fail flips the gate to non-ok', () => {
|
|
48
|
+
const s = summarize([
|
|
49
|
+
{ route: '/dashboard', levels: [{ level: 'presentation:render-health', status: 'pass' }] },
|
|
50
|
+
{ route: '/tasks', levels: [
|
|
51
|
+
{ level: 'presentation:render-health', status: 'fail', evidence: "pageerror: reading 'id'" },
|
|
52
|
+
{ level: 'presentation:spatial', status: 'blocked', evidence: 'not assessable' },
|
|
53
|
+
] },
|
|
54
|
+
])
|
|
55
|
+
assert.equal(s.ok, false)
|
|
56
|
+
assert.equal(s.failedCount, 2) // the fail + the blocked
|
|
57
|
+
assert.deepEqual(s.viewportFloor, [375, 1280])
|
|
58
|
+
const tasksHealth = s.results.find((r) => r.route === '/tasks' && r.level === 'presentation:render-health')
|
|
59
|
+
assert.equal(tasksHealth.status, 'fail')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('summarize: all-pass is ok', () => {
|
|
63
|
+
const s = summarize([
|
|
64
|
+
{ route: '/dashboard', levels: [
|
|
65
|
+
{ level: 'presentation:render-health', status: 'pass' },
|
|
66
|
+
{ level: 'presentation:spatial', status: 'pass' },
|
|
67
|
+
] },
|
|
68
|
+
])
|
|
69
|
+
assert.equal(s.ok, true)
|
|
70
|
+
assert.equal(s.failedCount, 0)
|
|
71
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Presentation-axis Temporal-stability instrument for e2e-test (issue #199).
|
|
2
|
+
//
|
|
3
|
+
// "Does the layout hold still" — content must not jump as things inject. Tag specs
|
|
4
|
+
// `@level presentation:temporal`. Drives a REAL browser; DOM-only is Over-mocked.
|
|
5
|
+
//
|
|
6
|
+
// Split by WHO renders the shift (the functional axis says "fake externals", but
|
|
7
|
+
// faking the payment iframe makes the shift we want to catch disappear):
|
|
8
|
+
//
|
|
9
|
+
// • FIRST-PARTY shift (lazy images, async data, our own dynamic fields) — a
|
|
10
|
+
// HERMETIC gate: real render, no network. Gate on a stable condition, not a
|
|
11
|
+
// fixed sleep.
|
|
12
|
+
// • THIRD-PARTY iframe shift (Stripe/Adyen/Braintree payfields) — the provider's
|
|
13
|
+
// official SANDBOX iframe in a SEPARATE NON-BLOCKING PR lane: a failure posts a
|
|
14
|
+
// visible annotation and the surface stays `Unverified` until a reviewer
|
|
15
|
+
// attests — never a silent advisory. (Faking the iframe defeats the check.)
|
|
16
|
+
//
|
|
17
|
+
// Why not top-page CLS: the Layout Instability API does NOT report shifts inside
|
|
18
|
+
// cross-origin iframes (WICG spec excludes descendant browsing contexts). So a
|
|
19
|
+
// top-page PerformanceObserver is blind to the payfields case — the deterministic
|
|
20
|
+
// answer is a before/after getBoundingClientRect assertion gated on a provider-ready
|
|
21
|
+
// signal. CLS IS fine for first-party shift.
|
|
22
|
+
|
|
23
|
+
// ---------- pure cores (no browser) ----------
|
|
24
|
+
|
|
25
|
+
// Compare two geometry snapshots and return elements that MOVED beyond tolerance.
|
|
26
|
+
// `before`/`after` are arrays of { selector, rect:{left,top,right,bottom} }; matched
|
|
27
|
+
// by selector. An element only present after (newly injected) is not itself a shift —
|
|
28
|
+
// what matters is whether EXISTING elements moved because of the injection.
|
|
29
|
+
export function findLayoutShift(before, after, { tolerancePx = 2 } = {}) {
|
|
30
|
+
const afterBySel = new Map(after.map((e) => [e.selector, e.rect]))
|
|
31
|
+
const shifted = []
|
|
32
|
+
for (const b of before) {
|
|
33
|
+
const a = afterBySel.get(b.selector)
|
|
34
|
+
if (!a) continue // element gone — not a shift of a stable element
|
|
35
|
+
const dx = Math.max(Math.abs(a.left - b.rect.left), Math.abs(a.right - b.rect.right))
|
|
36
|
+
const dy = Math.max(Math.abs(a.top - b.rect.top), Math.abs(a.bottom - b.rect.bottom))
|
|
37
|
+
if (dx > tolerancePx || dy > tolerancePx) {
|
|
38
|
+
shifted.push({ selector: b.selector, dx: Math.round(dx), dy: Math.round(dy) })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return shifted
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Cumulative Layout Shift from PerformanceObserver `layout-shift` entries: sum of
|
|
45
|
+
// `value` for entries NOT following recent user input. Pure; feed it the raw entries
|
|
46
|
+
// captured in-page. An OPTIONAL first-party alternative to the geometry diff — the
|
|
47
|
+
// wrappers below use `findLayoutShift` (before/after geometry) as the default
|
|
48
|
+
// mechanism because it pinpoints the offending element; CLS is offered for teams
|
|
49
|
+
// that prefer the Web Vital. (First-party only — see header note on the iframe blind spot.)
|
|
50
|
+
export function cumulativeShiftScore(entries = []) {
|
|
51
|
+
return entries
|
|
52
|
+
.filter((e) => !e.hadRecentInput)
|
|
53
|
+
.reduce((sum, e) => sum + (e.value || 0), 0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------- in-browser collectors ----------
|
|
57
|
+
|
|
58
|
+
// NOTE: elements are keyed by a derived selector (`#id` else `tag.class…`). Two
|
|
59
|
+
// id-less elements sharing tag+classes collide on the same key (last-wins), which
|
|
60
|
+
// can mask or spoof a shift. Prefer stable ids / `data-testid` on elements whose
|
|
61
|
+
// stability you assert; id'd elements are unaffected.
|
|
62
|
+
function collectGeometryAll() {
|
|
63
|
+
const sel = (el) => {
|
|
64
|
+
if (el.id) return '#' + el.id
|
|
65
|
+
let s = el.tagName.toLowerCase()
|
|
66
|
+
if (typeof el.className === 'string' && el.className.trim()) s += '.' + el.className.trim().split(/\s+/).join('.')
|
|
67
|
+
return s
|
|
68
|
+
}
|
|
69
|
+
return [...document.querySelectorAll('body *')].map((el) => {
|
|
70
|
+
const r = el.getBoundingClientRect()
|
|
71
|
+
return { selector: sel(el), rect: { left: r.left, top: r.top, right: r.right, bottom: r.bottom } }
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------- Playwright wrappers (need a real browser) ----------
|
|
76
|
+
|
|
77
|
+
// FIRST-PARTY hermetic gate. Snapshot geometry, run `trigger` (the action that
|
|
78
|
+
// injects first-party content — load more, resolve async data), wait for a STABLE
|
|
79
|
+
// condition (a settle locator becoming visible, else network-idle — never a fixed
|
|
80
|
+
// sleep), snapshot again, and fail if existing elements moved beyond tolerance.
|
|
81
|
+
export async function assertNoFirstPartyShift(page, { trigger, settle, tolerancePx = 2 } = {}) {
|
|
82
|
+
const before = await page.evaluate(collectGeometryAll)
|
|
83
|
+
if (trigger) await trigger()
|
|
84
|
+
if (settle) await settle.waitFor({ state: 'visible' })
|
|
85
|
+
else await page.waitForLoadState('networkidle').catch(() => {})
|
|
86
|
+
const after = await page.evaluate(collectGeometryAll)
|
|
87
|
+
const shifted = findLayoutShift(before, after, { tolerancePx })
|
|
88
|
+
if (shifted.length) {
|
|
89
|
+
const lines = shifted.map((s) => ` ${s.selector} moved Δx=${s.dx} Δy=${s.dy}px`)
|
|
90
|
+
throw new Error(`presentation:temporal (first-party) — layout shifted after injection:\n${lines.join('\n')}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// THIRD-PARTY (payfields) check. Runs against the provider's SANDBOX iframe. Wait on
|
|
95
|
+
// a provider-ready signal (`ready`: a locator or async fn — NOT a sleep), then assert
|
|
96
|
+
// the elements AROUND the payment container did not move. Intended for the separate
|
|
97
|
+
// NON-BLOCKING lane; a failure here should be annotated and tracked as `Unverified`,
|
|
98
|
+
// not block the PR (it touches an external sandbox).
|
|
99
|
+
export async function assertNoThirdPartyShift(page, { container, ready, tolerancePx = 2 } = {}) {
|
|
100
|
+
const before = await page.evaluate(collectGeometryAll)
|
|
101
|
+
if (typeof ready === 'function') await ready()
|
|
102
|
+
else if (ready) await ready.waitFor({ state: 'visible' })
|
|
103
|
+
const after = await page.evaluate(collectGeometryAll)
|
|
104
|
+
const shifted = findLayoutShift(before, after, { tolerancePx })
|
|
105
|
+
.filter((s) => !container || !s.selector.includes(container)) // the iframe box itself may grow; we care about NEIGHBORS moving
|
|
106
|
+
if (shifted.length) {
|
|
107
|
+
const lines = shifted.map((s) => ` ${s.selector} moved Δx=${s.dx} Δy=${s.dy}px`)
|
|
108
|
+
throw new Error(`presentation:temporal (third-party payfields) — surrounding layout shifted when the provider iframe injected:\n${lines.join('\n')}`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Mobile dynamic-viewport (dvh) gate. Emulate a mobile viewport, then shrink the
|
|
113
|
+
// height (the mobile address bar collapsing/expanding) and assert content does not
|
|
114
|
+
// jump — catches `100vh`-behind-the-chrome bugs. Gated via emulation with a tuned
|
|
115
|
+
// tolerance; fall back to staging if flake proves unmanageable (see design doc).
|
|
116
|
+
export async function assertNoDynamicViewportShift(page, { width = 390, tall = 844, short = 740, tolerancePx = 2 } = {}) {
|
|
117
|
+
await page.setViewportSize({ width, height: tall })
|
|
118
|
+
await page.waitForLoadState('networkidle').catch(() => {})
|
|
119
|
+
const before = await page.evaluate(collectGeometryAll)
|
|
120
|
+
await page.setViewportSize({ width, height: short }) // address bar appears
|
|
121
|
+
const after = await page.evaluate(collectGeometryAll)
|
|
122
|
+
const shifted = findLayoutShift(before, after, { tolerancePx })
|
|
123
|
+
if (shifted.length) {
|
|
124
|
+
const lines = shifted.map((s) => ` ${s.selector} moved Δx=${s.dx} Δy=${s.dy}px`)
|
|
125
|
+
throw new Error(`presentation:temporal (dynamic viewport) — content jumped when the mobile viewport height changed (100vh/dvh):\n${lines.join('\n')}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Unit tests for the pure Temporal-stability cores (issue #199). Run: `node --test`.
|
|
2
|
+
// The Playwright wrappers (assertNoFirstPartyShift / assertNoThirdPartyShift /
|
|
3
|
+
// assertNoDynamicViewportShift) need a real browser and are exercised downstream.
|
|
4
|
+
|
|
5
|
+
import { test } from 'node:test'
|
|
6
|
+
import assert from 'node:assert/strict'
|
|
7
|
+
import { findLayoutShift, cumulativeShiftScore } from './presentation-temporal.mjs'
|
|
8
|
+
|
|
9
|
+
const r = (left, top, right, bottom) => ({ rect: { left, top, right, bottom } })
|
|
10
|
+
const at = (selector, left, top, right, bottom) => ({ selector, ...r(left, top, right, bottom) })
|
|
11
|
+
|
|
12
|
+
// ---------- findLayoutShift ----------
|
|
13
|
+
|
|
14
|
+
test('no shift when geometry is unchanged', () => {
|
|
15
|
+
const snap = [at('.header', 0, 0, 375, 60), at('.cta', 0, 100, 375, 140)]
|
|
16
|
+
assert.deepEqual(findLayoutShift(snap, snap), [])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('flags an element pushed down by injected content', () => {
|
|
20
|
+
const before = [at('.cta', 0, 100, 375, 140)]
|
|
21
|
+
const after = [at('.cta', 0, 260, 375, 300)] // shoved down 160px (a banner injected above)
|
|
22
|
+
const shifted = findLayoutShift(before, after)
|
|
23
|
+
assert.equal(shifted.length, 1)
|
|
24
|
+
assert.equal(shifted[0].selector, '.cta')
|
|
25
|
+
assert.equal(shifted[0].dy, 160)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('sub-tolerance movement is not a shift', () => {
|
|
29
|
+
const before = [at('.x', 0, 100, 375, 140)]
|
|
30
|
+
const after = [at('.x', 0, 101, 375, 141)] // 1px < default 2px tolerance
|
|
31
|
+
assert.deepEqual(findLayoutShift(before, after), [])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('a newly-injected element is not itself counted as a shift', () => {
|
|
35
|
+
const before = [at('.cta', 0, 100, 375, 140)]
|
|
36
|
+
const after = [at('.cta', 0, 100, 375, 140), at('.banner', 0, 0, 375, 80)] // banner is new, cta did not move
|
|
37
|
+
assert.deepEqual(findLayoutShift(before, after), [])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('an element that disappears is not a shift of a stable element', () => {
|
|
41
|
+
const before = [at('.spinner', 0, 0, 50, 50), at('.cta', 0, 100, 375, 140)]
|
|
42
|
+
const after = [at('.cta', 0, 100, 375, 140)] // spinner gone, cta stable
|
|
43
|
+
assert.deepEqual(findLayoutShift(before, after), [])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// ---------- cumulativeShiftScore ----------
|
|
47
|
+
|
|
48
|
+
test('cumulativeShiftScore sums values without recent input', () => {
|
|
49
|
+
const entries = [
|
|
50
|
+
{ value: 0.05, hadRecentInput: false },
|
|
51
|
+
{ value: 0.10, hadRecentInput: false },
|
|
52
|
+
{ value: 0.30, hadRecentInput: true }, // user-initiated — excluded
|
|
53
|
+
]
|
|
54
|
+
assert.ok(Math.abs(cumulativeShiftScore(entries) - 0.15) < 1e-9)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('cumulativeShiftScore is 0 for no entries', () => {
|
|
58
|
+
assert.equal(cumulativeShiftScore([]), 0)
|
|
59
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Project the E2E route-manifest FROM the canonical surface inventory + a curated
|
|
2
|
+
// overlay. The manifest is GENERATED, never hand-enumerated — so its route set
|
|
3
|
+
// cannot drift from the inventory (regenerate and compare during the audit).
|
|
4
|
+
//
|
|
5
|
+
// node project-route-manifest.mjs \
|
|
6
|
+
// --inventory docs/tests/_generated/surface-inventory.json \
|
|
7
|
+
// --overlay e2e/route-manifest.overlay.json \
|
|
8
|
+
// --out e2e/route-manifest.json
|
|
9
|
+
//
|
|
10
|
+
// What lives where:
|
|
11
|
+
// - inventory (ensure-coverage, generated) = the route SET + (kind, class).
|
|
12
|
+
// - overlay (curated, small) = E2E-only deviations: tier, ignore
|
|
13
|
+
// (don't sweep), spatial allowlist.
|
|
14
|
+
// - manifest (generated here) = inventory(routes) ⨝ overlay.
|
|
15
|
+
// Coverage is NOT tracked here — the ledger computes it from `@covers` annotations.
|
|
16
|
+
// `specs` is optional/informational only; it is not a coverage signal.
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs'
|
|
19
|
+
import path from 'node:path'
|
|
20
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
21
|
+
|
|
22
|
+
// Default E2E tier from the inventory's mechanical class. Overlay can raise it
|
|
23
|
+
// (e.g. a route on a core journey → tier 3); it cannot be derived from a single
|
|
24
|
+
// route because "journey" is a cross-page property.
|
|
25
|
+
const DEFAULT_TIER = { read: 1, detail: 2, mutation: 2 }
|
|
26
|
+
|
|
27
|
+
// Pure: inventory + overlay -> manifest object. Deterministic + sorted so the diff
|
|
28
|
+
// is stable.
|
|
29
|
+
export function projectManifest(inventory, overlay = {}) {
|
|
30
|
+
const ov = overlay.routes || {}
|
|
31
|
+
const routes = (inventory?.surfaces || [])
|
|
32
|
+
.filter((s) => s.kind === 'route')
|
|
33
|
+
.filter((s) => !ov[s.id]?.ignore) // curated: explicitly not swept (still owes its ledger floor)
|
|
34
|
+
.map((s) => {
|
|
35
|
+
const o = ov[s.id] || {}
|
|
36
|
+
const entry = { path: s.id, tier: o.tier ?? DEFAULT_TIER[s.class] ?? 1 }
|
|
37
|
+
if (o.specs) entry.specs = o.specs // informational only
|
|
38
|
+
return entry
|
|
39
|
+
})
|
|
40
|
+
.sort((a, b) => a.path.localeCompare(b.path))
|
|
41
|
+
const manifest = { version: 1, generated: true, routes }
|
|
42
|
+
if (overlay.spatialIgnore) manifest.spatialIgnore = overlay.spatialIgnore
|
|
43
|
+
return manifest
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Report overlay entries that reference a route no longer in the inventory — curated
|
|
47
|
+
// rot the diff alone won't explain. Returned so the CLI can warn (not throw).
|
|
48
|
+
export function staleOverlayEntries(inventory, overlay = {}) {
|
|
49
|
+
const ids = new Set((inventory?.surfaces || []).filter((s) => s.kind === 'route').map((s) => s.id))
|
|
50
|
+
return Object.keys(overlay.routes || {}).filter((id) => !ids.has(id))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function arg(flags, name, def) { return flags[name] ?? def }
|
|
54
|
+
|
|
55
|
+
const USAGE = 'usage: project-route-manifest.mjs --inventory <surface-inventory.json> [--overlay e2e/route-manifest.overlay.json] [--out e2e/route-manifest.json]'
|
|
56
|
+
|
|
57
|
+
function main() {
|
|
58
|
+
const argv = process.argv.slice(2)
|
|
59
|
+
if (argv.includes('--help') || argv.includes('-h')) { console.log(USAGE); return }
|
|
60
|
+
const flags = {}
|
|
61
|
+
for (let i = 0; i < argv.length; i++) if (argv[i].startsWith('--')) flags[argv[i].slice(2)] = argv[++i]
|
|
62
|
+
const inventoryPath = arg(flags, 'inventory', 'docs/tests/_generated/surface-inventory.json')
|
|
63
|
+
const overlayPath = arg(flags, 'overlay', 'e2e/route-manifest.overlay.json')
|
|
64
|
+
const outPath = arg(flags, 'out', 'e2e/route-manifest.json')
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(inventoryPath)) {
|
|
67
|
+
console.error(`project-route-manifest: inventory not found: ${inventoryPath} — run your inventory adapter first\n${USAGE}`)
|
|
68
|
+
process.exit(1)
|
|
69
|
+
}
|
|
70
|
+
const inventory = JSON.parse(fs.readFileSync(inventoryPath, 'utf8'))
|
|
71
|
+
const overlay = fs.existsSync(overlayPath) ? JSON.parse(fs.readFileSync(overlayPath, 'utf8')) : {}
|
|
72
|
+
|
|
73
|
+
const stale = staleOverlayEntries(inventory, overlay)
|
|
74
|
+
if (stale.length) console.error(`⚠ overlay references routes not in inventory (stale curation): ${stale.join(', ')}`)
|
|
75
|
+
|
|
76
|
+
const manifest = projectManifest(inventory, overlay)
|
|
77
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
|
78
|
+
fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2) + '\n')
|
|
79
|
+
console.log(`wrote ${outPath} — ${manifest.routes.length} routes (generated from ${inventoryPath})`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isMainModule(import.meta.url)) main()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { projectManifest, staleOverlayEntries } from './project-route-manifest.mjs'
|
|
5
|
+
|
|
6
|
+
const INVENTORY = {
|
|
7
|
+
surfaces: [
|
|
8
|
+
{ id: '/tasks', kind: 'route', class: 'read', reachability: 'reachable' },
|
|
9
|
+
{ id: '/tasks/[id]', kind: 'route', class: 'detail', reachability: 'reachable' },
|
|
10
|
+
{ id: '/settings/models', kind: 'route', class: 'mutation', reachability: 'reachable' },
|
|
11
|
+
{ id: '/legacy', kind: 'route', class: 'read', reachability: 'reachable' },
|
|
12
|
+
{ id: '/api/tasks/list', kind: 'api', class: 'read' }, // not a browser route
|
|
13
|
+
{ id: 'tasks_table', kind: 'table' },
|
|
14
|
+
],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test('projects only browser routes, default tier from class, sorted', () => {
|
|
18
|
+
const m = projectManifest(INVENTORY)
|
|
19
|
+
assert.equal(m.generated, true)
|
|
20
|
+
assert.deepEqual(m.routes, [
|
|
21
|
+
{ path: '/legacy', tier: 1 },
|
|
22
|
+
{ path: '/settings/models', tier: 2 },
|
|
23
|
+
{ path: '/tasks', tier: 1 },
|
|
24
|
+
{ path: '/tasks/[id]', tier: 2 },
|
|
25
|
+
])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('overlay raises tier and drops ignored routes', () => {
|
|
29
|
+
const overlay = { routes: { '/tasks': { tier: 3 }, '/legacy': { ignore: true } }, spatialIgnore: ['.sticky-header'] }
|
|
30
|
+
const m = projectManifest(INVENTORY, overlay)
|
|
31
|
+
assert.equal(m.routes.find((r) => r.path === '/tasks').tier, 3)
|
|
32
|
+
assert.ok(!m.routes.some((r) => r.path === '/legacy'), 'ignored route dropped from sweep')
|
|
33
|
+
assert.deepEqual(m.spatialIgnore, ['.sticky-header'])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('overlay specs are passed through but optional (informational only)', () => {
|
|
37
|
+
const m = projectManifest(INVENTORY, { routes: { '/tasks': { specs: ['render/tasks.spec.ts'] } } })
|
|
38
|
+
assert.deepEqual(m.routes.find((r) => r.path === '/tasks').specs, ['render/tasks.spec.ts'])
|
|
39
|
+
// a route with no overlay has no specs key
|
|
40
|
+
assert.equal('specs' in m.routes.find((r) => r.path === '/settings/models'), false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('same inventory + overlay → identical manifest (no drift, stable diff)', () => {
|
|
44
|
+
const a = projectManifest(INVENTORY, { routes: { '/tasks': { tier: 3 } } })
|
|
45
|
+
const b = projectManifest(INVENTORY, { routes: { '/tasks': { tier: 3 } } })
|
|
46
|
+
assert.deepEqual(a, b)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('staleOverlayEntries flags curation for a route no longer in inventory', () => {
|
|
50
|
+
const stale = staleOverlayEntries(INVENTORY, { routes: { '/tasks': { tier: 3 }, '/removed': { ignore: true } } })
|
|
51
|
+
assert.deepEqual(stale, ['/removed'])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
import { spawnSync } from 'node:child_process'
|
|
55
|
+
import { fileURLToPath } from 'node:url'
|
|
56
|
+
import fs from 'node:fs'
|
|
57
|
+
import os from 'node:os'
|
|
58
|
+
import path from 'node:path'
|
|
59
|
+
|
|
60
|
+
const CLI = fileURLToPath(new URL('./project-route-manifest.mjs', import.meta.url))
|
|
61
|
+
|
|
62
|
+
test('CLI: --help prints usage; a missing inventory is a one-line error, not a stack trace', () => {
|
|
63
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'prm-cli-'))
|
|
64
|
+
try {
|
|
65
|
+
const help = spawnSync('node', [CLI, '--help'], { cwd, encoding: 'utf8' })
|
|
66
|
+
assert.equal(help.status, 0)
|
|
67
|
+
assert.match(help.stdout, /usage: project-route-manifest\.mjs/)
|
|
68
|
+
const missing = spawnSync('node', [CLI, '--inventory', path.join(cwd, 'nope.json')], { cwd, encoding: 'utf8' })
|
|
69
|
+
assert.equal(missing.status, 1)
|
|
70
|
+
assert.match(missing.stderr, /inventory not found/)
|
|
71
|
+
assert.doesNotMatch(missing.stderr, /at Object\.|node:internal/, 'no stack trace')
|
|
72
|
+
assert.deepEqual(fs.readdirSync(cwd), [], 'nothing written')
|
|
73
|
+
} finally {
|
|
74
|
+
fs.rmSync(cwd, { recursive: true, force: true })
|
|
75
|
+
}
|
|
76
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate route manifest against actual spec files.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/validate-manifest.mjs [--manifest e2e/route-manifest.json] [--e2e-dir e2e]
|
|
8
|
+
*
|
|
9
|
+
* Checks:
|
|
10
|
+
* 1. Manifest is valid JSON with required fields
|
|
11
|
+
* 2. All referenced spec files exist
|
|
12
|
+
* 3. Reports uncovered routes (specs: [])
|
|
13
|
+
* 4. Reports coverage percentage
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* 0 = valid (may have warnings)
|
|
17
|
+
* 1 = errors found (missing files, invalid format)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, existsSync } from 'fs';
|
|
21
|
+
import { join, resolve } from 'path';
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
function getArg(name, defaultVal) {
|
|
25
|
+
const idx = args.indexOf(`--${name}`);
|
|
26
|
+
return idx >= 0 && args[idx + 1] ? args[idx + 1] : defaultVal;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const manifestPath = resolve(getArg('manifest', 'e2e/route-manifest.json'));
|
|
30
|
+
const e2eDir = resolve(getArg('e2e-dir', 'e2e'));
|
|
31
|
+
|
|
32
|
+
let errors = 0;
|
|
33
|
+
let warnings = 0;
|
|
34
|
+
|
|
35
|
+
// Check manifest exists
|
|
36
|
+
if (!existsSync(manifestPath)) {
|
|
37
|
+
console.error(`❌ Manifest not found: ${manifestPath}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Parse manifest
|
|
42
|
+
let manifest;
|
|
43
|
+
try {
|
|
44
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error(`❌ Invalid JSON in manifest: ${e.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const routes = manifest.routes || manifest.endpoints || [];
|
|
51
|
+
|
|
52
|
+
if (routes.length === 0) {
|
|
53
|
+
console.error('❌ Manifest has no routes/endpoints');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(`📋 Validating ${routes.length} routes from ${manifestPath}\n`);
|
|
58
|
+
|
|
59
|
+
// Validate each route
|
|
60
|
+
let covered = 0;
|
|
61
|
+
for (const route of routes) {
|
|
62
|
+
const label = route.method ? `${route.method} ${route.path}` : route.path;
|
|
63
|
+
|
|
64
|
+
// Check required fields
|
|
65
|
+
if (!route.path) {
|
|
66
|
+
console.error(`❌ Route missing "path" field`);
|
|
67
|
+
errors++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (typeof route.tier !== 'number' || route.tier < 1 || route.tier > 3) {
|
|
71
|
+
console.error(`❌ ${label}: invalid tier (must be 1-3, got ${route.tier})`);
|
|
72
|
+
errors++;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check specs
|
|
76
|
+
const specs = route.specs || [];
|
|
77
|
+
if (specs.length === 0) {
|
|
78
|
+
console.warn(`⚠️ ${label}: uncovered (no specs)`);
|
|
79
|
+
warnings++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
covered++;
|
|
84
|
+
for (const spec of specs) {
|
|
85
|
+
const specPath = join(e2eDir, spec);
|
|
86
|
+
if (!existsSync(specPath)) {
|
|
87
|
+
console.error(`❌ ${label}: spec file not found: ${specPath}`);
|
|
88
|
+
errors++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Summary
|
|
94
|
+
const coverage = Math.round((covered / routes.length) * 100);
|
|
95
|
+
console.log(`\n${'─'.repeat(50)}`);
|
|
96
|
+
console.log(`📊 Coverage: ${coverage}% (${covered}/${routes.length} routes covered)`);
|
|
97
|
+
console.log(`❌ Errors: ${errors}`);
|
|
98
|
+
console.log(`⚠️ Warnings: ${warnings}`);
|
|
99
|
+
|
|
100
|
+
if (errors > 0) {
|
|
101
|
+
console.log('\n🚫 Validation failed');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
} else {
|
|
104
|
+
console.log('\n✅ Validation passed');
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|