@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,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// REFERENCE Next.js App Router discovery adapter — copy and adapt per repo.
|
|
3
|
+
// Emits a surface inventory satisfying references/breadth/inventory-contract.md.
|
|
4
|
+
//
|
|
5
|
+
// node nextjs-inventory.mjs <appDir> [--out docs/tests/_generated/surface-inventory.json] [--check]
|
|
6
|
+
//
|
|
7
|
+
// Routes come from `page.*` files; APIs from `route.*` files. Route groups
|
|
8
|
+
// `(group)` are stripped; dynamic segments `[id]`/`[...slug]` are kept as-is so
|
|
9
|
+
// ids are stable.
|
|
10
|
+
//
|
|
11
|
+
// It also emits the MECHANICAL taxonomy the Surface Baseline Contract needs:
|
|
12
|
+
// - class: route -> read | detail | mutation; api -> read | mutation
|
|
13
|
+
// - reachability: best-effort reachable | guarded (from the layout/middleware chain)
|
|
14
|
+
// These are deliberately conservative heuristics: the adapter may only emit the
|
|
15
|
+
// mechanical class (never `sensitive`/exclusion — those are human-declared). When
|
|
16
|
+
// in doubt about a write surface it biases to `mutation`, because under-calling a
|
|
17
|
+
// write form `read` would wrongly let it be excluded. Reachability here is a hint;
|
|
18
|
+
// the full Route Reachability Protocol (and human review) refine `blocked`/
|
|
19
|
+
// `unreachable`. It does NOT cover DB tables or events — extend for those kinds.
|
|
20
|
+
|
|
21
|
+
import fs from 'node:fs'
|
|
22
|
+
import path from 'node:path'
|
|
23
|
+
import process from 'node:process'
|
|
24
|
+
|
|
25
|
+
const IGNORE = new Set(['node_modules', '.git', '.next'])
|
|
26
|
+
const PAGE_RE = /^page\.(tsx|ts|jsx|js|mdx)$/
|
|
27
|
+
const ROUTE_RE = /^route\.(ts|js)$/
|
|
28
|
+
const LAYOUT_RE = /^layout\.(tsx|ts|jsx|js)$/
|
|
29
|
+
const COMPONENT_RE = /\.(tsx|ts|jsx|js|mdx)$/
|
|
30
|
+
|
|
31
|
+
// A page (or a component colocated in its route segment) that mutates state.
|
|
32
|
+
const WRITE_RE = /<form\b|onSubmit|action=\{|formAction|use(FormState|ActionState|Mutation)\b|type=["']submit["']|method:\s*["'](post|put|patch|delete)["']|fetch\([^)]*method\s*:\s*["'](post|put|patch|delete)|['"]use server['"]/i
|
|
33
|
+
// An API route file exporting a write method.
|
|
34
|
+
const METHOD_EXPORT_RE = /export\s+(?:async\s+function|const|let|var|function)\s+(POST|PUT|PATCH|DELETE)\b|export\s*\{[^}]*\b(POST|PUT|PATCH|DELETE)\b/
|
|
35
|
+
// Guard signals in a layout / middleware that gate reachability.
|
|
36
|
+
const GUARD_RE = /\bredirect\(|\bnotFound\(|getServerSession|\bauth\(\)|requireAuth|getToken|currentUser|\bunauthorized\(|clerkMiddleware|withAuth/i
|
|
37
|
+
|
|
38
|
+
function* walk(dir) {
|
|
39
|
+
let entries
|
|
40
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return }
|
|
41
|
+
for (const e of entries) {
|
|
42
|
+
const full = path.join(dir, e.name)
|
|
43
|
+
if (e.isDirectory()) { if (!IGNORE.has(e.name)) yield* walk(full) }
|
|
44
|
+
else yield full
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function read(file) { try { return fs.readFileSync(file, 'utf8') } catch { return '' } }
|
|
49
|
+
|
|
50
|
+
function toRoute(appDir, file) {
|
|
51
|
+
const rel = path.relative(appDir, path.dirname(file)).split(path.sep)
|
|
52
|
+
.filter((seg) => seg && !(seg.startsWith('(') && seg.endsWith(')'))) // drop route groups
|
|
53
|
+
.filter((seg) => !seg.startsWith('@')) // drop parallel route slots
|
|
54
|
+
.join('/')
|
|
55
|
+
return '/' + rel
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Mechanical class for a page: mutation > detail > read.
|
|
59
|
+
function routeClass(pageFile) {
|
|
60
|
+
const dir = path.dirname(pageFile)
|
|
61
|
+
let text = ''
|
|
62
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
63
|
+
if (!e.isFile() || !COMPONENT_RE.test(e.name)) continue
|
|
64
|
+
if (LAYOUT_RE.test(e.name) || ROUTE_RE.test(e.name)) continue
|
|
65
|
+
text += read(path.join(dir, e.name)) + '\n'
|
|
66
|
+
}
|
|
67
|
+
if (WRITE_RE.test(text)) return 'mutation'
|
|
68
|
+
if (/\[[^\]]+\]/.test(dir)) return 'detail' // dynamic segment -> renders one entity
|
|
69
|
+
return 'read'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Mechanical class for an API route: mutation if it exports a write method.
|
|
73
|
+
function apiClass(routeFile) {
|
|
74
|
+
return METHOD_EXPORT_RE.test(read(routeFile)) ? 'mutation' : 'read'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Best-effort reachability from the layout chain + root middleware.
|
|
78
|
+
function reachability(appDir, pageFile, hasMiddleware) {
|
|
79
|
+
if (hasMiddleware) return 'guarded'
|
|
80
|
+
let dir = path.dirname(pageFile)
|
|
81
|
+
const stop = path.resolve(appDir)
|
|
82
|
+
while (true) {
|
|
83
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
84
|
+
if (e.isFile() && LAYOUT_RE.test(e.name) && GUARD_RE.test(read(path.join(dir, e.name)))) return 'guarded'
|
|
85
|
+
}
|
|
86
|
+
if (path.resolve(dir) === stop) break
|
|
87
|
+
const parent = path.dirname(dir)
|
|
88
|
+
if (parent === dir) break
|
|
89
|
+
dir = parent
|
|
90
|
+
}
|
|
91
|
+
return 'reachable'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function detectMiddleware(appDir) {
|
|
95
|
+
const parent = path.dirname(appDir)
|
|
96
|
+
for (const base of [appDir, parent]) {
|
|
97
|
+
for (const m of ['middleware.ts', 'middleware.js']) {
|
|
98
|
+
if (fs.existsSync(path.join(base, m))) return true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Feature-derived presentation signals (Dimension B escalation). Detected
|
|
105
|
+
// deterministically at the app level and attached to every route, so the ledger
|
|
106
|
+
// raises the presentation floor (dark-theme cell, RTL/long-locale cells). This is
|
|
107
|
+
// the reference granularity; a stack can refine to per-route detection.
|
|
108
|
+
// - themeToggle: a `next-themes` dep or a useTheme/ThemeProvider reference
|
|
109
|
+
// - i18n: a known i18n library dependency
|
|
110
|
+
function detectSignals(appDir) {
|
|
111
|
+
const roots = [appDir, path.dirname(appDir)]
|
|
112
|
+
let pkg = {}
|
|
113
|
+
for (const base of roots) {
|
|
114
|
+
const p = path.join(base, 'package.json')
|
|
115
|
+
if (fs.existsSync(p)) { try { pkg = JSON.parse(fs.readFileSync(p, 'utf8')) } catch { /* ignore */ } break }
|
|
116
|
+
}
|
|
117
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
|
|
118
|
+
const dep = (re) => Object.keys(deps).some((d) => re.test(d))
|
|
119
|
+
const I18N_DEP = /^(next-intl|next-i18next|react-i18next|i18next|@lingui|react-intl|@formatjs)/
|
|
120
|
+
const THEME_DEP = /^(next-themes)$/
|
|
121
|
+
// a cheap source grep for theme toggling when there's no dedicated dep
|
|
122
|
+
let themeInSource = false
|
|
123
|
+
for (const f of walk(appDir)) {
|
|
124
|
+
if (!COMPONENT_RE.test(f)) continue
|
|
125
|
+
if (/\buseTheme\b|<ThemeProvider\b|data-theme=/.test(fs.readFileSync(f, 'utf8'))) { themeInSource = true; break }
|
|
126
|
+
}
|
|
127
|
+
const signals = {}
|
|
128
|
+
if (dep(THEME_DEP) || themeInSource) signals.themeToggle = true
|
|
129
|
+
if (dep(I18N_DEP)) signals.i18n = true
|
|
130
|
+
return Object.keys(signals).length ? signals : null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function build(appDir) {
|
|
134
|
+
const hasMiddleware = detectMiddleware(appDir)
|
|
135
|
+
const signals = detectSignals(appDir)
|
|
136
|
+
const surfaces = []
|
|
137
|
+
for (const f of walk(appDir)) {
|
|
138
|
+
const base = path.basename(f)
|
|
139
|
+
if (PAGE_RE.test(base)) {
|
|
140
|
+
const s = { id: toRoute(appDir, f), kind: 'route', class: routeClass(f), reachability: reachability(appDir, f, hasMiddleware) }
|
|
141
|
+
if (signals) s.signals = signals
|
|
142
|
+
surfaces.push(s)
|
|
143
|
+
} else if (ROUTE_RE.test(base)) {
|
|
144
|
+
surfaces.push({ id: toRoute(appDir, f), kind: 'api', class: apiClass(f) })
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// de-dupe by id (e.g. page + nested layout edge cases), keep strongest class.
|
|
148
|
+
const byId = new Map()
|
|
149
|
+
const rank = { mutation: 3, detail: 2, read: 1, undefined: 0 }
|
|
150
|
+
for (const s of surfaces) {
|
|
151
|
+
const prev = byId.get(s.id)
|
|
152
|
+
if (!prev || (rank[s.class] || 0) > (rank[prev.class] || 0)) byId.set(s.id, s)
|
|
153
|
+
}
|
|
154
|
+
return { surfaces: [...byId.values()].sort((a, b) => a.id.localeCompare(b.id)) }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function main() {
|
|
158
|
+
const [, , appDir, ...rest] = process.argv
|
|
159
|
+
if (!appDir) { console.error('usage: nextjs-inventory.mjs <appDir> [--out file] [--check]'); process.exit(2) }
|
|
160
|
+
let out = 'docs/tests/_generated/surface-inventory.json'
|
|
161
|
+
let check = false
|
|
162
|
+
for (let i = 0; i < rest.length; i++) {
|
|
163
|
+
if (rest[i] === '--out') out = rest[++i]
|
|
164
|
+
else if (rest[i] === '--check') check = true
|
|
165
|
+
}
|
|
166
|
+
const inv = build(appDir)
|
|
167
|
+
const json = JSON.stringify(inv, null, 2) + '\n'
|
|
168
|
+
if (check) {
|
|
169
|
+
const prev = fs.existsSync(out) ? fs.readFileSync(out, 'utf8') : ''
|
|
170
|
+
if (prev !== json) { console.error(`inventory stale: ${out} does not match discovered surfaces. Re-run without --check.`); process.exit(1) }
|
|
171
|
+
console.log(`inventory up to date (${inv.surfaces.length} surfaces)`); return
|
|
172
|
+
}
|
|
173
|
+
fs.mkdirSync(path.dirname(out), { recursive: true })
|
|
174
|
+
fs.writeFileSync(out, json)
|
|
175
|
+
console.log(`wrote ${out} (${inv.surfaces.length} surfaces)`)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// check-quarantine-expiry — the auto-resurface gate for dated exits.
|
|
2
|
+
//
|
|
3
|
+
// A quarantine (or any deferred-but-tracked suppression) is a holding pen with a
|
|
4
|
+
// dated exit, never a destination. The convention is a single marker, anywhere in
|
|
5
|
+
// a file (a test-file comment, a suppressions list, an ADR):
|
|
6
|
+
//
|
|
7
|
+
// @quarantine(until=2026-07-01, issue=#123) // canonical
|
|
8
|
+
// until=2026-07-01 // also matched (bare)
|
|
9
|
+
// until: 2026-07-01 // also matched (yaml-style)
|
|
10
|
+
//
|
|
11
|
+
// Until the date, the item is allowed to sit non-blocking. On/after the date it
|
|
12
|
+
// EXPIRES: this gate fails, so the item re-blocks until it is fixed or the date is
|
|
13
|
+
// extended with a stated reason. Run it in the BLOCKING lane.
|
|
14
|
+
//
|
|
15
|
+
// node check-quarantine-expiry.mjs --files a,b # specific files
|
|
16
|
+
// node check-quarantine-expiry.mjs # whole-tree scan of test files
|
|
17
|
+
// node check-quarantine-expiry.mjs --now 2026-06-08 # pin "today" (default: system date; for tests/CI determinism)
|
|
18
|
+
// --json # machine output
|
|
19
|
+
//
|
|
20
|
+
// Exit 0 = no expired exits. Exit 1 = at least one expired (the gate blocks).
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs'
|
|
23
|
+
import path from 'node:path'
|
|
24
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
25
|
+
|
|
26
|
+
// Marker forms: `until=YYYY-MM-DD` or `until: YYYY-MM-DD` (case-insensitive key).
|
|
27
|
+
const UNTIL = /\buntil\s*[:=]\s*(\d{4}-\d{2}-\d{2})\b/gi
|
|
28
|
+
// Pull an issue ref from the same line when present, for the report.
|
|
29
|
+
const ISSUE = /\bissue\s*[:=]\s*(#?\w[\w/-]*)/i
|
|
30
|
+
|
|
31
|
+
export function isTestFile(p) {
|
|
32
|
+
return /\.(test|spec)\.[jt]sx?$/.test(p) || /(^|\/)tests?\//.test(p) || /\.coverage\.ya?ml$/.test(p)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Pure: scan one file's text → expired/pending records against `now` (YYYY-MM-DD).
|
|
36
|
+
export function scanText(file, text, now) {
|
|
37
|
+
const out = []
|
|
38
|
+
const lines = (text || '').split(/\r?\n/)
|
|
39
|
+
lines.forEach((raw, i) => {
|
|
40
|
+
for (const m of raw.matchAll(UNTIL)) {
|
|
41
|
+
const until = m[1]
|
|
42
|
+
const issue = (raw.match(ISSUE) || [])[1] || null
|
|
43
|
+
out.push({ file, line: i + 1, until, issue, expired: until < now, snippet: raw.trim().slice(0, 120) })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
return out
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function discover(dir, acc = []) {
|
|
50
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
51
|
+
if (e.name === 'node_modules' || e.name.startsWith('.git')) continue
|
|
52
|
+
const full = path.join(dir, e.name)
|
|
53
|
+
if (e.isDirectory()) discover(full, acc)
|
|
54
|
+
else if (isTestFile(full)) acc.push(full)
|
|
55
|
+
}
|
|
56
|
+
return acc
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function today() {
|
|
60
|
+
// Standalone CI tool — reading the wall clock is correct here (unlike workflow scripts).
|
|
61
|
+
return new Date().toISOString().slice(0, 10)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function runCli(argv, { name = 'check-quarantine-expiry' } = {}) {
|
|
65
|
+
const flags = {}
|
|
66
|
+
for (let i = 0; i < argv.length; i++) {
|
|
67
|
+
const a = argv[i]
|
|
68
|
+
if (a === '--json') flags.json = true
|
|
69
|
+
else if (a === '--now') flags.now = argv[++i]
|
|
70
|
+
else if (a === '--files') flags.files = argv[++i]
|
|
71
|
+
}
|
|
72
|
+
const now = flags.now || today()
|
|
73
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(now)) {
|
|
74
|
+
console.error(`${name}: --now must be YYYY-MM-DD, got "${now}"`)
|
|
75
|
+
return 2
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const files = flags.files
|
|
79
|
+
? flags.files.split(',').map((s) => s.trim()).filter(Boolean)
|
|
80
|
+
: discover(process.cwd())
|
|
81
|
+
|
|
82
|
+
const records = []
|
|
83
|
+
for (const f of files) {
|
|
84
|
+
if (fs.existsSync(f)) records.push(...scanText(f, fs.readFileSync(f, 'utf8'), now))
|
|
85
|
+
}
|
|
86
|
+
const expired = records.filter((r) => r.expired)
|
|
87
|
+
|
|
88
|
+
if (flags.json) {
|
|
89
|
+
console.log(JSON.stringify({ now, total: records.length, expired }, null, 2))
|
|
90
|
+
} else {
|
|
91
|
+
if (!records.length) console.log(`${name}: no dated exits found.`)
|
|
92
|
+
else if (!expired.length) console.log(`${name}: ${records.length} dated exit(s), none expired (as of ${now}).`)
|
|
93
|
+
else {
|
|
94
|
+
console.log(`${name}: ${expired.length} EXPIRED dated exit(s) (as of ${now}) — fix, or extend the date with a reason:`)
|
|
95
|
+
for (const r of expired) console.log(` ${r.file}:${r.line} until=${r.until}${r.issue ? ` (${r.issue})` : ''} ${r.snippet}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return expired.length ? 1 : 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isMainModule(import.meta.url)) process.exit(runCli(process.argv.slice(2)))
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// CI audit — the meta-gate. Verifies a repo's CI config actually WIRES the gates
|
|
2
|
+
// in references/enforcement/ci-contract.md and keeps them BLOCKING. The gates catch bugs; this catches the
|
|
3
|
+
// gates being deleted, never added, or quietly turned to warn-only.
|
|
4
|
+
//
|
|
5
|
+
// node ci-audit.mjs [--files a.yml,b.yaml] [--allow-warn] [--json] # audit: gate on wired-and-blocking
|
|
6
|
+
// node ci-audit.mjs --adoption [--files …] [--json] # bridge: emit a wiring issue/plan for
|
|
7
|
+
// # a pre-existing CI that lacks the gates
|
|
8
|
+
//
|
|
9
|
+
// Static + best-effort by design: CI configs are heterogeneous, so this is a
|
|
10
|
+
// BENCHMARK that abstains rather than a parser. `absent` and `warn-only` fail the
|
|
11
|
+
// audit; a gate present under an unrecognized command reads as `absent` → a
|
|
12
|
+
// reviewer confirms (false-negative-safe: it never green-lights a missing gate it
|
|
13
|
+
// couldn't see, it flags for a human). Treat the output as leads, not gospel.
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
18
|
+
|
|
19
|
+
// Each gate is "present" if ANY pattern matches a config line. Signatures are the
|
|
20
|
+
// reference commands; extend per-repo if you wrap them under different names.
|
|
21
|
+
export const GATES = [
|
|
22
|
+
{ key: 'inventory', label: 'Gate 1 — inventory freshness', patterns: [/detect-routes/, /gen-inventory/, /surface-inventory/] },
|
|
23
|
+
{ key: 'accounting', label: 'Gate 2 — accounting (ledger validate)', patterns: [/coverage-ledger(\.mjs)?\b[^\n]*\bvalidate\b/] },
|
|
24
|
+
{ key: 'sweep', label: 'Gate 3 — presentation render sweep', patterns: [/presentation-sweep/] },
|
|
25
|
+
{ key: 'e2e', label: 'Gate 4 — functional + journey E2E (integration/nightly lane, not per-PR)', patterns: [/playwright\s+test/, /\btest:e2e\b/, /\brun\s+e2e\b/] },
|
|
26
|
+
{ key: 'mock-seam', label: 'Gate 5 — no over-mock (mock-internal-seam, depth axis)', patterns: [/mock-internal-seam/] },
|
|
27
|
+
{ key: 'checklist', label: 'Gate 6 — Review-Contract checklist validate', patterns: [/coverage-checklist(\.mjs)?\b[^\n]*\bvalidate\b/] },
|
|
28
|
+
// Newer than the rest: an already-wired repo will report this one absent on its next
|
|
29
|
+
// audit. That is the adoption path working, not a regression — phase it in with
|
|
30
|
+
// --allow-warn while the job is added, rather than treating the red as noise.
|
|
31
|
+
{ key: 'evidence-block', label: 'Gate 6b — Review-Contract evidence-block freshness (stamp = HEAD)', patterns: [/evidence-block(\.mjs)?\b[^\n]*\bvalidate\b/] },
|
|
32
|
+
{ key: 'quarantine-expiry', label: 'Gate 7 — quarantine auto-resurface (dated-exit expiry)', patterns: [/check-quarantine-expiry/] },
|
|
33
|
+
{ key: 'smoke', label: 'Gate 8 — curated E2E smoke (golden-path liveness, per-PR floor)', patterns: [/smoke(\.mjs)?\b[^\n]*\bverdict\b/, /smoke-gate/] },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
// Markers that turn a blocking gate into theatre. Proximity-scoped to a gate hit so
|
|
37
|
+
// an unrelated `continue-on-error` elsewhere in a big workflow doesn't false-flag.
|
|
38
|
+
export const WEAKEN = [
|
|
39
|
+
/continue-on-error:\s*true/i,
|
|
40
|
+
/allowFailure:\s*true/i,
|
|
41
|
+
/\|\|\s*true\b/,
|
|
42
|
+
/--warn-only\b/,
|
|
43
|
+
/--no-strict\b/,
|
|
44
|
+
/set\s+\+e\b/,
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// The meta-gate (gate 0 / bootstrap): is ci-audit ITSELF run by the CI? Without
|
|
48
|
+
// this, nothing catches a gate being deleted or weakened later — the audit only
|
|
49
|
+
// guards the gates if someone runs it. A repo that wires gates but not this can
|
|
50
|
+
// silently lose them. Advisory in normal audit mode; a required step in adoption.
|
|
51
|
+
export const META_GATE = { key: 'meta', label: 'Meta-gate — ci-audit self-check (guards the gates against rot)', patterns: [/ci-audit(\.mjs)?\b/] }
|
|
52
|
+
|
|
53
|
+
// CI wiring conventions — audited alongside the gates, platform-scoped (a Cloud
|
|
54
|
+
// Build build needs no umbrella job: its single build status IS the one check).
|
|
55
|
+
// `required: true` conventions fail the audit when absent on an applicable platform;
|
|
56
|
+
// `required: false` are PREFERRED — reported as advisory, never failing `ok`
|
|
57
|
+
// (docker-preferred has a sanctioned plain-runner alternative, so it can't hard-fail).
|
|
58
|
+
// Presence-only classification: no weaken-proximity logic (a `timeout-minutes` on the
|
|
59
|
+
// non-blocking quarantine job must not read as weakened).
|
|
60
|
+
export const CONVENTIONS = [
|
|
61
|
+
{
|
|
62
|
+
key: 'umbrella',
|
|
63
|
+
label: 'Umbrella job (all-gates) — branch protection requires exactly ONE check',
|
|
64
|
+
required: true,
|
|
65
|
+
platforms: ['github'],
|
|
66
|
+
patterns: [/^\s*all-gates\s*:/],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
key: 'time-budget',
|
|
70
|
+
label: 'Time budget — PR-lane jobs carry an explicit timeout (target ≤5 min)',
|
|
71
|
+
required: true,
|
|
72
|
+
platforms: ['github', 'cloudbuild'],
|
|
73
|
+
patterns: [/timeout-minutes:\s*\d+/, /^\s*timeout:\s*['"]?\d+(\.\d+)?s['"]?/],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: 'playwright-container',
|
|
77
|
+
label: 'Docker-preferred — browser jobs run in the pinned official Playwright container',
|
|
78
|
+
required: false,
|
|
79
|
+
platforms: ['github', 'cloudbuild'],
|
|
80
|
+
patterns: [/mcr\.microsoft\.com\/playwright:v?\d+\.\d+/],
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
// Pure: classify one convention. Presence-only (no warn-only state): a convention is
|
|
85
|
+
// `present`, `absent`, or `n/a` when the scanned platform is out of its scope.
|
|
86
|
+
export function classifyConvention(corpus, conv, platform) {
|
|
87
|
+
if (!conv.platforms.includes(platform)) {
|
|
88
|
+
return { status: 'n/a', evidence: `not applicable to ${platform || 'unknown'} configs` }
|
|
89
|
+
}
|
|
90
|
+
for (const file of corpus) {
|
|
91
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
92
|
+
if (conv.patterns.some((p) => p.test(stripComment(file.lines[i])))) {
|
|
93
|
+
return { status: 'present', evidence: `${file.name}:${i + 1}` }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { status: 'absent', evidence: 'no matching config found in scanned CI config' }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Visual regression (`presentation:visual`) is the one OPT-IN presentation level —
|
|
101
|
+
// required only when a surface declares it, which CI config alone can't tell us
|
|
102
|
+
// (it lives in coverage.yml, not the workflow). So this is ADVISORY, never part of
|
|
103
|
+
// `ok`: we report whether a `visual-gate` job is wired, but a repo with no opted-in
|
|
104
|
+
// surface legitimately has none. A repo that DOES opt in but lacks the job has a
|
|
105
|
+
// real gap a reviewer must catch — the advisory line is the prompt to check.
|
|
106
|
+
export const VISUAL_GATE = { key: 'visual', label: 'Visual regression (presentation:visual) — advisory, required only when surfaces opt in', patterns: [/visual-gate/, /presentation-visual/] }
|
|
107
|
+
|
|
108
|
+
// Strip a `#` comment so doc/warning text ("NEVER add continue-on-error: true")
|
|
109
|
+
// is not mistaken for a real gate command or a real weakening. Best-effort: ignores
|
|
110
|
+
// `#` inside quotes (acceptable for a benchmark). A real `run: x || true` has no `#`
|
|
111
|
+
// and is still detected.
|
|
112
|
+
export function stripComment(line) {
|
|
113
|
+
return line.replace(/(^|\s)#.*$/, '$1')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// A line that runs the quarantine selection (`--grep @quarantine`, not the
|
|
117
|
+
// `--grep-invert` exclusion the blocking gate uses).
|
|
118
|
+
const QUARANTINE_RUN = /--grep(?!-invert)[\s=]+["']?@?quarantine/i
|
|
119
|
+
|
|
120
|
+
// The quarantine *lane's* job/step HEADER (`quarantine:` GH job key, or `- id:
|
|
121
|
+
// quarantine` Cloud Build step). The lane's weaken marker is JOB-LEVEL
|
|
122
|
+
// (`continue-on-error: true` / `allowFailure: true`) and so sits a few lines BELOW
|
|
123
|
+
// this header but can be ~10 lines ABOVE the `--grep @quarantine` run — too far for
|
|
124
|
+
// the run-anchored check below. Anchored on a `quarantine` job/step identifier
|
|
125
|
+
// specifically (line-start, not any mention), so gate 7 (`check-quarantine-expiry`),
|
|
126
|
+
// whose name merely contains the substring, is never mistaken for the lane.
|
|
127
|
+
const QUARANTINE_LANE = /^\s*(?:quarantine\s*:|-\s*id:\s*quarantine\b)/i
|
|
128
|
+
|
|
129
|
+
// Is line j within a few lines of the quarantine *lane* (i.e. a weaken marker that
|
|
130
|
+
// belongs to the non-blocking quarantine job, not to the real gate near it)? Anchored
|
|
131
|
+
// on either the lane's `--grep @quarantine` run or its job/step header — NOT any line
|
|
132
|
+
// mentioning "quarantine" — so the blocking `check-quarantine-expiry` gate (gate 7)
|
|
133
|
+
// can never be mistaken for the lane and can never mask a real weakening on a gate
|
|
134
|
+
// wired next to it.
|
|
135
|
+
function nearQuarantine(lines, j, w = 3) {
|
|
136
|
+
const lo = Math.max(0, j - w)
|
|
137
|
+
const hi = Math.min(lines.length - 1, j + w)
|
|
138
|
+
for (let k = lo; k <= hi; k++) if (QUARANTINE_RUN.test(lines[k]) || QUARANTINE_LANE.test(lines[k])) return true
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Pure: classify one gate against the corpus. corpus: [{ name, lines:[string] }].
|
|
143
|
+
// Matching runs against comment-stripped code so a command/marker only counts when
|
|
144
|
+
// it is actually wired, not when it is mentioned in a comment.
|
|
145
|
+
export function classifyGate(corpus, gate, { proximity = 10 } = {}) {
|
|
146
|
+
const hits = []
|
|
147
|
+
for (const file of corpus) {
|
|
148
|
+
file.lines.forEach((line, i) => {
|
|
149
|
+
const code = stripComment(line)
|
|
150
|
+
// The quarantine lane re-runs a gate command non-blocking (`playwright test
|
|
151
|
+
// --grep @quarantine`); that line is NOT the blocking gate. But the blocking
|
|
152
|
+
// gate's `--grep-invert @quarantine` (excluding quarantine) IS the gate — keep it.
|
|
153
|
+
if (QUARANTINE_RUN.test(code)) return
|
|
154
|
+
if (gate.patterns.some((p) => p.test(code))) hits.push({ file, i })
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
if (hits.length === 0) return { status: 'absent', evidence: 'no matching command found in scanned CI config' }
|
|
158
|
+
for (const h of hits) {
|
|
159
|
+
const lo = Math.max(0, h.i - proximity)
|
|
160
|
+
const hi = Math.min(h.file.lines.length - 1, h.i + proximity)
|
|
161
|
+
for (let j = lo; j <= hi; j++) {
|
|
162
|
+
if (WEAKEN.some((w) => w.test(stripComment(h.file.lines[j])))) {
|
|
163
|
+
// A weaken marker belonging to the (by-design non-blocking) quarantine lane
|
|
164
|
+
// must not weaken a real gate hit that happens to sit near it.
|
|
165
|
+
if (nearQuarantine(h.file.lines, j)) continue
|
|
166
|
+
return { status: 'warn-only', evidence: `${h.file.name}:${h.i + 1} (weakened near ${h.file.name}:${j + 1})` }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { status: 'present', evidence: `${hits[0].file.name}:${hits[0].i + 1}` }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Pure: is ci-audit itself referenced anywhere in the corpus (the bootstrap check)?
|
|
174
|
+
export function selfAuditWired(corpus) {
|
|
175
|
+
for (const file of corpus) {
|
|
176
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
177
|
+
if (META_GATE.patterns.some((p) => p.test(stripComment(file.lines[i])))) {
|
|
178
|
+
return { wired: true, evidence: `${file.name}:${i + 1}` }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { wired: false, evidence: 'ci-audit is not run by any scanned CI config' }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Pure: audit the whole corpus. ok = every required gate present (warn-only fails
|
|
186
|
+
// unless allowWarn). absent/warn-only are findings.
|
|
187
|
+
export function auditCorpus(files, opts = {}) {
|
|
188
|
+
const corpus = files.map((f) => ({ name: f.name, lines: (f.text || '').split(/\r?\n/) }))
|
|
189
|
+
const gates = GATES.map((g) => ({ key: g.key, label: g.label, ...classifyGate(corpus, g, opts) }))
|
|
190
|
+
const metaGate = selfAuditWired(corpus)
|
|
191
|
+
// Advisory: report whether the opt-in visual gate is wired, but keep it OUT of `ok`
|
|
192
|
+
// (a repo with no opted-in surface correctly has no job — see VISUAL_GATE).
|
|
193
|
+
const visualGate = { key: VISUAL_GATE.key, label: VISUAL_GATE.label, ...classifyGate(corpus, VISUAL_GATE, opts) }
|
|
194
|
+
// Conventions are platform-scoped; on an unrecognized platform they all read n/a.
|
|
195
|
+
// Note this is an ABSTAIN, not a pass the audit vouches for — we don't guess
|
|
196
|
+
// github/cloudbuild conventions for a CI we can't identify; a reviewer owns n/a.
|
|
197
|
+
const platform = detectPlatform(files.map((f) => f.name))
|
|
198
|
+
const conventions = CONVENTIONS.map((c) => ({ key: c.key, label: c.label, required: c.required, ...classifyConvention(corpus, c, platform) }))
|
|
199
|
+
// `ok` = the required content gates + the required conventions. The meta-gate,
|
|
200
|
+
// visual gate, and preferred (required:false) conventions stay advisory here;
|
|
201
|
+
// adoption mode makes the meta-gate a required step.
|
|
202
|
+
const gatesOk = gates.every((g) => g.status === 'present' || (opts.allowWarn && g.status === 'warn-only'))
|
|
203
|
+
const conventionsOk = conventions.every((c) => !c.required || c.status !== 'absent')
|
|
204
|
+
const ok = gatesOk && conventionsOk
|
|
205
|
+
return { gates, conventions, platform, metaGate, visualGate, ok, scanned: files.map((f) => f.name) }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ---- Adoption: turn a gap into one reviewable PR ----
|
|
209
|
+
//
|
|
210
|
+
// A skill can't enforce against a pre-existing pipeline — the gates must be wired
|
|
211
|
+
// in. This closes the gap: given a repo's CI, report which gates are absent/weak,
|
|
212
|
+
// pick the matching template, and emit a checklist scoped to ONLY the gaps (it
|
|
213
|
+
// never asks you to re-wire a gate you already have), including the bootstrap step.
|
|
214
|
+
|
|
215
|
+
const TEMPLATE = {
|
|
216
|
+
github: 'e2e-test/assets/ci-gates.github.yml',
|
|
217
|
+
cloudbuild: 'e2e-test/assets/ci-gates.cloudbuild.yaml',
|
|
218
|
+
unknown: null,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Pure: which template applies, inferred from the scanned paths.
|
|
222
|
+
export function detectPlatform(scanned = []) {
|
|
223
|
+
if (scanned.some((p) => /\.github[/\\]workflows[/\\]/.test(p))) return 'github'
|
|
224
|
+
if (scanned.some((p) => /cloudbuild/i.test(p))) return 'cloudbuild'
|
|
225
|
+
return 'unknown'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Pure: the adoption plan. `toWire` = gates needing work (absent or warn-only);
|
|
229
|
+
// `ok` = gates already present (no action). `needed` is true if anything is left
|
|
230
|
+
// to wire OR the meta-gate isn't wired (the bootstrap).
|
|
231
|
+
export function buildAdoptionPlan(report, { platform } = {}) {
|
|
232
|
+
const plat = platform || detectPlatform(report.scanned || [])
|
|
233
|
+
const toWire = report.gates.filter((g) => g.status !== 'present')
|
|
234
|
+
const okGates = report.gates.filter((g) => g.status === 'present')
|
|
235
|
+
const metaWired = report.metaGate?.wired === true
|
|
236
|
+
// Required conventions that are absent on an applicable platform are part of the
|
|
237
|
+
// gap; preferred (required:false) ones are surfaced as recommendations only.
|
|
238
|
+
const conventionsToWire = (report.conventions || []).filter((c) => c.required && c.status === 'absent')
|
|
239
|
+
const conventionsPreferred = (report.conventions || []).filter((c) => !c.required && c.status === 'absent')
|
|
240
|
+
return {
|
|
241
|
+
needed: toWire.length > 0 || conventionsToWire.length > 0 || !metaWired,
|
|
242
|
+
platform: plat,
|
|
243
|
+
templatePath: TEMPLATE[plat] ?? null,
|
|
244
|
+
toWire,
|
|
245
|
+
conventionsToWire,
|
|
246
|
+
conventionsPreferred,
|
|
247
|
+
ok: okGates.map((g) => ({ key: g.key, label: g.label })),
|
|
248
|
+
metaGate: report.metaGate,
|
|
249
|
+
scanned: report.scanned || [],
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Pure: render the plan as a ready-to-file issue body (gh issue create --body-file -).
|
|
254
|
+
export function renderAdoptionIssue(plan) {
|
|
255
|
+
const mark = (s) => (s === 'present' ? '✓' : s === 'warn-only' ? '⚠' : '✗')
|
|
256
|
+
const L = []
|
|
257
|
+
L.push('## Wire coverage gates into CI (ensure-coverage CI contract)')
|
|
258
|
+
L.push('')
|
|
259
|
+
L.push('CI does not fully enforce the coverage gate contract (ensure-coverage → `references/enforcement/ci-contract.md`). Until every gate is wired **and blocking**, a route can ship broken with green CI. Close the gap in one PR.')
|
|
260
|
+
L.push('')
|
|
261
|
+
L.push(`Scanned: ${plan.scanned.length ? plan.scanned.map((s) => '`' + s + '`').join(', ') : '_no CI config found_'}`)
|
|
262
|
+
L.push('')
|
|
263
|
+
L.push('### Gap (from `ci-audit`)')
|
|
264
|
+
for (const g of plan.toWire) L.push(`- ${mark(g.status)} **${g.label}**: ${g.status} (${g.evidence})`)
|
|
265
|
+
for (const g of plan.ok) L.push(`- ✓ ${g.label}: present — no action`)
|
|
266
|
+
L.push(`- ${plan.metaGate?.wired ? '✓' : '✗'} ${META_GATE.label}: ${plan.metaGate?.wired ? `wired (${plan.metaGate.evidence})` : 'not wired'}`)
|
|
267
|
+
L.push('')
|
|
268
|
+
L.push('### Close it (one PR)')
|
|
269
|
+
L.push(plan.templatePath
|
|
270
|
+
? `Platform detected: **${plan.platform}** → copy template \`${plan.templatePath}\`.`
|
|
271
|
+
: 'Platform not detected — pick the template matching your CI: `e2e-test/assets/ci-gates.github.yml` or `ci-gates.cloudbuild.yaml`.')
|
|
272
|
+
L.push('')
|
|
273
|
+
let n = 1
|
|
274
|
+
if (plan.templatePath) L.push(`${n++}. [ ] Copy \`${plan.templatePath}\` into your CI config.`)
|
|
275
|
+
for (const g of plan.toWire) {
|
|
276
|
+
L.push(g.status === 'warn-only'
|
|
277
|
+
? `${n++}. [ ] Make **${g.label}** blocking — remove the weakening near \`${g.evidence}\` (\`continue-on-error\` / \`|| true\` / \`--warn-only\` / \`--no-strict\`).`
|
|
278
|
+
: `${n++}. [ ] Wire **${g.label}** as its own blocking job.`)
|
|
279
|
+
}
|
|
280
|
+
for (const c of plan.conventionsToWire || []) {
|
|
281
|
+
L.push(`${n++}. [ ] Wire the **${c.label}** convention (see the template — it encodes this).`)
|
|
282
|
+
}
|
|
283
|
+
for (const c of plan.conventionsPreferred || []) {
|
|
284
|
+
L.push(`${n++}. [ ] (preferred) ${c.label} — recommended, not blocking; the template's default.`)
|
|
285
|
+
}
|
|
286
|
+
if (!plan.metaGate?.wired) L.push(`${n++}. [ ] **Bootstrap** — add a job running \`node <ensure-coverage>/scripts/ci-audit.mjs --files <your ci files>\` so the gates can't be silently deleted or weakened later. Without it, nothing guards the gates.`)
|
|
287
|
+
L.push(`${n++}. [ ] Fill the \`<…>\` placeholders (\`<skill>\` path, \`<seed + migrate>\`, \`<boot app>\`, DB URL) and confirm no gate carries \`continue-on-error\`/\`|| true\`/\`--warn-only\`.`)
|
|
288
|
+
L.push('')
|
|
289
|
+
L.push('### Verify')
|
|
290
|
+
L.push('```bash')
|
|
291
|
+
L.push(`node <ensure-coverage>/scripts/ci-audit.mjs --files ${plan.scanned.join(',') || '<your ci files>'}`)
|
|
292
|
+
L.push('```')
|
|
293
|
+
L.push('Must report every gate `present` (and the meta-gate wired). `absent`/`warn-only` fail the audit.')
|
|
294
|
+
return L.join('\n')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---- CLI (file discovery) ----
|
|
298
|
+
|
|
299
|
+
function discover(explicit) {
|
|
300
|
+
if (explicit) return explicit.split(',').map((s) => s.trim()).filter(Boolean)
|
|
301
|
+
const found = []
|
|
302
|
+
const wf = '.github/workflows'
|
|
303
|
+
if (fs.existsSync(wf)) for (const f of fs.readdirSync(wf)) if (/\.ya?ml$/.test(f)) found.push(path.join(wf, f))
|
|
304
|
+
for (const f of fs.existsSync('.') ? fs.readdirSync('.') : []) {
|
|
305
|
+
if (/^cloudbuild.*\.ya?ml$/.test(f)) found.push(f)
|
|
306
|
+
}
|
|
307
|
+
for (const f of ['Makefile', 'package.json']) if (fs.existsSync(f)) found.push(f)
|
|
308
|
+
return found
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function main() {
|
|
312
|
+
const argv = process.argv.slice(2)
|
|
313
|
+
const flags = {}
|
|
314
|
+
for (let i = 0; i < argv.length; i++) {
|
|
315
|
+
const a = argv[i]
|
|
316
|
+
if (a === '--allow-warn') flags.allowWarn = true
|
|
317
|
+
else if (a === '--json') flags.json = true
|
|
318
|
+
else if (a === '--adoption') flags.adoption = true
|
|
319
|
+
else if (a === '--files') flags.files = argv[++i]
|
|
320
|
+
}
|
|
321
|
+
const paths = discover(flags.files)
|
|
322
|
+
if (paths.length === 0) {
|
|
323
|
+
console.error('ci-audit: no CI config files found to scan (pass --files a.yml,b.yaml)')
|
|
324
|
+
process.exit(2)
|
|
325
|
+
}
|
|
326
|
+
const files = paths.map((p) => ({ name: p, text: fs.readFileSync(p, 'utf8') }))
|
|
327
|
+
const report = auditCorpus(files, { allowWarn: flags.allowWarn })
|
|
328
|
+
if (flags.adoption) {
|
|
329
|
+
const plan = buildAdoptionPlan(report)
|
|
330
|
+
if (flags.json) console.log(JSON.stringify(plan, null, 2))
|
|
331
|
+
else if (!plan.needed) console.log(`✓ nothing to adopt — all required gates wired and blocking, and the meta-gate runs (scanned: ${plan.scanned.join(', ')})`)
|
|
332
|
+
else console.log(renderAdoptionIssue(plan))
|
|
333
|
+
process.exit(plan.needed ? 1 : 0)
|
|
334
|
+
}
|
|
335
|
+
if (flags.json) {
|
|
336
|
+
console.log(JSON.stringify(report, null, 2))
|
|
337
|
+
} else {
|
|
338
|
+
console.log(`ci-audit — scanned: ${report.scanned.join(', ')}`)
|
|
339
|
+
for (const g of report.gates) {
|
|
340
|
+
const mark = g.status === 'present' ? '✓' : g.status === 'warn-only' ? '⚠' : '✗'
|
|
341
|
+
console.log(` ${mark} ${g.label}: ${g.status} (${g.evidence})`)
|
|
342
|
+
}
|
|
343
|
+
for (const c of report.conventions || []) {
|
|
344
|
+
const cmark = c.status === 'present' ? '✓' : c.status === 'n/a' ? '○' : c.required ? '✗' : '○'
|
|
345
|
+
const suffix = c.required ? '' : ' — preferred, advisory'
|
|
346
|
+
console.log(` ${cmark} ${c.label}: ${c.status}${suffix} (${c.evidence})`)
|
|
347
|
+
}
|
|
348
|
+
const m = report.metaGate?.wired ? '✓' : '○'
|
|
349
|
+
console.log(` ${m} ${META_GATE.label}: ${report.metaGate?.wired ? 'wired' : 'not wired (advisory — run --adoption)'} (${report.metaGate?.evidence})`)
|
|
350
|
+
const vmark = report.visualGate?.status === 'present' ? '✓' : '○'
|
|
351
|
+
console.log(` ${vmark} ${VISUAL_GATE.label}: ${report.visualGate?.status} (${report.visualGate?.evidence}) — advisory; only a gap if a surface opts into presentation:visual`)
|
|
352
|
+
console.log(report.ok ? '\nPASS — all required gates wired and blocking' : '\nFAIL — missing/weakened gate(s) or missing required convention(s); see references/enforcement/ci-contract.md')
|
|
353
|
+
if (!report.ok || !report.metaGate?.wired) console.log(' → generate a wiring issue: node ci-audit.mjs --adoption --files ' + report.scanned.join(','))
|
|
354
|
+
}
|
|
355
|
+
process.exit(report.ok ? 0 : 1)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (isMainModule(import.meta.url)) main()
|