@1aboveio/skills 0.10.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/LICENSE +3 -0
- package/README.md +90 -0
- package/bin/1aboveio-skills.mjs +18 -0
- package/package.json +28 -0
- package/runtime/skills/distribution/generated/recipes.json +1189 -0
- package/runtime/skills/distribution/scripts/bundles.mjs +280 -0
- package/runtime/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
- package/skills/backend/airflow-dag-develop/LICENSE +3 -0
- package/skills/backend/airflow-dag-develop/SKILL.md +111 -0
- package/skills/backend/app-debug/LICENSE +3 -0
- package/skills/backend/app-debug/SKILL.md +109 -0
- package/skills/backend/app-debug/references/common-errors.md +128 -0
- package/skills/backend/python-backend/LICENSE +3 -0
- package/skills/backend/python-backend/SKILL.md +326 -0
- package/skills/cicd-pipeline/cloud-build/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-build/SKILL.md +707 -0
- package/skills/cicd-pipeline/cloud-debug/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-debug/SKILL.md +316 -0
- package/skills/cicd-pipeline/cloud-debug/references/build-failures.md +238 -0
- package/skills/cicd-pipeline/cloud-debug/references/deploy-failures.md +376 -0
- package/skills/cicd-pipeline/cloud-debug/references/pipeline-failures.md +378 -0
- package/skills/cicd-pipeline/cloud-deploy/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-deploy/SKILL.md +229 -0
- package/skills/cicd-pipeline/cloud-deploy/references/config-templates.md +257 -0
- package/skills/cicd-pipeline/docker/LICENSE +3 -0
- package/skills/cicd-pipeline/docker/SKILL.md +126 -0
- package/skills/cicd-pipeline/google-cloud/LICENSE +3 -0
- package/skills/cicd-pipeline/google-cloud/SKILL.md +118 -0
- package/skills/cicd-pipeline/google-cloud/references/gcs.md +469 -0
- package/skills/cicd-pipeline/google-cloud/references/iam.md +451 -0
- package/skills/cicd-pipeline/google-cloud/references/project.md +349 -0
- package/skills/cicd-pipeline/google-cloud/references/secrets.md +336 -0
- package/skills/cicd-pipeline/google-cloud/references/vpc.md +312 -0
- package/skills/cicd-pipeline/google-cloud/scripts/create-sa.sh +36 -0
- package/skills/cicd-pipeline/google-cloud/scripts/gcp-config.sh +31 -0
- package/skills/cicd-pipeline/google-cloud/scripts/grant-iap.sh +41 -0
- package/skills/cicd-pipeline/google-cloud/scripts/setup-secrets.sh +48 -0
- package/skills/cicd-pipeline/mergify/LICENSE +3 -0
- package/skills/cicd-pipeline/mergify/SKILL.md +138 -0
- package/skills/cicd-pipeline/mergify/assets/templates/mergify.yml +237 -0
- package/skills/cicd-pipeline/mergify/assets/templates/ruleset.json +46 -0
- package/skills/cicd-pipeline/mergify/references/branch-protection.md +277 -0
- package/skills/cicd-pipeline/mergify/references/configuration.md +183 -0
- package/skills/cicd-pipeline/mergify/references/diagnosis.md +73 -0
- package/skills/cicd-pipeline/mergify/references/traps.md +78 -0
- package/skills/cicd-pipeline/mergify/references/watch-contract.md +218 -0
- package/skills/cicd-pipeline/mergify/scripts/audit-core.mjs +131 -0
- package/skills/cicd-pipeline/mergify/scripts/audit.mjs +4 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +663 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery.mjs +4 -0
- package/skills/cicd-pipeline/podman/LICENSE +3 -0
- package/skills/cicd-pipeline/podman/SKILL.md +70 -0
- package/skills/cicd-pipeline/podman/agents/openai.yaml +4 -0
- package/skills/cicd-pipeline/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
- package/skills/cicd-pipeline/podman/assets/templates/podman-service-override.conf +3 -0
- package/skills/cicd-pipeline/podman/references/compose-compatibility.md +70 -0
- package/skills/cicd-pipeline/podman/references/networking-and-ports.md +74 -0
- package/skills/cicd-pipeline/podman/references/rootless-services-and-sockets.md +156 -0
- package/skills/cicd-pipeline/podman/references/troubleshooting.md +98 -0
- package/skills/engineering/e2e-test/LICENSE +3 -0
- package/skills/engineering/e2e-test/SKILL.md +156 -0
- package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +272 -0
- package/skills/engineering/e2e-test/assets/ci-gates.github.yml +451 -0
- package/skills/engineering/e2e-test/assets/e2e-workflow.yml +282 -0
- package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-config.md +71 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +219 -0
- package/skills/engineering/e2e-test/references/authoring/test-skipping.md +44 -0
- package/skills/engineering/e2e-test/references/ci-integration.md +121 -0
- package/skills/engineering/e2e-test/references/ci-playwright-container.md +280 -0
- package/skills/engineering/e2e-test/references/debugging.md +36 -0
- package/skills/engineering/e2e-test/references/presentation-sweep.md +131 -0
- package/skills/engineering/e2e-test/references/reviewing.md +39 -0
- package/skills/engineering/e2e-test/references/route-discovery.md +50 -0
- package/skills/engineering/e2e-test/references/route-manifest.md +44 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-fastapi.py +290 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
- package/skills/engineering/e2e-test/scripts/post-visual-evidence.mjs +158 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +148 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
- package/skills/engineering/e2e-test/scripts/presentation-visual.mjs +84 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +75 -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 +10 -0
- package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/engineering-runtime/coherence/workflow.json +553 -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/main-module.mjs +80 -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/workflow-coherence.mjs +576 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +166 -0
- package/skills/engineering/ensure-coverage/LICENSE +3 -0
- package/skills/engineering/ensure-coverage/SKILL.md +136 -0
- package/skills/engineering/ensure-coverage/evals/evals.json +125 -0
- package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +91 -0
- package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +83 -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 +25 -0
- package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
- package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +87 -0
- package/skills/engineering/ensure-coverage/references/depth/test-smells.md +23 -0
- package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +164 -0
- package/skills/engineering/ensure-coverage/references/enforcement/hooks.md +85 -0
- package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +109 -0
- package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +33 -0
- package/skills/engineering/ensure-coverage/references/presentation/axis.md +78 -0
- package/skills/engineering/ensure-coverage/references/presentation/runner-contract.md +74 -0
- package/skills/engineering/ensure-coverage/references/process/audit-mode.md +33 -0
- package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
- package/skills/engineering/ensure-coverage/references/process/review-contract-template.md +119 -0
- package/skills/engineering/ensure-coverage/references/process/scope-class.md +178 -0
- package/skills/engineering/ensure-coverage/references/process/test-strategy.md +55 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +45 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +93 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +178 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +101 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +358 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-checklist.mjs +494 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +663 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
- package/skills/engineering/ensure-coverage/scripts/evidence-block.mjs +367 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +269 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/ensure-coverage/scripts/package-lock.json +76 -0
- package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
- package/skills/engineering/ensure-coverage/scripts/scope-class.mjs +554 -0
- package/skills/engineering/harness-runtime/LICENSE +3 -0
- package/skills/engineering/harness-runtime/SKILL.md +18 -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/fixtures/native-question-schemas.json +33 -0
- package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
- package/skills/engineering/harness-runtime/index.mjs +767 -0
- package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
- package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
- package/skills/engineering/harness-runtime/references/model-catalog-seed.json +159 -0
- package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
- package/skills/engineering/implement-and-pr/LICENSE +3 -0
- package/skills/engineering/implement-and-pr/SKILL.md +176 -0
- package/skills/engineering/implement-and-pr/references/ci-iteration.md +10 -0
- package/skills/engineering/implement-and-pr/references/closeout.md +27 -0
- package/skills/engineering/implement-and-pr/references/contract-complete-fix-rounds.md +34 -0
- package/skills/engineering/implement-and-pr/references/evidence-rules.md +39 -0
- package/skills/engineering/implement-and-pr/references/incremental-plan.md +16 -0
- package/skills/engineering/implement-and-pr/references/self-review.md +23 -0
- package/skills/engineering/implement-and-pr/references/tdd-mode.md +18 -0
- package/skills/engineering/resolve-issues/LICENSE +3 -0
- package/skills/engineering/resolve-issues/SKILL.md +167 -0
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +448 -0
- package/skills/engineering/resolve-issues/references/breaker.md +82 -0
- package/skills/engineering/resolve-issues/references/deliverables.md +27 -0
- package/skills/engineering/resolve-issues/references/delivery.md +108 -0
- package/skills/engineering/resolve-issues/references/evidence-lane.md +21 -0
- package/skills/engineering/resolve-issues/references/exact-head-ci.md +287 -0
- package/skills/engineering/resolve-issues/references/fan-out.md +33 -0
- package/skills/engineering/resolve-issues/references/finalization.md +68 -0
- package/skills/engineering/resolve-issues/references/guarantees.md +10 -0
- package/skills/engineering/resolve-issues/references/high-risk.md +29 -0
- package/skills/engineering/resolve-issues/references/incidents/848/README.md +156 -0
- package/skills/engineering/resolve-issues/references/intake.md +86 -0
- package/skills/engineering/resolve-issues/references/integration-gate.md +53 -0
- package/skills/engineering/resolve-issues/references/interference.md +87 -0
- package/skills/engineering/resolve-issues/references/loop.md +134 -0
- package/skills/engineering/resolve-issues/references/model-catalog.md +9 -0
- package/skills/engineering/resolve-issues/references/postmortem.md +27 -0
- package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +41 -0
- package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +48 -0
- package/skills/engineering/resolve-issues/references/pre-flight.md +41 -0
- package/skills/engineering/resolve-issues/references/regression-checklist.md +26 -0
- package/skills/engineering/resolve-issues/references/run-state.md +288 -0
- package/skills/engineering/resolve-issues/references/sandboxed-testing.md +48 -0
- package/skills/engineering/resolve-issues/references/spawn-contract.md +96 -0
- package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +40 -0
- package/skills/engineering/resolve-issues/references/why.md +653 -0
- package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +49 -0
- package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +721 -0
- package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +962 -0
- package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +220 -0
- package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +420 -0
- package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +256 -0
- package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +168 -0
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/doctrine.mjs +62 -0
- package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +191 -0
- package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +413 -0
- package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +332 -0
- package/skills/engineering/resolve-issues/scripts/finalize.mjs +488 -0
- package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +3307 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +16 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +51 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +17 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +166 -0
- package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +92 -0
- package/skills/engineering/resolve-issues/scripts/guide-index.mjs +73 -0
- package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +159 -0
- package/skills/engineering/resolve-issues/scripts/interference.mjs +427 -0
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/next-operations.mjs +419 -0
- package/skills/engineering/resolve-issues/scripts/postmortem.mjs +909 -0
- package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +322 -0
- package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +415 -0
- package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +697 -0
- package/skills/engineering/resolve-issues/scripts/report.mjs +494 -0
- package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +172 -0
- package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +79 -0
- package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +334 -0
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +4784 -0
- package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +395 -0
- package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +290 -0
- package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +170 -0
- package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +293 -0
- package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +197 -0
- package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +127 -0
- package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +893 -0
- package/skills/engineering/resolve-issues/scripts/workspaces.mjs +829 -0
- package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +290 -0
- package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +80 -0
- package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +102 -0
- package/skills/engineering/resolve-release/LICENSE +3 -0
- package/skills/engineering/resolve-release/SKILL.md +112 -0
- package/skills/engineering/resolve-release/references/assembly.md +137 -0
- package/skills/engineering/resolve-release/references/auto-when-green.md +56 -0
- package/skills/engineering/resolve-release/references/candidate.md +167 -0
- package/skills/engineering/resolve-release/references/exposure.md +178 -0
- package/skills/engineering/resolve-release/references/handoff.md +24 -0
- package/skills/engineering/resolve-release/references/postmortem.md +230 -0
- package/skills/engineering/resolve-release/references/preflight.md +207 -0
- package/skills/engineering/resolve-release/references/principles.md +94 -0
- package/skills/engineering/resolve-release/references/regression-checklist.md +36 -0
- package/skills/engineering/resolve-release/references/related-skills.md +12 -0
- package/skills/engineering/resolve-release/references/routing.md +149 -0
- package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +285 -0
- package/skills/engineering/resolve-release/references/versioning.md +202 -0
- package/skills/engineering/resolve-release/references/why.md +53 -0
- package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +389 -0
- package/skills/engineering/resolve-release/scripts/build-changes.mjs +209 -0
- package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +407 -0
- package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +904 -0
- package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +81 -0
- package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +612 -0
- package/skills/engineering/resolve-release/scripts/close-attempt.mjs +135 -0
- package/skills/engineering/resolve-release/scripts/closeout-release.mjs +161 -0
- package/skills/engineering/resolve-release/scripts/doctrine.mjs +106 -0
- package/skills/engineering/resolve-release/scripts/durable-processing.mjs +522 -0
- package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +659 -0
- package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +1014 -0
- package/skills/engineering/resolve-release/scripts/finalize-release.mjs +526 -0
- package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +169 -0
- package/skills/engineering/resolve-release/scripts/green-gate.mjs +599 -0
- package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +752 -0
- package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +56 -0
- package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +1538 -0
- package/skills/engineering/resolve-release/scripts/postmortem.mjs +381 -0
- package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +498 -0
- package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +326 -0
- package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +272 -0
- package/skills/engineering/resolve-release/scripts/report.mjs +417 -0
- package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +114 -0
- package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +238 -0
- package/skills/engineering/resolve-release/scripts/review-packet.mjs +503 -0
- package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +263 -0
- package/skills/engineering/resolve-release/scripts/version-assert.mjs +339 -0
- package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +485 -0
- package/skills/engineering/resolve-release/scripts/version.mjs +1199 -0
- package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +449 -0
- package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/README.md +60 -0
- package/skills/engineering/resolve-release/vendor/acorn.mjs +6233 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +341 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +70 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +7 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +2033 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +2033 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +7 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +49 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +5 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +549 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +549 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +36 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +47 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +82 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +22 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +25 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +85 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +85 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +20 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +20 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +61 -0
- package/skills/engineering/review-pr/LICENSE +3 -0
- package/skills/engineering/review-pr/SKILL.md +123 -0
- package/skills/engineering/review-pr/references/adversarial-reviewer-prompt.md +42 -0
- package/skills/engineering/review-pr/references/code-correctness.md +5 -0
- package/skills/engineering/review-pr/references/contract-freshness.md +9 -0
- package/skills/engineering/review-pr/references/coordination.md +18 -0
- package/skills/engineering/review-pr/references/domain-hazards.md +123 -0
- package/skills/engineering/review-pr/references/finding-themes.md +7 -0
- package/skills/engineering/review-pr/references/github-posting.md +98 -0
- package/skills/engineering/review-pr/references/golden-path-smoke.md +5 -0
- package/skills/engineering/review-pr/references/incremental-output.md +16 -0
- package/skills/engineering/review-pr/references/inputs-and-discovery.md +31 -0
- package/skills/engineering/review-pr/references/output-format.md +99 -0
- package/skills/engineering/review-pr/references/over-mock-screen.md +7 -0
- package/skills/engineering/review-pr/references/promotion-prs.md +16 -0
- package/skills/engineering/review-pr/references/re-review.md +18 -0
- package/skills/engineering/review-pr/references/review-method.md +199 -0
- package/skills/engineering/review-pr/references/review-mode.md +30 -0
- package/skills/engineering/review-pr/references/review-posture.md +53 -0
- package/skills/engineering/review-pr/references/round1-depth.md +62 -0
- package/skills/engineering/review-pr/references/scripts.md +17 -0
- package/skills/engineering/review-pr/references/workflow.md +16 -0
- package/skills/engineering/review-pr/schemas/findings.schema.json +282 -0
- package/skills/engineering/review-pr/scripts/finding-contract.mjs +285 -0
- package/skills/engineering/review-pr/scripts/post-review.mjs +405 -0
- package/skills/engineering/review-pr/scripts/pr-context.mjs +207 -0
- package/skills/engineering/review-pr/scripts/scan-diff.mjs +365 -0
- package/skills/engineering/review-pr/scripts/theme-contract.mjs +57 -0
- package/skills/engineering/smoke/LICENSE +3 -0
- package/skills/engineering/smoke/SKILL.md +131 -0
- package/skills/engineering/smoke/assets/smoke.manifest.example.json +53 -0
- package/skills/engineering/smoke/references/manifest.md +192 -0
- package/skills/engineering/smoke/scripts/smoke.mjs +713 -0
- package/skills/fullstack/better-auth/LICENSE +3 -0
- package/skills/fullstack/better-auth/SKILL.md +601 -0
- package/skills/fullstack/better-auth/references/feishu-api.md +270 -0
- package/skills/fullstack/monorepo/LICENSE +3 -0
- package/skills/fullstack/monorepo/SKILL.md +465 -0
- package/skills/fullstack/nextjs-fullstack/LICENSE +3 -0
- package/skills/fullstack/nextjs-fullstack/SKILL.md +210 -0
- package/skills/fullstack/nextjs-fullstack/conventions.md +318 -0
- package/skills/fullstack/nextjs-fullstack/frontend-conventions.md +61 -0
- package/skills/fullstack/nextjs-fullstack/nextjs16.md +287 -0
- package/skills/fullstack/nextjs-fullstack/server-actions.md +409 -0
- package/skills/fullstack/prisma-setup/LICENSE +3 -0
- package/skills/fullstack/prisma-setup/SKILL.md +180 -0
- package/skills/fullstack/prisma-setup/nextjs.md +258 -0
- package/skills/fullstack/prisma-setup/turborepo.md +301 -0
- package/skills/fullstack/shadcn/LICENSE +3 -0
- package/skills/fullstack/shadcn/SKILL.md +119 -0
- package/skills/fullstack/shadcn/assets/shadcn-small.png +0 -0
- package/skills/fullstack/shadcn/assets/shadcn.png +0 -0
- package/skills/fullstack/shadcn/cli.md +411 -0
- package/skills/fullstack/shadcn/customization.md +224 -0
- package/skills/fullstack/shadcn/evals/evals.json +90 -0
- package/skills/fullstack/shadcn/mcp.md +101 -0
- package/skills/fullstack/shadcn/rules/base-vs-radix.md +323 -0
- package/skills/fullstack/shadcn/rules/component-selection.md +67 -0
- package/skills/fullstack/shadcn/rules/composition.md +195 -0
- package/skills/fullstack/shadcn/rules/data-table.md +201 -0
- package/skills/fullstack/shadcn/rules/forms.md +255 -0
- package/skills/fullstack/shadcn/rules/icons.md +103 -0
- package/skills/fullstack/shadcn/rules/styling.md +167 -0
- package/skills/fullstack/zod-v4/LICENSE +3 -0
- package/skills/fullstack/zod-v4/SKILL.md +287 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// evidence-block — derive the Review Contract's volatile evidence, never transcribe it.
|
|
2
|
+
//
|
|
3
|
+
// A Review Contract carries two kinds of claim with very different lifetimes:
|
|
4
|
+
//
|
|
5
|
+
// DURABLE what this PR proves, which surfaces it touches, which rows are
|
|
6
|
+
// Partial/Unverified and why. Changes when the CLAIM changes.
|
|
7
|
+
// VOLATILE scan counts, CI job tables, test counts, gate results, SHAs.
|
|
8
|
+
// Invalidated by ANY commit.
|
|
9
|
+
//
|
|
10
|
+
// Hand-writing the volatile half is a defect generator: every fix round rewrites
|
|
11
|
+
// the document, and every rewrite is a fresh chance to overclaim or go stale. The
|
|
12
|
+
// observed incident: one PR carried five docs-only "record round N evidence"
|
|
13
|
+
// commits, each invalidated within the hour by the next, producing evidence-fidelity
|
|
14
|
+
// review findings for three consecutive rounds while the production diff was empty.
|
|
15
|
+
//
|
|
16
|
+
// So: the volatile block is GENERATED at a known SHA and STAMPED with it, and
|
|
17
|
+
// `validate` fails when the stamp is not the current HEAD. Nobody transcribes it,
|
|
18
|
+
// so nobody can transcribe it wrong; staleness becomes mechanical rather than a
|
|
19
|
+
// reviewer's catch. The contract prose references this file instead of restating it.
|
|
20
|
+
//
|
|
21
|
+
// node evidence-block.mjs collect --base <sha> [--head <sha>] [--source a,b,c]
|
|
22
|
+
// [--scan-diff <path to review-pr/scripts/scan-diff.mjs>]
|
|
23
|
+
// [--ingest <file.json>] [--no-ci] [-o evidence.json]
|
|
24
|
+
// [--exact-head <exact-head-artifact.json>]
|
|
25
|
+
// node evidence-block.mjs validate <evidence.json> [--source a,b,c]
|
|
26
|
+
//
|
|
27
|
+
// `--source` is COMMA-separated in a single argv token (`--source src,app,packages`).
|
|
28
|
+
// Space-separating it silently yields one bogus pathspec, and git exits 0 on a pathspec
|
|
29
|
+
// that matches nothing — so the production-diff claim would read EMPTY on every run and
|
|
30
|
+
// tell the orchestrator to skip the review round. Failing open is the one failure mode
|
|
31
|
+
// this file cannot afford, so the shape is spelled out here and in the CI templates.
|
|
32
|
+
//
|
|
33
|
+
// `collect` derives what it can run (git identity, scan-diff channels, CI jobs for
|
|
34
|
+
// the exact SHA, production-diff emptiness) and INGESTS what it cannot (repo-specific
|
|
35
|
+
// suite results — see --ingest below). Everything lands stamped with the same head.
|
|
36
|
+
//
|
|
37
|
+
// --ingest takes { "entries": [ { "name", "command", "result", "status", "sha"?,
|
|
38
|
+
// "reused"?, "note"? } ] }. `status` is Pass | Fail | Partial/Unverified. An entry
|
|
39
|
+
// whose sha differs from head must set `reused: true` and carry a note — that is the
|
|
40
|
+
// honest label for reusing a prior probe, and validate reports it rather than
|
|
41
|
+
// letting it read as fresh exact-head evidence.
|
|
42
|
+
|
|
43
|
+
import fs from 'node:fs'
|
|
44
|
+
import path from 'node:path'
|
|
45
|
+
import { execFileSync } from 'node:child_process'
|
|
46
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
47
|
+
|
|
48
|
+
import { validateExactHeadArtifact } from '../../engineering-runtime/scripts/exact-head-artifact.mjs'
|
|
49
|
+
import { ciAbsence, observeActionsRuns, startupFailures } from '../../engineering-runtime/scripts/head-check-set.mjs'
|
|
50
|
+
export const EVIDENCE_BLOCK_VERSION = 1
|
|
51
|
+
export const INGEST_STATUSES = ['Pass', 'Fail', 'Partial/Unverified']
|
|
52
|
+
|
|
53
|
+
// ---- pure core ----
|
|
54
|
+
|
|
55
|
+
// A stamped entry is fresh only when it was produced at the head being claimed.
|
|
56
|
+
// Anything else must be explicitly labelled as reuse; silence is the failure mode
|
|
57
|
+
// this whole module exists to remove.
|
|
58
|
+
export function classifyEntry(entry, headSha) {
|
|
59
|
+
if (!entry || typeof entry !== 'object') return { ok: false, errors: ['ingested entry must be an object'] }
|
|
60
|
+
const errors = []
|
|
61
|
+
for (const field of ['name', 'command', 'result']) {
|
|
62
|
+
if (typeof entry[field] !== 'string' || !entry[field].trim()) errors.push(`ingested entry requires a non-empty ${field}`)
|
|
63
|
+
}
|
|
64
|
+
if (!INGEST_STATUSES.includes(entry.status)) errors.push(`ingested entry "${entry.name}" needs status one of ${INGEST_STATUSES.join(' | ')}`)
|
|
65
|
+
const sha = entry.sha || headSha
|
|
66
|
+
const fresh = sha === headSha
|
|
67
|
+
if (!fresh && !entry.reused) errors.push(`ingested entry "${entry.name}" was produced at ${sha}, not head ${headSha} — mark it reused:true with a note or re-run it`)
|
|
68
|
+
if (!fresh && entry.reused && !String(entry.note || '').trim()) errors.push(`reused entry "${entry.name}" requires a note saying what it covers and why reuse is honest`)
|
|
69
|
+
if (entry.reused && entry.status === 'Pass') errors.push(`reused entry "${entry.name}" cannot be Pass — reuse is Partial/Unverified by definition`)
|
|
70
|
+
return { ok: errors.length === 0, errors, fresh, sha }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateBlock(block, { headSha, productionDiffEmpty = null } = {}) {
|
|
74
|
+
const errors = []
|
|
75
|
+
const warnings = []
|
|
76
|
+
if (!block || typeof block !== 'object') return { ok: false, errors: ['evidence block must be an object'], warnings }
|
|
77
|
+
if (block.version !== EVIDENCE_BLOCK_VERSION) errors.push(`unsupported evidence block version ${block.version}`)
|
|
78
|
+
if (!block.headSha) errors.push('evidence block requires a headSha stamp')
|
|
79
|
+
// The load-bearing check: a block that does not describe the current tree is not
|
|
80
|
+
// evidence about it. This is what a hand-written table could never enforce.
|
|
81
|
+
if (headSha && block.headSha && block.headSha !== headSha) {
|
|
82
|
+
errors.push(`evidence block is STALE: stamped ${block.headSha}, current HEAD is ${headSha} — re-run collect`)
|
|
83
|
+
}
|
|
84
|
+
for (const entry of block.ingested || []) {
|
|
85
|
+
const verdict = classifyEntry(entry, block.headSha)
|
|
86
|
+
errors.push(...verdict.errors)
|
|
87
|
+
if (verdict.ok && entry.reused) warnings.push(`reused (not fresh exact-head): ${entry.name} — ${entry.note}`)
|
|
88
|
+
if (verdict.ok && entry.status === 'Partial/Unverified' && !entry.reused) warnings.push(`Partial/Unverified: ${entry.name} — ${entry.note || 'no note given'}`)
|
|
89
|
+
}
|
|
90
|
+
// A WARNING and not an error, deliberately, and the distinction is #1082 AC4. A workflow
|
|
91
|
+
// GitHub refused to parse is red, but nothing declared it required, and turning that into
|
|
92
|
+
// an error here would make this block silently decide a merge policy that belongs to the
|
|
93
|
+
// human and to `.mergify.yml`. Silence is the only outcome that is not allowed.
|
|
94
|
+
for (const failure of block.ci?.startupFailures || []) {
|
|
95
|
+
warnings.push(
|
|
96
|
+
`WORKFLOW RAN ZERO JOBS at ${block.headSha}: run ${failure.runId} "${failure.name}" completed ${failure.conclusion} having executed no job at all`
|
|
97
|
+
+ `${failure.nameEqualsPath ? ', and its name equals its file path' : ''}`
|
|
98
|
+
+ ' — the signature of a workflow GitHub rejected at validation (#1081). It produces no check-run, so no check is red; this is not blocking here and it is not nothing.',
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
if (block.ci?.status === 'collected') {
|
|
102
|
+
for (const job of block.ci.skipped || []) warnings.push(`CI job SKIPPED (not passed): ${job}`)
|
|
103
|
+
if ((block.ci.failed || []).length) errors.push(`CI jobs failed at ${block.headSha}: ${block.ci.failed.join(', ')}`)
|
|
104
|
+
} else if (block.ci?.status) {
|
|
105
|
+
// Intentional: uncollected/unavailable CI is a WARNING, not an error, so
|
|
106
|
+
// `validate` still exits 0. This block is the *pre-spawn decidable check*, not
|
|
107
|
+
// the loop's exit gate — CI-green on the final HEAD is enforced separately (the
|
|
108
|
+
// loop exit condition), and a pre-spawn block legitimately runs before CI has
|
|
109
|
+
// been gathered. What must never read as green is a *collected* result with
|
|
110
|
+
// failures (errored above); "not yet collected" is honestly reported as such,
|
|
111
|
+
// never as pass, and the caller reads block.ci.status to see it.
|
|
112
|
+
warnings.push(`CI evidence ${block.ci.status} (not a pass — the exit gate, not this block, enforces CI-green on the final HEAD): ${block.ci.reason || 'no reason recorded'}`)
|
|
113
|
+
}
|
|
114
|
+
if (block.exactHead !== undefined) {
|
|
115
|
+
const verdict = validateExactHeadArtifact(block.exactHead, { expectedHeadSha: block.headSha })
|
|
116
|
+
errors.push(...verdict.errors.map((error) => `exact-head: ${error}`))
|
|
117
|
+
for (const job of block.exactHead.jobs || []) {
|
|
118
|
+
if (job.conclusion === 'skipped') warnings.push(`Exact-head job ${job.name} SKIPPED (not passed)`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Surfaced, never enforced: an empty production diff is the orchestrator's signal
|
|
122
|
+
// that a round is evidence-only and must not buy a full re-review round.
|
|
123
|
+
if (productionDiffEmpty === true) warnings.push('production diff is EMPTY since the last reviewed SHA — the first of the two evidence-lane signals; the lane also needs every surviving finding classed subject: evidence by the reviewer, and its fix runs as the constrained evidence-fix spawn, not an inline commit')
|
|
124
|
+
return { ok: errors.length === 0, errors, warnings }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function renderMarkdown(block) {
|
|
128
|
+
const lines = [`<!-- generated by evidence-block.mjs — do not hand-edit; re-run collect -->`,
|
|
129
|
+
`**Evidence at \`${block.headSha}\`** (base \`${block.baseSha}\`, collected ${block.collectedAt})`, '']
|
|
130
|
+
lines.push(`- Scan \`${block.baseSha}...${block.headSha}\`: findings ${JSON.stringify(block.scan?.findingCounts ?? {})}, ${block.scan?.leadCount ?? 0} heuristic leads ${JSON.stringify(block.scan?.leadCounts ?? {})}`)
|
|
131
|
+
if (block.ci?.status === 'collected') {
|
|
132
|
+
lines.push(`- CI at this exact SHA: ${block.ci.success.length} succeeded, ${block.ci.skipped.length} skipped, ${block.ci.failed.length} failed`)
|
|
133
|
+
if (block.ci.skipped.length) lines.push(` - skipped (NOT passed): ${block.ci.skipped.join('; ')}`)
|
|
134
|
+
if (block.ci.failed.length) lines.push(` - failed: ${block.ci.failed.join('; ')}`)
|
|
135
|
+
} else lines.push(`- CI at this exact SHA: ${block.ci?.status || 'not collected'}${block.ci?.ciState ? ` (${block.ci.ciState})` : ''} — ${block.ci?.reason || ''}`)
|
|
136
|
+
for (const failure of block.ci?.startupFailures || []) {
|
|
137
|
+
lines.push(` - **workflow ran ZERO jobs**: run ${failure.runId} \`${failure.name}\` (${failure.conclusion})${failure.nameEqualsPath ? ', name equals file path' : ''} — rejected at validation; it produces no check-run, so nothing is red (#1081). Not blocking here.`)
|
|
138
|
+
}
|
|
139
|
+
if (block.exactHead) {
|
|
140
|
+
const provenance = block.exactHead.collectionMode === 'choreographed'
|
|
141
|
+
? `collection choreographed; restoration ${block.exactHead.restoration?.proved ? 'proved' : 'unproved'}`
|
|
142
|
+
: `collection observed-existing-run; live head observed before and after provider evidence acquisition`
|
|
143
|
+
lines.push(`- Exact-head CI at this SHA: **${block.exactHead.summary?.outcome || 'unknown'}** — ${provenance}`)
|
|
144
|
+
for (const job of block.exactHead.jobs || []) {
|
|
145
|
+
if (job.conclusion === 'skipped') lines.push(` - ${job.name}: skipped (NOT passed)`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (block.productionDiff) lines.push(`- Production diff vs \`${block.productionDiff.since}\`: ${block.productionDiff.empty ? 'EMPTY (evidence-only round)' : 'non-empty'} over ${block.productionDiff.paths.join(', ')}`)
|
|
149
|
+
for (const entry of block.ingested || []) {
|
|
150
|
+
const tag = entry.reused ? ' _(reused, not fresh exact-head)_' : ''
|
|
151
|
+
lines.push(`- ${entry.name}: **${entry.status}** — \`${entry.command}\` → ${entry.result}${tag}${entry.note ? ` (${entry.note})` : ''}`)
|
|
152
|
+
}
|
|
153
|
+
return lines.join('\n')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---- impure edges (each isolated so the core above stays testable) ----
|
|
157
|
+
|
|
158
|
+
function git(args, cwd) {
|
|
159
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).trim()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// `collect` needs review-pr's scan-diff.mjs — a hop out of this skill into a sibling.
|
|
163
|
+
// The relative shape below covers both real layouts (a monorepo checkout, and skills
|
|
164
|
+
// installed side-by-side in ~/.claude/skills — Node resolves a symlinked module to its
|
|
165
|
+
// realpath, so a symlink farm lands in the monorepo case). It still misses when
|
|
166
|
+
// ensure-coverage is COPIED somewhere without review-pr, or under --preserve-symlinks.
|
|
167
|
+
//
|
|
168
|
+
// When it misses, say so and stop. The tempting alternative — carry on and record a
|
|
169
|
+
// scan channel of zero — is the `leadCount=0` incident with extra steps: "we did not
|
|
170
|
+
// scan" and "the scan found nothing" must never render identically, because the whole
|
|
171
|
+
// point of this file is that a reviewer can trust the numbers without re-deriving them.
|
|
172
|
+
export function resolveScanDiff(here, override, exists = fs.existsSync) {
|
|
173
|
+
if (override) {
|
|
174
|
+
if (exists(override)) return override
|
|
175
|
+
throw new Error(`--scan-diff points at ${override}, which does not exist`)
|
|
176
|
+
}
|
|
177
|
+
const candidate = path.resolve(here, '../../review-pr/scripts/scan-diff.mjs')
|
|
178
|
+
if (exists(candidate)) return candidate
|
|
179
|
+
throw new Error(
|
|
180
|
+
`cannot find review-pr's scan-diff.mjs (looked in ${candidate}). ` +
|
|
181
|
+
`The scan channel is load-bearing evidence, so this fails rather than reporting an empty scan. ` +
|
|
182
|
+
`Install review-pr beside ensure-coverage, or pass --scan-diff <path>.`,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function collectScan(baseSha, headSha, cwd, scanDiffPath) {
|
|
187
|
+
const diff = git(['diff', `${baseSha}...${headSha}`], cwd)
|
|
188
|
+
const raw = execFileSync(process.execPath, [scanDiffPath], { input: diff, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
189
|
+
const parsed = JSON.parse(raw)
|
|
190
|
+
const leadCount = Object.values(parsed.counts || {}).reduce((sum, n) => sum + n, 0)
|
|
191
|
+
return { findingCounts: parsed.findingCounts || {}, findings: parsed.findings || [], leadCounts: parsed.counts || {}, leadCount }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Best-effort: `gh` may be absent, unauthenticated, or the SHA may have no run yet.
|
|
195
|
+
// An unavailable CI channel is recorded as unavailable — never as "passed".
|
|
196
|
+
export function ghJson(args, cwd) {
|
|
197
|
+
const text = execFileSync('gh', args, { cwd, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 }).trim()
|
|
198
|
+
return text ? JSON.parse(text) : null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The CI channel, read as a QUESTION ABOUT THE SHA rather than about a run list.
|
|
203
|
+
*
|
|
204
|
+
* Two defects this closes, and they are the same defect seen from two sides (#1058, #1081):
|
|
205
|
+
*
|
|
206
|
+
* · "no workflow run found for <sha>" was the reported reason for three situations that
|
|
207
|
+
* call for opposite responses — the event was never delivered (waiting is futile), the
|
|
208
|
+
* run exists but no runner has picked it up (waiting is exactly right), and a run
|
|
209
|
+
* exists only because somebody dispatched it by hand (honest exact-head evidence that
|
|
210
|
+
* satisfies no queue condition). All three read as *still queued*. Now each carries
|
|
211
|
+
* `ciState` plus a reason a reader can act on without a second investigation.
|
|
212
|
+
* · A workflow GitHub refuses to parse contributes NOTHING to this channel: it produces
|
|
213
|
+
* a run with zero jobs and no check-run at all, so a job-by-job walk sees nothing and
|
|
214
|
+
* reports the SHA as clean. That is #1081, and it is why `startupFailures` is read
|
|
215
|
+
* from the run list rather than inferred from the jobs.
|
|
216
|
+
*
|
|
217
|
+
* `startupFailures` is a WARNING, never an error. It is not this block's business to
|
|
218
|
+
* decide that a red workflow nobody declared required blocks a merge — see head-check-set's
|
|
219
|
+
* header and #1082 AC4. What it must not be is silent.
|
|
220
|
+
*
|
|
221
|
+
* The run list moved from `gh run list` to the REST API for one reason: `path`. The
|
|
222
|
+
* corroborating half of the #1081 signature is a run whose `name` equals its file path,
|
|
223
|
+
* and `gh run list --json` cannot report the path.
|
|
224
|
+
*/
|
|
225
|
+
export function collectCi(headSha, cwd, api = ghJson) {
|
|
226
|
+
try {
|
|
227
|
+
const runs = api(['api', `repos/{owner}/{repo}/actions/runs?head_sha=${headSha}&per_page=100`], cwd)?.workflow_runs || []
|
|
228
|
+
const success = [], skipped = [], failed = [], pending = []
|
|
229
|
+
const counted = runs.map((run) => {
|
|
230
|
+
let jobs = null
|
|
231
|
+
try { jobs = api(['api', `repos/{owner}/{repo}/actions/runs/${run.id}/jobs?per_page=100`], cwd) } catch { jobs = null }
|
|
232
|
+
for (const job of jobs?.jobs || []) {
|
|
233
|
+
if (job.conclusion === 'success') success.push(job.name)
|
|
234
|
+
else if (job.conclusion === 'skipped') skipped.push(job.name)
|
|
235
|
+
else if (job.conclusion == null) pending.push(job.name)
|
|
236
|
+
else failed.push(`${job.name} (${job.conclusion})`)
|
|
237
|
+
}
|
|
238
|
+
// `total_count`, never the length of a page we may have failed to read: "we did not
|
|
239
|
+
// look" and "it ran nothing" are the two readings this whole function separates.
|
|
240
|
+
return { ...run, jobCount: Number.isInteger(jobs?.total_count) ? jobs.total_count : null }
|
|
241
|
+
})
|
|
242
|
+
const observations = observeActionsRuns(counted)
|
|
243
|
+
const zeroJob = startupFailures(observations)
|
|
244
|
+
if (success.length || skipped.length || failed.length || pending.length) {
|
|
245
|
+
return { status: 'collected', success, skipped, failed, pending, startupFailures: zeroJob }
|
|
246
|
+
}
|
|
247
|
+
const absence = ciAbsence(observations)
|
|
248
|
+
return { status: 'unavailable', ciState: absence.reason, reason: `${absence.reason} for ${headSha} — ${absence.detail}`, startupFailures: zeroJob }
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return { status: 'unavailable', ciState: 'unreadable', reason: String(error.message || error).split('\n')[0], startupFailures: [] }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function productionDiffEmpty(sinceSha, headSha, sourcePaths, cwd) {
|
|
255
|
+
try {
|
|
256
|
+
git(['diff', '--quiet', `${sinceSha}..${headSha}`, '--', ...sourcePaths], cwd)
|
|
257
|
+
return true
|
|
258
|
+
} catch {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function collect({ base, head, cwd = process.cwd(), sourcePaths = [], since = null, withCi = true, ingest = [], exactHead = null, scanDiffPath, now = new Date().toISOString() }) {
|
|
264
|
+
const headSha = head || git(['rev-parse', 'HEAD'], cwd)
|
|
265
|
+
const baseSha = base || git(['merge-base', 'HEAD@{upstream}', 'HEAD'], cwd)
|
|
266
|
+
const block = {
|
|
267
|
+
version: EVIDENCE_BLOCK_VERSION,
|
|
268
|
+
headSha,
|
|
269
|
+
baseSha,
|
|
270
|
+
collectedAt: now,
|
|
271
|
+
scan: collectScan(baseSha, headSha, cwd, scanDiffPath),
|
|
272
|
+
ci: withCi ? collectCi(headSha, cwd) : { status: 'not-collected', reason: '--no-ci' },
|
|
273
|
+
ingested: ingest,
|
|
274
|
+
}
|
|
275
|
+
if (exactHead) block.exactHead = exactHead
|
|
276
|
+
if (since && sourcePaths.length) {
|
|
277
|
+
block.productionDiff = { since, paths: sourcePaths, empty: productionDiffEmpty(since, headSha, sourcePaths, cwd) }
|
|
278
|
+
}
|
|
279
|
+
return block
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---- CLI ----
|
|
283
|
+
|
|
284
|
+
function parseArgs(argv) {
|
|
285
|
+
const positional = []
|
|
286
|
+
const opts = { ci: true }
|
|
287
|
+
for (let i = 0; i < argv.length; i++) {
|
|
288
|
+
const a = argv[i]
|
|
289
|
+
if (a === '--base') opts.base = argv[++i]
|
|
290
|
+
else if (a === '--head') opts.head = argv[++i]
|
|
291
|
+
else if (a === '--since') opts.since = argv[++i]
|
|
292
|
+
else if (a === '--source') opts.source = argv[++i]
|
|
293
|
+
else if (a === '--ingest') opts.ingest = argv[++i]
|
|
294
|
+
else if (a === '--exact-head') opts.exactHead = argv[++i]
|
|
295
|
+
else if (a === '--no-ci') opts.ci = false
|
|
296
|
+
else if (a === '-o' || a === '--out') opts.out = argv[++i]
|
|
297
|
+
else if (a === '--markdown') opts.markdown = true
|
|
298
|
+
else if (a === '--cwd') opts.cwd = argv[++i]
|
|
299
|
+
else if (a === '--scan-diff') opts.scanDiff = argv[++i]
|
|
300
|
+
else positional.push(a)
|
|
301
|
+
}
|
|
302
|
+
return { positional, opts }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function main(argv) {
|
|
306
|
+
const [cmd, ...rest] = argv
|
|
307
|
+
const { positional, opts } = parseArgs(rest)
|
|
308
|
+
const cwd = opts.cwd || process.cwd()
|
|
309
|
+
const sourcePaths = (opts.source || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
310
|
+
const here = path.dirname(decodeURIComponent(new URL(import.meta.url).pathname))
|
|
311
|
+
|
|
312
|
+
if (cmd === 'collect') {
|
|
313
|
+
if (!opts.base) { console.error('evidence-block collect: --base <sha> is required (the merge-base the PR is reviewed against)'); return 2 }
|
|
314
|
+
// Resolved inside `collect` only — `validate` and `render` never scan, so a repo
|
|
315
|
+
// without review-pr can still run the CI gate.
|
|
316
|
+
let scanDiffPath
|
|
317
|
+
try {
|
|
318
|
+
scanDiffPath = resolveScanDiff(here, opts.scanDiff)
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.error(`evidence-block collect: ${e.message}`)
|
|
321
|
+
return 2
|
|
322
|
+
}
|
|
323
|
+
let ingest = []
|
|
324
|
+
if (opts.ingest) {
|
|
325
|
+
const parsed = JSON.parse(fs.readFileSync(opts.ingest, 'utf8'))
|
|
326
|
+
ingest = Array.isArray(parsed) ? parsed : parsed.entries || []
|
|
327
|
+
}
|
|
328
|
+
const exactHead = opts.exactHead ? JSON.parse(fs.readFileSync(opts.exactHead, 'utf8')) : null
|
|
329
|
+
const block = collect({ base: opts.base, head: opts.head, cwd, sourcePaths, since: opts.since, withCi: opts.ci, ingest, exactHead, scanDiffPath })
|
|
330
|
+
const { ok, errors, warnings } = validateBlock(block, { headSha: block.headSha })
|
|
331
|
+
for (const w of warnings) console.error(`warn: ${w}`)
|
|
332
|
+
for (const e of errors) console.error(`error: ${e}`)
|
|
333
|
+
const out = opts.markdown ? renderMarkdown(block) : `${JSON.stringify(block, null, 2)}\n`
|
|
334
|
+
if (opts.out) fs.writeFileSync(opts.out, out.endsWith('\n') ? out : `${out}\n`)
|
|
335
|
+
else console.log(out)
|
|
336
|
+
return ok ? 0 : 1
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (cmd === 'validate') {
|
|
340
|
+
const file = positional[0]
|
|
341
|
+
if (!file) { console.error('evidence-block validate: <evidence.json> is required'); return 2 }
|
|
342
|
+
const block = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
343
|
+
const headSha = git(['rev-parse', 'HEAD'], cwd)
|
|
344
|
+
const empty = block.productionDiff?.since && sourcePaths.length
|
|
345
|
+
? productionDiffEmpty(block.productionDiff.since, headSha, sourcePaths, cwd)
|
|
346
|
+
: null
|
|
347
|
+
const { ok, errors, warnings } = validateBlock(block, { headSha, productionDiffEmpty: empty })
|
|
348
|
+
for (const w of warnings) console.error(`warn: ${w}`)
|
|
349
|
+
for (const e of errors) console.error(`error: ${e}`)
|
|
350
|
+
if (ok) console.error(`evidence-block: valid at ${headSha}`)
|
|
351
|
+
return ok ? 0 : 1
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (cmd === 'render') {
|
|
355
|
+
const file = positional[0]
|
|
356
|
+
if (!file) { console.error('evidence-block render: <evidence.json> is required'); return 2 }
|
|
357
|
+
console.log(renderMarkdown(JSON.parse(fs.readFileSync(file, 'utf8'))))
|
|
358
|
+
return 0
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
console.error('evidence-block: unknown command. Use collect|validate|render (see header).')
|
|
362
|
+
return 2
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (isMainModule(import.meta.url)) {
|
|
366
|
+
process.exit(main(process.argv.slice(2)))
|
|
367
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// lint-tests — static GATE lints over test files. The decidable subset of the
|
|
2
|
+
// skills' test-authoring doctrine, turned into deterministic checks that don't
|
|
3
|
+
// need an LLM: fast, free, robust, run in CI on every PR.
|
|
4
|
+
//
|
|
5
|
+
// node lint-tests.mjs --diff <base> # PR mode: only violations ADDED in base...HEAD (CI default)
|
|
6
|
+
// node lint-tests.mjs [--files a,b] # specific files
|
|
7
|
+
// node lint-tests.mjs # whole-tree scan of discovered test files
|
|
8
|
+
// --rules no-skip,no-fixed-timeout # run a subset (default: all gate rules)
|
|
9
|
+
// --json # machine output
|
|
10
|
+
//
|
|
11
|
+
// (Named lint-tests, not test-lint, so node's --test runner doesn't glob it as a
|
|
12
|
+
// test file via its `test-*` pattern and execute the CLI mid-suite.)
|
|
13
|
+
//
|
|
14
|
+
// SCOPE — what belongs here vs. not:
|
|
15
|
+
// GATE (here): syntactically decidable, ~zero false positives → safe to BLOCK.
|
|
16
|
+
// A noisy blocking lint gets disabled, so only near-certain rules live here.
|
|
17
|
+
// LEAD (NOT here): heuristic/judgment — "is this assertion meaningful?",
|
|
18
|
+
// over-mocking, asserting on an error page. Those stay LEADS for the reviewer
|
|
19
|
+
// (review-pr → scripts/scan-diff.mjs) and human/LLM judgment. Lint the
|
|
20
|
+
// decidable shell; leave the semantics to review.
|
|
21
|
+
//
|
|
22
|
+
// Adding a gate rule = append to RULES below (id + message + line patterns). Keep
|
|
23
|
+
// the false-positive bar high: if a legitimate test could trip it, it's a lead.
|
|
24
|
+
|
|
25
|
+
import fs from 'node:fs'
|
|
26
|
+
import path from 'node:path'
|
|
27
|
+
import { execSync } from 'node:child_process'
|
|
28
|
+
import { loadMockPolicyFromFile, extractMockTarget, classifyMockTarget } from './mock-policy-config.mjs'
|
|
29
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
30
|
+
|
|
31
|
+
// Skip / focus / expected-fail forms. `.only`/focus silently disable every OTHER
|
|
32
|
+
// test; skip/xfail/fixme disable this one. All banned (see test-skipping.md).
|
|
33
|
+
export const SKIP_PATTERNS = [
|
|
34
|
+
{ kind: 'skip', re: /\b(?:test|it|describe|suite|context)\s*\.\s*skip\b/ },
|
|
35
|
+
{ kind: 'only', re: /\b(?:test|it|describe|suite|context)\s*\.\s*only\b/ },
|
|
36
|
+
{ kind: 'fixme', re: /\b(?:test|it)\s*\.\s*fixme\b/ },
|
|
37
|
+
{ kind: 'skip', re: /\b(?:xit|xdescribe|xtest)\s*\(/ },
|
|
38
|
+
{ kind: 'only', re: /\b(?:fit|fdescribe|ftest)\s*\(/ },
|
|
39
|
+
{ kind: 'skip', re: /@(?:pytest\.mark\.)?skip(?:if)?\b/ },
|
|
40
|
+
{ kind: 'skip', re: /@unittest\.skip/ },
|
|
41
|
+
{ kind: 'skip', re: /\bpytest\.skip\s*\(/ },
|
|
42
|
+
{ kind: 'xfail', re: /@(?:pytest\.mark\.)?xfail\b/ },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
// Fixed sleeps — a timer instead of an explicit wait. The single biggest source of
|
|
46
|
+
// flake; doctrine is "never waitForTimeout()". Precise: these forms are ~never
|
|
47
|
+
// legitimate in a test (an aliased `cy.wait('@x')` or polling helper is not matched).
|
|
48
|
+
export const FIXED_TIMEOUT_PATTERNS = [
|
|
49
|
+
{ kind: 'waitForTimeout', re: /\.waitForTimeout\s*\(/ },
|
|
50
|
+
{ kind: 'cy.wait', re: /\bcy\.wait\s*\(\s*\d/ }, // numeric only; cy.wait('@alias') is fine
|
|
51
|
+
{ kind: 'time.sleep', re: /\btime\.sleep\s*\(/ },
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
// Empty test body — a `test`/`it` whose callback body is `{}`. A placeholder
|
|
55
|
+
// that passes vacuously and asserts nothing: the Missing-Assertion smell in its
|
|
56
|
+
// one decidable, ~zero-false-positive form. The BROADER "has statements but no
|
|
57
|
+
// assertion" case is NOT here — a test can assert through a helper, so a block
|
|
58
|
+
// with no `expect`/`assert` token may still assert; that stays a review LEAD
|
|
59
|
+
// (review-pr) and the test-smells doctrine ([../references/depth/test-smells.md]).
|
|
60
|
+
// Matches only single-line empty bodies (`() => {}` / `function () {}`), incl.
|
|
61
|
+
// `.skip`/`.each(...)`/`.concurrent` chains and Playwright `async ({ page })`
|
|
62
|
+
// fixtures; `describe`/`suite` are excluded (an empty suite is not a fake test).
|
|
63
|
+
// A multi-line empty body is rarer and deliberately left to the lead, not
|
|
64
|
+
// false-blocked.
|
|
65
|
+
export const EMPTY_TEST_PATTERNS = [
|
|
66
|
+
{ kind: 'empty-test', re: /\b(?:it|test)\s*(?:\.\s*\w+\s*(?:\([^)]*\)\s*)?)?\(\s*[`'"][^`'"]*[`'"]\s*,\s*(?:async\s+)?(?:function\s*\*?\s*)?\([^)]*\)\s*(?:=>\s*)?\{\s*\}\s*\)?/ },
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
// Swallowed assertion — an assertion matcher whose rejection is eaten by a
|
|
70
|
+
// chained `.catch(...)`, so the assertion can NEVER fail the test. The vacuous
|
|
71
|
+
// form behind the #237 login regression: `expect(locator).not.toBeVisible().catch(() => {})`
|
|
72
|
+
// passed whether or not the button was actually visible. Decidable and
|
|
73
|
+
// ~zero-false-positive: a Playwright web-first matcher (`.toXxx(...)`) directly
|
|
74
|
+
// chained to `.catch(` is never legitimate — a real assertion's rejection must
|
|
75
|
+
// surface, not be discarded. (Synchronous Jest matchers don't return a promise,
|
|
76
|
+
// so `.toBe(x).catch()` only appears as a deliberate swallow.) The broader
|
|
77
|
+
// "assertion guarded by a try/catch that re-passes" case stays a review LEAD
|
|
78
|
+
// (review-pr); only the directly-chained matcher form is gated here.
|
|
79
|
+
export const SWALLOWED_ASSERTION_PATTERNS = [
|
|
80
|
+
{ kind: 'swallowed-catch', re: /\.to[A-Z]\w*\s*\([^)]*\)\s*\.catch\s*\(/ },
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
export const RULES = [
|
|
84
|
+
{ id: 'no-skip', message: 'test skipped/focused — seed/quarantine/route/delete instead (test-skipping.md)', patterns: SKIP_PATTERNS },
|
|
85
|
+
{ id: 'no-fixed-timeout', message: 'fixed sleep — use an explicit wait (waitForResponse / expect.poll / web-first assertion), never a timer', patterns: FIXED_TIMEOUT_PATTERNS },
|
|
86
|
+
{ id: 'no-empty-test', message: 'empty test body — a placeholder that asserts nothing (passes vacuously); write the behavior assertion or delete it (test-smells.md)', patterns: EMPTY_TEST_PATTERNS },
|
|
87
|
+
{ id: 'no-swallowed-assertion', message: 'assertion failure swallowed by .catch() — the matcher can never fail (vacuous green); let it throw, or assert the real condition (test-smells.md)', patterns: SWALLOWED_ASSERTION_PATTERNS },
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
// Default-on GATE for over-mocking — the decidable core of the depth axis
|
|
91
|
+
// (mock-policy.md), mechanized via the externals-allowlist inversion
|
|
92
|
+
// (mock-policy-config.mjs). A `match`-style rule (not a line pattern): it pulls
|
|
93
|
+
// the mock target, exempts declared externals, and flags a mock
|
|
94
|
+
// of a first-party internal seam. ZERO-TOLERANCE: default-on, no waiver, and
|
|
95
|
+
// never silently inert — over-mock is a hard gate (mock-policy.md), not an
|
|
96
|
+
// opt-in. `coverage.config.json` only *declares topology* (a monorepo's own
|
|
97
|
+
// package scope, generated dirs to exempt); it cannot turn the gate off. With
|
|
98
|
+
// no config the rule runs on conventional `@/`/`~/` defaults. Run it diff-scoped
|
|
99
|
+
// in the PR lane (catch new seams) AND whole-tree (no inherited seam is
|
|
100
|
+
// grandfathered — the tree stays red until they are removed).
|
|
101
|
+
export const MOCK_INTERNAL_SEAM_RULE = {
|
|
102
|
+
id: 'mock-internal-seam',
|
|
103
|
+
defaultOn: true,
|
|
104
|
+
message: 'mocks an internal seam — real internals, faked externals (mock-policy.md): replace it with a real-boundary test, or fake the external instead. No waivers; no inherited grace.',
|
|
105
|
+
match(raw, code, ctx) {
|
|
106
|
+
const target = extractMockTarget(code)
|
|
107
|
+
if (!target) return null
|
|
108
|
+
// classifyMockTarget falls back to DEFAULT_MOCK_POLICY when ctx has no
|
|
109
|
+
// policy, so the gate can never be silently disabled by omitting config.
|
|
110
|
+
const { internal, reason } = classifyMockTarget(target, ctx && ctx.policy)
|
|
111
|
+
if (!internal) return null
|
|
112
|
+
return { kind: 'internal-seam', detail: target, reason }
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Every known rule. RULES is the original authoring-gate set; ALL_RULES is the
|
|
117
|
+
// full registry. mock-internal-seam is default-on (defaultOn:true), so it is in
|
|
118
|
+
// the default selection too — it is a standard gate now, not an opt-in.
|
|
119
|
+
export const ALL_RULES = [...RULES, MOCK_INTERNAL_SEAM_RULE]
|
|
120
|
+
|
|
121
|
+
export function selectRules(ruleIds) {
|
|
122
|
+
if (ruleIds && ruleIds.length > 0) {
|
|
123
|
+
const set = new Set(ruleIds)
|
|
124
|
+
return ALL_RULES.filter((r) => set.has(r.id))
|
|
125
|
+
}
|
|
126
|
+
// Default: every rule that isn't explicitly opted out (defaultOn:false).
|
|
127
|
+
return ALL_RULES.filter((r) => r.defaultOn !== false)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Strip line comments so a doc line or a commented-out example is not flagged.
|
|
131
|
+
// Best-effort (ignores strings/block comments) — live code has no leading `//`.
|
|
132
|
+
export function stripComment(line) {
|
|
133
|
+
return line.replace(/\/\/.*$/, '').replace(/(^|\s)#.*$/, '$1')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Skips/timeouts only matter in tests; linting app code would false-flag a
|
|
137
|
+
// legitimate `.skip()`/`.only` array method or a real `sleep`.
|
|
138
|
+
export function isTestFile(p) {
|
|
139
|
+
if (!p) return false
|
|
140
|
+
return /\.(spec|test)\.[cm]?[jt]sx?$/.test(p)
|
|
141
|
+
|| /(^|\/)(e2e|tests?|__tests__)\//.test(p)
|
|
142
|
+
|| /(^|\/)test_[^/]*\.py$/.test(p)
|
|
143
|
+
|| /_test\.py$/.test(p)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Pure: run the selected rules over one line → findings (≤ one per rule). `ctx`
|
|
147
|
+
// carries the resolved mock policy (declared topology) for `match`-style rules;
|
|
148
|
+
// pattern rules ignore it. A `match` rule sees the raw line and the
|
|
149
|
+
// comment-stripped code (so a commented-out example never trips).
|
|
150
|
+
function scanLine(file, raw, lineNo, rules, ctx) {
|
|
151
|
+
const code = stripComment(raw)
|
|
152
|
+
const out = []
|
|
153
|
+
for (const rule of rules) {
|
|
154
|
+
if (typeof rule.match === 'function') {
|
|
155
|
+
const hit = rule.match(raw, code, ctx)
|
|
156
|
+
if (hit) {
|
|
157
|
+
out.push({
|
|
158
|
+
file, line: lineNo, rule: rule.id, kind: hit.kind || rule.id,
|
|
159
|
+
snippet: raw.trim().slice(0, 120),
|
|
160
|
+
...(hit.detail ? { detail: hit.detail } : {}),
|
|
161
|
+
...(hit.reason ? { reason: hit.reason } : {}),
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
const hit = rule.patterns.find((p) => p.re.test(code))
|
|
167
|
+
if (hit) out.push({ file, line: lineNo, rule: rule.id, kind: hit.kind || rule.id, snippet: raw.trim().slice(0, 120) })
|
|
168
|
+
}
|
|
169
|
+
return out
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function scanLines(file, lines, ruleIds, ctx) {
|
|
173
|
+
const rules = selectRules(ruleIds)
|
|
174
|
+
const out = []
|
|
175
|
+
lines.forEach((raw, i) => out.push(...scanLine(file, raw, i + 1, rules, ctx)))
|
|
176
|
+
return out
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function scanText(file, text, ruleIds, ctx) {
|
|
180
|
+
return scanLines(file, (text || '').split(/\r?\n/), ruleIds, ctx)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Pure: scan a unified diff, flagging only ADDED lines in test files (inherited
|
|
184
|
+
// violations on context lines are left alone — review-pr diff-discipline).
|
|
185
|
+
export function scanDiff(diffText, ruleIds, ctx) {
|
|
186
|
+
const rules = selectRules(ruleIds)
|
|
187
|
+
const out = []
|
|
188
|
+
let file = null
|
|
189
|
+
let newLine = 0
|
|
190
|
+
for (const raw of (diffText || '').split(/\r?\n/)) {
|
|
191
|
+
if (raw.startsWith('+++ ')) {
|
|
192
|
+
const m = raw.match(/^\+\+\+ (?:b\/)?(.+)$/)
|
|
193
|
+
file = m && m[1] !== '/dev/null' ? m[1] : null
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
if (raw.startsWith('@@')) {
|
|
197
|
+
const m = raw.match(/\+(\d+)/)
|
|
198
|
+
newLine = m ? parseInt(m[1], 10) : 0
|
|
199
|
+
continue
|
|
200
|
+
}
|
|
201
|
+
if (raw.startsWith('+')) {
|
|
202
|
+
if (isTestFile(file)) out.push(...scanLine(file, raw.slice(1), newLine, rules, ctx))
|
|
203
|
+
newLine++
|
|
204
|
+
} else if (raw.startsWith('-') || raw.startsWith('\\')) {
|
|
205
|
+
// removed line / "No newline" marker — do not advance the new-file counter
|
|
206
|
+
} else {
|
|
207
|
+
newLine++ // context line
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return out
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- CLI ----
|
|
214
|
+
|
|
215
|
+
function discover(dir, acc = []) {
|
|
216
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
217
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
|
|
218
|
+
const full = path.join(dir, entry.name)
|
|
219
|
+
if (entry.isDirectory()) discover(full, acc)
|
|
220
|
+
else if (isTestFile(full)) acc.push(full)
|
|
221
|
+
}
|
|
222
|
+
return acc
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function runCli(argv, { name = 'lint-tests' } = {}) {
|
|
226
|
+
const flags = {}
|
|
227
|
+
for (let i = 0; i < argv.length; i++) {
|
|
228
|
+
const a = argv[i]
|
|
229
|
+
if (a === '--json') flags.json = true
|
|
230
|
+
else if (a === '--diff') flags.diff = argv[++i]
|
|
231
|
+
else if (a === '--files') flags.files = argv[++i]
|
|
232
|
+
else if (a === '--rules') flags.rules = argv[++i].split(',').map((s) => s.trim()).filter(Boolean)
|
|
233
|
+
else if (a === '--config') flags.config = argv[++i]
|
|
234
|
+
}
|
|
235
|
+
const ruleIds = flags.rules || null
|
|
236
|
+
// Resolve the project's mock policy once — this only declares *topology*
|
|
237
|
+
// (first-party scopes, exempt externals); it cannot disable the gate.
|
|
238
|
+
// `--config <file>` reads THAT file; otherwise discover coverage.config.json
|
|
239
|
+
// in cwd; with neither, the conventional `@/`/`~/` defaults apply.
|
|
240
|
+
const ctx = { policy: loadMockPolicyFromFile(flags.config) }
|
|
241
|
+
|
|
242
|
+
let findings = []
|
|
243
|
+
if (flags.diff) {
|
|
244
|
+
const diff = execSync(`git diff ${flags.diff}...HEAD`, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
245
|
+
findings = scanDiff(diff, ruleIds, ctx)
|
|
246
|
+
} else {
|
|
247
|
+
const files = flags.files
|
|
248
|
+
? flags.files.split(',').map((s) => s.trim()).filter(Boolean)
|
|
249
|
+
: discover('.')
|
|
250
|
+
for (const f of files) {
|
|
251
|
+
if (fs.existsSync(f)) findings.push(...scanText(f, fs.readFileSync(f, 'utf8'), ruleIds, ctx))
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (flags.json) {
|
|
256
|
+
console.log(JSON.stringify({ ok: findings.length === 0, findings }, null, 2))
|
|
257
|
+
} else if (findings.length === 0) {
|
|
258
|
+
console.log(`${name}: ✓ no violations` + (flags.diff ? ' introduced in this change' : ' in scanned test files'))
|
|
259
|
+
} else {
|
|
260
|
+
console.log(`${name}: ✗ ${findings.length} violation(s):\n`)
|
|
261
|
+
for (const f of findings) {
|
|
262
|
+
const why = f.detail ? ` → ${f.detail}${f.reason ? ` (${f.reason})` : ''}` : ''
|
|
263
|
+
console.log(` ${f.file}:${f.line} [${f.rule}/${f.kind}] ${f.snippet}${why}`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return findings.length === 0 ? 0 : 1
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (isMainModule(import.meta.url)) process.exit(runCli(process.argv.slice(2), { name: 'lint-tests' }))
|