@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,787 @@
|
|
|
1
|
+
// model-catalog — canonical harness-runtime read/write helper for the banked model catalog.
|
|
2
|
+
//
|
|
3
|
+
// The catalog (references/model-catalog.md) is the per-harness user-level JSON where
|
|
4
|
+
// pre-flight banks human-confirmed model classifications (family · `tiers`, a map
|
|
5
|
+
// from effort to the tier the model reaches there, in that harness's own id
|
|
6
|
+
// vocabulary) so later runs read them instead of re-asking. Since auto-pruning landed, every
|
|
7
|
+
// pre-flight read is also a WRITE (bump `lastSeenAt` on discovered entries, prune
|
|
8
|
+
// entries unseen for more than PRUNE_AFTER_DAYS) — exactly the hand-rolled
|
|
9
|
+
// read-modify-write on a load-bearing artifact that run-state.mjs exists to avoid
|
|
10
|
+
// on the manifest. This script single-sources the prune predicate and the two
|
|
11
|
+
// write paths (maintenance sync at read, classification bank at confirmation).
|
|
12
|
+
//
|
|
13
|
+
// node model-catalog.mjs read # print JSON (exit 3 if absent — normal; alias: list)
|
|
14
|
+
// node model-catalog.mjs path # print the resolved file path
|
|
15
|
+
// node model-catalog.mjs points [<id>] # list banked operating points
|
|
16
|
+
// # ({effort, tier, slotEligible}, strongest
|
|
17
|
+
// # first). slotEligible is wired to
|
|
18
|
+
// # SLOT_ELIGIBLE_TIERS so a scoped-coder is
|
|
19
|
+
// # MARKED, never hidden — see operatingPoints.
|
|
20
|
+
// node model-catalog.mjs sync --seen <id,id,...> # bump lastSeenAt for the discovered ids,
|
|
21
|
+
// # prune stale entries, print {bumped, pruned}
|
|
22
|
+
// node model-catalog.mjs bank <entryJson> # upsert a classification by id (sets
|
|
23
|
+
// # confirmedAt + lastSeenAt; creates the file)
|
|
24
|
+
//
|
|
25
|
+
// --harness <name> resolve the file to that harness's own catalog location
|
|
26
|
+
// (claude-code | codex | pi — one catalog PER HARNESS, never shared:
|
|
27
|
+
// model-catalog.md "Location"); writes guard against the file's own
|
|
28
|
+
// `harness` stamp so a codex catalog can't absorb claude-code entries.
|
|
29
|
+
// Default: claude-code (~/.claude/model-catalog.json).
|
|
30
|
+
// --file <path> operate on an explicit catalog path (overrides --harness; for
|
|
31
|
+
// harnesses this script has no location mapping for)
|
|
32
|
+
// --today <date> pin the date (YYYY-MM-DD; default: system clock; for deterministic tests)
|
|
33
|
+
// --stdin read <entryJson> from stdin instead of argv
|
|
34
|
+
//
|
|
35
|
+
// node model-catalog.mjs tiers --seen <id,id,...> [--harness <h>]
|
|
36
|
+
// # PREFER THIS at pre-flight step 2: composes
|
|
37
|
+
// # banked -> cross-harness -> seed with the
|
|
38
|
+
// # precedence guaranteed, returning one decided
|
|
39
|
+
// # answer per model plus seedDisagreements.
|
|
40
|
+
// # The three arms below exist for isolation.
|
|
41
|
+
// node model-catalog.mjs cross-harness --seen <id,id,...> [--harness <h>]
|
|
42
|
+
// # rule 5: tier pre-fills from the OTHER
|
|
43
|
+
// # harnesses' catalogs, restricted to this
|
|
44
|
+
// # run's discovered ids. Prose-only until
|
|
45
|
+
// # 2026-07-20 despite OUTRANKING the seed.
|
|
46
|
+
// node model-catalog.mjs bank-from-seed <id> [--harness <h>]
|
|
47
|
+
// # bank a confirmed seed-backed proposal via
|
|
48
|
+
// # entryFromSeedConfirmation (fresh confirmedAt,
|
|
49
|
+
// # this harness's tiers) instead of hand-writing
|
|
50
|
+
// # the entry JSON.
|
|
51
|
+
// node model-catalog.mjs help # the command list
|
|
52
|
+
// node model-catalog.mjs seed # print the team seed (read-only; exit 3 if absent)
|
|
53
|
+
// node model-catalog.mjs seed-proposals --seen <id,id,...> [--harness <h>]
|
|
54
|
+
// # {proposals, suppressed} — seed-backed pre-fills
|
|
55
|
+
// # for THIS run's discovered ids only (rule 3) and
|
|
56
|
+
// # only where this harness's own catalog has banked
|
|
57
|
+
// # nothing (rule 7 precedence, per MODEL); each
|
|
58
|
+
// # carries its citation. `suppressed` names what
|
|
59
|
+
// # the catalog outranked, flagging `disagrees`
|
|
60
|
+
// # where the seed has gone stale.
|
|
61
|
+
//
|
|
62
|
+
// --seed-file <path> operate on an explicit seed path (default: the shipped
|
|
63
|
+
// references/model-catalog-seed.json beside this module)
|
|
64
|
+
//
|
|
65
|
+
// Absence is NORMAL (model-catalog.md "Location"): `read`/`sync` exit 3 so the
|
|
66
|
+
// caller classifies from discovery-source evidence; only `bank` creates the file.
|
|
67
|
+
//
|
|
68
|
+
// The seed (references/model-catalog-seed.json, model-catalog.md rule 7) is a
|
|
69
|
+
// SEPARATE, READ-ONLY file — evidence, not truth, the same epistemic class as
|
|
70
|
+
// rule 5 (cross-harness) and rule 6 (benchmark). Neither `bank` nor `sync` ever
|
|
71
|
+
// accepts the seed as a write target (see refuseIfSeedWrite below): a seed write
|
|
72
|
+
// would let a machine guess masquerade as a human confirmation, which is the one
|
|
73
|
+
// thing `bank` exists to prevent.
|
|
74
|
+
|
|
75
|
+
import fs from 'node:fs'
|
|
76
|
+
import os from 'node:os'
|
|
77
|
+
import path from 'node:path'
|
|
78
|
+
import { fileURLToPath } from 'node:url'
|
|
79
|
+
|
|
80
|
+
// model-catalog.md rule 4: an entry unseen for MORE THAN this many days is pruned.
|
|
81
|
+
export const PRUNE_AFTER_DAYS = 60
|
|
82
|
+
|
|
83
|
+
// Freshness on the OTHER axis. PRUNE_AFTER_DAYS watches `lastSeenAt` (availability
|
|
84
|
+
// recency — has the model been discovered lately). This watches `confirmedAt`
|
|
85
|
+
// (classification recency — how old is the human judgment). A model discovered every
|
|
86
|
+
// day never prunes, yet its tier can be frozen for weeks: grok-4.5 stayed
|
|
87
|
+
// `deep-reasoner` (confirmed 2026-07-19) while the seed had demoted it to
|
|
88
|
+
// `balanced-coder`. An entry whose classification is older than this is FLAGGED
|
|
89
|
+
// `staleClassification` at pre-flight — surfaced for re-confirmation, never
|
|
90
|
+
// auto-changed (unlike prune, which acts).
|
|
91
|
+
export const CLASSIFY_STALE_AFTER_DAYS = 30
|
|
92
|
+
|
|
93
|
+
// Capability bands, top to bottom. Every name describes what a model IS — never
|
|
94
|
+
// where it sits in a list, never when it shipped; both renames below were cases of
|
|
95
|
+
// a name drifting off that rule (rationale on LEGACY_TIERS). Note `deep-reasoner`
|
|
96
|
+
// does not by itself separate its band from `frontier` (frontier models reason
|
|
97
|
+
// deeply too) — the definition carries that: it is the band BELOW the current top
|
|
98
|
+
// end (model-catalog.md `tiers`).
|
|
99
|
+
//
|
|
100
|
+
// `scoped-coder` (added 2026-07-20) is the one band that exists to NOT be picked.
|
|
101
|
+
// The other three each have a home slot; this one has none, and that is its point.
|
|
102
|
+
// It marks a model that executes a change someone else scoped but cannot hold a
|
|
103
|
+
// task together across a long agentic trajectory — so banking it answers "what is
|
|
104
|
+
// this model" once and stops pre-flight re-asking every run (rule 1 would otherwise
|
|
105
|
+
// tag it `unconfirmed — please classify` forever), while leaving it below every
|
|
106
|
+
// slot's floor. Named for what it IS, not its rank: `low-tier` would repeat the
|
|
107
|
+
// `mid-tier` mistake, and `budget` would name a price rather than a capability.
|
|
108
|
+
export const TIERS = ['frontier', 'deep-reasoner', 'balanced-coder', 'scoped-coder']
|
|
109
|
+
|
|
110
|
+
// Bands that may fill a slot. `scoped-coder` is deliberately absent — pre-flight
|
|
111
|
+
// offers it as a *classification*, never as a candidate for implementer or
|
|
112
|
+
// reviewer. Kept as its own list rather than "all but the last" so adding a future
|
|
113
|
+
// band has to state its own answer instead of inheriting one by position.
|
|
114
|
+
export const SLOT_ELIGIBLE_TIERS = ['frontier', 'deep-reasoner', 'balanced-coder']
|
|
115
|
+
|
|
116
|
+
// Renamed tiers, old -> new. Legacy values are ACCEPTED and normalized rather
|
|
117
|
+
// than rejected: catalogs are user data that predate the renames, and a hard
|
|
118
|
+
// failure would strand them (same posture as run-state's obsolete-field warnings).
|
|
119
|
+
// prior-frontier -> deep-reasoner encoded a CHRONOLOGY ("the previous
|
|
120
|
+
// generation's frontier") for what is a capability band, so a newly released
|
|
121
|
+
// model that landed in it had no honest home.
|
|
122
|
+
// mid-tier -> balanced-coder encoded an ORDINAL POSITION, and an
|
|
123
|
+
// inaccurate one: with the three bands that existed at the rename it was the
|
|
124
|
+
// bottom, not the middle (scoped-coder was added below it later).
|
|
125
|
+
export const LEGACY_TIERS = {
|
|
126
|
+
'prior-frontier': 'deep-reasoner',
|
|
127
|
+
'mid-tier': 'balanced-coder',
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function normalizeTier(tier) {
|
|
131
|
+
return LEGACY_TIERS[tier] || tier
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Efforts an operating point may be banked at (model-catalog.md `tiers`).
|
|
135
|
+
// Deliberately floored at `high`: a point records what a model REACHES when
|
|
136
|
+
// pushed, never a throttled-down cheap seat — a cheaper operating point is a
|
|
137
|
+
// cheaper MODEL (pre-flight.md step 2). `high` is the default point, so a
|
|
138
|
+
// single-point entry banked without an effort behaves as it always did.
|
|
139
|
+
export const EFFORTS = ['high', 'xhigh', 'max']
|
|
140
|
+
export const DEFAULT_EFFORT = 'high'
|
|
141
|
+
|
|
142
|
+
// An entry's capability is a MAP from effort to the tier it reaches there:
|
|
143
|
+
//
|
|
144
|
+
// "tiers": { "high": "balanced-coder", "max": "deep-reasoner" }
|
|
145
|
+
//
|
|
146
|
+
// One model can therefore be a routine implementer at high AND a deep reasoner
|
|
147
|
+
// at max, and pre-flight offers each point as its own slot candidate. This is
|
|
148
|
+
// tier = f(model, effort) — the shape removed on 2026-07-13 — restored on
|
|
149
|
+
// 2026-07-19 with the cost that killed it removed: the pairs are banked ONCE at
|
|
150
|
+
// classification, so no per-slot, per-run effort question ever returns.
|
|
151
|
+
//
|
|
152
|
+
// Legacy single-point entries ({tier} or {tier, effort}) fold into a one-key map.
|
|
153
|
+
export function toTiers(entry) {
|
|
154
|
+
if (entry?.tiers) {
|
|
155
|
+
const out = {}
|
|
156
|
+
for (const [effort, tier] of Object.entries(entry.tiers)) out[effort] = normalizeTier(tier)
|
|
157
|
+
return out
|
|
158
|
+
}
|
|
159
|
+
if (entry?.tier) return { [entry.effort || DEFAULT_EFFORT]: normalizeTier(entry.tier) }
|
|
160
|
+
return {}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Whether a tier may fill a slot. Wired here rather than left for a caller to
|
|
164
|
+
// remember, because SLOT_ELIGIBLE_TIERS spent its whole life as a constant no
|
|
165
|
+
// production code read: model-catalog.md said banking `scoped-coder` "keeps the
|
|
166
|
+
// model out of every slot's candidate list (SLOT_ELIGIBLE_TIERS)" and nothing
|
|
167
|
+
// consulted it, so `points` handed scoped-coder models back undistinguished and
|
|
168
|
+
// an agent building slot lists from that output offered them as implementer
|
|
169
|
+
// candidates — inverting the one rule the band exists to enforce.
|
|
170
|
+
export function isSlotEligible(tier) {
|
|
171
|
+
return SLOT_ELIGIBLE_TIERS.includes(tier)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Every banked operating point, strongest first, as [{ effort, tier, slotEligible }].
|
|
175
|
+
//
|
|
176
|
+
// Each point is MARKED, never filtered out. The distinction matters: a
|
|
177
|
+
// scoped-coder classification is a real, useful answer — banking it is what
|
|
178
|
+
// stops pre-flight re-asking about the same model every run (rule 1) — so
|
|
179
|
+
// hiding those models would defeat the band's purpose. What must not happen is
|
|
180
|
+
// the classification flowing into a slot candidate list. Marking lets pre-flight
|
|
181
|
+
// show "classified scoped-coder — not a slot candidate" and keeps both
|
|
182
|
+
// properties; filtering would buy the second by destroying the first.
|
|
183
|
+
export function pointsFromTiers(tiers) {
|
|
184
|
+
return Object.entries(tiers || {})
|
|
185
|
+
.map(([effort, tier]) => ({ effort, tier, slotEligible: isSlotEligible(tier) }))
|
|
186
|
+
.sort((a, b) => TIERS.indexOf(a.tier) - TIERS.indexOf(b.tier) || EFFORTS.indexOf(a.effort) - EFFORTS.indexOf(b.effort))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function operatingPoints(catalog, id) {
|
|
190
|
+
return pointsFromTiers(toTiers((catalog?.models || []).find((m) => m && m.id === id)))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// The tier this model reaches at a given effort (null if no such point is banked).
|
|
194
|
+
export function tierAt(catalog, id, effort) {
|
|
195
|
+
return toTiers((catalog?.models || []).find((m) => m && m.id === id))[effort] || null
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Known harness → user-config catalog location (model-catalog.md "Location" —
|
|
199
|
+
// one catalog per harness, never shared across harnesses).
|
|
200
|
+
export const HARNESS_PATHS = {
|
|
201
|
+
'claude-code': ['.claude', 'model-catalog.json'],
|
|
202
|
+
codex: ['.codex', 'model-catalog.json'],
|
|
203
|
+
pi: ['.pi', 'agent', 'model-catalog.json'],
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function pathForHarness(harness, env = process.env) {
|
|
207
|
+
const segs = HARNESS_PATHS[harness]
|
|
208
|
+
if (!segs) return null
|
|
209
|
+
const home = env.HOME || os.homedir()
|
|
210
|
+
return path.join(home, ...segs)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function defaultPath(env = process.env) {
|
|
214
|
+
return pathForHarness('claude-code', env)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---- the team seed (model-catalog.md rule 7) — read-only, evidence not truth ----
|
|
218
|
+
|
|
219
|
+
// The seed ships beside this module under references/model-catalog-seed.json.
|
|
220
|
+
// Resolve from import.meta.url so source, copied flat installs, and Claude Code
|
|
221
|
+
// symlink launches all read the installed package's immutable seed.
|
|
222
|
+
export function defaultSeedPath() {
|
|
223
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), 'references', 'model-catalog-seed.json')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Same absent-is-normal contract as readCatalog: a skill install that hasn't
|
|
227
|
+
// shipped a seed (or a caller pointing at a scratch path in a test) just gets
|
|
228
|
+
// null, and the caller falls through to discovery-source evidence.
|
|
229
|
+
export function readSeed(file) {
|
|
230
|
+
if (!fs.existsSync(file)) return null
|
|
231
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// A seed entry's `ids` map already holds the fully-resolved per-harness id —
|
|
235
|
+
// alias resolution happened ONCE, by a human, when the entry was authored
|
|
236
|
+
// (model-catalog.md rule 7's "must not guess"), so a lookup here is a direct
|
|
237
|
+
// equality check, never a live normalization guess.
|
|
238
|
+
export function findSeedEntry(seed, harness, id) {
|
|
239
|
+
return (seed?.models || []).find((m) => m?.ids && m.ids[harness] === id) || null
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The tiers this seed entry claims for one harness, {} if the entry doesn't
|
|
243
|
+
// cover that harness at all (e.g. an entry seeded from a single harness's
|
|
244
|
+
// catalog only, model-catalog-seed.json's `sonnet` row).
|
|
245
|
+
export function seedTiersFor(seed, harness, id) {
|
|
246
|
+
const entry = findSeedEntry(seed, harness, id)
|
|
247
|
+
return entry?.tiers?.[harness] || {}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function seedTierAt(seed, harness, id, effort) {
|
|
251
|
+
return seedTiersFor(seed, harness, id)[effort] || null
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// A tiers map as one readable clause, for the citations pre-flight prints.
|
|
255
|
+
function fmtTiers(tiers) {
|
|
256
|
+
return `{${Object.entries(tiers).map(([e, t]) => `${e}:${t}`).sort().join(', ')}}`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// True when two tiers maps state exactly the same operating points.
|
|
260
|
+
function sameTiers(a, b) {
|
|
261
|
+
const ka = Object.keys(a).sort()
|
|
262
|
+
const kb = Object.keys(b).sort()
|
|
263
|
+
return ka.length === kb.length && ka.every((k, i) => k === kb[i] && a[k] === b[k])
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Rules 3 AND 7, both mechanized here, because this is the one call pre-flight
|
|
267
|
+
// actually makes — a precedence rule the caller is trusted to apply by hand is
|
|
268
|
+
// a rule that silently stops holding.
|
|
269
|
+
//
|
|
270
|
+
// { proposals, suppressed }
|
|
271
|
+
//
|
|
272
|
+
// `proposals` — seed entries that survive BOTH filters:
|
|
273
|
+
// rule 3 (availability): only ids THIS RUN'S DISCOVERY yielded (discoveredIds).
|
|
274
|
+
// An entry the seed carries for an id discovery didn't produce is silently
|
|
275
|
+
// excluded, never surfaced (issue #370) — a seed that leaked into
|
|
276
|
+
// availability would let pre-flight propose a model the runtime cannot run.
|
|
277
|
+
// rule 7 (precedence): only ids with NO banked entry in the caller's own
|
|
278
|
+
// catalog. "A user's own measurement always beats the shipped default" —
|
|
279
|
+
// and it beats it PER MODEL, not per effort. That distinction is the whole
|
|
280
|
+
// point: a human who deliberately drops an operating point (banking example-coder-2
|
|
281
|
+
// at {high} after deciding its max point wasn't real) leaves no banked tier
|
|
282
|
+
// at `max`, so a per-effort merge would read the deletion as silence and
|
|
283
|
+
// hand the point straight back from the seed, every run, forever. A banked
|
|
284
|
+
// entry is a complete statement of that model's points in this harness.
|
|
285
|
+
//
|
|
286
|
+
// `suppressed` — what rule 7 dropped, with `disagrees` set when the seed's
|
|
287
|
+
// claim differs from what the human banked. Reported, never silent: a
|
|
288
|
+
// disagreement is the signal that the shipped seed has gone stale relative to
|
|
289
|
+
// this user's measurements, and it is the only place that staleness is visible
|
|
290
|
+
// (the seed can't be diffed against user-level catalogs in CI — they aren't in
|
|
291
|
+
// the repo). Pre-flight surfaces it as a nudge to a seed owner, never as a
|
|
292
|
+
// proposal, and never as a reason to re-ask a classification rule 1 settled.
|
|
293
|
+
export function seedProposals(seed, harness, discoveredIds, catalog = null) {
|
|
294
|
+
const discovered = new Set(discoveredIds)
|
|
295
|
+
const proposals = []
|
|
296
|
+
const suppressed = []
|
|
297
|
+
for (const entry of seed?.models || []) {
|
|
298
|
+
const id = entry?.ids?.[harness]
|
|
299
|
+
if (!id || !discovered.has(id)) continue
|
|
300
|
+
const tiers = entry.tiers?.[harness]
|
|
301
|
+
if (!tiers || !Object.keys(tiers).length) continue // this entry says nothing about THIS harness
|
|
302
|
+
// Check the POINTS, not the field: an entry carrying `tiers: {}` classifies
|
|
303
|
+
// nothing, so it must not suppress the seed's pre-fill (same guard as bank).
|
|
304
|
+
const bankedEntry = (catalog?.models || []).find((m) => m && m.id === id)
|
|
305
|
+
const banked = toTiers(bankedEntry)
|
|
306
|
+
if (Object.keys(banked).length) {
|
|
307
|
+
const disagrees = !sameTiers(banked, tiers)
|
|
308
|
+
// Date-aware disagreement. "Your catalog wins (rule 7)" is the right default only
|
|
309
|
+
// while the banked entry is at least as fresh as the seed. When the seed DISAGREES
|
|
310
|
+
// and is NEWER than the human's banked confirmation, the seed may carry a
|
|
311
|
+
// reclassification the banked entry predates — the grok-4.5 case: seed demoted
|
|
312
|
+
// 2026-07-22, banked confirmed 2026-07-19. Flag it so pre-flight nudges toward
|
|
313
|
+
// re-confirmation instead of silently keeping the stale banked tier.
|
|
314
|
+
const seededAt = seed?.seededAt
|
|
315
|
+
const bankedConfirmedAt = bankedEntry?.confirmedAt
|
|
316
|
+
const seedNewer = disagrees && !!seededAt && !!bankedConfirmedAt && seededAt > bankedConfirmedAt
|
|
317
|
+
suppressed.push({ id, canonical: entry.canonical, banked, seed: tiers, disagrees, seededAt, bankedConfirmedAt, seedNewer })
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
// `points` carries the same slotEligible marking as operatingPoints, so a
|
|
321
|
+
// seed-backed pre-fill and a banked entry are read the same way. Without it
|
|
322
|
+
// the shipped seed's six scoped-coder entries reached a fresh install's
|
|
323
|
+
// proposal block as team-endorsed slot options.
|
|
324
|
+
proposals.push({ id, canonical: entry.canonical, family: entry.family, tiers, points: pointsFromTiers(tiers), note: entry.note })
|
|
325
|
+
}
|
|
326
|
+
return { proposals, suppressed }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ---- rule 5: another harness's catalog as tiering evidence ----
|
|
330
|
+
|
|
331
|
+
// "Plainly matches" from model-catalog.md rule 5, made mechanical. The rule
|
|
332
|
+
// transfers a tier between harnesses ONLY when one id is the other with a
|
|
333
|
+
// `provider/` prefix attached or removed (`openai/gpt-5.6-sol` <-> `gpt-5.6-sol`).
|
|
334
|
+
//
|
|
335
|
+
// What it must REFUSE is the point. A harness-local alias (`sonnet`) normalizes
|
|
336
|
+
// to nothing and gets no cross-harness pre-fill. And a pair that merely looks
|
|
337
|
+
// related is not a match: the shipped seed documents this exact trap — stripping
|
|
338
|
+
// `kimi-coding/` from `kimi-coding/k3` gives `k3`, not `kimi-k3`, so the seed's
|
|
339
|
+
// authors deliberately kept those as two entries pending human confirmation. An
|
|
340
|
+
// agent hand-applying rule 5 under time pressure makes that merge anyway and
|
|
341
|
+
// transfers a tier its authors declined to transfer — while citing it as
|
|
342
|
+
// `tier from pi catalog`, which reads like mechanized provenance. This function
|
|
343
|
+
// is what makes the refusal survive contact with a tired reader.
|
|
344
|
+
export function plainlyMatches(idA, idB) {
|
|
345
|
+
if (idA === idB) return true
|
|
346
|
+
const strip = (id) => (id.includes('/') ? id.slice(id.indexOf('/') + 1) : null)
|
|
347
|
+
return strip(idA) === idB || strip(idB) === idA
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Rule 5, end to end: for each discovered id with nothing banked in THIS
|
|
351
|
+
// harness's catalog, look through the OTHER harnesses' catalogs for a plainly
|
|
352
|
+
// matching entry and return its tiers as a pre-fill, citation included.
|
|
353
|
+
//
|
|
354
|
+
// Availability never transfers (rule 3, which applies doubly here): the result
|
|
355
|
+
// is restricted to discoveredIds, so a model another harness has banked but this
|
|
356
|
+
// harness cannot run never becomes a proposal.
|
|
357
|
+
export function crossHarnessProposals(harness, discoveredIds, { readAt = readCatalog, paths = HARNESS_PATHS } = {}) {
|
|
358
|
+
const discovered = new Set(discoveredIds)
|
|
359
|
+
const own = readAt(pathForHarness(harness)) || { models: [] }
|
|
360
|
+
const out = []
|
|
361
|
+
for (const id of discovered) {
|
|
362
|
+
if (Object.keys(toTiers((own.models || []).find((m) => m && m.id === id))).length) continue // rule 1 outranks
|
|
363
|
+
for (const other of Object.keys(paths)) {
|
|
364
|
+
if (other === harness) continue
|
|
365
|
+
const cat = readAt(pathForHarness(other))
|
|
366
|
+
const hit = (cat?.models || []).find((m) => m && plainlyMatches(m.id, id))
|
|
367
|
+
const tiers = toTiers(hit)
|
|
368
|
+
if (!Object.keys(tiers).length) continue
|
|
369
|
+
out.push({
|
|
370
|
+
id,
|
|
371
|
+
matchedId: hit.id,
|
|
372
|
+
fromHarness: other,
|
|
373
|
+
family: hit.family,
|
|
374
|
+
tiers,
|
|
375
|
+
points: pointsFromTiers(tiers),
|
|
376
|
+
citation: `tier from ${other} catalog: ${hit.id}`,
|
|
377
|
+
})
|
|
378
|
+
break // first match wins; the harness order in HARNESS_PATHS is the tiebreak
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return out
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Precedence order (model-catalog.md rule 7): own banked entry -> own
|
|
385
|
+
// other-harness catalog (rule 5) -> team seed -> discovery-source evidence ->
|
|
386
|
+
// unconfirmed. Resolved per MODEL, on whole `tiers` maps.
|
|
387
|
+
//
|
|
388
|
+
// This REPLACED resolveTierPrecedence on 2026-07-20. That function took one
|
|
389
|
+
// scalar tier per source and resolved a single (model, effort) point — a shape
|
|
390
|
+
// that had to be defended with a comment telling callers not to merge a seed's
|
|
391
|
+
// points into a banked entry one effort at a time. A signature whose parameters
|
|
392
|
+
// invite the bug its own comment forbids is a trap, not a safeguard: the next
|
|
393
|
+
// caller reads the types, not the paragraph. Taking whole maps makes the
|
|
394
|
+
// per-model rule the only thing you *can* express, so a deliberately-deleted
|
|
395
|
+
// operating point cannot be restored from a weaker source no matter how the
|
|
396
|
+
// call is written.
|
|
397
|
+
//
|
|
398
|
+
// `discoveryTiers` is the one arm no command can supply — it is evidence the
|
|
399
|
+
// agent gathers (provider docs, pricing, benchmarks per rule 6). Pass it when
|
|
400
|
+
// you have it; without it a model no banked/cross-harness/seed source covers
|
|
401
|
+
// resolves to `unconfirmed`, which is the honest answer and the one pre-flight
|
|
402
|
+
// prints as `tier: unconfirmed — please classify`.
|
|
403
|
+
export function resolveModelTiers({ banked, crossHarness, seed, discoveryTiers } = {}) {
|
|
404
|
+
const has = (t) => t && Object.keys(t).length > 0
|
|
405
|
+
if (has(banked)) return { tiers: banked, source: 'banked', citation: null }
|
|
406
|
+
if (has(crossHarness?.tiers)) {
|
|
407
|
+
return { tiers: crossHarness.tiers, source: 'cross-harness', citation: crossHarness.citation }
|
|
408
|
+
}
|
|
409
|
+
if (has(seed?.tiers)) return { tiers: seed.tiers, source: 'team-seed', citation: seed.citation }
|
|
410
|
+
if (has(discoveryTiers)) return { tiers: discoveryTiers, source: 'discovery', citation: null }
|
|
411
|
+
return { tiers: {}, source: 'unconfirmed', citation: null }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// The whole of pre-flight step 2's tiering, in one call: for every discovered
|
|
415
|
+
// id, which source wins and what it says. Composing the three mechanized arms
|
|
416
|
+
// here is the point — run separately, their precedence is re-applied by hand at
|
|
417
|
+
// the gate, and that hand-application is what failed. Callers get a decided
|
|
418
|
+
// answer plus the citation to print, never a pile of candidate sources to rank.
|
|
419
|
+
export function tierProposals(harness, discoveredIds, { seedFile = null, readAt = readCatalog, today = null, catalogFile = null } = {}) {
|
|
420
|
+
const day = today || new Date().toISOString().slice(0, 10)
|
|
421
|
+
// The OWN catalog honors an explicit path (CLI `--file`), same as seed-proposals/sync/bank.
|
|
422
|
+
// Without this, `tiers --file <catalog>` silently reads the default per-harness path instead —
|
|
423
|
+
// so its `seedDisagreements`/`staleClassifications` describe the wrong catalog. Cross-harness
|
|
424
|
+
// reads below stay at the standard per-harness locations (a foreign catalog is not relocatable).
|
|
425
|
+
const own = readAt(catalogFile || pathForHarness(harness)) || { models: [] }
|
|
426
|
+
const seed = readSeed(seedFile || defaultSeedPath())
|
|
427
|
+
const cross = crossHarnessProposals(harness, discoveredIds, { readAt })
|
|
428
|
+
const { proposals: seeded, suppressed } = seedProposals(seed, harness, discoveredIds, own)
|
|
429
|
+
|
|
430
|
+
const byId = (list) => new Map(list.map((x) => [x.id, x]))
|
|
431
|
+
const crossById = byId(cross)
|
|
432
|
+
const seedById = byId(seeded)
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
models: discoveredIds.map((id) => {
|
|
436
|
+
const banked = toTiers((own.models || []).find((m) => m && m.id === id))
|
|
437
|
+
const r = resolveModelTiers({ banked, crossHarness: crossById.get(id), seed: seedById.get(id) })
|
|
438
|
+
return { id, ...r, points: pointsFromTiers(r.tiers) }
|
|
439
|
+
}),
|
|
440
|
+
// Surfaced, not swallowed: a seed that disagrees with what the human banked
|
|
441
|
+
// is the only signal that the shipped seed has gone stale (it cannot be
|
|
442
|
+
// diffed against user-level catalogs in CI — they are not in the repo).
|
|
443
|
+
// `seedNewer` on a disagreement flips the nudge: the SEED may be the fresher answer.
|
|
444
|
+
seedDisagreements: suppressed.filter((s) => s.disagrees),
|
|
445
|
+
// Classification-staleness (CLASSIFY_STALE_AFTER_DAYS): a discovered model whose
|
|
446
|
+
// banked classification is older than the threshold — a candidate for re-confirmation
|
|
447
|
+
// before it is offered for a slot. Surfaced, never changed.
|
|
448
|
+
staleClassifications: (own.models || [])
|
|
449
|
+
.filter((m) => m && discoveredIds.includes(m.id) && m.confirmedAt && dayDiff(m.confirmedAt, day) > CLASSIFY_STALE_AFTER_DAYS)
|
|
450
|
+
.map((m) => ({ id: m.id, confirmedAt: m.confirmedAt, ageDays: dayDiff(m.confirmedAt, day) })),
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// A seed entry is never treated as confirmedAt (model-catalog.md rule 7): this
|
|
455
|
+
// is the shape `bank` writes once the human confirms a seed-backed proposal —
|
|
456
|
+
// a normal entry, stamped fresh, into the user's OWN per-harness file. It
|
|
457
|
+
// carries no trace back to the seed beyond the note a caller chooses to keep.
|
|
458
|
+
export function entryFromSeedConfirmation(seed, harness, id, today) {
|
|
459
|
+
const entry = findSeedEntry(seed, harness, id)
|
|
460
|
+
if (!entry) throw new Error(`entryFromSeedConfirmation: no seed entry for "${id}" under harness "${harness}"`)
|
|
461
|
+
const tiers = entry.tiers?.[harness]
|
|
462
|
+
if (!tiers || !Object.keys(tiers).length) {
|
|
463
|
+
throw new Error(`entryFromSeedConfirmation: seed entry "${entry.canonical}" has no tiers for harness "${harness}"`)
|
|
464
|
+
}
|
|
465
|
+
return { id, family: entry.family, tiers, confirmedAt: today, lastSeenAt: today }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ---- pure catalog transforms ----
|
|
469
|
+
|
|
470
|
+
function dayDiff(fromIso, toIso) {
|
|
471
|
+
return Math.floor((Date.parse(toIso) - Date.parse(fromIso)) / 86_400_000)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Rule 4 in one place: bump lastSeenAt for every seen id, then drop entries whose
|
|
475
|
+
// lastSeenAt (falling back to confirmedAt) is more than PRUNE_AFTER_DAYS old.
|
|
476
|
+
// Bump-before-prune, so a rediscovered entry is never pruned in the same sync.
|
|
477
|
+
export function syncCatalog(catalog, seenIds, today) {
|
|
478
|
+
const seen = new Set(seenIds)
|
|
479
|
+
const bumped = []
|
|
480
|
+
const kept = []
|
|
481
|
+
const pruned = []
|
|
482
|
+
const migrated = []
|
|
483
|
+
for (const entry of catalog.models || []) {
|
|
484
|
+
const e = { ...entry }
|
|
485
|
+
// Schema migration rides along with the sync write that every pre-flight read
|
|
486
|
+
// already performs — no separate migration step, nothing stale left behind.
|
|
487
|
+
// Two generations are healed here: renamed tier values, and the single-point
|
|
488
|
+
// {tier, effort} shape that predates the `tiers` map.
|
|
489
|
+
const fmt = (m) => Object.entries(m).map(([k, v]) => `${k}:${v}`).sort().join(',')
|
|
490
|
+
const before = e.tiers ? fmt(e.tiers) : (e.tier ? `${e.effort || DEFAULT_EFFORT}:${e.tier}` : '')
|
|
491
|
+
const tiers = toTiers(e)
|
|
492
|
+
const after = fmt(tiers)
|
|
493
|
+
if (before !== after || !e.tiers) {
|
|
494
|
+
if (before !== after) migrated.push(`${e.id}: {${before}} -> {${after}}`)
|
|
495
|
+
else migrated.push(`${e.id}: tier/effort -> tiers map {${after}}`)
|
|
496
|
+
}
|
|
497
|
+
e.tiers = tiers
|
|
498
|
+
delete e.tier
|
|
499
|
+
delete e.effort
|
|
500
|
+
if (seen.has(e.id)) {
|
|
501
|
+
e.lastSeenAt = today
|
|
502
|
+
bumped.push(e.id)
|
|
503
|
+
}
|
|
504
|
+
const basis = e.lastSeenAt || e.confirmedAt
|
|
505
|
+
if (basis && dayDiff(basis, today) > PRUNE_AFTER_DAYS) pruned.push(e.id)
|
|
506
|
+
else kept.push(e)
|
|
507
|
+
}
|
|
508
|
+
return { catalog: { ...catalog, models: kept }, bumped, pruned, migrated }
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Rule 2: a bank is a human confirmation — upsert by id, refresh confirmedAt
|
|
512
|
+
// (and lastSeenAt: a model being confirmed was necessarily just discovered).
|
|
513
|
+
export function bankEntry(catalog, entry, today) {
|
|
514
|
+
if (!entry || !entry.id) throw new Error('bank: the entry must have an "id"')
|
|
515
|
+
if (!entry.family) throw new Error('bank: "family" is required (model-catalog.md schema)')
|
|
516
|
+
if (!entry.tiers && !entry.tier) throw new Error('bank: one of "tiers" (effort -> tier map) or "tier" is required (model-catalog.md schema)')
|
|
517
|
+
|
|
518
|
+
if (entry.tiers && (Array.isArray(entry.tiers) || typeof entry.tiers !== 'object')) {
|
|
519
|
+
throw new Error('bank: "tiers" must be an object mapping effort -> tier (model-catalog.md schema)')
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const incoming = toTiers(entry)
|
|
523
|
+
// An entry with no operating point is a cataloged model with no classification:
|
|
524
|
+
// tierAt returns null at every effort, pre-flight sees a hit with nothing in it,
|
|
525
|
+
// and no later sync flags it. `tiers: {}` passes a truthiness guard, so check the
|
|
526
|
+
// POINTS, not the field.
|
|
527
|
+
if (!Object.keys(incoming).length) {
|
|
528
|
+
throw new Error('bank: the entry has no operating points — "tiers" must hold at least one effort -> tier pair')
|
|
529
|
+
}
|
|
530
|
+
for (const [effort, tier] of Object.entries(incoming)) {
|
|
531
|
+
if (!EFFORTS.includes(effort)) {
|
|
532
|
+
throw new Error(`bank: effort "${effort}" not in ${EFFORTS.join('|')} — a point records what a model reaches when pushed, never a throttled-down cheap seat (pick a cheaper model instead)`)
|
|
533
|
+
}
|
|
534
|
+
if (!TIERS.includes(tier)) throw new Error(`bank: tier "${tier}" not in ${TIERS.join('|')}`)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const models = [...(catalog.models || [])]
|
|
538
|
+
const i = models.findIndex((m) => m && m.id === entry.id)
|
|
539
|
+
const existing = i === -1 ? null : models[i]
|
|
540
|
+
|
|
541
|
+
// A full `tiers` map REPLACES what was banked (the human restated the whole
|
|
542
|
+
// picture); a single `tier` UPSERTS just that point, so operating points can be
|
|
543
|
+
// confirmed one at a time as they are measured, without erasing the others.
|
|
544
|
+
const tiers = entry.tiers ? incoming : { ...toTiers(existing), ...incoming }
|
|
545
|
+
|
|
546
|
+
// The legacy single-point fields never persist — and that has to be enforced on
|
|
547
|
+
// BOTH sides of the merge. Stripping only the incoming entry left `existing.tier`
|
|
548
|
+
// spread back in with nothing in `stamped` to shadow it, so an upsert onto a
|
|
549
|
+
// pre-`tiers` entry stored a `tier` that could flatly contradict the map
|
|
550
|
+
// ("tier": "frontier" beside "tiers": {"high": "balanced-coder"}). Reads go
|
|
551
|
+
// through toTiers and were fine; the FILE was wrong, which is what a human and a
|
|
552
|
+
// cross-harness lookup (catalog rule 5) actually inspect.
|
|
553
|
+
const strip = ({ tier: _t, effort: _e, ...rest }) => rest
|
|
554
|
+
const stamped = { ...strip(entry), tiers, confirmedAt: today, lastSeenAt: today }
|
|
555
|
+
if (i === -1) models.push(stamped)
|
|
556
|
+
else models[i] = { ...strip(existing), ...stamped }
|
|
557
|
+
return { ...catalog, models }
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ---- file I/O (atomic write, same as run-state.mjs) ----
|
|
561
|
+
|
|
562
|
+
export function readCatalog(file) {
|
|
563
|
+
if (!fs.existsSync(file)) return null
|
|
564
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function writeCatalog(file, catalog) {
|
|
568
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
569
|
+
const tmp = `${file}.tmp`
|
|
570
|
+
fs.writeFileSync(tmp, `${JSON.stringify(catalog, null, 2)}\n`)
|
|
571
|
+
fs.renameSync(tmp, file)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ---- CLI ----
|
|
575
|
+
|
|
576
|
+
function parseArgs(argv) {
|
|
577
|
+
const positional = []
|
|
578
|
+
const opts = {}
|
|
579
|
+
for (let i = 0; i < argv.length; i++) {
|
|
580
|
+
const a = argv[i]
|
|
581
|
+
if (a === '--stdin') opts.stdin = true
|
|
582
|
+
else if (a === '--file') opts.file = argv[++i]
|
|
583
|
+
else if (a === '--harness') opts.harness = argv[++i]
|
|
584
|
+
else if (a === '--today') opts.today = argv[++i]
|
|
585
|
+
else if (a === '--seen') opts.seen = argv[++i]
|
|
586
|
+
else if (a === '--seed-file') opts.seedFile = argv[++i]
|
|
587
|
+
else positional.push(a)
|
|
588
|
+
}
|
|
589
|
+
return { positional, opts }
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function todayIso(opts) {
|
|
593
|
+
return opts.today || new Date().toISOString().slice(0, 10)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// A write into a file stamped for another harness is cross-contamination
|
|
597
|
+
// (its ids live in a different vocabulary) — refuse it.
|
|
598
|
+
function harnessMismatch(catalog, opts, file) {
|
|
599
|
+
if (catalog && catalog.harness && opts.harness && catalog.harness !== opts.harness) {
|
|
600
|
+
return `model-catalog: catalog at ${file} belongs to harness "${catalog.harness}", not "${opts.harness}" — one catalog per harness (model-catalog.md).`
|
|
601
|
+
}
|
|
602
|
+
return null
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// model-catalog.md rule 7: the seed is read-only — `bank`/`sync` refuse it as a
|
|
606
|
+
// write target. Checked two ways so a copy of the seed at a non-default path is
|
|
607
|
+
// still caught: the resolved path matches the shipped seed location, OR the
|
|
608
|
+
// file on disk carries the seed's own `seededAt` stamp (the same shape test
|
|
609
|
+
// readSeed's caller would use to tell a seed from a per-harness catalog).
|
|
610
|
+
function refuseIfSeedWrite(file, catalog) {
|
|
611
|
+
const isDefaultSeedPath = path.resolve(file) === path.resolve(defaultSeedPath())
|
|
612
|
+
const looksLikeSeed = !!(catalog && catalog.seededAt)
|
|
613
|
+
if (isDefaultSeedPath || looksLikeSeed) {
|
|
614
|
+
return `model-catalog: ${file} is the team seed — read-only (model-catalog.md rule 7). bank/sync never write to it; confirm into your own per-harness catalog instead.`
|
|
615
|
+
}
|
|
616
|
+
return null
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function runCli(argv) {
|
|
620
|
+
const [cmd, ...rest] = argv
|
|
621
|
+
const { positional, opts } = parseArgs(rest)
|
|
622
|
+
let file = opts.file
|
|
623
|
+
if (!file && opts.harness) {
|
|
624
|
+
file = pathForHarness(opts.harness)
|
|
625
|
+
if (!file) {
|
|
626
|
+
console.error(`model-catalog: no known catalog location for harness "${opts.harness}" (${Object.keys(HARNESS_PATHS).join('|')}) — pass --file with its user-config path.`)
|
|
627
|
+
return 2
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (!file) file = defaultPath()
|
|
631
|
+
|
|
632
|
+
switch (cmd) {
|
|
633
|
+
case 'path': {
|
|
634
|
+
console.log(file)
|
|
635
|
+
return 0
|
|
636
|
+
}
|
|
637
|
+
case 'read':
|
|
638
|
+
case 'list': { // alias: "list" is the verb agents guess first
|
|
639
|
+
const c = readCatalog(file)
|
|
640
|
+
if (c == null) {
|
|
641
|
+
console.error(`model-catalog: no catalog at ${file} (absent is normal — classify from discovery-source evidence).`)
|
|
642
|
+
return 3
|
|
643
|
+
}
|
|
644
|
+
console.log(JSON.stringify(c, null, 2))
|
|
645
|
+
return 0
|
|
646
|
+
}
|
|
647
|
+
case 'points': {
|
|
648
|
+
// Pre-flight builds its slot candidates from these — without a subcommand an
|
|
649
|
+
// agent has to hand-derive them from `read`, which is exactly the hand-rolling
|
|
650
|
+
// this script exists to prevent.
|
|
651
|
+
const c = readCatalog(file)
|
|
652
|
+
if (c == null) {
|
|
653
|
+
console.error(`model-catalog: no catalog at ${file} (absent is normal — no banked points to list).`)
|
|
654
|
+
return 3
|
|
655
|
+
}
|
|
656
|
+
const id = positional[0]
|
|
657
|
+
const out = id
|
|
658
|
+
? { id, points: operatingPoints(c, id) }
|
|
659
|
+
: (c.models || []).map((m) => ({ id: m.id, family: m.family, points: operatingPoints(c, m.id) }))
|
|
660
|
+
console.log(JSON.stringify(out, null, 2))
|
|
661
|
+
return 0
|
|
662
|
+
}
|
|
663
|
+
case 'sync': {
|
|
664
|
+
const c = readCatalog(file)
|
|
665
|
+
if (c == null) {
|
|
666
|
+
console.error(`model-catalog: no catalog at ${file} (absent is normal — nothing to sync).`)
|
|
667
|
+
return 3
|
|
668
|
+
}
|
|
669
|
+
const seedRefusal = refuseIfSeedWrite(file, c)
|
|
670
|
+
if (seedRefusal) { console.error(seedRefusal); return 2 }
|
|
671
|
+
const mismatch = harnessMismatch(c, opts, file)
|
|
672
|
+
if (mismatch) { console.error(mismatch); return 2 }
|
|
673
|
+
const seenIds = (opts.seen || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
674
|
+
const { catalog, bumped, pruned, migrated } = syncCatalog(c, seenIds, todayIso(opts))
|
|
675
|
+
writeCatalog(file, catalog)
|
|
676
|
+
console.log(JSON.stringify({ bumped, pruned, migrated }, null, 2))
|
|
677
|
+
return 0
|
|
678
|
+
}
|
|
679
|
+
case 'bank': {
|
|
680
|
+
const raw = opts.stdin ? fs.readFileSync(0, 'utf8') : positional[0]
|
|
681
|
+
const entry = JSON.parse(raw)
|
|
682
|
+
const c = readCatalog(file) || { ...(opts.harness ? { harness: opts.harness } : {}), models: [] }
|
|
683
|
+
const seedRefusal = refuseIfSeedWrite(file, c)
|
|
684
|
+
if (seedRefusal) { console.error(seedRefusal); return 2 }
|
|
685
|
+
const mismatch = harnessMismatch(c, opts, file)
|
|
686
|
+
if (mismatch) { console.error(mismatch); return 2 }
|
|
687
|
+
if (!c.harness && opts.harness) c.harness = opts.harness // stamp a pre-field file at the next confirmed write
|
|
688
|
+
writeCatalog(file, bankEntry(c, entry, todayIso(opts)))
|
|
689
|
+
return 0
|
|
690
|
+
}
|
|
691
|
+
case 'seed': {
|
|
692
|
+
const seedFile = opts.seedFile || defaultSeedPath()
|
|
693
|
+
const seed = readSeed(seedFile)
|
|
694
|
+
if (seed == null) {
|
|
695
|
+
console.error(`model-catalog: no seed at ${seedFile} (absent is normal — a fresh checkout with no seed just gets no pre-fills).`)
|
|
696
|
+
return 3
|
|
697
|
+
}
|
|
698
|
+
console.log(JSON.stringify(seed, null, 2))
|
|
699
|
+
return 0
|
|
700
|
+
}
|
|
701
|
+
case 'seed-proposals': {
|
|
702
|
+
// Pre-flight builds its seed-backed pre-fills from these — restricted to
|
|
703
|
+
// --seen (rule 3: an id the seed knows but this run's discovery didn't
|
|
704
|
+
// yield never appears) and to models with no banked entry in THIS
|
|
705
|
+
// harness's own catalog (rule 7 precedence). Both filters live in
|
|
706
|
+
// seedProposals so neither depends on the caller remembering them.
|
|
707
|
+
const seedFile = opts.seedFile || defaultSeedPath()
|
|
708
|
+
const seed = readSeed(seedFile)
|
|
709
|
+
if (seed == null) {
|
|
710
|
+
console.error(`model-catalog: no seed at ${seedFile} (absent is normal — nothing to propose from).`)
|
|
711
|
+
return 3
|
|
712
|
+
}
|
|
713
|
+
const harness = opts.harness || 'claude-code'
|
|
714
|
+
const seenIds = (opts.seen || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
715
|
+
const { proposals, suppressed } = seedProposals(seed, harness, seenIds, readCatalog(file))
|
|
716
|
+
console.log(JSON.stringify({
|
|
717
|
+
proposals: proposals.map((p) => ({ ...p, citation: `tier from team seed: ${p.id}` })),
|
|
718
|
+
suppressed: suppressed.map((s) => ({
|
|
719
|
+
...s,
|
|
720
|
+
citation: s.disagrees
|
|
721
|
+
? (s.seedNewer
|
|
722
|
+
? `team seed is NEWER than your banked ${s.id} and disagrees — seed says ${fmtTiers(s.seed)} (seededAt ${s.seededAt}), you banked ${fmtTiers(s.banked)} (confirmedAt ${s.bankedConfirmedAt}). The seed may carry a reclassification your entry predates — RE-CONFIRM rather than assume your catalog wins.`
|
|
723
|
+
: `team seed disagrees with your banked ${s.id} — seed says ${fmtTiers(s.seed)}, you banked ${fmtTiers(s.banked)}; your catalog wins (rule 7). Refresh the seed if yours is the better answer.`)
|
|
724
|
+
: `already banked: ${s.id} — seed adds nothing (rule 7)`,
|
|
725
|
+
})),
|
|
726
|
+
}, null, 2))
|
|
727
|
+
return 0
|
|
728
|
+
}
|
|
729
|
+
case 'tiers': {
|
|
730
|
+
// Pre-flight step 2's tiering in ONE call. The three arms exist as separate
|
|
731
|
+
// commands too, but running them separately means re-applying precedence by
|
|
732
|
+
// hand at the gate — and hand-applied precedence is the failure this whole
|
|
733
|
+
// 2026-07-20 audit traced. Prefer this command; reach for the individual
|
|
734
|
+
// ones only when you need to see a single source in isolation.
|
|
735
|
+
const harness = opts.harness || 'claude-code'
|
|
736
|
+
const seenIds = (opts.seen || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
737
|
+
console.log(JSON.stringify(tierProposals(harness, seenIds, { seedFile: opts.seedFile, today: todayIso(opts), catalogFile: file }), null, 2))
|
|
738
|
+
return 0
|
|
739
|
+
}
|
|
740
|
+
case 'cross-harness': {
|
|
741
|
+
// Rule 5 had no implementation at all until now — it was the only
|
|
742
|
+
// precedence rule left to the agent's prose comprehension mid-gate, and
|
|
743
|
+
// it OUTRANKS the team seed, so the stronger evidence source was the least
|
|
744
|
+
// enforced one. An agent that skipped the hand-step fell through to the
|
|
745
|
+
// seed, which is exactly how a stale seed tier reached a live run.
|
|
746
|
+
const harness = opts.harness || 'claude-code'
|
|
747
|
+
const seenIds = (opts.seen || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
748
|
+
console.log(JSON.stringify(crossHarnessProposals(harness, seenIds), null, 2))
|
|
749
|
+
return 0
|
|
750
|
+
}
|
|
751
|
+
case 'bank-from-seed': {
|
|
752
|
+
// Turns a confirmed seed-backed proposal into a banked entry. Hand-writing
|
|
753
|
+
// this JSON was the gap: `bank` accepts whatever confirmedAt it is handed,
|
|
754
|
+
// so "a seed entry is never treated as confirmedAt" (rule 7) depended on
|
|
755
|
+
// the agent not copying `seededAt` across. entryFromSeedConfirmation
|
|
756
|
+
// enforces it — resolving the alias, taking THIS harness's tiers, and
|
|
757
|
+
// stamping today — and until now nothing called it.
|
|
758
|
+
const id = positional[0]
|
|
759
|
+
if (!id) { console.error('model-catalog: bank-from-seed needs a model id (the id as THIS harness exposes it).'); return 2 }
|
|
760
|
+
const seed = readSeed(opts.seedFile || defaultSeedPath())
|
|
761
|
+
if (seed == null) { console.error('model-catalog: no seed to confirm from.'); return 3 }
|
|
762
|
+
const harness = opts.harness || 'claude-code'
|
|
763
|
+
let entry
|
|
764
|
+
try {
|
|
765
|
+
entry = entryFromSeedConfirmation(seed, harness, id, todayIso(opts))
|
|
766
|
+
} catch (e) { console.error(`model-catalog: ${e.message}`); return 2 }
|
|
767
|
+
const c = readCatalog(file) || { ...(opts.harness ? { harness: opts.harness } : {}), models: [] }
|
|
768
|
+
const seedRefusal = refuseIfSeedWrite(file, c)
|
|
769
|
+
if (seedRefusal) { console.error(seedRefusal); return 2 }
|
|
770
|
+
const mismatch = harnessMismatch(c, opts, file)
|
|
771
|
+
if (mismatch) { console.error(mismatch); return 2 }
|
|
772
|
+
if (!c.harness && opts.harness) c.harness = opts.harness
|
|
773
|
+
writeCatalog(file, bankEntry(c, entry, todayIso(opts)))
|
|
774
|
+
console.log(JSON.stringify(entry, null, 2))
|
|
775
|
+
return 0
|
|
776
|
+
}
|
|
777
|
+
case 'help':
|
|
778
|
+
case '--help':
|
|
779
|
+
console.log('model-catalog: read (alias: list) | path | points [<id>] | tiers --seen <id,id,...> | sync --seen <id,id,...> | bank <entryJson> | bank-from-seed <id> | cross-harness --seen <id,id,...> | seed | seed-proposals --seen <id,id,...> — options --harness <claude-code|codex|pi>, --file <path>, --seed-file <path>, --today <YYYY-MM-DD>, --stdin (see header).')
|
|
780
|
+
return 0
|
|
781
|
+
default:
|
|
782
|
+
console.error('model-catalog: unknown command. Use read|path|points|sync|bank|seed|seed-proposals (see header).')
|
|
783
|
+
return 2
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Only run the CLI when invoked directly (so tests can import the pure functions).
|