@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,663 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Coverage Ledger reference tool for the ensure-coverage skill.
|
|
3
|
+
//
|
|
4
|
+
// node coverage-ledger.mjs aggregate # build the computed coverage-index
|
|
5
|
+
// node coverage-ledger.mjs validate # run reconciliation checks, exit non-zero on errors
|
|
6
|
+
// node coverage-ledger.mjs report # render a human-readable index + ADR matrix view
|
|
7
|
+
//
|
|
8
|
+
// It is framework-agnostic: it consumes a generated surface inventory (see
|
|
9
|
+
// references/breadth/inventory-contract.md) plus the curated per-requirement
|
|
10
|
+
// `*.coverage.yml` tree, and resolves evidence from `@covers` annotations in
|
|
11
|
+
// tests. Surface DISCOVERY is repo-specific and is NOT done here — supply the
|
|
12
|
+
// inventory via your own adapter (a reference Next.js one ships in adapters/).
|
|
13
|
+
//
|
|
14
|
+
// Curated files may be .yml/.yaml (needs `js-yaml`) or .json (zero-dep).
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
import process from 'node:process'
|
|
19
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
20
|
+
|
|
21
|
+
// ---------- config ----------
|
|
22
|
+
|
|
23
|
+
const DEFAULTS = {
|
|
24
|
+
inventory: 'docs/tests/_generated/surface-inventory.json',
|
|
25
|
+
coverageDir: 'docs/tests',
|
|
26
|
+
testRoots: ['.'],
|
|
27
|
+
indexOut: 'docs/tests/_generated/coverage-index.json',
|
|
28
|
+
reportOut: 'docs/tests/_generated/coverage-index.md',
|
|
29
|
+
// Optional acceptance-gate inputs. Left empty -> gate is skipped with a notice.
|
|
30
|
+
requirementGlobs: {}, // e.g. { adr: 'docs/adr', prd: 'docs/prd' }
|
|
31
|
+
acceptedMarker: /status:\s*accepted/i,
|
|
32
|
+
// A statically-adequate but un-attested test lands at `Unverified`. Doctrine
|
|
33
|
+
// makes that a BLOCKING state (a yellow that fails CI until a human attests
|
|
34
|
+
// via `verified: true`). Set false to phase in (downgrade Unverified to warn).
|
|
35
|
+
unverifiedBlocks: true,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', '.next', 'dist', 'build', 'coverage'])
|
|
39
|
+
const TEST_RE = /\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/
|
|
40
|
+
// Internal interception / first-party mocking that makes a surface LOOK covered
|
|
41
|
+
// while proving only proxy wiring. Playwright route faking plus MSW server/worker
|
|
42
|
+
// setup (MSW handlers for first-party paths are the non-Playwright vector).
|
|
43
|
+
const INTERCEPT_RE = /\broute\.fulfill\b|\bpage\.route\s*\(|\bsetupServer\s*\(|\bsetupWorker\s*\(|from\s+['"]msw['"]/
|
|
44
|
+
// Real-rendered-page evidence: a Presentation invariant must be proven against a
|
|
45
|
+
// browser that actually lays out and paints the page. These markers signal a real
|
|
46
|
+
// browser drive (Playwright). Their ABSENCE on a presentation-level test means the
|
|
47
|
+
// claim rests on the DOM/accessibility tree only — the axis's over-mock.
|
|
48
|
+
const BROWSER_RE = /@playwright\/test|\bplaywright\b|\bpage\.(goto|locator|evaluate|getByRole|getByText|getByLabel|setViewportSize|click|fill|type|press|check)\b|\bboundingBox\b|\btoHaveScreenshot\b/
|
|
49
|
+
// JSDOM / Testing-Library render: a non-painting DOM. Useful as an explicit
|
|
50
|
+
// negative signal for the DOM-only over-mock note.
|
|
51
|
+
const DOMONLY_RE = /\bjsdom\b|@testing-library|environment['"\s:]+jsdom/
|
|
52
|
+
|
|
53
|
+
// ---------- surface baseline contract ----------
|
|
54
|
+
//
|
|
55
|
+
// Every inventory surface carries a minimum required-level FLOOR keyed on its
|
|
56
|
+
// (kind, class). `class` is mechanically derived by the adapter (read/detail/
|
|
57
|
+
// mutation); humans may only RAISE the floor (curated requiredLevels, sensitive)
|
|
58
|
+
// or EXCLUDE a surface (gated below). Internal surfaces carry no floor — they are
|
|
59
|
+
// proven transitively and only dead-surface-warned when nothing reaches them.
|
|
60
|
+
// Presentation axis (third coverage axis — can a human perceive/act on the page,
|
|
61
|
+
// not just does the DOM exist). Every live browser route auto-carries a presentation
|
|
62
|
+
// floor on top of its functional browser-journey floor.
|
|
63
|
+
//
|
|
64
|
+
// This file GRADES presentation levels (does a qualifying real-render test exist,
|
|
65
|
+
// is it non-intercepting, is it attested) — it does NOT render the page. The
|
|
66
|
+
// instruments that actually open a browser and observe these invariants live in
|
|
67
|
+
// the e2e-test skill's presentation sweep; ensure-coverage owns the CONTRACT
|
|
68
|
+
// (references/presentation/runner-contract.md), not the runner.
|
|
69
|
+
//
|
|
70
|
+
// Levels, spine first:
|
|
71
|
+
// render-health — the page renders without a pageerror / console.error /
|
|
72
|
+
// error boundary / HTTP >=500. The cheapest, most foundational
|
|
73
|
+
// catch (an uncaught TypeError on mount fails here first), so it
|
|
74
|
+
// is the SPINE of the axis.
|
|
75
|
+
// spatial — no unintended horizontal overflow / element overlap.
|
|
76
|
+
// perceivability — contrast / invisible controls; negative: wrong-role/absent.
|
|
77
|
+
// reachability — navigational reachability + actionable affordance cues.
|
|
78
|
+
// temporal — layout shift (first-party hermetic; third-party sandbox lane).
|
|
79
|
+
const PRESENTATION_ROUTE_FLOOR = ['presentation:render-health', 'presentation:spatial', 'presentation:perceivability', 'presentation:reachability', 'presentation:temporal']
|
|
80
|
+
// The condition-matrix floor the presentation instruments run at. Feature-derived
|
|
81
|
+
// escalation adds cells (dark theme, RTL, …); see the Presentation axis section of
|
|
82
|
+
// SKILL.md. Kept here so the floor is one source of truth for the e2e instrument.
|
|
83
|
+
const PRESENTATION_VIEWPORT_FLOOR = [375, 1280]
|
|
84
|
+
|
|
85
|
+
const BASELINE = {
|
|
86
|
+
// browser routes: every live page must be driven (browser-journey floor);
|
|
87
|
+
// class only changes WHAT the journey must additionally assert (durable state
|
|
88
|
+
// for mutation, seeded entity for detail) — enforced via depth refutation.
|
|
89
|
+
// Every route also carries the Presentation floor (see PRESENTATION_ROUTE_FLOOR).
|
|
90
|
+
route: {
|
|
91
|
+
read: ['browser-journey', ...PRESENTATION_ROUTE_FLOOR],
|
|
92
|
+
detail: ['browser-journey', ...PRESENTATION_ROUTE_FLOOR],
|
|
93
|
+
mutation: ['browser-journey', ...PRESENTATION_ROUTE_FLOOR],
|
|
94
|
+
_default: ['browser-journey', ...PRESENTATION_ROUTE_FLOOR],
|
|
95
|
+
},
|
|
96
|
+
// bare API endpoints (no browser route): contract floor; writes also need durable-state.
|
|
97
|
+
api: { read: ['api'], mutation: ['api', 'integration'], _default: ['api'] },
|
|
98
|
+
permission: { _default: ['integration'] },
|
|
99
|
+
integration: { _default: ['integration'] },
|
|
100
|
+
// internal surfaces — no independent baseline obligation.
|
|
101
|
+
table: { _default: [] },
|
|
102
|
+
event: { _default: [] },
|
|
103
|
+
job: { _default: [] },
|
|
104
|
+
}
|
|
105
|
+
const INTERNAL_KINDS = new Set(['table', 'event', 'job'])
|
|
106
|
+
|
|
107
|
+
function baselineFor(kind, cls) {
|
|
108
|
+
const k = BASELINE[kind]
|
|
109
|
+
if (!k) return []
|
|
110
|
+
return k[cls] || k._default || []
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// `sensitive` (human-declared escalation): credential/admin/settings surfaces
|
|
114
|
+
// add a contract + auth obligation on top of the mechanical floor.
|
|
115
|
+
function sensitiveExtra(kind) {
|
|
116
|
+
if (kind === 'route') return ['api'] // page journey + API contract; auth asserted at depth
|
|
117
|
+
return []
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function strictest(...levelLists) {
|
|
121
|
+
return [...new Set(levelLists.flat().filter(Boolean))]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Exclusion is gated by adapter-confirmed signals, never by a human reason alone:
|
|
125
|
+
// a reachable write surface can NEVER be excluded below its journey floor.
|
|
126
|
+
function exclusionAllowed(kind, cls, reachability) {
|
|
127
|
+
if (kind === 'route') {
|
|
128
|
+
if (cls === 'read') return { ok: true } // static/content read pages may drop to render-health
|
|
129
|
+
if (reachability === 'blocked' || reachability === 'unreachable') return { ok: true }
|
|
130
|
+
return { ok: false, why: `reachable ${cls} route cannot be excluded (reachability=${reachability || 'unknown'}); a reachable write surface must keep its journey floor` }
|
|
131
|
+
}
|
|
132
|
+
if (kind === 'api') {
|
|
133
|
+
if (cls === 'mutation') return { ok: false, why: 'mutation API cannot be excluded' }
|
|
134
|
+
return { ok: true }
|
|
135
|
+
}
|
|
136
|
+
return { ok: true } // internal kinds are excludable
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// A required level is "behavior" (not satisfiable by unit/mock alone).
|
|
140
|
+
const BEHAVIOR_LEVELS = new Set([
|
|
141
|
+
'pg-api', 'api', 'integration', 'browser-journey', 'journey', 'ui-e2e', 'e2e',
|
|
142
|
+
'no-secret-assertion',
|
|
143
|
+
])
|
|
144
|
+
// Presentation levels are proven against a REAL rendered page (not the DOM tree).
|
|
145
|
+
// They get a stricter "clean test" rule than ordinary behavior levels: the proving
|
|
146
|
+
// test must drive a real browser (BROWSER_RE); a DOM-only/JSDOM test is Over-mocked.
|
|
147
|
+
// `render-health` is the spine (page renders without pageerror/console.error/error
|
|
148
|
+
// boundary/5xx); spatial, perceivability, reachability, temporal sit on top. These
|
|
149
|
+
// are the GRADED tokens; the e2e-test presentation sweep is the runner that emits
|
|
150
|
+
// pass/fail per (route, level) — see references/presentation/runner-contract.md.
|
|
151
|
+
const PRESENTATION_LEVELS = new Set([
|
|
152
|
+
'presentation:render-health',
|
|
153
|
+
'presentation:spatial',
|
|
154
|
+
'presentation:perceivability',
|
|
155
|
+
'presentation:reachability',
|
|
156
|
+
'presentation:temporal',
|
|
157
|
+
// `presentation:visual` is a graded presentation token like the rest, but it is
|
|
158
|
+
// OPT-IN, not part of the route floor (PRESENTATION_ROUTE_FLOOR above): a surface
|
|
159
|
+
// carries it only by declaring it in its curated requiredLevels. A pixel baseline
|
|
160
|
+
// is a maintenance liability, so which routes earn one is a per-surface breadth
|
|
161
|
+
// call — see references/presentation/runner-contract.md ("the opt-in level").
|
|
162
|
+
'presentation:visual',
|
|
163
|
+
// `presentation:fidelity` is the other opt-in token, and the only one that is not
|
|
164
|
+
// SELF-referential: every level above (visual included) takes the implementation
|
|
165
|
+
// as its own authority, so a page built wrong from a right design passes all of
|
|
166
|
+
// them — a pixel baseline shot of the wrong page is green forever. Fidelity asks
|
|
167
|
+
// whether the surface matches the design source it was DERIVED from, so a surface
|
|
168
|
+
// carries it only when its work item cites one (prototype/mockup/layout-fixing
|
|
169
|
+
// doc). Proven by a real-browser spec that drives `design-parity.mjs probe` and
|
|
170
|
+
// asserts via `design-parity.mjs validate --observed` — see
|
|
171
|
+
// references/presentation/axis.md ("presentation:fidelity").
|
|
172
|
+
'presentation:fidelity',
|
|
173
|
+
])
|
|
174
|
+
|
|
175
|
+
// Any `presentation:*` level (incl. cell-qualified ones like
|
|
176
|
+
// `presentation:perceivability@dark`) is a presentation level — proven against a
|
|
177
|
+
// real rendered page. Prefix-based so feature-derived escalation can mint new
|
|
178
|
+
// condition cells without enumerating every combination here.
|
|
179
|
+
function isPresentationLevel(level) {
|
|
180
|
+
return typeof level === 'string' && level.startsWith('presentation:')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Feature-derived escalation (Dimension B of the condition matrix). The adapter
|
|
184
|
+
// emits deterministic per-surface `signals`; each signal RAISES the floor by adding
|
|
185
|
+
// cell-qualified presentation levels. Pure + deterministic: same signals → same
|
|
186
|
+
// levels (sorted), so two agents on the same code compute identical obligations.
|
|
187
|
+
// - themeToggle → perceivability must also hold in dark theme
|
|
188
|
+
// - i18n → spatial must also hold under a long locale and RTL
|
|
189
|
+
function presentationEscalation(signals = {}) {
|
|
190
|
+
const out = []
|
|
191
|
+
if (signals.themeToggle) out.push('presentation:perceivability@dark')
|
|
192
|
+
if (signals.i18n) out.push('presentation:spatial@long-locale', 'presentation:spatial@rtl')
|
|
193
|
+
return [...new Set(out)].sort()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Gated removal of an escalated cell (Dimension B exclusion). A cell may be dropped
|
|
197
|
+
// only with a `category` + `reason`; the mechanical base floor (unqualified levels)
|
|
198
|
+
// can NEVER be removed this way, and every removal is recorded for the census.
|
|
199
|
+
// `cellExclusions` is a map: { 'presentation:perceivability@dark': { category, reason } }.
|
|
200
|
+
function applyCellExclusions(levels, cellExclusions = {}) {
|
|
201
|
+
const kept = [], census = [], rejected = []
|
|
202
|
+
for (const lvl of levels) {
|
|
203
|
+
const ex = cellExclusions[lvl]
|
|
204
|
+
if (!ex) { kept.push(lvl); continue }
|
|
205
|
+
if (!lvl.includes('@')) { rejected.push({ level: lvl, why: 'cannot exclude a base (unqualified) floor level' }); kept.push(lvl); continue }
|
|
206
|
+
if (!ex.category || !ex.reason) { rejected.push({ level: lvl, why: 'cell exclusion needs category + reason' }); kept.push(lvl); continue }
|
|
207
|
+
census.push({ level: lvl, category: ex.category, reason: ex.reason })
|
|
208
|
+
}
|
|
209
|
+
return { kept, census, rejected }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Synonyms: a test level satisfies a required level if it intersects this set.
|
|
213
|
+
const SYNONYMS = {
|
|
214
|
+
'pg-api': ['pg-api', 'integration', 'api', 'db'],
|
|
215
|
+
'api': ['api', 'pg-api', 'integration'],
|
|
216
|
+
'integration': ['integration', 'pg-api', 'api', 'db'],
|
|
217
|
+
'browser-journey': ['browser-journey', 'journey', 'e2e', 'ui-e2e'],
|
|
218
|
+
'journey': ['journey', 'browser-journey', 'e2e', 'ui-e2e'],
|
|
219
|
+
'ui-e2e': ['ui-e2e', 'e2e', 'browser-journey', 'journey'],
|
|
220
|
+
'e2e': ['e2e', 'journey', 'browser-journey', 'ui-e2e'],
|
|
221
|
+
'unit': ['unit'],
|
|
222
|
+
// `render-health` is a minimal BROWSER reachability/render floor (NOT a behavior
|
|
223
|
+
// level — see BEHAVIOR_LEVELS — and deliberately NOT satisfiable by `api`: a real
|
|
224
|
+
// browser must load the page and render it, not just return HTTP 200). A fuller
|
|
225
|
+
// browser journey also satisfies it. This is the breadth-floor sibling of the
|
|
226
|
+
// presentation spine `presentation:render-health` and of e2e-test's Tier-1
|
|
227
|
+
// (Render-health). `smoke` is kept as a back-compat alias (same floor, old name).
|
|
228
|
+
'render-health': ['render-health', 'smoke', 'browser-journey', 'journey', 'ui-e2e', 'e2e'],
|
|
229
|
+
'smoke': ['render-health', 'smoke', 'browser-journey', 'journey', 'ui-e2e', 'e2e'],
|
|
230
|
+
'no-secret-assertion': ['no-secret-assertion'],
|
|
231
|
+
// Presentation levels are exact-match: a `presentation:spatial` obligation is
|
|
232
|
+
// only satisfied by a test that declares `@level presentation:spatial`. A
|
|
233
|
+
// functional browser-journey does NOT satisfy it (a journey clicks the DOM; it
|
|
234
|
+
// does not assert the page is laid out without overflow).
|
|
235
|
+
'presentation:spatial': ['presentation:spatial'],
|
|
236
|
+
'presentation:perceivability': ['presentation:perceivability'],
|
|
237
|
+
'presentation:reachability': ['presentation:reachability'],
|
|
238
|
+
'presentation:temporal': ['presentation:temporal'],
|
|
239
|
+
// Opt-in pixel baseline — exact-match like its siblings (a functional journey or
|
|
240
|
+
// any other presentation level does not satisfy it). Only present on a surface
|
|
241
|
+
// whose curated requiredLevels raised it in.
|
|
242
|
+
'presentation:visual': ['presentation:visual'],
|
|
243
|
+
// Exact-match for the same reason, and one more: `presentation:visual` is the
|
|
244
|
+
// level most likely to be offered as a substitute ("we have a baseline, isn't
|
|
245
|
+
// that the same check?"). It is not — a baseline captured from the built page
|
|
246
|
+
// ratifies whatever shipped, which is precisely the failure fidelity exists to
|
|
247
|
+
// catch. Neither satisfies the other.
|
|
248
|
+
'presentation:fidelity': ['presentation:fidelity'],
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------- small fs helpers ----------
|
|
252
|
+
|
|
253
|
+
function* walk(dir) {
|
|
254
|
+
let entries
|
|
255
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return }
|
|
256
|
+
for (const e of entries) {
|
|
257
|
+
if (e.name.startsWith('.') && e.name !== '.') continue
|
|
258
|
+
const full = path.join(dir, e.name)
|
|
259
|
+
if (e.isDirectory()) {
|
|
260
|
+
if (IGNORE_DIRS.has(e.name)) continue
|
|
261
|
+
yield* walk(full)
|
|
262
|
+
} else {
|
|
263
|
+
yield full
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function loadStructured(file) {
|
|
269
|
+
const raw = fs.readFileSync(file, 'utf8')
|
|
270
|
+
if (file.endsWith('.json')) return JSON.parse(raw)
|
|
271
|
+
let yaml
|
|
272
|
+
try { ({ default: yaml } = await import('js-yaml')) }
|
|
273
|
+
catch { throw new Error(`Reading ${file} needs YAML support. Run \`npm install js-yaml\` in scripts/, or use .json coverage files.`) }
|
|
274
|
+
return yaml.load(raw)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------- loaders ----------
|
|
278
|
+
|
|
279
|
+
function loadInventory(cfg) {
|
|
280
|
+
if (!fs.existsSync(cfg.inventory)) {
|
|
281
|
+
throw new Error(`Inventory not found: ${cfg.inventory}. Generate it first (see references/breadth/inventory-contract.md).`)
|
|
282
|
+
}
|
|
283
|
+
const data = JSON.parse(fs.readFileSync(cfg.inventory, 'utf8'))
|
|
284
|
+
const surfaces = new Map() // id -> { kind, class, reachability }
|
|
285
|
+
if (Array.isArray(data.surfaces)) {
|
|
286
|
+
for (const s of data.surfaces) surfaces.set(s.id, { kind: s.kind || 'unknown', class: s.class || null, reachability: s.reachability || null, signals: s.signals || null })
|
|
287
|
+
} else {
|
|
288
|
+
// lenient grouped form: { routes:[...], apis:[...], tables:[...], events:[...] }
|
|
289
|
+
// (no class/reachability; entry surfaces fall back to the _default floor)
|
|
290
|
+
for (const [group, list] of Object.entries(data)) {
|
|
291
|
+
if (!Array.isArray(list)) continue
|
|
292
|
+
const kind = group.replace(/s$/, '')
|
|
293
|
+
for (const id of list) surfaces.set(id, { kind, class: null, reachability: null })
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return surfaces
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function loadCurated(cfg) {
|
|
300
|
+
const files = []
|
|
301
|
+
for (const f of walk(cfg.coverageDir)) {
|
|
302
|
+
if (!/\.coverage\.(ya?ml|json)$/.test(f)) continue
|
|
303
|
+
const source = path.basename(path.dirname(f)) // adr | prd | design | ...
|
|
304
|
+
const base = path.basename(f).replace(/\.coverage\.(ya?ml|json)$/, '')
|
|
305
|
+
const id = base.split('-')[0]
|
|
306
|
+
const ref = `${source}:${id}`
|
|
307
|
+
let doc
|
|
308
|
+
try { doc = await loadStructured(f) } catch (e) { throw new Error(`${f}: ${e.message}`) }
|
|
309
|
+
const surfaces = doc?.surfaces || {}
|
|
310
|
+
files.push({ file: f, source, id, ref, criteria: doc?.criteria || null, surfaces })
|
|
311
|
+
}
|
|
312
|
+
return files
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function scanTests(cfg) {
|
|
316
|
+
const index = new Map() // surfaceId -> [{file, levels:Set, intercepts}]
|
|
317
|
+
for (const root of cfg.testRoots) {
|
|
318
|
+
for (const f of walk(root)) {
|
|
319
|
+
if (!TEST_RE.test(f)) continue
|
|
320
|
+
const text = fs.readFileSync(f, 'utf8')
|
|
321
|
+
const covers = [...text.matchAll(/@covers\s+(\S+)/g)].map((m) => m[1].replace(/^route:/, ''))
|
|
322
|
+
if (covers.length === 0) continue
|
|
323
|
+
const levels = new Set([...text.matchAll(/@level\s+(\S+)/g)].map((m) => m[1]))
|
|
324
|
+
if (levels.size === 0) inferLevels(f).forEach((l) => levels.add(l))
|
|
325
|
+
const intercepts = INTERCEPT_RE.test(text)
|
|
326
|
+
const realBrowser = BROWSER_RE.test(text)
|
|
327
|
+
const domOnly = DOMONLY_RE.test(text)
|
|
328
|
+
for (const id of covers) {
|
|
329
|
+
if (!index.has(id)) index.set(id, [])
|
|
330
|
+
index.get(id).push({ file: f, levels, intercepts, realBrowser, domOnly })
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return index
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function inferLevels(file) {
|
|
338
|
+
const p = file.replace(/\\/g, '/')
|
|
339
|
+
if (/\/e2e\//.test(p)) return ['e2e']
|
|
340
|
+
if (/\/integration\//.test(p)) return ['integration']
|
|
341
|
+
if (/\/unit\//.test(p)) return ['unit']
|
|
342
|
+
return ['unit']
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---------- aggregation ----------
|
|
346
|
+
|
|
347
|
+
function satisfies(testLevels, required) {
|
|
348
|
+
const syn = SYNONYMS[required] || [required]
|
|
349
|
+
for (const tl of testLevels) if (syn.includes(tl)) return true
|
|
350
|
+
return false
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function depthStatus(requiredLevels, tests, opts = {}) {
|
|
354
|
+
if (tests.length === 0) return { status: 'Missing', note: 'no test annotated @covers this surface' }
|
|
355
|
+
const required = requiredLevels.length ? requiredLevels : ['unit']
|
|
356
|
+
let unmet = []
|
|
357
|
+
let blockedByInterception = false
|
|
358
|
+
let presentationDomOnly = [] // presentation levels proven without real-render evidence
|
|
359
|
+
for (const rl of required) {
|
|
360
|
+
const proving = tests.filter((t) => satisfies(t.levels, rl))
|
|
361
|
+
if (proving.length === 0) { unmet.push(rl); continue }
|
|
362
|
+
if (isPresentationLevel(rl)) {
|
|
363
|
+
// Presentation invariants must be proven against a real rendered page; a
|
|
364
|
+
// DOM-only / JSDOM test (no real browser drive) is the axis's over-mock.
|
|
365
|
+
const rendered = proving.filter((t) => t.realBrowser && !t.intercepts)
|
|
366
|
+
if (rendered.length === 0) { presentationDomOnly.push(rl); unmet.push(rl) }
|
|
367
|
+
} else if (BEHAVIOR_LEVELS.has(rl)) {
|
|
368
|
+
const clean = proving.filter((t) => !t.intercepts)
|
|
369
|
+
if (clean.length === 0) { blockedByInterception = true; unmet.push(rl) }
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (blockedByInterception) return { status: 'Over-mocked', note: `behavior level met only by internal interception / first-party mocking (route.fulfill/page.route/msw): ${unmet.join(', ')}` }
|
|
373
|
+
if (presentationDomOnly.length) return { status: 'Over-mocked', note: `presentation level proven without real rendered-page evidence (DOM-only/JSDOM — drive a real browser): ${presentationDomOnly.join(', ')}` }
|
|
374
|
+
if (unmet.length) return { status: 'Wrong Level', note: `required level(s) not proven: ${unmet.join(', ')}` }
|
|
375
|
+
// A self-declared @level can never reach Covered on its own. With reviewer
|
|
376
|
+
// attestation (`verified: true` in the curated file) it promotes; otherwise it
|
|
377
|
+
// lands at a BLOCKING Unverified.
|
|
378
|
+
if (opts.attested) return { status: 'Covered', note: 'required level(s) proven by clean tests and reviewer-attested (verified)' }
|
|
379
|
+
return { status: 'Unverified', note: 'level satisfied statically but not reviewer-attested — assertion/execution quality unconfirmed; add `verified: true` (with evidence) to the curated file to promote to Covered' }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Turn a depth status into a finding. Missing/Over-mocked/Wrong Level always
|
|
383
|
+
// fail; Unverified blocks the gate by default (see cfg.unverifiedBlocks).
|
|
384
|
+
function emitDepth(findings, cfg, sid, ds) {
|
|
385
|
+
if (ds.status === 'Missing' || ds.status === 'Over-mocked' || ds.status === 'Wrong Level') {
|
|
386
|
+
findings.push({ severity: 'error', kind: ds.status, surface: sid, detail: ds.note })
|
|
387
|
+
} else if (ds.status === 'Unverified') {
|
|
388
|
+
findings.push({ severity: cfg.unverifiedBlocks === false ? 'warn' : 'error', kind: 'Unverified', surface: sid, detail: ds.note })
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function build(cfg) {
|
|
393
|
+
const inventory = loadInventory(cfg)
|
|
394
|
+
const curated = await loadCurated(cfg)
|
|
395
|
+
const tests = scanTests(cfg)
|
|
396
|
+
|
|
397
|
+
const index = new Map() // surfaceId -> row
|
|
398
|
+
const findings = []
|
|
399
|
+
|
|
400
|
+
// owned surfaces from curated files
|
|
401
|
+
const owners = new Map() // surfaceId -> { refs, requiredLevels, files, sensitive, verified, exclude }
|
|
402
|
+
for (const cf of curated) {
|
|
403
|
+
if (cf.criteria && !fs.existsSync(cf.criteria)) {
|
|
404
|
+
findings.push({ severity: 'error', kind: 'Stale link', surface: '(criteria)', detail: `${cf.ref} criteria missing: ${cf.criteria}` })
|
|
405
|
+
}
|
|
406
|
+
for (const [sid, meta] of Object.entries(cf.surfaces)) {
|
|
407
|
+
if (!owners.has(sid)) owners.set(sid, { refs: new Set(), requiredLevels: new Set(), files: [], sensitive: false, verified: false, exclude: null, excludeCells: {} })
|
|
408
|
+
const o = owners.get(sid)
|
|
409
|
+
o.refs.add(cf.ref)
|
|
410
|
+
for (const l of meta?.requiredLevels || []) o.requiredLevels.add(l)
|
|
411
|
+
if (meta?.sensitive) o.sensitive = true
|
|
412
|
+
if (meta?.verified) o.verified = true
|
|
413
|
+
if (meta?.exclude) o.exclude = meta.exclude
|
|
414
|
+
if (meta?.excludeCells) Object.assign(o.excludeCells, meta.excludeCells)
|
|
415
|
+
o.files.push(cf.file)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Unrealized: curated surface absent from inventory
|
|
420
|
+
for (const [sid, o] of owners) {
|
|
421
|
+
if (!inventory.has(sid)) {
|
|
422
|
+
index.set(sid, { surface: sid, requirementRefs: [...o.refs], requiredLevels: [...o.requiredLevels], alignment: 'Unrealized requirement', depthStatus: null, evidence: [] })
|
|
423
|
+
findings.push({ severity: 'error', kind: 'Unrealized requirement', surface: sid, detail: `owned by ${[...o.refs].join(', ')} but not in inventory` })
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Inventory surfaces. Orphan is no longer a hard error: ownership (traceability
|
|
428
|
+
// to a requirement) is decoupled from the TEST obligation. The baseline floor
|
|
429
|
+
// auto-applies and is graded at depth whether or not a requirement owns it.
|
|
430
|
+
for (const [sid, surf] of inventory) {
|
|
431
|
+
const { kind, class: cls, reachability } = surf
|
|
432
|
+
const o = owners.get(sid)
|
|
433
|
+
const ts = tests.get(sid) || []
|
|
434
|
+
|
|
435
|
+
// Exclusion (human-declared, gated by adapter signals).
|
|
436
|
+
if (o?.exclude) {
|
|
437
|
+
const ex = o.exclude
|
|
438
|
+
const allowed = exclusionAllowed(kind, cls, reachability)
|
|
439
|
+
if (!ex.category || !ex.reason) {
|
|
440
|
+
findings.push({ severity: 'error', kind: 'Exclusion invalid', surface: sid, detail: 'exclude requires both `category` and `reason`' })
|
|
441
|
+
} else if (!allowed.ok) {
|
|
442
|
+
findings.push({ severity: 'error', kind: 'Exclusion rejected', surface: sid, detail: allowed.why })
|
|
443
|
+
} else {
|
|
444
|
+
// `static` keeps a minimal render-health floor (no surface ships with zero
|
|
445
|
+
// test); seed/deprecated/internal drop to manifest-only.
|
|
446
|
+
const residual = ex.category === 'static' ? ['render-health'] : []
|
|
447
|
+
if (residual.length) {
|
|
448
|
+
const ds = depthStatus(residual, ts, { attested: !!o.verified })
|
|
449
|
+
index.set(sid, { surface: sid, kind, class: cls, requirementRefs: [...o.refs].sort(), requiredLevels: residual, alignment: o ? 'Aligned' : 'Orphan surface', depthStatus: ds.status, depthNote: ds.note, evidence: ts.map((t) => t.file) })
|
|
450
|
+
findings.push({ severity: 'warn', kind: 'Excluded', surface: sid, detail: `${ex.category} → render-health floor: ${ex.reason}${ex.gapIssue ? ` (${ex.gapIssue})` : ''}` })
|
|
451
|
+
emitDepth(findings, cfg, sid, ds)
|
|
452
|
+
} else {
|
|
453
|
+
index.set(sid, { surface: sid, kind, class: cls, requirementRefs: [...o.refs].sort(), requiredLevels: [], alignment: 'Excluded', depthStatus: 'Excluded', evidence: ts.map((t) => t.file) })
|
|
454
|
+
findings.push({ severity: 'warn', kind: 'Excluded', surface: sid, detail: `${ex.category}: ${ex.reason}${ex.gapIssue ? ` (${ex.gapIssue})` : ''}` })
|
|
455
|
+
}
|
|
456
|
+
continue
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Internal surfaces carry no baseline; warn only when nothing reaches them.
|
|
461
|
+
if (INTERNAL_KINDS.has(kind)) {
|
|
462
|
+
const reached = ts.length > 0
|
|
463
|
+
index.set(sid, { surface: sid, kind, class: cls, requirementRefs: [...(o?.refs || [])].sort(), requiredLevels: [], alignment: o ? 'Aligned' : 'Orphan surface', depthStatus: reached ? 'Reached' : 'Dead', evidence: ts.map((t) => t.file) })
|
|
464
|
+
if (!reached) findings.push({ severity: 'warn', kind: 'Dead surface', surface: sid, detail: `${kind} reached by no test/entry evidence — likely dead or untracked` })
|
|
465
|
+
continue
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Entry surfaces: floor = strictest(baseline, curated raise, sensitive
|
|
469
|
+
// escalation, feature-derived presentation cells), minus any gated cell
|
|
470
|
+
// exclusions. The base mechanical floor can never be removed; only escalated
|
|
471
|
+
// (`@`-qualified) cells may be excluded with a category + reason.
|
|
472
|
+
const sensitive = !!o?.sensitive
|
|
473
|
+
const escalation = presentationEscalation(surf.signals || {})
|
|
474
|
+
const fullFloor = strictest(baselineFor(kind, cls), [...(o?.requiredLevels || [])], sensitive ? sensitiveExtra(kind) : [], escalation)
|
|
475
|
+
const { kept: required, census: cellCensus, rejected: cellRejected } = applyCellExclusions(fullFloor, o?.excludeCells || {})
|
|
476
|
+
for (const c of cellCensus) findings.push({ severity: 'warn', kind: 'Cell excluded', surface: sid, detail: `${c.level}: ${c.category} — ${c.reason}` })
|
|
477
|
+
for (const r of cellRejected) findings.push({ severity: 'error', kind: 'Cell exclusion rejected', surface: sid, detail: `${r.level}: ${r.why}` })
|
|
478
|
+
const ds = depthStatus(required, ts, { attested: !!o?.verified })
|
|
479
|
+
index.set(sid, {
|
|
480
|
+
surface: sid, kind, class: cls,
|
|
481
|
+
requirementRefs: [...(o?.refs || [])].sort(),
|
|
482
|
+
requiredLevels: required,
|
|
483
|
+
alignment: o ? 'Aligned' : 'Orphan surface',
|
|
484
|
+
depthStatus: ds.status,
|
|
485
|
+
depthNote: ds.note,
|
|
486
|
+
evidence: ts.map((t) => t.file),
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
// Ownership gap is a WARN (decoupled), and notes the baseline still applies.
|
|
490
|
+
if (!o) findings.push({ severity: 'warn', kind: 'Orphan surface', surface: sid, detail: `${kind} owned by no requirement; baseline ${required.join(' ') || '-'} still enforced` })
|
|
491
|
+
|
|
492
|
+
// Test obligation is the hard gate.
|
|
493
|
+
emitDepth(findings, cfg, sid, ds)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Acceptance gate (optional)
|
|
497
|
+
gateFindings(cfg, curated, findings)
|
|
498
|
+
|
|
499
|
+
return { cfg, index, findings }
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function gateFindings(cfg, curated, findings) {
|
|
503
|
+
const sources = Object.entries(cfg.requirementGlobs || {})
|
|
504
|
+
if (sources.length === 0) {
|
|
505
|
+
findings.push({ severity: 'info', kind: 'Gate skipped', surface: '-', detail: 'no requirementGlobs configured; acceptance gate not enforced' })
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
const haveCriteria = new Set(curated.map((c) => c.ref))
|
|
509
|
+
for (const [source, dir] of sources) {
|
|
510
|
+
for (const f of walk(dir)) {
|
|
511
|
+
if (!/\.mdx?$/.test(f)) continue
|
|
512
|
+
const text = fs.readFileSync(f, 'utf8')
|
|
513
|
+
if (!cfg.acceptedMarker.test(text)) continue
|
|
514
|
+
const id = path.basename(f).split('-')[0].replace(/\.mdx?$/, '')
|
|
515
|
+
const ref = `${source}:${id}`
|
|
516
|
+
const noRuntime = /no runtime behavior/i.test(text)
|
|
517
|
+
if (!haveCriteria.has(ref) && !noRuntime) {
|
|
518
|
+
findings.push({ severity: 'error', kind: 'Gate', surface: ref, detail: `accepted ${ref} has no ${source}/${id}-*.coverage.* file` })
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ---------- outputs ----------
|
|
525
|
+
|
|
526
|
+
function indexToObject(index) {
|
|
527
|
+
const out = {}
|
|
528
|
+
for (const [sid, row] of [...index].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
529
|
+
const { surface, ...rest } = row
|
|
530
|
+
out[sid] = rest
|
|
531
|
+
}
|
|
532
|
+
return out
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Over-mock rate (depth-axis honesty metric). A raw over-mock *count* rises just
|
|
536
|
+
// because the suite grew; a *rate* trends whether the tests that exist are honest.
|
|
537
|
+
// Denominator is surfaces that owe a depth check AND have tests — over-mock is a
|
|
538
|
+
// property of a test that exists but fakes its own internals, so a `Missing`
|
|
539
|
+
// surface (no test at all) is not over-mocked, just absent, and `Excluded`
|
|
540
|
+
// surfaces owe nothing. Numerator is the `Over-mocked` depthStatus the ledger
|
|
541
|
+
// already assigns (behavior level met only by interception, or a presentation
|
|
542
|
+
// level proven DOM-only). Split by axis via the note so a regression points at
|
|
543
|
+
// the right axis. `overMockRate` is 0 when nothing is tested (no honesty to
|
|
544
|
+
// measure), never NaN — a trend consumer can read it unconditionally.
|
|
545
|
+
const DEPTH_GRADED = new Set(['Missing', 'Over-mocked', 'Wrong Level', 'Covered', 'Unverified', 'Partial'])
|
|
546
|
+
function computeMetrics(index) {
|
|
547
|
+
const rows = [...index.values()]
|
|
548
|
+
// Only depth-graded surfaces count — not the `Reached`/`Dead` reachability
|
|
549
|
+
// states (events/jobs), `Excluded`, or `null` (unrealized requirements).
|
|
550
|
+
const graded = rows.filter((r) => DEPTH_GRADED.has(r.depthStatus))
|
|
551
|
+
const tested = graded.filter((r) => r.depthStatus !== 'Missing')
|
|
552
|
+
const overMocked = graded.filter((r) => r.depthStatus === 'Over-mocked')
|
|
553
|
+
const overMockedPresentation = overMocked.filter((r) => /presentation level/i.test(r.depthNote || '')).length
|
|
554
|
+
const overMockedBehavior = overMocked.length - overMockedPresentation
|
|
555
|
+
const rate = tested.length ? overMocked.length / tested.length : 0
|
|
556
|
+
return {
|
|
557
|
+
surfaces: index.size,
|
|
558
|
+
depthGraded: graded.length,
|
|
559
|
+
tested: tested.length,
|
|
560
|
+
overMocked: overMocked.length,
|
|
561
|
+
overMockedBehavior,
|
|
562
|
+
overMockedPresentation,
|
|
563
|
+
overMockRate: Math.round(rate * 1e4) / 1e4,
|
|
564
|
+
overMockRatePct: Math.round(rate * 1000) / 10,
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const fmtOverMock = (m) => `${m.overMocked}/${m.tested} tested surfaces over-mocked (${m.overMockRatePct}%)`
|
|
569
|
+
|
|
570
|
+
function renderReport(index) {
|
|
571
|
+
const rows = [...index.values()].sort((a, b) => a.surface.localeCompare(b.surface))
|
|
572
|
+
const line = (r) => `| \`${r.surface}\` | ${r.kind || '-'} | ${r.alignment} | ${r.depthStatus || '-'} | ${(r.requirementRefs || []).join(', ') || '-'} | ${(r.requiredLevels || []).join(' ') || '-'} | ${(r.evidence || []).length} |`
|
|
573
|
+
const out = []
|
|
574
|
+
out.push('# Coverage Index (generated — do not hand-edit)\n')
|
|
575
|
+
out.push(`**Over-mock rate:** ${fmtOverMock(computeMetrics(index))} — the share of tested surfaces whose depth level is met only by faking first-party internals (behavior interception or DOM-only presentation). Lower is healthier; the gate requires 0.\n`)
|
|
576
|
+
out.push('| Surface | Kind | Alignment | Depth | Requirements | Required levels | Evidence files |')
|
|
577
|
+
out.push('|---|---|---|---|---|---|---|')
|
|
578
|
+
for (const r of rows) out.push(line(r))
|
|
579
|
+
const adr = rows.filter((r) => (r.requirementRefs || []).some((x) => x.startsWith('adr:')))
|
|
580
|
+
if (adr.length) {
|
|
581
|
+
out.push('\n## ADR Coverage Matrix (view: rows with `adr:` refs)\n')
|
|
582
|
+
out.push('| Surface | Alignment | Depth | ADRs | Required levels |')
|
|
583
|
+
out.push('|---|---|---|---|---|')
|
|
584
|
+
for (const r of adr) out.push(`| \`${r.surface}\` | ${r.alignment} | ${r.depthStatus || '-'} | ${(r.requirementRefs).filter((x) => x.startsWith('adr:')).join(', ')} | ${(r.requiredLevels || []).join(' ') || '-'} |`)
|
|
585
|
+
}
|
|
586
|
+
return out.join('\n') + '\n'
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ---------- CLI ----------
|
|
590
|
+
|
|
591
|
+
function parseArgs(argv) {
|
|
592
|
+
const cmd = argv[2]
|
|
593
|
+
const cfg = { ...DEFAULTS }
|
|
594
|
+
const flags = {}
|
|
595
|
+
for (let i = 3; i < argv.length; i++) {
|
|
596
|
+
const a = argv[i]
|
|
597
|
+
if (a === '--strict') flags.strict = true
|
|
598
|
+
else if (a === '--warn-only') flags.warnOnly = true
|
|
599
|
+
else if (a.startsWith('--')) { flags[a.slice(2)] = argv[++i] }
|
|
600
|
+
}
|
|
601
|
+
if (flags.config && fs.existsSync(flags.config)) Object.assign(cfg, JSON.parse(fs.readFileSync(flags.config, 'utf8')))
|
|
602
|
+
if (flags.inventory) cfg.inventory = flags.inventory
|
|
603
|
+
if (flags['coverage-dir']) cfg.coverageDir = flags['coverage-dir']
|
|
604
|
+
if (flags['test-roots']) cfg.testRoots = flags['test-roots'].split(',')
|
|
605
|
+
if (flags.out) cfg.indexOut = cfg.reportOut = flags.out
|
|
606
|
+
if (cfg.acceptedMarker && typeof cfg.acceptedMarker === 'string') cfg.acceptedMarker = new RegExp(cfg.acceptedMarker, 'i')
|
|
607
|
+
return { cmd, cfg, flags }
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function ensureDir(p) { fs.mkdirSync(path.dirname(p), { recursive: true }) }
|
|
611
|
+
|
|
612
|
+
async function main() {
|
|
613
|
+
const { cmd, cfg, flags } = parseArgs(process.argv)
|
|
614
|
+
if (!['aggregate', 'validate', 'report'].includes(cmd)) {
|
|
615
|
+
console.error('usage: coverage-ledger <aggregate|validate|report> [--config f] [--inventory f] [--coverage-dir d] [--test-roots a,b] [--out f] [--strict] [--warn-only]')
|
|
616
|
+
process.exit(2)
|
|
617
|
+
}
|
|
618
|
+
const { index, findings } = await build(cfg)
|
|
619
|
+
|
|
620
|
+
if (cmd === 'aggregate') {
|
|
621
|
+
ensureDir(cfg.indexOut)
|
|
622
|
+
fs.writeFileSync(cfg.indexOut, JSON.stringify(indexToObject(index), null, 2) + '\n')
|
|
623
|
+
// Emit the metrics as a sibling artifact (not a key in the surface-keyed
|
|
624
|
+
// index, which consumers iterate) so CI / review-pr can trend the rate over
|
|
625
|
+
// time instead of only gating on zero.
|
|
626
|
+
const metrics = computeMetrics(index)
|
|
627
|
+
const metricsOut = path.join(path.dirname(cfg.indexOut), 'coverage-metrics.json')
|
|
628
|
+
fs.writeFileSync(metricsOut, JSON.stringify(metrics, null, 2) + '\n')
|
|
629
|
+
console.log(`wrote ${cfg.indexOut} (${index.size} surfaces) · ${metricsOut} (${fmtOverMock(metrics)})`)
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
if (cmd === 'report') {
|
|
633
|
+
ensureDir(cfg.reportOut)
|
|
634
|
+
fs.writeFileSync(cfg.reportOut, renderReport(index))
|
|
635
|
+
console.log(`wrote ${cfg.reportOut}`)
|
|
636
|
+
return
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// validate
|
|
640
|
+
const errors = findings.filter((f) => f.severity === 'error' || (flags.strict && f.severity === 'warn'))
|
|
641
|
+
const warns = findings.filter((f) => f.severity === 'warn' && !flags.strict)
|
|
642
|
+
const infos = findings.filter((f) => f.severity === 'info')
|
|
643
|
+
const fmt = (f) => ` [${f.kind}] ${f.surface}: ${f.detail}`
|
|
644
|
+
if (errors.length) { console.log(`\nERRORS (${errors.length}):`); errors.forEach((f) => console.log(fmt(f))) }
|
|
645
|
+
if (warns.length) { console.log(`\nWARNINGS (${warns.length}):`); warns.forEach((f) => console.log(fmt(f))) }
|
|
646
|
+
if (infos.length) infos.forEach((f) => console.log(`\nnote: ${f.detail}`))
|
|
647
|
+
const aligned = [...index.values()].filter((r) => r.alignment === 'Aligned').length
|
|
648
|
+
console.log(`\n${index.size} surfaces · ${aligned} aligned · ${fmtOverMock(computeMetrics(index))} · ${errors.length} errors · ${warns.length} warnings`)
|
|
649
|
+
if (errors.length && !flags.warnOnly) process.exit(1)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Run the CLI only when invoked directly; importing this module (e.g. from tests)
|
|
653
|
+
// must not execute main().
|
|
654
|
+
if (isMainModule(import.meta.url)) {
|
|
655
|
+
main().catch((e) => { console.error(`coverage-ledger: ${e.message}`); process.exit(2) })
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export {
|
|
659
|
+
build, depthStatus, baselineFor, sensitiveExtra, strictest, satisfies,
|
|
660
|
+
isPresentationLevel, presentationEscalation, applyCellExclusions, computeMetrics,
|
|
661
|
+
BASELINE, SYNONYMS, BEHAVIOR_LEVELS, PRESENTATION_LEVELS,
|
|
662
|
+
PRESENTATION_ROUTE_FLOOR, PRESENTATION_VIEWPORT_FLOOR,
|
|
663
|
+
}
|