@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.
Files changed (397) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +90 -0
  3. package/bin/1aboveio-skills.mjs +18 -0
  4. package/package.json +28 -0
  5. package/runtime/skills/distribution/generated/recipes.json +1189 -0
  6. package/runtime/skills/distribution/scripts/bundles.mjs +280 -0
  7. package/runtime/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
  8. package/skills/backend/airflow-dag-develop/LICENSE +3 -0
  9. package/skills/backend/airflow-dag-develop/SKILL.md +111 -0
  10. package/skills/backend/app-debug/LICENSE +3 -0
  11. package/skills/backend/app-debug/SKILL.md +109 -0
  12. package/skills/backend/app-debug/references/common-errors.md +128 -0
  13. package/skills/backend/python-backend/LICENSE +3 -0
  14. package/skills/backend/python-backend/SKILL.md +326 -0
  15. package/skills/cicd-pipeline/cloud-build/LICENSE +3 -0
  16. package/skills/cicd-pipeline/cloud-build/SKILL.md +707 -0
  17. package/skills/cicd-pipeline/cloud-debug/LICENSE +3 -0
  18. package/skills/cicd-pipeline/cloud-debug/SKILL.md +316 -0
  19. package/skills/cicd-pipeline/cloud-debug/references/build-failures.md +238 -0
  20. package/skills/cicd-pipeline/cloud-debug/references/deploy-failures.md +376 -0
  21. package/skills/cicd-pipeline/cloud-debug/references/pipeline-failures.md +378 -0
  22. package/skills/cicd-pipeline/cloud-deploy/LICENSE +3 -0
  23. package/skills/cicd-pipeline/cloud-deploy/SKILL.md +229 -0
  24. package/skills/cicd-pipeline/cloud-deploy/references/config-templates.md +257 -0
  25. package/skills/cicd-pipeline/docker/LICENSE +3 -0
  26. package/skills/cicd-pipeline/docker/SKILL.md +126 -0
  27. package/skills/cicd-pipeline/google-cloud/LICENSE +3 -0
  28. package/skills/cicd-pipeline/google-cloud/SKILL.md +118 -0
  29. package/skills/cicd-pipeline/google-cloud/references/gcs.md +469 -0
  30. package/skills/cicd-pipeline/google-cloud/references/iam.md +451 -0
  31. package/skills/cicd-pipeline/google-cloud/references/project.md +349 -0
  32. package/skills/cicd-pipeline/google-cloud/references/secrets.md +336 -0
  33. package/skills/cicd-pipeline/google-cloud/references/vpc.md +312 -0
  34. package/skills/cicd-pipeline/google-cloud/scripts/create-sa.sh +36 -0
  35. package/skills/cicd-pipeline/google-cloud/scripts/gcp-config.sh +31 -0
  36. package/skills/cicd-pipeline/google-cloud/scripts/grant-iap.sh +41 -0
  37. package/skills/cicd-pipeline/google-cloud/scripts/setup-secrets.sh +48 -0
  38. package/skills/cicd-pipeline/mergify/LICENSE +3 -0
  39. package/skills/cicd-pipeline/mergify/SKILL.md +138 -0
  40. package/skills/cicd-pipeline/mergify/assets/templates/mergify.yml +237 -0
  41. package/skills/cicd-pipeline/mergify/assets/templates/ruleset.json +46 -0
  42. package/skills/cicd-pipeline/mergify/references/branch-protection.md +277 -0
  43. package/skills/cicd-pipeline/mergify/references/configuration.md +183 -0
  44. package/skills/cicd-pipeline/mergify/references/diagnosis.md +73 -0
  45. package/skills/cicd-pipeline/mergify/references/traps.md +78 -0
  46. package/skills/cicd-pipeline/mergify/references/watch-contract.md +218 -0
  47. package/skills/cicd-pipeline/mergify/scripts/audit-core.mjs +131 -0
  48. package/skills/cicd-pipeline/mergify/scripts/audit.mjs +4 -0
  49. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +663 -0
  50. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery.mjs +4 -0
  51. package/skills/cicd-pipeline/podman/LICENSE +3 -0
  52. package/skills/cicd-pipeline/podman/SKILL.md +70 -0
  53. package/skills/cicd-pipeline/podman/agents/openai.yaml +4 -0
  54. package/skills/cicd-pipeline/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
  55. package/skills/cicd-pipeline/podman/assets/templates/podman-service-override.conf +3 -0
  56. package/skills/cicd-pipeline/podman/references/compose-compatibility.md +70 -0
  57. package/skills/cicd-pipeline/podman/references/networking-and-ports.md +74 -0
  58. package/skills/cicd-pipeline/podman/references/rootless-services-and-sockets.md +156 -0
  59. package/skills/cicd-pipeline/podman/references/troubleshooting.md +98 -0
  60. package/skills/engineering/e2e-test/LICENSE +3 -0
  61. package/skills/engineering/e2e-test/SKILL.md +156 -0
  62. package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +272 -0
  63. package/skills/engineering/e2e-test/assets/ci-gates.github.yml +451 -0
  64. package/skills/engineering/e2e-test/assets/e2e-workflow.yml +282 -0
  65. package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
  66. package/skills/engineering/e2e-test/references/authoring/playwright-config.md +71 -0
  67. package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +219 -0
  68. package/skills/engineering/e2e-test/references/authoring/test-skipping.md +44 -0
  69. package/skills/engineering/e2e-test/references/ci-integration.md +121 -0
  70. package/skills/engineering/e2e-test/references/ci-playwright-container.md +280 -0
  71. package/skills/engineering/e2e-test/references/debugging.md +36 -0
  72. package/skills/engineering/e2e-test/references/presentation-sweep.md +131 -0
  73. package/skills/engineering/e2e-test/references/reviewing.md +39 -0
  74. package/skills/engineering/e2e-test/references/route-discovery.md +50 -0
  75. package/skills/engineering/e2e-test/references/route-manifest.md +44 -0
  76. package/skills/engineering/e2e-test/scripts/detect-routes-fastapi.py +290 -0
  77. package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
  78. package/skills/engineering/e2e-test/scripts/post-visual-evidence.mjs +158 -0
  79. package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
  80. package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
  81. package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
  82. package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
  83. package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +148 -0
  84. package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
  85. package/skills/engineering/e2e-test/scripts/presentation-visual.mjs +84 -0
  86. package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +75 -0
  87. package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
  88. package/skills/engineering/engineering-runtime/LICENSE +3 -0
  89. package/skills/engineering/engineering-runtime/SKILL.md +10 -0
  90. package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
  91. package/skills/engineering/engineering-runtime/coherence/workflow.json +553 -0
  92. package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
  93. package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
  94. package/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
  95. package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
  96. package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
  97. package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
  98. package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
  99. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +576 -0
  100. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +166 -0
  101. package/skills/engineering/ensure-coverage/LICENSE +3 -0
  102. package/skills/engineering/ensure-coverage/SKILL.md +136 -0
  103. package/skills/engineering/ensure-coverage/evals/evals.json +125 -0
  104. package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +91 -0
  105. package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +83 -0
  106. package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
  107. package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
  108. package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
  109. package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +25 -0
  110. package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
  111. package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +87 -0
  112. package/skills/engineering/ensure-coverage/references/depth/test-smells.md +23 -0
  113. package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +164 -0
  114. package/skills/engineering/ensure-coverage/references/enforcement/hooks.md +85 -0
  115. package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +109 -0
  116. package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +33 -0
  117. package/skills/engineering/ensure-coverage/references/presentation/axis.md +78 -0
  118. package/skills/engineering/ensure-coverage/references/presentation/runner-contract.md +74 -0
  119. package/skills/engineering/ensure-coverage/references/process/audit-mode.md +33 -0
  120. package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
  121. package/skills/engineering/ensure-coverage/references/process/review-contract-template.md +119 -0
  122. package/skills/engineering/ensure-coverage/references/process/scope-class.md +178 -0
  123. package/skills/engineering/ensure-coverage/references/process/test-strategy.md +55 -0
  124. package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +45 -0
  125. package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +93 -0
  126. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +178 -0
  127. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +101 -0
  128. package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +358 -0
  129. package/skills/engineering/ensure-coverage/scripts/coverage-checklist.mjs +494 -0
  130. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +663 -0
  131. package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
  132. package/skills/engineering/ensure-coverage/scripts/evidence-block.mjs +367 -0
  133. package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +269 -0
  134. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
  135. package/skills/engineering/ensure-coverage/scripts/package-lock.json +76 -0
  136. package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
  137. package/skills/engineering/ensure-coverage/scripts/scope-class.mjs +554 -0
  138. package/skills/engineering/harness-runtime/LICENSE +3 -0
  139. package/skills/engineering/harness-runtime/SKILL.md +18 -0
  140. package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
  141. package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
  142. package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
  143. package/skills/engineering/harness-runtime/contracts.md +15 -0
  144. package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
  145. package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
  146. package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
  147. package/skills/engineering/harness-runtime/index.mjs +767 -0
  148. package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
  149. package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
  150. package/skills/engineering/harness-runtime/references/model-catalog-seed.json +159 -0
  151. package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
  152. package/skills/engineering/implement-and-pr/LICENSE +3 -0
  153. package/skills/engineering/implement-and-pr/SKILL.md +176 -0
  154. package/skills/engineering/implement-and-pr/references/ci-iteration.md +10 -0
  155. package/skills/engineering/implement-and-pr/references/closeout.md +27 -0
  156. package/skills/engineering/implement-and-pr/references/contract-complete-fix-rounds.md +34 -0
  157. package/skills/engineering/implement-and-pr/references/evidence-rules.md +39 -0
  158. package/skills/engineering/implement-and-pr/references/incremental-plan.md +16 -0
  159. package/skills/engineering/implement-and-pr/references/self-review.md +23 -0
  160. package/skills/engineering/implement-and-pr/references/tdd-mode.md +18 -0
  161. package/skills/engineering/resolve-issues/LICENSE +3 -0
  162. package/skills/engineering/resolve-issues/SKILL.md +167 -0
  163. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +448 -0
  164. package/skills/engineering/resolve-issues/references/breaker.md +82 -0
  165. package/skills/engineering/resolve-issues/references/deliverables.md +27 -0
  166. package/skills/engineering/resolve-issues/references/delivery.md +108 -0
  167. package/skills/engineering/resolve-issues/references/evidence-lane.md +21 -0
  168. package/skills/engineering/resolve-issues/references/exact-head-ci.md +287 -0
  169. package/skills/engineering/resolve-issues/references/fan-out.md +33 -0
  170. package/skills/engineering/resolve-issues/references/finalization.md +68 -0
  171. package/skills/engineering/resolve-issues/references/guarantees.md +10 -0
  172. package/skills/engineering/resolve-issues/references/high-risk.md +29 -0
  173. package/skills/engineering/resolve-issues/references/incidents/848/README.md +156 -0
  174. package/skills/engineering/resolve-issues/references/intake.md +86 -0
  175. package/skills/engineering/resolve-issues/references/integration-gate.md +53 -0
  176. package/skills/engineering/resolve-issues/references/interference.md +87 -0
  177. package/skills/engineering/resolve-issues/references/loop.md +134 -0
  178. package/skills/engineering/resolve-issues/references/model-catalog.md +9 -0
  179. package/skills/engineering/resolve-issues/references/postmortem.md +27 -0
  180. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +41 -0
  181. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +48 -0
  182. package/skills/engineering/resolve-issues/references/pre-flight.md +41 -0
  183. package/skills/engineering/resolve-issues/references/regression-checklist.md +26 -0
  184. package/skills/engineering/resolve-issues/references/run-state.md +288 -0
  185. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +48 -0
  186. package/skills/engineering/resolve-issues/references/spawn-contract.md +96 -0
  187. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +40 -0
  188. package/skills/engineering/resolve-issues/references/why.md +653 -0
  189. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +49 -0
  190. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +721 -0
  191. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +962 -0
  192. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +220 -0
  193. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +420 -0
  194. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +256 -0
  195. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +168 -0
  196. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
  197. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +62 -0
  198. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +191 -0
  199. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +413 -0
  200. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +332 -0
  201. package/skills/engineering/resolve-issues/scripts/finalize.mjs +488 -0
  202. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +3307 -0
  203. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +16 -0
  204. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +51 -0
  205. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +17 -0
  206. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +166 -0
  207. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +92 -0
  208. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +73 -0
  209. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +159 -0
  210. package/skills/engineering/resolve-issues/scripts/interference.mjs +427 -0
  211. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
  212. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +419 -0
  213. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +909 -0
  214. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +322 -0
  215. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +415 -0
  216. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +697 -0
  217. package/skills/engineering/resolve-issues/scripts/report.mjs +494 -0
  218. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +172 -0
  219. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +79 -0
  220. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +334 -0
  221. package/skills/engineering/resolve-issues/scripts/run-state.mjs +4784 -0
  222. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +395 -0
  223. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +290 -0
  224. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +170 -0
  225. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +293 -0
  226. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +197 -0
  227. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +127 -0
  228. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +893 -0
  229. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +829 -0
  230. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +290 -0
  231. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +80 -0
  232. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +102 -0
  233. package/skills/engineering/resolve-release/LICENSE +3 -0
  234. package/skills/engineering/resolve-release/SKILL.md +112 -0
  235. package/skills/engineering/resolve-release/references/assembly.md +137 -0
  236. package/skills/engineering/resolve-release/references/auto-when-green.md +56 -0
  237. package/skills/engineering/resolve-release/references/candidate.md +167 -0
  238. package/skills/engineering/resolve-release/references/exposure.md +178 -0
  239. package/skills/engineering/resolve-release/references/handoff.md +24 -0
  240. package/skills/engineering/resolve-release/references/postmortem.md +230 -0
  241. package/skills/engineering/resolve-release/references/preflight.md +207 -0
  242. package/skills/engineering/resolve-release/references/principles.md +94 -0
  243. package/skills/engineering/resolve-release/references/regression-checklist.md +36 -0
  244. package/skills/engineering/resolve-release/references/related-skills.md +12 -0
  245. package/skills/engineering/resolve-release/references/routing.md +149 -0
  246. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +285 -0
  247. package/skills/engineering/resolve-release/references/versioning.md +202 -0
  248. package/skills/engineering/resolve-release/references/why.md +53 -0
  249. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +389 -0
  250. package/skills/engineering/resolve-release/scripts/build-changes.mjs +209 -0
  251. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +407 -0
  252. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +904 -0
  253. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +81 -0
  254. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +612 -0
  255. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +135 -0
  256. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +161 -0
  257. package/skills/engineering/resolve-release/scripts/doctrine.mjs +106 -0
  258. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +522 -0
  259. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +659 -0
  260. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +1014 -0
  261. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +526 -0
  262. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +169 -0
  263. package/skills/engineering/resolve-release/scripts/green-gate.mjs +599 -0
  264. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +752 -0
  265. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +56 -0
  266. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +1538 -0
  267. package/skills/engineering/resolve-release/scripts/postmortem.mjs +381 -0
  268. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +498 -0
  269. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +326 -0
  270. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +272 -0
  271. package/skills/engineering/resolve-release/scripts/report.mjs +417 -0
  272. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +114 -0
  273. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +238 -0
  274. package/skills/engineering/resolve-release/scripts/review-packet.mjs +503 -0
  275. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +263 -0
  276. package/skills/engineering/resolve-release/scripts/version-assert.mjs +339 -0
  277. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +485 -0
  278. package/skills/engineering/resolve-release/scripts/version.mjs +1199 -0
  279. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +449 -0
  280. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +21 -0
  281. package/skills/engineering/resolve-release/vendor/README.md +60 -0
  282. package/skills/engineering/resolve-release/vendor/acorn.mjs +6233 -0
  283. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +21 -0
  284. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +341 -0
  285. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +70 -0
  286. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +2 -0
  287. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +1 -0
  288. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +7 -0
  289. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
  290. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +2033 -0
  291. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +2033 -0
  292. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +7 -0
  293. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +1 -0
  294. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +2 -0
  295. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +1 -0
  296. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +2 -0
  297. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +1 -0
  298. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +2 -0
  299. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
  300. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +49 -0
  301. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +21 -0
  302. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +5 -0
  303. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +549 -0
  304. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +549 -0
  305. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +2 -0
  306. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +1 -0
  307. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +2 -0
  308. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +1 -0
  309. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +36 -0
  310. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +47 -0
  311. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +82 -0
  312. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +11 -0
  313. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +22 -0
  314. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +21 -0
  315. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
  316. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
  317. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +11 -0
  318. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +25 -0
  319. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  320. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +85 -0
  321. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +85 -0
  322. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +2 -0
  323. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +1 -0
  324. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +2 -0
  325. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +1 -0
  326. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +20 -0
  327. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +20 -0
  328. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +2 -0
  329. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +1 -0
  330. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +2 -0
  331. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +1 -0
  332. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +61 -0
  333. package/skills/engineering/review-pr/LICENSE +3 -0
  334. package/skills/engineering/review-pr/SKILL.md +123 -0
  335. package/skills/engineering/review-pr/references/adversarial-reviewer-prompt.md +42 -0
  336. package/skills/engineering/review-pr/references/code-correctness.md +5 -0
  337. package/skills/engineering/review-pr/references/contract-freshness.md +9 -0
  338. package/skills/engineering/review-pr/references/coordination.md +18 -0
  339. package/skills/engineering/review-pr/references/domain-hazards.md +123 -0
  340. package/skills/engineering/review-pr/references/finding-themes.md +7 -0
  341. package/skills/engineering/review-pr/references/github-posting.md +98 -0
  342. package/skills/engineering/review-pr/references/golden-path-smoke.md +5 -0
  343. package/skills/engineering/review-pr/references/incremental-output.md +16 -0
  344. package/skills/engineering/review-pr/references/inputs-and-discovery.md +31 -0
  345. package/skills/engineering/review-pr/references/output-format.md +99 -0
  346. package/skills/engineering/review-pr/references/over-mock-screen.md +7 -0
  347. package/skills/engineering/review-pr/references/promotion-prs.md +16 -0
  348. package/skills/engineering/review-pr/references/re-review.md +18 -0
  349. package/skills/engineering/review-pr/references/review-method.md +199 -0
  350. package/skills/engineering/review-pr/references/review-mode.md +30 -0
  351. package/skills/engineering/review-pr/references/review-posture.md +53 -0
  352. package/skills/engineering/review-pr/references/round1-depth.md +62 -0
  353. package/skills/engineering/review-pr/references/scripts.md +17 -0
  354. package/skills/engineering/review-pr/references/workflow.md +16 -0
  355. package/skills/engineering/review-pr/schemas/findings.schema.json +282 -0
  356. package/skills/engineering/review-pr/scripts/finding-contract.mjs +285 -0
  357. package/skills/engineering/review-pr/scripts/post-review.mjs +405 -0
  358. package/skills/engineering/review-pr/scripts/pr-context.mjs +207 -0
  359. package/skills/engineering/review-pr/scripts/scan-diff.mjs +365 -0
  360. package/skills/engineering/review-pr/scripts/theme-contract.mjs +57 -0
  361. package/skills/engineering/smoke/LICENSE +3 -0
  362. package/skills/engineering/smoke/SKILL.md +131 -0
  363. package/skills/engineering/smoke/assets/smoke.manifest.example.json +53 -0
  364. package/skills/engineering/smoke/references/manifest.md +192 -0
  365. package/skills/engineering/smoke/scripts/smoke.mjs +713 -0
  366. package/skills/fullstack/better-auth/LICENSE +3 -0
  367. package/skills/fullstack/better-auth/SKILL.md +601 -0
  368. package/skills/fullstack/better-auth/references/feishu-api.md +270 -0
  369. package/skills/fullstack/monorepo/LICENSE +3 -0
  370. package/skills/fullstack/monorepo/SKILL.md +465 -0
  371. package/skills/fullstack/nextjs-fullstack/LICENSE +3 -0
  372. package/skills/fullstack/nextjs-fullstack/SKILL.md +210 -0
  373. package/skills/fullstack/nextjs-fullstack/conventions.md +318 -0
  374. package/skills/fullstack/nextjs-fullstack/frontend-conventions.md +61 -0
  375. package/skills/fullstack/nextjs-fullstack/nextjs16.md +287 -0
  376. package/skills/fullstack/nextjs-fullstack/server-actions.md +409 -0
  377. package/skills/fullstack/prisma-setup/LICENSE +3 -0
  378. package/skills/fullstack/prisma-setup/SKILL.md +180 -0
  379. package/skills/fullstack/prisma-setup/nextjs.md +258 -0
  380. package/skills/fullstack/prisma-setup/turborepo.md +301 -0
  381. package/skills/fullstack/shadcn/LICENSE +3 -0
  382. package/skills/fullstack/shadcn/SKILL.md +119 -0
  383. package/skills/fullstack/shadcn/assets/shadcn-small.png +0 -0
  384. package/skills/fullstack/shadcn/assets/shadcn.png +0 -0
  385. package/skills/fullstack/shadcn/cli.md +411 -0
  386. package/skills/fullstack/shadcn/customization.md +224 -0
  387. package/skills/fullstack/shadcn/evals/evals.json +90 -0
  388. package/skills/fullstack/shadcn/mcp.md +101 -0
  389. package/skills/fullstack/shadcn/rules/base-vs-radix.md +323 -0
  390. package/skills/fullstack/shadcn/rules/component-selection.md +67 -0
  391. package/skills/fullstack/shadcn/rules/composition.md +195 -0
  392. package/skills/fullstack/shadcn/rules/data-table.md +201 -0
  393. package/skills/fullstack/shadcn/rules/forms.md +255 -0
  394. package/skills/fullstack/shadcn/rules/icons.md +103 -0
  395. package/skills/fullstack/shadcn/rules/styling.md +167 -0
  396. package/skills/fullstack/zod-v4/LICENSE +3 -0
  397. 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).