feat(core): tiered model aliases — replace opus/sonnet/haiku with high/medium/low - #64
Conversation
…ases Adds PRD and TRD for replacing opus/sonnet/haiku aliases with high/medium/low tier aliases backed by a project-level .claude/ensemble-model-config.json file. Includes /ensemble:map-model wizard and /ensemble:migrate-model-config commands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… schema, config-loader, model-resolver - Add packages/core/lib/known-model-ids.js: frozen KNOWN_MODEL_IDS array (single source of truth) - Add schemas/ensemble-model-config-schema.json: JSON Schema draft-07 for .claude/ensemble-model-config.json - Rewrite packages/core/lib/config-loader.js: findProjectRoot, getProjectConfigPath, getDefaultConfig (tiers: high/medium/low), validateConfig (rejects legacy opus/sonnet/haiku keys), loadConfig (project-local file), checkLegacyXdgFile (one-time migration warning), emitFirstRunHint (first-run sentinel) - Rewrite packages/core/lib/model-resolver.js: PreflightError, preflightValidate, resolveModel, selectModel (ENSEMBLE_MODEL_OVERRIDE must be tier alias; legacy aliases and raw model IDs rejected) - Update packages/core/lib/index.js: export new tier-based APIs alongside existing exports - Add/update all tests: known-model-ids, schema, config-loader (incl. TRD-006 first-run hint), model-resolver, usage-logger (self-contained config), integration suite - Close beads: cow2, aecg, wkad, 22ic, 7xe6, 0ez5, 6bd2, onms, 0cez, ropv, u4ir, s2z9 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…migration, YAML migration, agent tier assignments - Update command-yaml-schema.json model enum: opus/sonnet/haiku → high/medium/low - Add optional model field to agent-yaml-schema.json metadata properties - Migrate 7 command YAMLs: opus → high (configure-team, create-trd-foreman, create-trd, refine-trd, refine-prd, feature, create-prd) - Migrate 11 command YAMLs: sonnet → medium (discover-standards, inject-standards, analyze-requirements, beads-build, beads-plan, fix-issue, implement-bead, implement-trd, requirement-status, implement-trd-beads, validate-requirements) - Migrate inline model refs in fix-issue.yaml: haiku → low, sonnet → medium - Add model: high to 9 high-tier agent YAMLs (ensemble-orchestrator, tech-lead-orchestrator, product-management-orchestrator, qa-orchestrator, infrastructure-orchestrator, code-reviewer, deep-debugger, agent-meta-engineer, release-agent) - Add model: medium to 15 medium-tier agent YAMLs (backend-developer, frontend-developer, infrastructure-developer, documentation-specialist, api-documentation-specialist, postgresql-specialist, helm-chart-specialist, build-orchestrator, deployment-orchestrator, playwright-tester, test-runner, github-specialist, git-workflow, manager-dashboard-agent, general-purpose) - Add model: low to 3 low-tier agent YAMLs (file-creator, context-fetcher, directory-monitor) - Update 18 generated .md frontmatter files to match YAML sources Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…igrator, command definitions - Add map-model-wizard.js with runWizard (TTY interactive), runOneShotUpdate (non-interactive), and writeConfigAtomic helper - Add legacy-config-migrator.js with findLegacyConfig, migrateLegacyConfig (opus→high, sonnet→medium, haiku→low mapping, commandOverrides/costTracking warnings) - Add map-model.yaml and migrate-model-config.yaml command definitions with generated .md files - Add 22 tests covering writeConfigAtomic, runOneShotUpdate, runWizard TTY detection, findLegacyConfig, and migrateLegacyConfig edge cases - BYPASS_COMMANDS in config-loader.js already included both map-model and migrate-model-config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ELOG, README, usage-logger cleanup - TRD-019: Add scripts/lint-model-ids.js — scans all command/agent YAMLs, validates model: values against KNOWN_MODEL_IDS + tier aliases, exits 0/1 - TRD-019-TEST: Add scripts/tests/lint-model-ids.test.js — unit tests for extractModelValue and integration test for exit codes - TRD-020: Add lint:model-ids script to root package.json; integrate into validate chain - TRD-020-TEST: Smoke test confirmed — 58 files scanned, exit 0 on clean codebase - TRD-021: Add [5.0.0] breaking-change entry to packages/core/CHANGELOG.md - TRD-022: Add "Model Tier Configuration" section to root README.md - TRD-023: Update usage-logger.js — hardcoded XDG log path, remove costTracking guard, MODEL_PRICING updated to current model IDs - TRD-023-TEST: Rewrite usage-logger.test.js for new API (always-log, new model IDs, new tier aliases) - All TRD-001 through TRD-023-TEST checkboxes marked complete in TRD Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…loper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review — feat(core): tiered model aliasesOverall this is a well-architected, well-tested change. The motivation is clear (abstract away model-name churn), the phased implementation is organized, and the test coverage looks solid. I have a few concerns worth addressing before merge. Unintended Files in the PR
Bugs / Correctness Issues1. const match = yamlContent.match(/^ model:\s+(\S+)\s*$/m) || yamlContent.match(/^model:\s+(\S+)\s*$/m);
Consider using a proper YAML parser (js-yaml is already available in the monorepo ecosystem), or at minimum use 2. Silent key-dropping in // Any other keys are silently droppedCustom model aliases that don't map to 3. The old signature Design / Architecture Concerns4. Schema inconsistency: In "additionalProperties": true ← root levelBut 5. The schema accepts any string in 6.
7. parsed.modelAliases = parsed.tiers; // same referenceThis mutation of the loaded config object is subtle and could cause bugs if any caller modifies 8. Prefixing with Minor Observations
What's Working Well
Priority Summary
🤖 Reviewed with Claude Code |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review — feat(core): tiered model aliasesOverviewThis PR replaces opaque model aliases ( Strengths
Issues & SuggestionsBug —
|
Update haiku→low and sonnet→medium in fix-issue-command.test.js to match the Phase 2 command YAML migration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review: feat(core) tiered model aliasesOverall this is a clean, well-structured change. The phased implementation, comprehensive test suite, and CI lint script are all solid. A few issues worth addressing before merge: Bugs / Correctness1. BYPASS_COMMANDS name mismatch (potential bug)
// model-resolver.js:38
if (BYPASS_COMMANDS.includes(commandName)) { // only matches 'map-model', not 'ensemble:map-model'Consider normalising with 2.
Design / API3. Preflight runs before In Consider validating only the tier that will actually be selected, or checking the override first. 4. In const XDG_LEGACY_PATHS = [
path.join(process.env.XDG_CONFIG_HOME || ..., ...)This is evaluated once at Schema / Validation5. The schema allows unknown top-level keys silently. A typo like 6. No format constraint on The schema accepts any non-empty string in Non-functional / Clean-up7. Ephemeral
Positives worth calling out
SummaryThe two most important items are #1 (BYPASS_COMMANDS mismatch — a latent bug that will surface if the namespaced form is ever used as |
Code Review — feat(core): tiered model aliasesOverviewThis PR replaces legacy Issues1. Ephemeral
|
Summary
opus/sonnet/haikumodel aliases with abstract tier aliaseshigh/medium/lowacross all 18 command YAMLs, all 28 agent YAMLs, and the schema files<project>/.claude/ensemble-model-config.jsonas the per-project tier→model-ID mapping (replaces XDG user-level config)/ensemble:map-modelinteractive wizard and/ensemble:migrate-model-configone-shot migration commandpackages/core/lib/known-model-ids.jsas single source of truth for valid Claude model IDsnpm run lint:model-idsCI lint script — scans all YAML for retired/unknown model IDsBreaking Changes
opus,sonnet,haikualiases are no longer valid — rename tohigh,medium,low<project_root>/.claude/ensemble-model-config.json(not XDG path)ENSEMBLE_MODEL_OVERRIDEmust be a tier alias (high/medium/low), not a model ID or legacy aliascommandOverridesblock in legacy config is not carried forward (setmetadata.modelin each command YAML instead)Test plan
packages/core/tests(all new code covered)node scripts/lint-model-ids.jsexits 0 — 58 YAML files scanned, zero legacy/unknown model valuesgrep -rn "model: opus|model: sonnet|model: haiku|model: inherit" packages/*/agents/ packages/*/commands/→ 0 matchesmodel: high|medium|lowfieldschemas/command-yaml-schema.jsonenum updated to["high","medium","low"]schemas/agent-yaml-schema.jsonnow has optionalmodelfield with same enumpackages/core/lib/usage-logger.jsMODEL_PRICING updated to current (non-retired) model IDsImplementation phases
e9f9af7known-model-ids.js, JSON Schema,config-loader.js,model-resolver.jsrewrite3524c2e5ec858fmap-model-wizard.js,legacy-config-migrator.js, 2 command definitions09254bd🤖 Generated with Claude Code