@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,574 @@
|
|
|
1
|
+
// lint-tests — static checks over test files for the test-quality principles
|
|
2
|
+
// (../SKILL.md). Copied from ensure-coverage's lint-tests and extended; the two
|
|
3
|
+
// copies evolve independently.
|
|
4
|
+
//
|
|
5
|
+
// node lint-tests.mjs # whole-tree scan of discovered test files
|
|
6
|
+
// node lint-tests.mjs --diff <base> # only findings on lines ADDED in base...HEAD
|
|
7
|
+
// node lint-tests.mjs --files a,b # specific files
|
|
8
|
+
// --rules no-skip,text-oracle # run a subset (default: every rule)
|
|
9
|
+
// --json # machine output
|
|
10
|
+
// --shares 4 # split the flagged files into 4 review shares
|
|
11
|
+
//
|
|
12
|
+
// (Named lint-tests, not test-lint, so node's --test runner doesn't glob it as a
|
|
13
|
+
// test file via its `test-*` pattern and execute the CLI mid-suite.)
|
|
14
|
+
//
|
|
15
|
+
// Two severities:
|
|
16
|
+
// gate — syntactically decidable, ~zero false positives. Any gate finding makes
|
|
17
|
+
// the exit code 1.
|
|
18
|
+
// lead — a heuristic that also matches some legitimate tests. Reported for the
|
|
19
|
+
// auditor to read and confirm against the principle's "Not a failure"
|
|
20
|
+
// cases; never changes the exit code.
|
|
21
|
+
//
|
|
22
|
+
// Adding a rule = append to ALL_RULES with id, principle, severity, message and
|
|
23
|
+
// one of: `patterns` (line regexes), `match` (per line), `scanFile` (whole file),
|
|
24
|
+
// or `crossFile: true` (resolved by the CLI over every scanned file). If a
|
|
25
|
+
// legitimate test could trip it, it is a lead.
|
|
26
|
+
|
|
27
|
+
import fs from 'node:fs'
|
|
28
|
+
import path from 'node:path'
|
|
29
|
+
import crypto from 'node:crypto'
|
|
30
|
+
import { execSync } from 'node:child_process'
|
|
31
|
+
import { loadMockPolicyFromFile, extractMockTarget, classifyMockTarget } from './mock-policy-config.mjs'
|
|
32
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
33
|
+
|
|
34
|
+
// Skip / focus / expected-fail forms. `.only`/focus silently disable every OTHER
|
|
35
|
+
// test; skip/xfail/fixme disable this one. All banned (SKILL.md principle 4).
|
|
36
|
+
// Unconditional forms only: a declared-skipped test (`test.skip('name', …)`),
|
|
37
|
+
// focus, fixme, todo, and the unconditional decorators. Conditional skips
|
|
38
|
+
// (skipif, a runtime `test.skip()` / `t.Skip()`, xfail) are CONDITIONAL_SKIP_PATTERNS.
|
|
39
|
+
export const SKIP_PATTERNS = [
|
|
40
|
+
{ kind: 'skip', re: /\b(?:test|it|describe|suite|context)\s*\.\s*skip\s*(?:\.\s*each\b|\(\s*['"`])/ },
|
|
41
|
+
{ kind: 'only', re: /\b(?:test|it|describe|suite|context)\s*\.\s*only\b/ },
|
|
42
|
+
{ kind: 'fixme', re: /\b(?:test|it)\s*\.\s*fixme\b/ },
|
|
43
|
+
{ kind: 'todo', re: /\b(?:test|it)\s*\.\s*todo\s*\(/ },
|
|
44
|
+
{ kind: 'skip', re: /\b(?:xit|xdescribe|xtest)\s*\(/ },
|
|
45
|
+
{ kind: 'only', re: /\b(?:fit|fdescribe|ftest)\s*\(/ },
|
|
46
|
+
{ kind: 'skip', re: /@(?:pytest\.mark\.)?skip\b/ },
|
|
47
|
+
{ kind: 'skip', re: /@unittest\.skip\b/ },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
// A skip that depends on a condition: legitimate for a platform or an optional
|
|
51
|
+
// dependency, a hidden hole when the condition always holds in CI. A lead.
|
|
52
|
+
export const CONDITIONAL_SKIP_PATTERNS = [
|
|
53
|
+
{ kind: 'skipif', re: /@(?:pytest\.mark\.)?skipif\b|@unittest\.skip(?:If|Unless)\b/ },
|
|
54
|
+
{ kind: 'runtime-skip', re: /\bpytest\.(?:skip|importorskip)\s*\(|\bt\.Skip(?:f|Now)?\s*\(|\b(?:t|ctx)\.skip\s*\(/ },
|
|
55
|
+
{ kind: 'runtime-skip', re: /\b(?:test|it|describe)\s*\.\s*skip\s*\(\s*(?!['"`])/ },
|
|
56
|
+
{ kind: 'xfail', re: /@(?:pytest\.mark\.)?xfail\b/ },
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
// Fixed sleeps — a timer instead of an explicit wait. The single biggest source of
|
|
60
|
+
// flake; doctrine is "never waitForTimeout()". Precise: these forms are ~never
|
|
61
|
+
// legitimate in a test (an aliased `cy.wait('@x')` or polling helper is not matched).
|
|
62
|
+
export const FIXED_TIMEOUT_PATTERNS = [
|
|
63
|
+
{ kind: 'waitForTimeout', re: /\.waitForTimeout\s*\(/ },
|
|
64
|
+
{ kind: 'cy.wait', re: /\bcy\.wait\s*\(\s*\d/ }, // numeric only; cy.wait('@alias') is fine
|
|
65
|
+
{ kind: 'time.sleep', re: /\btime\.sleep\s*\(/ },
|
|
66
|
+
{ kind: 'time.Sleep', re: /\btime\.Sleep\s*\(/ },
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
// Empty test body — a `test`/`it` whose callback body is `{}`. A placeholder
|
|
70
|
+
// that passes vacuously and asserts nothing: the Missing-Assertion smell in its
|
|
71
|
+
// one decidable, ~zero-false-positive form. The BROADER "has statements but no
|
|
72
|
+
// assertion" case is NOT here — a test can assert through a helper, so a block
|
|
73
|
+
// with no `expect`/`assert` token may still assert; that stays a review LEAD
|
|
74
|
+
// for the auditor (SKILL.md principle 4).
|
|
75
|
+
// Matches only single-line empty bodies (`() => {}` / `function () {}`), incl.
|
|
76
|
+
// `.skip`/`.each(...)`/`.concurrent` chains and Playwright `async ({ page })`
|
|
77
|
+
// fixtures; `describe`/`suite` are excluded (an empty suite is not a fake test).
|
|
78
|
+
// A multi-line empty body is rarer and deliberately left to the lead, not
|
|
79
|
+
// false-blocked.
|
|
80
|
+
export const EMPTY_TEST_PATTERNS = [
|
|
81
|
+
{ kind: 'empty-test', re: /^\s*(?:async\s+)?def\s+test\w*\s*\([^)]*\)\s*(?:->\s*[\w\[\], ]+)?:\s*(?:pass|\.\.\.)\s*$/ },
|
|
82
|
+
{ kind: 'empty-test', re: /\b(?:it|test)\s*(?:\.\s*\w+\s*(?:\([^)]*\)\s*)?)?\(\s*[`'"][^`'"]*[`'"]\s*,\s*(?:async\s+)?(?:function\s*\*?\s*)?\([^)]*\)\s*(?:=>\s*)?\{\s*\}\s*\)?/ },
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
// Swallowed assertion — an assertion matcher whose rejection is eaten by a
|
|
86
|
+
// chained `.catch(...)`, so the assertion can NEVER fail the test. The vacuous
|
|
87
|
+
// form behind the #237 login regression: `expect(locator).not.toBeVisible().catch(() => {})`
|
|
88
|
+
// passed whether or not the button was actually visible. Decidable and
|
|
89
|
+
// ~zero-false-positive: a Playwright web-first matcher (`.toXxx(...)`) directly
|
|
90
|
+
// chained to `.catch(` is never legitimate — a real assertion's rejection must
|
|
91
|
+
// surface, not be discarded. (Synchronous Jest matchers don't return a promise,
|
|
92
|
+
// so `.toBe(x).catch()` only appears as a deliberate swallow.) The broader
|
|
93
|
+
// "assertion guarded by a try/catch that re-passes" case stays a review LEAD
|
|
94
|
+
// for the auditor; only the directly-chained matcher form is gated here.
|
|
95
|
+
export const SWALLOWED_ASSERTION_PATTERNS = [
|
|
96
|
+
{ kind: 'swallowed-catch', re: /\.to[A-Z]\w*\s*\([^)]*\)\s*\.catch\s*\(/ },
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
export const RULES = [
|
|
100
|
+
{ id: 'no-skip', principle: 4, severity: 'gate', message: 'test skipped, focused, or todo: a skipped test cannot fail (principle 4)', patterns: SKIP_PATTERNS },
|
|
101
|
+
{ id: 'no-fixed-timeout', principle: 7, severity: 'gate', message: 'fixed sleep: flaky by construction; wait on the condition instead (principle 7)', patterns: FIXED_TIMEOUT_PATTERNS },
|
|
102
|
+
{ id: 'no-empty-test', principle: 4, severity: 'gate', message: 'empty test body: passes vacuously (principle 4)', patterns: EMPTY_TEST_PATTERNS },
|
|
103
|
+
{ id: 'no-swallowed-assertion', principle: 4, severity: 'gate', message: 'assertion failure swallowed by .catch(): the matcher can never fail (principle 4)', patterns: SWALLOWED_ASSERTION_PATTERNS },
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
export const CONDITIONAL_SKIP_RULE = {
|
|
107
|
+
id: 'conditional-skip',
|
|
108
|
+
principle: 4,
|
|
109
|
+
severity: 'lead',
|
|
110
|
+
message: 'conditional skip: fine for a platform or optional dependency; a hole if the condition always holds in CI (principle 4)',
|
|
111
|
+
patterns: CONDITIONAL_SKIP_PATTERNS,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Over-mocking via the externals-allowlist inversion (mock-policy-config.mjs): a
|
|
115
|
+
// mock whose target resolves to first-party code is a finding; declared
|
|
116
|
+
// externals, bare packages and the stdlib are exempt. `coverage.config.json`
|
|
117
|
+
// only declares topology; with no config the conventional `@/`/`~/` defaults
|
|
118
|
+
// apply, so the rule is never silently inert.
|
|
119
|
+
export const MOCK_INTERNAL_SEAM_RULE = {
|
|
120
|
+
id: 'mock-internal-seam',
|
|
121
|
+
principle: 3,
|
|
122
|
+
severity: 'gate',
|
|
123
|
+
defaultOn: true,
|
|
124
|
+
message: 'mocks a first-party module: fake only what you do not own (principle 3)',
|
|
125
|
+
match(raw, code, ctx) {
|
|
126
|
+
const target = extractMockTarget(code)
|
|
127
|
+
if (!target) return null
|
|
128
|
+
const { internal, reason } = classifyMockTarget(target, ctx && ctx.policy)
|
|
129
|
+
if (!internal) return null
|
|
130
|
+
return { kind: 'internal-seam', detail: target, reason }
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A matcher that adds nothing: Testing Library `getBy*` and vue-test-utils
|
|
135
|
+
// `get()` already throw when the element is missing, so `toBeDefined()` after
|
|
136
|
+
// them is redundant. The test still fails on a missing element; it proves
|
|
137
|
+
// presence only. A lead, not a gate: the test is shallow, not vacuous.
|
|
138
|
+
export const REDUNDANT_MATCHER_PATTERNS = [
|
|
139
|
+
{ kind: 'getBy-exists', re: /\bexpect\(\s*(?:[\w$]+(?:\([^()]*\))?\.)*(?:getBy|getAllBy)\w+\(.*\)\s*\)\s*\.(?:toBeDefined|toBeTruthy|not\.toBeNull|not\.toBeUndefined)\(\s*\)/ },
|
|
140
|
+
{ kind: 'get-exists', re: /\bexpect\(\s*[\w$.]+\.get(?:Component)?\(.*\)\.exists\(\)\s*\)\s*\.toBe\(\s*true\s*\)/ },
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
export const REDUNDANT_MATCHER_RULE = {
|
|
144
|
+
id: 'redundant-matcher',
|
|
145
|
+
principle: 4,
|
|
146
|
+
severity: 'lead',
|
|
147
|
+
message: 'the query already throws when missing, so this matcher adds nothing; the test proves presence only (principle 4)',
|
|
148
|
+
patterns: REDUNDANT_MATCHER_PATTERNS,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Text oracle: the test reads a checked-in doc or source file and matches its
|
|
152
|
+
// text. File-level because the path, the read and the match sit on different
|
|
153
|
+
// lines. A lead: reading a file to execute it, comparing it to a value from
|
|
154
|
+
// code, or a forbidden-capability pin are all legitimate (SKILL.md principle 1).
|
|
155
|
+
// Config files belong to config-read, which fires whatever the assertion.
|
|
156
|
+
const READ_CALL = /\b(?:readFileSync|readFile|read_text|ReadFile|open)\s*\(/
|
|
157
|
+
const WRITE_CALL = /\b(?:writeFileSync|writeFile|write_text|WriteFile|appendFileSync|copyFileSync|cpSync|copyfile|copy2?)\s*\(/
|
|
158
|
+
const TEXT_ASSERT = /\.(?:toContain|toMatch|includes)\s*\(|\bassert\.(?:match|doesNotMatch)\s*\(|\b(?:Contains|NotContains|Regexp)\s*\(\s*t\s*,|\bassert(?:In|NotIn|Regex)\s*\(|['"][^'"\n]{3,}['"]\s+(?:not\s+)?in\s+[\w.]/
|
|
159
|
+
export const CHECKED_IN_TEXT = /['"`]([^'"`\s]*\.(?:md|mdx|txt|sh|sql|tsx?|jsx?|[cm]js|vue|py|go|css|html|prisma))['"`]/
|
|
160
|
+
// Config by name, not by extension: tests read JSON and YAML as data (run
|
|
161
|
+
// state, artifacts, temp files) far more often than as config.
|
|
162
|
+
const CONFIG_NAME = [
|
|
163
|
+
String.raw`\.github\/workflows\/[^'"\`\s/]+\.ya?ml`,
|
|
164
|
+
String.raw`\.gitlab-ci\.ya?ml`,
|
|
165
|
+
String.raw`\.?(?:mergify|cloudbuild|docker-compose|compose|skaffold|kustomization|pnpm-workspace|action|dependabot|renovate|codecov|app|netlify|vercel|firebase|fly|wrangler|turbo|nx|biome|tsconfig|jsconfig|package|deno|pyproject|Cargo|setup|tox)(?:[.-][\w.-]*)?\.(?:ya?ml|json[c5]?|toml|cfg|ini)`,
|
|
166
|
+
String.raw`\.(?:eslint|prettier|babel|stylelint)rc(?:\.[\w]+)?`,
|
|
167
|
+
String.raw`(?:Dockerfile|Containerfile|\.env)(?:\.[\w.-]+)?`,
|
|
168
|
+
].join('|')
|
|
169
|
+
export const CONFIG_FILE = new RegExp(String.raw`['"\`]((?:[^'"\`\s]*\/)?(?:${CONFIG_NAME}))['"\`]`)
|
|
170
|
+
const FIXTURE_PATH = /(?:^|[/'"`])(?:fixtures?|__fixtures__|testdata|__snapshots__|golden)(?:[/'"`]|$)/
|
|
171
|
+
const RUNS_OR_IMPORTS = /\b(?:spawn|spawnSync|execFile|execFileSync|execSync|exec|fork|import|require|run|subprocess\.\w+|runpy\.\w+)\s*\(|^\s*(?:import|export)\b|\bfrom\s+['"]/
|
|
172
|
+
const DOC_COMMENT = /^\s*(?:\*|\/\*)/
|
|
173
|
+
|
|
174
|
+
// Every checked-in path matching `pathRe` that the test reads as an oracle.
|
|
175
|
+
// A path that is written, copied, or bound to a name later executed or
|
|
176
|
+
// imported is produced or run by the test, not read as an oracle.
|
|
177
|
+
function checkedInReads(lines, code, pathRe) {
|
|
178
|
+
const exempt = new Set()
|
|
179
|
+
for (const l of code) {
|
|
180
|
+
const m = WRITE_CALL.test(l) && l.match(pathRe)
|
|
181
|
+
if (m) exempt.add(m[1])
|
|
182
|
+
}
|
|
183
|
+
code.forEach((l) => {
|
|
184
|
+
const m = l.match(pathRe)
|
|
185
|
+
const bound = m && l.match(/^\s*(?:(?:const|let|var)\s+)?([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=/)
|
|
186
|
+
if (!bound) return
|
|
187
|
+
const name = new RegExp(`\\b${bound[1].replace(/\$/g, '\\$')}\\b`)
|
|
188
|
+
if (code.some((other) => other !== l && name.test(other) && RUNS_OR_IMPORTS.test(other))) exempt.add(m[1])
|
|
189
|
+
})
|
|
190
|
+
const out = []
|
|
191
|
+
code.forEach((l, i) => {
|
|
192
|
+
const m = l.match(pathRe)
|
|
193
|
+
if (!m || exempt.has(m[1])) return
|
|
194
|
+
if (DOC_COMMENT.test(lines[i]) || FIXTURE_PATH.test(m[1]) || RUNS_OR_IMPORTS.test(l) || WRITE_CALL.test(l)) return
|
|
195
|
+
out.push({ line: i + 1, detail: m[1] })
|
|
196
|
+
})
|
|
197
|
+
return out
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const TEXT_ORACLE_RULE = {
|
|
201
|
+
id: 'text-oracle',
|
|
202
|
+
principle: 1,
|
|
203
|
+
severity: 'lead',
|
|
204
|
+
collapse: true,
|
|
205
|
+
message: 'reads a checked-in file and matches its text: test behavior, not text; a text-only test is not coverage (principle 1)',
|
|
206
|
+
scanFile(file, lines) {
|
|
207
|
+
const code = lines.map(stripComment)
|
|
208
|
+
if (!code.some((l) => READ_CALL.test(l)) || !code.some((l) => TEXT_ASSERT.test(l))) return []
|
|
209
|
+
return checkedInReads(lines, code, CHECKED_IN_TEXT).map((hit) => ({ ...hit, kind: 'checked-in-text' }))
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Config read: the test reads a checked-in config file (Dockerfile, workflow,
|
|
214
|
+
// cloudbuild, .mergify.yml, package.json, tsconfig, pyproject). Any assertion
|
|
215
|
+
// counts: parsing the file and comparing fields restates it as surely as a
|
|
216
|
+
// text match. A lead: JSON data the app loads (locales, price tables) is not
|
|
217
|
+
// config (SKILL.md principle 6).
|
|
218
|
+
export const CONFIG_READ_RULE = {
|
|
219
|
+
id: 'config-read',
|
|
220
|
+
principle: 6,
|
|
221
|
+
severity: 'lead',
|
|
222
|
+
collapse: true,
|
|
223
|
+
message: 'reads a checked-in config file: the tool that reads it is the test; do not test config (principle 6)',
|
|
224
|
+
scanFile(file, lines) {
|
|
225
|
+
const code = lines.map(stripComment)
|
|
226
|
+
if (!code.some((l) => READ_CALL.test(l))) return []
|
|
227
|
+
return checkedInReads(lines, code, CONFIG_FILE).map((hit) => ({ ...hit, kind: 'config-file' }))
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Count pin: an exact size of five or more. Lengths that encode an external
|
|
232
|
+
// format (ULID 26, MD5 32, UUID 36, SHA-1 40, SHA-256 64, SHA-512 128) are
|
|
233
|
+
// excluded; a lead because a fixture's own size is a legitimate pin.
|
|
234
|
+
const FORMAT_LENGTHS = new Set([26, 32, 36, 40, 64, 128])
|
|
235
|
+
const COUNT_PIN_PATTERNS = [
|
|
236
|
+
/\.toHaveLength\(\s*(\d+)\s*\)/,
|
|
237
|
+
/\bexpect\([\w$.[\]()]+\.length\)\s*\.to(?:Be|Equal|StrictEqual)\(\s*(\d+)\s*\)/,
|
|
238
|
+
/\bassert\.(?:strict)?[Ee]qual\(\s*[\w$.[\]()]+\.length\s*,\s*(\d+)\s*[,)]/,
|
|
239
|
+
/\b(?:assert|require)\.Len\(\s*t\s*,\s*[^,]+,\s*(\d+)\s*\)/,
|
|
240
|
+
/\bassert\s+len\([^)]*\)\s*==\s*(\d+)\b/,
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
export const COUNT_PIN_RULE = {
|
|
244
|
+
id: 'count-pin',
|
|
245
|
+
principle: 2,
|
|
246
|
+
severity: 'lead',
|
|
247
|
+
message: 'pins an exact count: every legitimate addition fails it; derive the expected size or assert the property (principle 2)',
|
|
248
|
+
match(raw, code) {
|
|
249
|
+
for (const re of COUNT_PIN_PATTERNS) {
|
|
250
|
+
const m = code.match(re)
|
|
251
|
+
if (!m) continue
|
|
252
|
+
const n = Number(m[1])
|
|
253
|
+
if (n >= 5 && !FORMAT_LENGTHS.has(n)) return { kind: 'count-pin', detail: String(n) }
|
|
254
|
+
}
|
|
255
|
+
return null
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Utility-class assertion: a Tailwind-shaped token asserted on a DOM class list.
|
|
260
|
+
// jsdom has no layout, so this proves neither size nor position; a semantic
|
|
261
|
+
// state class (`health-unknown`) does not match.
|
|
262
|
+
const CLASS_ASSERT = /\.toHaveClass\(|\.classes\(\)\s*\)\s*\.toContain\(|\.className\s*\)\s*\.(?:toContain|toMatch)\(/
|
|
263
|
+
const STRING_LITERAL = /(['"`])((?:(?!\1).)*)\1/g
|
|
264
|
+
const UTILITY_TOKEN = /^!?(?:(?:sm|md|lg|xl|2xl|hover|focus|group-[\w-]+|data-\[[^\]]*\]):.+|.*-\[[^\]]+\]!?|-?(?:m[trblxy]?|p[trblxy]?|w|h|min-h|min-w|max-w|max-h|size|gap|rounded|shadow|grid-cols|space-[xy])(?:-[\w./]+)?!?)$/
|
|
265
|
+
const hasUtilityToken = (code) => [...code.matchAll(STRING_LITERAL)]
|
|
266
|
+
.some((m) => m[2].split(/\s+/).some((token) => UTILITY_TOKEN.test(token)))
|
|
267
|
+
|
|
268
|
+
export const UTILITY_CLASS_RULE = {
|
|
269
|
+
id: 'utility-class',
|
|
270
|
+
principle: 3,
|
|
271
|
+
severity: 'lead',
|
|
272
|
+
message: 'asserts a utility CSS class: jsdom has no layout; prove size or position in a real browser (principle 3)',
|
|
273
|
+
match(raw, code) {
|
|
274
|
+
return CLASS_ASSERT.test(code) && hasUtilityToken(code) ? { kind: 'utility-class' } : null
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Duplicate test file: identical content after whitespace normalisation.
|
|
279
|
+
// Resolved by the CLI across every scanned file (findDuplicateFiles).
|
|
280
|
+
export const DUPLICATE_FILE_RULE = {
|
|
281
|
+
id: 'duplicate-file',
|
|
282
|
+
principle: 5,
|
|
283
|
+
severity: 'lead',
|
|
284
|
+
crossFile: true,
|
|
285
|
+
message: 'identical to another test file: one behavior, one place (principle 5)',
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export const ALL_RULES = [
|
|
289
|
+
...RULES,
|
|
290
|
+
CONDITIONAL_SKIP_RULE,
|
|
291
|
+
MOCK_INTERNAL_SEAM_RULE,
|
|
292
|
+
REDUNDANT_MATCHER_RULE,
|
|
293
|
+
TEXT_ORACLE_RULE,
|
|
294
|
+
CONFIG_READ_RULE,
|
|
295
|
+
COUNT_PIN_RULE,
|
|
296
|
+
UTILITY_CLASS_RULE,
|
|
297
|
+
DUPLICATE_FILE_RULE,
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
export function selectRules(ruleIds) {
|
|
301
|
+
if (ruleIds && ruleIds.length > 0) {
|
|
302
|
+
const set = new Set(ruleIds)
|
|
303
|
+
return ALL_RULES.filter((r) => set.has(r.id))
|
|
304
|
+
}
|
|
305
|
+
return ALL_RULES.filter((r) => r.defaultOn !== false)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Strip line comments so a doc line or a commented-out example is not flagged.
|
|
309
|
+
// Best-effort (ignores strings/block comments) — live code has no leading `//`.
|
|
310
|
+
export function stripComment(line) {
|
|
311
|
+
return line.replace(/\/\/.*$/, '').replace(/(^|\s)#.*$/, '$1')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Only test code is scanned: linting app code would false-flag a legitimate
|
|
315
|
+
// `.skip()`/`.only` array method or a real `sleep`, and a data file under
|
|
316
|
+
// `tests/` (parquet, JSON fixtures) is not a test.
|
|
317
|
+
export function isTestFile(p) {
|
|
318
|
+
if (!p) return false
|
|
319
|
+
return /\.(spec|test)\.[cm]?[jt]sx?$/.test(p)
|
|
320
|
+
|| (/(^|\/)(e2e|tests?|__tests__)\//.test(p) && /\.(?:[cm]?[jt]sx?|py|go)$/.test(p))
|
|
321
|
+
|| /(^|\/)test_[^/]*\.py$/.test(p)
|
|
322
|
+
|| /_test\.py$/.test(p)
|
|
323
|
+
|| /_test\.go$/.test(p)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function finding(file, rule, line, raw, hit = {}) {
|
|
327
|
+
return {
|
|
328
|
+
file, line, rule: rule.id, severity: rule.severity, principle: rule.principle,
|
|
329
|
+
kind: hit.kind || rule.id,
|
|
330
|
+
snippet: (raw || '').trim().slice(0, 120),
|
|
331
|
+
...(hit.detail ? { detail: hit.detail } : {}),
|
|
332
|
+
...(hit.reason ? { reason: hit.reason } : {}),
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Pure: run the line-level rules over one line → findings (≤ one per rule).
|
|
337
|
+
function scanLine(file, raw, lineNo, rules, ctx) {
|
|
338
|
+
const code = stripComment(raw)
|
|
339
|
+
const out = []
|
|
340
|
+
for (const rule of rules) {
|
|
341
|
+
if (typeof rule.match === 'function') {
|
|
342
|
+
const hit = rule.match(raw, code, ctx)
|
|
343
|
+
if (hit) out.push(finding(file, rule, lineNo, raw, hit))
|
|
344
|
+
} else if (rule.patterns) {
|
|
345
|
+
const hit = rule.patterns.find((p) => p.re.test(code))
|
|
346
|
+
if (hit) out.push(finding(file, rule, lineNo, raw, hit))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return out
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// A `collapse` rule reports once per file, at its first hit, naming every
|
|
353
|
+
// distinct detail: one lead per test file is what an auditor reads.
|
|
354
|
+
function scanFileRules(file, lines, rules, ctx, keep = () => true) {
|
|
355
|
+
const out = []
|
|
356
|
+
for (const rule of rules) {
|
|
357
|
+
if (typeof rule.scanFile !== 'function') continue
|
|
358
|
+
const hits = rule.scanFile(file, lines, ctx).filter((hit) => keep(hit.line))
|
|
359
|
+
if (hits.length === 0) continue
|
|
360
|
+
if (rule.collapse) {
|
|
361
|
+
const detail = [...new Set(hits.map((hit) => hit.detail))].join(', ')
|
|
362
|
+
out.push(finding(file, rule, hits[0].line, lines[hits[0].line - 1], { ...hits[0], detail }))
|
|
363
|
+
} else {
|
|
364
|
+
for (const hit of hits) out.push(finding(file, rule, hit.line, lines[hit.line - 1], hit))
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return out
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const byPosition = (a, b) => a.line - b.line || a.rule.localeCompare(b.rule)
|
|
371
|
+
|
|
372
|
+
export function scanLines(file, lines, ruleIds, ctx) {
|
|
373
|
+
const rules = selectRules(ruleIds)
|
|
374
|
+
const out = []
|
|
375
|
+
lines.forEach((raw, i) => out.push(...scanLine(file, raw, i + 1, rules, ctx)))
|
|
376
|
+
out.push(...scanFileRules(file, lines, rules, ctx))
|
|
377
|
+
return out.sort(byPosition)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function scanText(file, text, ruleIds, ctx) {
|
|
381
|
+
return scanLines(file, (text || '').split(/\r?\n/), ruleIds, ctx)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Pure: scan a unified diff, reporting only findings on ADDED lines of test
|
|
385
|
+
// files. File-level rules need the whole file: pass `readFile(path) → text` and
|
|
386
|
+
// they run on it, keeping only findings that land on an added line.
|
|
387
|
+
export function scanDiff(diffText, ruleIds, ctx, readFile) {
|
|
388
|
+
const rules = selectRules(ruleIds)
|
|
389
|
+
const out = []
|
|
390
|
+
const added = new Map()
|
|
391
|
+
let file = null
|
|
392
|
+
let newLine = 0
|
|
393
|
+
for (const raw of (diffText || '').split(/\r?\n/)) {
|
|
394
|
+
if (raw.startsWith('+++ ')) {
|
|
395
|
+
const m = raw.match(/^\+\+\+ (?:b\/)?(.+)$/)
|
|
396
|
+
file = m && m[1] !== '/dev/null' ? m[1] : null
|
|
397
|
+
continue
|
|
398
|
+
}
|
|
399
|
+
if (raw.startsWith('@@')) {
|
|
400
|
+
const m = raw.match(/\+(\d+)/)
|
|
401
|
+
newLine = m ? parseInt(m[1], 10) : 0
|
|
402
|
+
continue
|
|
403
|
+
}
|
|
404
|
+
if (raw.startsWith('+')) {
|
|
405
|
+
if (isTestFile(file)) {
|
|
406
|
+
out.push(...scanLine(file, raw.slice(1), newLine, rules, ctx))
|
|
407
|
+
if (!added.has(file)) added.set(file, new Set())
|
|
408
|
+
added.get(file).add(newLine)
|
|
409
|
+
}
|
|
410
|
+
newLine++
|
|
411
|
+
} else if (raw.startsWith('-') || raw.startsWith('\\')) {
|
|
412
|
+
// removed line / "No newline" marker — do not advance the new-file counter
|
|
413
|
+
} else {
|
|
414
|
+
newLine++ // context line
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (typeof readFile === 'function' && rules.some((r) => typeof r.scanFile === 'function')) {
|
|
418
|
+
for (const [f, lines] of added) {
|
|
419
|
+
const text = readFile(f)
|
|
420
|
+
if (text == null) continue
|
|
421
|
+
out.push(...scanFileRules(f, text.split(/\r?\n/), rules, ctx, (line) => lines.has(line)))
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return out
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Pure: group files whose whitespace-normalised content is identical. Files
|
|
428
|
+
// under ten non-blank lines are ignored (tiny smoke files legitimately match).
|
|
429
|
+
export function findDuplicateFiles(entries) {
|
|
430
|
+
const groups = new Map()
|
|
431
|
+
for (const { file, text } of entries) {
|
|
432
|
+
const lines = (text || '').split(/\r?\n/).filter((l) => l.trim())
|
|
433
|
+
if (lines.length < 10) continue
|
|
434
|
+
const key = crypto.createHash('sha256').update(lines.map((l) => l.trim().replace(/\s+/g, ' ')).join('\n')).digest('hex')
|
|
435
|
+
if (!groups.has(key)) groups.set(key, [])
|
|
436
|
+
groups.get(key).push(file)
|
|
437
|
+
}
|
|
438
|
+
const out = []
|
|
439
|
+
for (const files of groups.values()) {
|
|
440
|
+
if (files.length < 2) continue
|
|
441
|
+
for (const f of files) {
|
|
442
|
+
out.push(finding(f, DUPLICATE_FILE_RULE, 1, '', { detail: files.filter((o) => o !== f).join(', ') }))
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return out
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ---- CLI ----
|
|
449
|
+
|
|
450
|
+
const SKIP_DIRS = new Set(['node_modules', 'site-packages', 'venv'])
|
|
451
|
+
|
|
452
|
+
function discover(dir, acc = []) {
|
|
453
|
+
let entries
|
|
454
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return acc } // unreadable dir: skip, never crash the scan
|
|
455
|
+
for (const entry of entries) {
|
|
456
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.') || entry.name.endsWith('-workspace')) continue
|
|
457
|
+
const full = path.join(dir, entry.name)
|
|
458
|
+
if (entry.isDirectory()) discover(full, acc)
|
|
459
|
+
else if (isTestFile(full)) acc.push(full)
|
|
460
|
+
}
|
|
461
|
+
return acc
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// `--exclude dist,vendor,fixtures`: drop any file with one of these as a path
|
|
465
|
+
// segment (or path prefix), for vendored, generated, and fixture trees.
|
|
466
|
+
export function excluded(file, segments) {
|
|
467
|
+
if (!segments || segments.length === 0) return false
|
|
468
|
+
const norm = file.split(path.sep).join('/').replace(/^\.\//, '')
|
|
469
|
+
const parts = norm.split('/')
|
|
470
|
+
return segments.some((s) => parts.includes(s) || norm.startsWith(`${s.replace(/\/$/, '')}/`))
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// `--shares N`: split the files with findings into at most N shares of about
|
|
474
|
+
// equal finding count, one per audit reviewer. A file stays whole; the
|
|
475
|
+
// biggest files are placed first, each into the lightest share.
|
|
476
|
+
export function splitShares(findings, n) {
|
|
477
|
+
const counts = new Map()
|
|
478
|
+
for (const f of findings) counts.set(f.file, (counts.get(f.file) || 0) + 1)
|
|
479
|
+
const files = [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
480
|
+
const shares = Array.from({ length: Math.min(n, files.length) }, () => ({ findings: 0, files: [] }))
|
|
481
|
+
for (const [file, count] of files) {
|
|
482
|
+
const lightest = shares.reduce((min, share) => (share.findings < min.findings ? share : min))
|
|
483
|
+
lightest.files.push(file)
|
|
484
|
+
lightest.findings += count
|
|
485
|
+
}
|
|
486
|
+
return shares
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const USAGE = [
|
|
490
|
+
'usage: lint-tests.mjs [--diff <base>] [--files a,b] [--rules a,b] [--exclude a,b] [--config coverage.config.json] [--shares N] [--json]',
|
|
491
|
+
' (no flags) scan every test file under the current directory; run from the repo root',
|
|
492
|
+
' --diff <base> report only findings on lines changed since the merge-base with <base>: commits,',
|
|
493
|
+
' staged and unstaged edits, and untracked test files (whole file)',
|
|
494
|
+
' --files a,b scan exactly these files; a missing file is an error',
|
|
495
|
+
` --rules a,b subset of: ${ALL_RULES.map((r) => r.id).join(', ')}`,
|
|
496
|
+
' --exclude a,b skip files with any of these path segments or prefixes (e.g. dist,vendor,fixtures)',
|
|
497
|
+
' --shares N also split the files with findings into at most N review shares of about equal finding count',
|
|
498
|
+
' exit 0 no gate findings · 1 gate findings · 2 usage error. Lead findings never change the exit code.',
|
|
499
|
+
].join('\n')
|
|
500
|
+
|
|
501
|
+
const VALUE_FLAGS = new Set(['--diff', '--files', '--rules', '--config', '--exclude', '--shares'])
|
|
502
|
+
const list = (v) => (v || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
503
|
+
|
|
504
|
+
export function runCli(argv, { name = 'lint-tests', cwd = process.cwd() } = {}) {
|
|
505
|
+
if (argv.includes('--help') || argv.includes('-h')) { console.log(USAGE); return 0 }
|
|
506
|
+
const usageError = (msg) => { console.error(`${name}: ${msg}\n\n${USAGE}`); return 2 }
|
|
507
|
+
const flags = {}
|
|
508
|
+
for (let i = 0; i < argv.length; i++) {
|
|
509
|
+
const a = argv[i]
|
|
510
|
+
if (a === '--json') { flags.json = true; continue }
|
|
511
|
+
if (!VALUE_FLAGS.has(a)) return usageError(`unknown argument ${JSON.stringify(a)}`)
|
|
512
|
+
const v = argv[++i]
|
|
513
|
+
if (v === undefined || v.startsWith('--')) return usageError(`${a} needs a value`)
|
|
514
|
+
flags[a.slice(2)] = v
|
|
515
|
+
}
|
|
516
|
+
const ruleIds = flags.rules ? list(flags.rules) : null
|
|
517
|
+
const unknown = (ruleIds || []).filter((id) => !ALL_RULES.some((r) => r.id === id))
|
|
518
|
+
if (unknown.length) return usageError(`unknown rule(s): ${unknown.join(', ')}`)
|
|
519
|
+
const shareCount = flags.shares === undefined ? null : Number(flags.shares)
|
|
520
|
+
if (shareCount !== null && !(Number.isInteger(shareCount) && shareCount > 0)) return usageError('--shares needs a positive whole number')
|
|
521
|
+
const exclude = list(flags.exclude)
|
|
522
|
+
const configPath = flags.config && path.resolve(cwd, flags.config)
|
|
523
|
+
const ctx = { policy: loadMockPolicyFromFile(configPath, cwd) }
|
|
524
|
+
|
|
525
|
+
let findings = []
|
|
526
|
+
if (flags.diff) {
|
|
527
|
+
// Diff from the merge-base to the WORKING TREE, so a writer's uncommitted
|
|
528
|
+
// tests count; git paths are repo-root-relative, so read files from there.
|
|
529
|
+
const git = (args) => execSync(`git ${args}`, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
530
|
+
const root = git('rev-parse --show-toplevel').trim()
|
|
531
|
+
const base = git(`merge-base ${flags.diff} HEAD`).trim()
|
|
532
|
+
const readAtRoot = (f) => { const p = path.join(root, f); return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null }
|
|
533
|
+
const diff = execSync(`git diff ${base}`, { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
534
|
+
findings = scanDiff(diff, ruleIds, ctx, readAtRoot).filter((f) => !excluded(f.file, exclude))
|
|
535
|
+
const untracked = execSync('git ls-files -o --exclude-standard', { cwd: root, encoding: 'utf8' })
|
|
536
|
+
.split('\n').filter((f) => f && isTestFile(f) && !excluded(f, exclude))
|
|
537
|
+
for (const f of untracked) findings.push(...scanText(f, readAtRoot(f), ruleIds, ctx))
|
|
538
|
+
} else {
|
|
539
|
+
let files
|
|
540
|
+
if (flags.files) {
|
|
541
|
+
files = list(flags.files)
|
|
542
|
+
const missing = files.filter((f) => !fs.existsSync(path.resolve(cwd, f)))
|
|
543
|
+
if (missing.length) return usageError(`no such file(s): ${missing.join(', ')}`)
|
|
544
|
+
} else {
|
|
545
|
+
files = discover(cwd).map((f) => path.relative(cwd, f) || f)
|
|
546
|
+
}
|
|
547
|
+
const entries = files.filter((f) => !excluded(f, exclude))
|
|
548
|
+
.map((f) => ({ file: f, text: fs.readFileSync(path.resolve(cwd, f), 'utf8') }))
|
|
549
|
+
for (const { file, text } of entries) findings.push(...scanText(file, text, ruleIds, ctx))
|
|
550
|
+
if (selectRules(ruleIds).some((r) => r.crossFile)) findings.push(...findDuplicateFiles(entries))
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const gates = findings.filter((f) => f.severity === 'gate').length
|
|
554
|
+
const leads = findings.length - gates
|
|
555
|
+
const shares = shareCount === null ? null : splitShares(findings, shareCount)
|
|
556
|
+
if (flags.json) {
|
|
557
|
+
console.log(JSON.stringify({ ok: gates === 0, gates, leads, findings, ...(shares && { shares }) }, null, 2))
|
|
558
|
+
} else if (findings.length === 0) {
|
|
559
|
+
console.log(`${name}: ✓ no findings` + (flags.diff ? ' in this change' : ' in scanned test files'))
|
|
560
|
+
} else {
|
|
561
|
+
console.log(`${name}: ${gates ? '✗' : '✓'} ${gates} gate, ${leads} lead finding(s):\n`)
|
|
562
|
+
for (const f of findings) {
|
|
563
|
+
const why = f.detail ? ` → ${f.detail}${f.reason ? ` (${f.reason})` : ''}` : ''
|
|
564
|
+
console.log(` ${f.file}:${f.line} [${f.severity} ${f.rule}/${f.kind}] ${f.snippet}${why}`)
|
|
565
|
+
}
|
|
566
|
+
for (const [i, share] of (shares || []).entries()) {
|
|
567
|
+
console.log(`\nshare ${i + 1}: ${share.findings} finding(s) in ${share.files.length} file(s)\n ${share.files.join('\n ')}`)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return gates === 0 ? 0 : 1
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// exitCode, not process.exit(): exiting early truncates piped stdout at 64KB.
|
|
574
|
+
if (isMainModule(import.meta.url)) process.exitCode = runCli(process.argv.slice(2), { name: 'lint-tests' })
|