@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,591 @@
|
|
|
1
|
+
// design-parity — the FIDELITY gate: does the deliverable match the design
|
|
2
|
+
// source it was derived from?
|
|
3
|
+
//
|
|
4
|
+
// node design-parity.mjs scaffold --source <ref>:<path> --surface <name> --url <path>
|
|
5
|
+
// [--role <name>=<selector> ...]
|
|
6
|
+
// → emits a manifest skeleton carrying EVERY catalog row for every declared
|
|
7
|
+
// role, each `expected: TODO`. The catalog is doctrine-fixed, so the rows
|
|
8
|
+
// you would have forgotten are the ones already in the file.
|
|
9
|
+
// node design-parity.mjs validate <manifest> [--observed <results.json>] [--diff <base>] [--root <dir>]
|
|
10
|
+
// → blocks on: a TODO row, a rejected row with no real reason, a `from:`
|
|
11
|
+
// citation that does not resolve in the source blob, a catalog row missing
|
|
12
|
+
// for a declared role, and — with --observed — any measured divergence.
|
|
13
|
+
// `--diff <base>` adds the re-baseline-to-green check: an `expected` value
|
|
14
|
+
// rewritten to match the page under an unmoved source is a blocker.
|
|
15
|
+
// node design-parity.mjs probe <manifest> --viewport <px>
|
|
16
|
+
// → prints the JS to evaluate in the page; its output IS the results file.
|
|
17
|
+
// Keeps this skill agent-agnostic: e2e-test, agent-browser or a raw
|
|
18
|
+
// Playwright call can drive it, none of them owns it.
|
|
19
|
+
//
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Why this is not a differ.
|
|
22
|
+
//
|
|
23
|
+
// The obvious tool here diffs the design source against the implementation. It
|
|
24
|
+
// only works when both are the same stack — and the incident that produced this
|
|
25
|
+
// script (fmm-express #521) is the case where they happened to be: a React+Tailwind
|
|
26
|
+
// prototype and a React+Tailwind page, so a class-token diff looked general when it
|
|
27
|
+
// was overfit. Point that differ at an HTML prototype, a v0 export or a Figma frame
|
|
28
|
+
// and it returns noise. Comparing ACROSS technologies is judgment and does not
|
|
29
|
+
// mechanize.
|
|
30
|
+
//
|
|
31
|
+
// So the work splits at that line. A human reads both, rendered, once, and writes
|
|
32
|
+
// down the load-bearing properties in units that survive a stack change — computed
|
|
33
|
+
// pixels, not `sm:text-3xl`. That manifest is the interchange format, and it is the
|
|
34
|
+
// only step that needs eyes. Everything after it — is the manifest complete, do its
|
|
35
|
+
// citations resolve, does the built page still measure that way — is mechanical and
|
|
36
|
+
// runs every round.
|
|
37
|
+
//
|
|
38
|
+
// Why FIDELITY and not drift. `presentation:visual` pins a page against a baseline
|
|
39
|
+
// captured FROM THE IMPLEMENTATION, so a baseline shot of the wrong page is green
|
|
40
|
+
// forever — in #521 it would have locked in the card plane and the flattened type
|
|
41
|
+
// scale and reported success. Here every `expected` is read off the SOURCE. That
|
|
42
|
+
// one difference is the whole point of the file.
|
|
43
|
+
//
|
|
44
|
+
// Named incident (fmm-express #521, 2026-08-06): a settled prototype was cited by
|
|
45
|
+
// exact path in the issue, summarized ONCE into four prose bullets, and every
|
|
46
|
+
// downstream artifact descended from the summary. What the summary dropped — page
|
|
47
|
+
// measure, card plane, the Amount input's responsive type steps, Description's
|
|
48
|
+
// scale — could never be found again, because nothing re-read the source and
|
|
49
|
+
// nothing rendered it (one image in a 3,040-message session, and the user pasted
|
|
50
|
+
// it). The criteria doc then listed "visual screenshot baselines" under Non-goals,
|
|
51
|
+
// so a fully green suite shipped a page that took four user corrections to fix.
|
|
52
|
+
// The manifest is that same act of summarizing, with an assertable output: "6px"
|
|
53
|
+
// either holds or it does not, where "the tip sits tight under Amount" was true of
|
|
54
|
+
// the broken page too.
|
|
55
|
+
|
|
56
|
+
import fs from 'node:fs'
|
|
57
|
+
import path from 'node:path'
|
|
58
|
+
import { execFileSync } from 'node:child_process'
|
|
59
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
60
|
+
|
|
61
|
+
// ---- The catalog (doctrine-fixed; you do not pick these per-unit) -----------
|
|
62
|
+
//
|
|
63
|
+
// These are the properties that carry a design across a re-implementation, chosen
|
|
64
|
+
// because each one is a thing #521 actually lost. A unit may REJECT a row with a
|
|
65
|
+
// written reason; it may not quietly omit one. Adding rows is free; the catalog is
|
|
66
|
+
// raise-only, mirroring the Surface Baseline Contract.
|
|
67
|
+
export const PARITY_CATALOG = [
|
|
68
|
+
{
|
|
69
|
+
id: 'container.measure', group: 'Container', role: 'container', css: ['max-width'],
|
|
70
|
+
why: 'the measure the page is laid out to — the first thing lost in a port, and invisible to every functional assertion (shipped max-w-md against a max-w-2xl source)',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'container.plane', group: 'Container', role: 'container',
|
|
74
|
+
css: ['background-color', 'border-top-width', 'box-shadow'],
|
|
75
|
+
why: 'card vs flat is one decision read through three properties; pinning only one lets a plane come back through the other two',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'container.padding', group: 'Container', role: 'container', css: ['padding-top', 'padding-left'],
|
|
79
|
+
why: 'a card that lost its border keeps its inset and still reads as a card',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'primary.scale', group: 'Hierarchy', role: 'primary', css: ['font-size'], perViewport: true,
|
|
83
|
+
why: 'the primary control carries the page. Per-viewport because a responsive step-up is exactly what a hand-port drops silently (source stepped 24px→30px; shipped held 24px)',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'primary.box', group: 'Hierarchy', role: 'primary', css: ['padding-top', 'border-radius'],
|
|
87
|
+
why: 'type scale alone does not make a control primary — its box has to grow with it',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'secondary.scale', group: 'Hierarchy', role: 'secondary', css: ['font-size'], perViewport: true,
|
|
91
|
+
why: 'a secondary field inheriting the shared input class reads at primary weight; that is the defect, and only a number catches it',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'secondary.box', group: 'Hierarchy', role: 'secondary', css: ['padding-top', 'border-radius'],
|
|
95
|
+
why: 'same shape as primary.box, from the other end',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'rhythm.coupled', group: 'Rhythm', role: 'coupled', css: ['margin-top'],
|
|
99
|
+
why: 'a tight gap says "this modifies that", a loose one says "next section" — the prose form ("sits tight under Amount") was true of the broken page too',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'hierarchy.order', group: 'Hierarchy', relation: 'primary.font-size > secondary.font-size',
|
|
103
|
+
why: 'the one row that survives both a stack change and a token change: whatever the numbers become, primary must out-scale secondary',
|
|
104
|
+
},
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
export const ROLES = ['container', 'primary', 'secondary', 'coupled']
|
|
108
|
+
|
|
109
|
+
// The presentation floor viewports (ensure-coverage → presentation/axis.md).
|
|
110
|
+
export const FLOOR_VIEWPORTS = [375, 1280]
|
|
111
|
+
|
|
112
|
+
// A reason has to be a reason. "n/a", "-", "none" are the ritual forms this exists
|
|
113
|
+
// to refuse — they are how a gated exclusion decays back into a silent omission.
|
|
114
|
+
const EMPTY_REASON = /^\s*(n\/?a|none|-+|todo|tbd|\.)?\s*$/i
|
|
115
|
+
|
|
116
|
+
export function isRealReason(s) {
|
|
117
|
+
const t = String(s ?? '').trim()
|
|
118
|
+
return t.length >= 12 && !EMPTY_REASON.test(t)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---- Manifest expansion ----------------------------------------------------
|
|
122
|
+
|
|
123
|
+
// Catalog × declared roles × viewports → the row set a complete manifest must
|
|
124
|
+
// carry. `validate` re-derives this rather than trusting the file: a manifest is
|
|
125
|
+
// hand-edited, and the edit that deletes an inconvenient row is the one to catch.
|
|
126
|
+
export function expectedRows(elements = {}, viewports = FLOOR_VIEWPORTS) {
|
|
127
|
+
const out = []
|
|
128
|
+
for (const entry of PARITY_CATALOG) {
|
|
129
|
+
if (entry.relation) {
|
|
130
|
+
// A relation is owed only when both of its operands are declared — otherwise
|
|
131
|
+
// it is a row nothing can ever measure, which trains readers to ignore rows.
|
|
132
|
+
const roles = entry.relation.split(/\s*(?:>=|<=|==|>|<)\s*/).map((s) => s.trim().split('.')[0])
|
|
133
|
+
if (roles.every((role) => elements[role])) out.push({ id: entry.id, relation: entry.relation })
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
if (!elements[entry.role]) continue // role not declared → its rows are not owed
|
|
137
|
+
for (const css of entry.css) {
|
|
138
|
+
if (entry.perViewport) for (const v of viewports) out.push({ id: entry.id, role: entry.role, css, viewport: v })
|
|
139
|
+
else out.push({ id: entry.id, role: entry.role, css })
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function rowKey(r) {
|
|
146
|
+
return r.relation
|
|
147
|
+
? `${r.id}|relation`
|
|
148
|
+
: `${r.id}|${r.role}|${r.css}|${r.viewport ?? '*'}`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---- Parsing ---------------------------------------------------------------
|
|
152
|
+
//
|
|
153
|
+
// .json is the zero-dep path (and what the tests use); .yml needs js-yaml, which
|
|
154
|
+
// scripts/ already carries for the ledger. Same split, same reason: a gate that
|
|
155
|
+
// cannot run without an install is a gate that gets skipped.
|
|
156
|
+
export async function parseText(raw, nameHint = '') {
|
|
157
|
+
if (/\.json$/i.test(nameHint)) return JSON.parse(raw)
|
|
158
|
+
let yaml
|
|
159
|
+
try { ({ default: yaml } = await import('js-yaml')) }
|
|
160
|
+
catch { throw new Error(`Reading ${nameHint || 'the manifest'} needs YAML support. Run \`npm install js-yaml\` in scripts/, or write the manifest as .json.`) }
|
|
161
|
+
return yaml.load(raw)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function loadManifest(file) {
|
|
165
|
+
return parseText(fs.readFileSync(file, 'utf8'), file)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---- Validation ------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
// `sourceResolver` mirrors coverage-checklist's `fileResolver`: it answers "does
|
|
171
|
+
// this citation point at something real", and when git cannot answer it says so
|
|
172
|
+
// rather than inventing a verdict. Every wrong block here costs a round the gate
|
|
173
|
+
// exists to save, so an unanswerable check downgrades to a warning.
|
|
174
|
+
export function sourceResolver(root, source) {
|
|
175
|
+
if (!source?.path) return null
|
|
176
|
+
const ref = source.sha || source.ref
|
|
177
|
+
if (!ref) return null
|
|
178
|
+
const cache = new Map()
|
|
179
|
+
return (citedPath, line) => {
|
|
180
|
+
// A citation may name the source file bare or with its full repo path.
|
|
181
|
+
const target = citedPath.endsWith(source.path) || source.path.endsWith(citedPath) ? source.path : citedPath
|
|
182
|
+
if (!cache.has(target)) {
|
|
183
|
+
try {
|
|
184
|
+
const blob = execFileSync('git', ['show', `${ref}:${target}`], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
185
|
+
cache.set(target, blob.split('\n').length)
|
|
186
|
+
} catch { cache.set(target, null) }
|
|
187
|
+
}
|
|
188
|
+
const lines = cache.get(target)
|
|
189
|
+
if (lines === null) return { ok: false, why: `no \`${target}\` in ${ref}` }
|
|
190
|
+
if (line != null && line > lines) return { ok: false, why: `${target} in ${ref} has ${lines} lines, cited :${line}` }
|
|
191
|
+
return { ok: true }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const CITATION_RE = /^([\w.][\w./-]*\.\w{1,12}):(\d+)$/
|
|
196
|
+
|
|
197
|
+
export function validateManifest(manifest, opts = {}) {
|
|
198
|
+
const violations = []
|
|
199
|
+
const warnings = []
|
|
200
|
+
const push = (row, problem) => violations.push({ row, problem })
|
|
201
|
+
|
|
202
|
+
const elements = manifest?.elements || {}
|
|
203
|
+
const viewports = manifest?.viewports?.length ? manifest.viewports : FLOOR_VIEWPORTS
|
|
204
|
+
const rows = manifest?.rows || []
|
|
205
|
+
|
|
206
|
+
// 0 — the manifest has to say what it was read off. A parity manifest with no
|
|
207
|
+
// resolvable source is a baseline wearing a fidelity label.
|
|
208
|
+
if (!manifest?.source?.path) push('source', 'no `source.path` — a parity manifest must name the design source it was read off')
|
|
209
|
+
if (!manifest?.source?.sha && !manifest?.source?.ref) push('source', 'no `source.sha` / `source.ref` — pin the design source at a commit; a moving ref is not a source')
|
|
210
|
+
if (!Object.keys(elements).length) push('elements', 'no `elements` — declare at least one role → selector')
|
|
211
|
+
for (const role of Object.keys(elements)) {
|
|
212
|
+
if (!ROLES.includes(role)) warnings.push({ row: `elements.${role}`, problem: `unknown role (catalog roles: ${ROLES.join(', ')}) — its rows are not derived, so nothing checks it` })
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 1 — completeness. The anti-omission device: a manifest missing a catalog row
|
|
216
|
+
// for a role it declared is the #521 failure in miniature (the container plane
|
|
217
|
+
// was never on anyone's list, so nobody ever decided against it).
|
|
218
|
+
const present = new Map(rows.map((r) => [rowKey(r), r]))
|
|
219
|
+
for (const owed of expectedRows(elements, viewports)) {
|
|
220
|
+
if (!present.has(rowKey(owed))) {
|
|
221
|
+
const entry = PARITY_CATALOG.find((c) => c.id === owed.id)
|
|
222
|
+
push(owed.id, `missing row ${rowKey(owed)} — ${entry?.why || 'in the catalog'}. Pin it or reject it with a reason; omission is not a decision`)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 2 — every row is decided, and decided against something checkable.
|
|
227
|
+
const resolve = opts.resolveSource
|
|
228
|
+
for (const r of rows) {
|
|
229
|
+
const label = rowKey(r)
|
|
230
|
+
const rejected = r.reject !== undefined && r.reject !== null
|
|
231
|
+
if (rejected) {
|
|
232
|
+
if (!isRealReason(r.reject)) push(label, `rejected with no real reason (\`reject: ${String(r.reject).trim() || '<empty>'}\`) — a gated exclusion needs an argument a reader can disagree with`)
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
if (r.expected === undefined || r.expected === null || /^\s*todo\s*$/i.test(String(r.expected))) {
|
|
236
|
+
if (!r.relation) { push(label, 'expected is TODO — read the value off the rendered source, or reject the row'); continue }
|
|
237
|
+
}
|
|
238
|
+
if (!r.from) { push(label, 'no `from:` citation — every expected value names the line of the source it was read off'); continue }
|
|
239
|
+
const m = CITATION_RE.exec(String(r.from).trim())
|
|
240
|
+
if (!m) { push(label, `\`from: ${r.from}\` is not a \`<path>:<line>\` citation`); continue }
|
|
241
|
+
if (resolve) {
|
|
242
|
+
const res = resolve(m[1], Number(m[2]))
|
|
243
|
+
if (!res.ok) push(label, `\`from: ${r.from}\` does not resolve — ${res.why}`)
|
|
244
|
+
} else {
|
|
245
|
+
warnings.push({ row: label, problem: 'citation not resolved (no git context) — shape checked only' })
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
violations.warnings = warnings
|
|
250
|
+
return violations
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---- Observed comparison ---------------------------------------------------
|
|
254
|
+
|
|
255
|
+
const LENGTH_RE = /^(-?\d+(?:\.\d+)?)px$/
|
|
256
|
+
const TOLERANCE_PX = 0.5 // sub-pixel layout is normal; 0.5px is not a design decision
|
|
257
|
+
|
|
258
|
+
export function normalize(value) {
|
|
259
|
+
const s = String(value ?? '').trim().toLowerCase().replace(/\s+/g, ' ')
|
|
260
|
+
const m = LENGTH_RE.exec(s)
|
|
261
|
+
return m ? { num: Number(m[1]) } : { str: s }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function valuesAgree(expected, observed) {
|
|
265
|
+
const e = normalize(expected)
|
|
266
|
+
const o = normalize(observed)
|
|
267
|
+
if (e.num !== undefined && o.num !== undefined) return Math.abs(e.num - o.num) <= TOLERANCE_PX
|
|
268
|
+
// A bare number in `expected` is a courtesy: "24" means 24px.
|
|
269
|
+
if (e.num === undefined && o.num !== undefined && /^-?\d+(\.\d+)?$/.test(e.str)) return Math.abs(Number(e.str) - o.num) <= TOLERANCE_PX
|
|
270
|
+
return e.str === o.str
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function observedIndex(observed) {
|
|
274
|
+
const idx = new Map()
|
|
275
|
+
for (const run of observed?.runs || []) {
|
|
276
|
+
for (const mm of run.measurements || []) idx.set(`${mm.role}|${mm.css}|${run.viewport}`, mm)
|
|
277
|
+
}
|
|
278
|
+
return idx
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function lookup(idx, role, css, viewport, viewports) {
|
|
282
|
+
if (viewport != null) return idx.get(`${role}|${css}|${viewport}`)
|
|
283
|
+
// A viewport-independent row is satisfied by any floor viewport that measured it,
|
|
284
|
+
// but it must AGREE across the ones that did — a property that is 0 at 375 and 24
|
|
285
|
+
// at 1280 is a responsive decision hiding in a row that claims not to be one.
|
|
286
|
+
const hits = viewports.map((v) => idx.get(`${role}|${css}|${v}`)).filter(Boolean)
|
|
287
|
+
if (!hits.length) return undefined
|
|
288
|
+
return hits.every((h) => valuesAgree(hits[0].value, h.value)) ? hits[0] : { ...hits[0], disagrees: hits }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function compareObserved(manifest, observed) {
|
|
292
|
+
const mismatches = []
|
|
293
|
+
const viewports = manifest?.viewports?.length ? manifest.viewports : FLOOR_VIEWPORTS
|
|
294
|
+
const idx = observedIndex(observed)
|
|
295
|
+
|
|
296
|
+
for (const r of manifest?.rows || []) {
|
|
297
|
+
if (r.reject !== undefined && r.reject !== null) continue
|
|
298
|
+
const label = rowKey(r)
|
|
299
|
+
|
|
300
|
+
if (r.relation) {
|
|
301
|
+
const parsed = /^\s*(\w+)\.([\w-]+)\s*(>|>=|<|<=|==)\s*(\w+)\.([\w-]+)\s*$/.exec(r.relation)
|
|
302
|
+
if (!parsed) { mismatches.push({ row: label, problem: `relation \`${r.relation}\` is not \`<role>.<css> <op> <role>.<css>\`` }); continue }
|
|
303
|
+
const [, lRole, lCss, op, rRole, rCss] = parsed
|
|
304
|
+
const l = lookup(idx, lRole, lCss, null, viewports)
|
|
305
|
+
const rr = lookup(idx, rRole, rCss, null, viewports)
|
|
306
|
+
if (!l || !rr) { mismatches.push({ row: label, problem: `not measured (${!l ? `${lRole}.${lCss}` : `${rRole}.${rCss}`} missing from the results)` }); continue }
|
|
307
|
+
const a = normalize(l.value).num
|
|
308
|
+
const b = normalize(rr.value).num
|
|
309
|
+
if (a === undefined || b === undefined) { mismatches.push({ row: label, problem: `relation needs two lengths, got ${l.value} and ${rr.value}` }); continue }
|
|
310
|
+
const ok = op === '>' ? a > b : op === '>=' ? a >= b : op === '<' ? a < b : op === '<=' ? a <= b : a === b
|
|
311
|
+
if (!ok) mismatches.push({ row: label, problem: `${lRole}.${lCss}=${l.value} is not ${op} ${rRole}.${rCss}=${rr.value}` })
|
|
312
|
+
continue
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const hit = lookup(idx, r.role, r.css, r.viewport, viewports)
|
|
316
|
+
if (!hit) { mismatches.push({ row: label, problem: 'not measured — the results file has no value for this row (element missing, or the probe did not run at this viewport)' }); continue }
|
|
317
|
+
if (hit.value === null || hit.value === undefined) { mismatches.push({ row: label, problem: `element not found on the page (\`${manifest.elements?.[r.role] || r.role}\`)` }); continue }
|
|
318
|
+
if (hit.disagrees) { mismatches.push({ row: label, problem: `pinned as viewport-independent but measures ${hit.disagrees.map((h) => `${h.value}@?`).join(' / ')} across viewports — split it per viewport` }); continue }
|
|
319
|
+
if (!valuesAgree(r.expected, hit.value)) {
|
|
320
|
+
mismatches.push({ row: label, problem: `expected ${r.expected}, page has ${hit.value}${r.from ? ` (source: ${r.from})` : ''}` })
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return mismatches
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ---- Expectation rewrites (the re-baseline-to-green analogue) ---------------
|
|
327
|
+
//
|
|
328
|
+
// The cheapest way to make this gate pass is to stop fixing the page and edit the
|
|
329
|
+
// manifest instead: change `expected: 672px` to `448px` and the divergence is gone.
|
|
330
|
+
// That is the same move as a bare `--update-snapshots`, which `review-pr` already
|
|
331
|
+
// blocks for `presentation:visual` — except here it is easier, because it is one
|
|
332
|
+
// character in a YAML file rather than a regenerated PNG, and it leaves no image in
|
|
333
|
+
// the diff for a reviewer to notice.
|
|
334
|
+
//
|
|
335
|
+
// It is also mechanically detectable, because of the one property that makes this
|
|
336
|
+
// fidelity and not drift: the source is pinned at a commit and is therefore
|
|
337
|
+
// IMMUTABLE. If `source.sha` has not moved and a row's `from:` line has not moved,
|
|
338
|
+
// then the value read off that line cannot have changed. A changed `expected` under
|
|
339
|
+
// those conditions is one of exactly two things — a mis-read being corrected (fine,
|
|
340
|
+
// and worth saying out loud) or a capitulation to the page (the thing to stop).
|
|
341
|
+
//
|
|
342
|
+
// So the rewrite is not forbidden; it is made to announce itself. `rebase: <reason>`
|
|
343
|
+
// on the row clears it, which is the same shape as the declared-intended-UI-change
|
|
344
|
+
// rule the visual baseline already uses. When the source itself moved, expectations
|
|
345
|
+
// are expected to change and this warns instead of blocking.
|
|
346
|
+
export function detectExpectationRewrites(prev, next) {
|
|
347
|
+
const out = []
|
|
348
|
+
if (!prev || !next) return out
|
|
349
|
+
const sourceMoved = (prev.source?.sha || prev.source?.ref) !== (next.source?.sha || next.source?.ref)
|
|
350
|
+
const before = new Map((prev.rows || []).map((r) => [rowKey(r), r]))
|
|
351
|
+
|
|
352
|
+
for (const r of next.rows || []) {
|
|
353
|
+
const was = before.get(rowKey(r))
|
|
354
|
+
if (!was) continue // new row: nothing was rewritten
|
|
355
|
+
if (was.expected === undefined || r.expected === undefined) continue
|
|
356
|
+
if (String(was.expected) === String(r.expected)) continue
|
|
357
|
+
|
|
358
|
+
const citationMoved = String(was.from ?? '') !== String(r.from ?? '')
|
|
359
|
+
const declared = isRealReason(r.rebase)
|
|
360
|
+
if (declared) continue
|
|
361
|
+
|
|
362
|
+
if (sourceMoved) {
|
|
363
|
+
out.push({ row: rowKey(r), level: 'warn', problem: `expected ${was.expected} → ${r.expected} and the design source moved (${prev.source?.sha} → ${next.source?.sha}) — plausible, but say so with \`rebase:\` so the reviewer is not re-deriving it` })
|
|
364
|
+
} else if (citationMoved) {
|
|
365
|
+
out.push({ row: rowKey(r), level: 'warn', problem: `expected ${was.expected} → ${r.expected} with a new citation (${was.from} → ${r.from}) — re-read from a different line; \`rebase:\` it if that was the correction` })
|
|
366
|
+
} else {
|
|
367
|
+
out.push({ row: rowKey(r), level: 'block', problem: `expected ${was.expected} → ${r.expected} with the SAME source (${next.source?.sha}) and the SAME citation (${r.from}). The source is pinned and cannot have changed, so this moves the target to the page instead of the page to the target. Fix the page, or state the mis-read with \`rebase: <reason>\`` })
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return out
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ---- Probe -----------------------------------------------------------------
|
|
374
|
+
//
|
|
375
|
+
// Emitted rather than executed: e2e-test owns browser runners, this skill owns
|
|
376
|
+
// what must be proven. The snippet is deliberately dependency-free so it pastes
|
|
377
|
+
// into Playwright's `page.evaluate`, agent-browser, or a devtools console alike.
|
|
378
|
+
export function probeScript(manifest, viewport) {
|
|
379
|
+
const wanted = []
|
|
380
|
+
const seen = new Set()
|
|
381
|
+
const viewports = manifest?.viewports?.length ? manifest.viewports : FLOOR_VIEWPORTS
|
|
382
|
+
for (const owed of expectedRows(manifest?.elements || {}, viewports)) {
|
|
383
|
+
if (owed.relation) continue
|
|
384
|
+
const k = `${owed.role}|${owed.css}`
|
|
385
|
+
if (seen.has(k)) continue
|
|
386
|
+
seen.add(k)
|
|
387
|
+
wanted.push({ role: owed.role, css: owed.css, selector: manifest.elements[owed.role] })
|
|
388
|
+
}
|
|
389
|
+
// Relational rows need their operands measured even when no plain row asks for them.
|
|
390
|
+
for (const entry of PARITY_CATALOG) {
|
|
391
|
+
if (!entry.relation) continue
|
|
392
|
+
for (const side of entry.relation.split(/\s*(?:>=|<=|==|>|<)\s*/)) {
|
|
393
|
+
const [role, css] = side.trim().split('.')
|
|
394
|
+
if (!manifest?.elements?.[role] || seen.has(`${role}|${css}`)) continue
|
|
395
|
+
seen.add(`${role}|${css}`)
|
|
396
|
+
wanted.push({ role, css, selector: manifest.elements[role] })
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return `(() => {
|
|
400
|
+
const want = ${JSON.stringify(wanted, null, 2).replace(/\n/g, '\n ')};
|
|
401
|
+
return {
|
|
402
|
+
surface: ${JSON.stringify(manifest?.surface || null)},
|
|
403
|
+
runs: [{
|
|
404
|
+
viewport: ${Number(viewport)},
|
|
405
|
+
measurements: want.map((w) => {
|
|
406
|
+
const el = document.querySelector(w.selector);
|
|
407
|
+
return { role: w.role, css: w.css, value: el ? getComputedStyle(el)[w.css] : null };
|
|
408
|
+
}),
|
|
409
|
+
}],
|
|
410
|
+
};
|
|
411
|
+
})()`
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ---- Scaffold --------------------------------------------------------------
|
|
415
|
+
//
|
|
416
|
+
// Rendered as a string rather than dumped from js-yaml, so each group carries the
|
|
417
|
+
// catalog's `why` into the file. The reason a row exists is the thing that stops it
|
|
418
|
+
// being deleted when it is inconvenient.
|
|
419
|
+
export function scaffoldManifest({ source, surface, url, elements = {}, viewports = FLOOR_VIEWPORTS }) {
|
|
420
|
+
const L = []
|
|
421
|
+
L.push(`# design-parity manifest — ${surface || '<surface>'}`)
|
|
422
|
+
L.push('#')
|
|
423
|
+
L.push('# FIDELITY, not drift: every `expected` below is read off the DESIGN SOURCE,')
|
|
424
|
+
L.push('# with it rendered in front of you — never off the implementation. A value')
|
|
425
|
+
L.push('# copied from the page you built is green forever, whatever it looks like.')
|
|
426
|
+
L.push('#')
|
|
427
|
+
L.push('# Fill `expected` in computed units (px), or `reject:` the row with a reason.')
|
|
428
|
+
L.push('# Omitting a row is not a decision and `validate` blocks on it.')
|
|
429
|
+
L.push('')
|
|
430
|
+
L.push('source:')
|
|
431
|
+
L.push(` ref: ${source?.ref || '<branch-or-tag>'}`)
|
|
432
|
+
L.push(` sha: ${source?.sha || '<commit-sha>'}`)
|
|
433
|
+
L.push(` path: ${source?.path || '<path/in/that/ref>'}`)
|
|
434
|
+
L.push(` kind: ${source?.kind || 'react'} # react | html | figma | image`)
|
|
435
|
+
L.push(`surface: ${surface || '<surface-id>'}`)
|
|
436
|
+
L.push(`url: ${url || '<route>'}`)
|
|
437
|
+
L.push(`viewports: [${viewports.join(', ')}]`)
|
|
438
|
+
L.push('')
|
|
439
|
+
L.push('elements:')
|
|
440
|
+
for (const role of ROLES) {
|
|
441
|
+
const sel = elements[role]
|
|
442
|
+
L.push(sel ? ` ${role}: '${sel}'` : ` # ${role}: '<selector>' # undeclared → its catalog rows are not owed`)
|
|
443
|
+
}
|
|
444
|
+
L.push('')
|
|
445
|
+
L.push('rows:')
|
|
446
|
+
const all = expectedRows(elements, viewports)
|
|
447
|
+
let group = null
|
|
448
|
+
for (const entry of PARITY_CATALOG) {
|
|
449
|
+
const owed = all.filter((r) => r.id === entry.id)
|
|
450
|
+
if (!owed.length) continue
|
|
451
|
+
if (entry.group !== group) { group = entry.group; L.push(` # ── ${group} ──`) }
|
|
452
|
+
L.push(` # ${entry.why}`)
|
|
453
|
+
for (const r of owed) {
|
|
454
|
+
L.push(` - id: ${r.id}`)
|
|
455
|
+
if (r.relation) {
|
|
456
|
+
// The relation IS the expectation — there is no value to fill in, only a
|
|
457
|
+
// line to cite. Emitting `expected: TODO` here would teach the reader that
|
|
458
|
+
// TODO is sometimes fine to leave.
|
|
459
|
+
L.push(` relation: '${r.relation}'`)
|
|
460
|
+
} else {
|
|
461
|
+
L.push(` role: ${r.role}`)
|
|
462
|
+
L.push(` css: ${r.css}`)
|
|
463
|
+
if (r.viewport != null) L.push(` viewport: ${r.viewport}`)
|
|
464
|
+
L.push(' expected: TODO')
|
|
465
|
+
}
|
|
466
|
+
L.push(` from: TODO # ${source?.path || '<source>'}:<line>`)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
L.push('')
|
|
470
|
+
return L.join('\n')
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ---- CLI -------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
function repoRoot(explicit) {
|
|
476
|
+
if (explicit) return explicit
|
|
477
|
+
try { return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim() }
|
|
478
|
+
catch { return process.cwd() }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function flag(argv, name) { const i = argv.indexOf(name); return i === -1 ? null : argv[i + 1] }
|
|
482
|
+
|
|
483
|
+
async function main() {
|
|
484
|
+
const [cmd, ...rest] = process.argv.slice(2)
|
|
485
|
+
|
|
486
|
+
if (cmd === 'scaffold') {
|
|
487
|
+
const src = flag(rest, '--source') || ''
|
|
488
|
+
const ci = src.lastIndexOf(':')
|
|
489
|
+
const ref = ci > 0 ? src.slice(0, ci) : ''
|
|
490
|
+
const p = ci > 0 ? src.slice(ci + 1) : ''
|
|
491
|
+
if (!ref || !p) { console.error('usage: design-parity.mjs scaffold --source <ref>:<path> --surface <name> --url <route> [--role primary=<selector> ...]'); process.exit(2) }
|
|
492
|
+
// If the ref will not resolve here (a prototype branch in another repo, a ref
|
|
493
|
+
// not fetched yet), leave the placeholder rather than copying the ref into
|
|
494
|
+
// `sha:`. A moving ref sitting in the pinned field looks pinned and is not —
|
|
495
|
+
// and the whole manifest rests on the source being a fixed commit.
|
|
496
|
+
let sha = null
|
|
497
|
+
try { sha = execFileSync('git', ['rev-parse', '--short', ref], { cwd: repoRoot(flag(rest, '--root')), encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() } catch { /* leave it unresolved */ }
|
|
498
|
+
if (!sha) console.error(`design-parity: could not resolve \`${ref}\` here — leaving \`sha:\` unfilled. Pin it before validating (\`git rev-parse --short ${ref}\`).`)
|
|
499
|
+
const elements = {}
|
|
500
|
+
for (let i = 0; i < rest.length; i++) {
|
|
501
|
+
if (rest[i] !== '--role') continue
|
|
502
|
+
const [role, ...sel] = String(rest[i + 1] || '').split('=')
|
|
503
|
+
if (role && sel.length) elements[role] = sel.join('=')
|
|
504
|
+
}
|
|
505
|
+
console.log(scaffoldManifest({
|
|
506
|
+
source: { ref, sha, path: p, kind: flag(rest, '--kind') || 'react' },
|
|
507
|
+
surface: flag(rest, '--surface'),
|
|
508
|
+
url: flag(rest, '--url'),
|
|
509
|
+
elements,
|
|
510
|
+
}))
|
|
511
|
+
process.exit(0)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (cmd === 'validate') {
|
|
515
|
+
const file = rest.find((a) => !a.startsWith('--'))
|
|
516
|
+
if (!file || !fs.existsSync(file)) { console.error('usage: design-parity.mjs validate <manifest.yml|.json> [--observed <results.json>] [--root <dir>]'); process.exit(2) }
|
|
517
|
+
const root = repoRoot(flag(rest, '--root'))
|
|
518
|
+
const manifest = await loadManifest(file)
|
|
519
|
+
const observedFile = flag(rest, '--observed')
|
|
520
|
+
|
|
521
|
+
const violations = validateManifest(manifest, { resolveSource: sourceResolver(root, manifest?.source) })
|
|
522
|
+
for (const w of violations.warnings) console.log(` ! ${w.row}: ${w.problem}`)
|
|
523
|
+
|
|
524
|
+
// Rewrite check: needs the manifest as it stood at the merge base. Silent when
|
|
525
|
+
// no --diff is given (the reduced form already says the run is partial), and
|
|
526
|
+
// never fatal on a git failure — a manifest added by this PR has no previous
|
|
527
|
+
// version, which is not a problem to report.
|
|
528
|
+
let rewrites = []
|
|
529
|
+
const base = flag(rest, '--diff')
|
|
530
|
+
if (base) {
|
|
531
|
+
// Both sides through realpath before differencing. `process.cwd()` returns a
|
|
532
|
+
// resolved path while an explicit --root is taken verbatim, so on any host
|
|
533
|
+
// where the tree sits behind a symlink (macOS `/var`→`/private/var`, and this
|
|
534
|
+
// skill family's own symlink-farm install) the naive relative path escapes the
|
|
535
|
+
// repo as `../../..` and `git show` silently finds nothing — which reads as
|
|
536
|
+
// "no previous version", i.e. the check passes exactly where it should fire.
|
|
537
|
+
const real = (p) => { try { return fs.realpathSync(p) } catch { return path.resolve(p) } }
|
|
538
|
+
const rel = path.relative(real(root), real(file))
|
|
539
|
+
try {
|
|
540
|
+
const prevRaw = execFileSync('git', ['show', `${base}:${rel}`], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
541
|
+
rewrites = detectExpectationRewrites(await parseText(prevRaw, file), manifest)
|
|
542
|
+
} catch { /* new manifest at this base — nothing to compare */ }
|
|
543
|
+
for (const w of rewrites.filter((x) => x.level === 'warn')) console.log(` ! ${w.row}: ${w.problem}`)
|
|
544
|
+
}
|
|
545
|
+
const blockingRewrites = rewrites.filter((x) => x.level === 'block')
|
|
546
|
+
|
|
547
|
+
let mismatches = []
|
|
548
|
+
if (observedFile) {
|
|
549
|
+
if (!fs.existsSync(observedFile)) { console.error(`design-parity: --observed ${observedFile} does not exist`); process.exit(2) }
|
|
550
|
+
mismatches = compareObserved(manifest, JSON.parse(fs.readFileSync(observedFile, 'utf8')))
|
|
551
|
+
} else {
|
|
552
|
+
// Same failure shape coverage-checklist hit: two runs of one command, opposite
|
|
553
|
+
// verdicts, and no way to tell from the output which question was asked. Without
|
|
554
|
+
// --observed this checks that the manifest is COMPLETE, never that the page
|
|
555
|
+
// conforms — and the pass is the misleading one, so it announces itself.
|
|
556
|
+
console.error('design-parity: NOTE — no --observed: this is the reduced form. It checks the manifest is complete and its citations resolve; it does NOT check the page. A green run here is not the green the reviewer will get.')
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (!violations.length && !mismatches.length && !blockingRewrites.length) {
|
|
560
|
+
const scope = observedFile ? `${(manifest.rows || []).length} row(s) hold against the rendered page` : `${(manifest.rows || []).length} row(s) decided and cited — manifest only, page unchecked`
|
|
561
|
+
console.log(`design-parity: ✓ ${scope}${violations.warnings.length ? ` (${violations.warnings.length} warning(s) above)` : ''}`)
|
|
562
|
+
process.exit(0)
|
|
563
|
+
}
|
|
564
|
+
if (violations.length) {
|
|
565
|
+
console.log(`design-parity: ✗ ${violations.length} manifest problem(s):\n`)
|
|
566
|
+
for (const v of violations) console.log(` • ${v.row}: ${v.problem}`)
|
|
567
|
+
}
|
|
568
|
+
if (blockingRewrites.length) {
|
|
569
|
+
console.log(`\ndesign-parity: ✗ ${blockingRewrites.length} expectation(s) rewritten to match the page:\n`)
|
|
570
|
+
for (const w of blockingRewrites) console.log(` • ${w.row}: ${w.problem}`)
|
|
571
|
+
}
|
|
572
|
+
if (mismatches.length) {
|
|
573
|
+
console.log(`\ndesign-parity: ✗ ${mismatches.length} divergence(s) from the design source:\n`)
|
|
574
|
+
for (const m of mismatches) console.log(` • ${m.row}: ${m.problem}`)
|
|
575
|
+
}
|
|
576
|
+
process.exit(1)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (cmd === 'probe') {
|
|
580
|
+
const file = rest.find((a) => !a.startsWith('--'))
|
|
581
|
+
const vp = flag(rest, '--viewport')
|
|
582
|
+
if (!file || !fs.existsSync(file) || !vp) { console.error('usage: design-parity.mjs probe <manifest.yml|.json> --viewport <px>'); process.exit(2) }
|
|
583
|
+
console.log(probeScript(await loadManifest(file), vp))
|
|
584
|
+
process.exit(0)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
console.error('usage: design-parity.mjs <scaffold|validate|probe>')
|
|
588
|
+
process.exit(2)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (isMainModule(import.meta.url)) await main()
|