claude-profile: remove provider-yaml system, embed model config in profiles

Provider selection phase removed; Anthropic driver→model_id is now
hardcoded in the script and non-Anthropic profiles supply provider.env
(MODEL_ID, ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY_FILE).

Test suite baseline changes 58 → 41 assertions: the 17 dropped
assertions covered the removed provider-yaml selection phase, not
retained behaviour.

Includes refactor-derived memory: decisions (provider architecture,
driver enforcement via --model), bash gotchas (read -rp under set -e,
never eval user config), process lessons.
This commit is contained in:
Paul O'Reilly
2026-06-12 21:26:07 +12:00
parent c0a2110ee6
commit 9e13b7f096
16 changed files with 271 additions and 398 deletions

View File

@@ -23,3 +23,59 @@ Scripts that need to read JSON (e.g., `.reflection-state.json`) use inline pytho
## Project hosted under `skynet` org
`small-scripts` lives in the `skynet` org on Gitea (`gitea.oreillyit.nz`) as an AI-focused project — specifically a testbed for agent-driven, spec-first development workflows.
## Mode files in `data/claude-profile/modes/` not profile directories
Engagement mode definitions live in `data/claude-profile/modes/<name>.md` in the repo, not in profile directories. Source of truth must be version-controlled; profile dirs are for user state, not script data. This established `data/` as a new repo top-level convention.
## Driver enforcement via `claude --model`, not system prompt
Driver model is set via `claude --model <full-id>` which is session-scoped. This is real enforcement, not advisory injection of "you are Sonnet" into an Opus session. Confirmed via the `claude-code-guide` agent.
## Full model IDs not aliases
Use `claude-sonnet-4-6`, `claude-haiku-4-5-20251001`, `claude-opus-4-6` instead of `sonnet`/`opus`/`haiku` aliases. Removes ambiguity about which version is selected; haiku alias was unverified.
## v1 claude-profile skips interactive project picker
v1 does not ask for a project interactively because that would duplicate the existing CLAUDE.md session-start project picker. The `--project` flag works for non-interactive use; CLAUDE.md integration is deferred to FUTURE.md. <!-- STALE? CLAUDE.md session-start now auto-selects project from cwd or CLAUDE_PROJECT in active-mode.env (commit 340c403) — the deferred integration has shipped. -->
## Deep Work mode drives with Sonnet, not Opus
Sonnet is the workhorse; Opus is the consultant. Opus only enters via named workflows (plan review, spec review, dual-model second opinion, gnarly-bug consult, architecture review). Default reasoning depth is "think hard", not ultrathink.
## Pure awk for frontmatter and presets parsing
Frontmatter and `presets.yaml` are parsed by pure awk, not yq. small-scripts targets a clean Linux env; adding yq for one launcher is overkill. Parsing is simple key:value with one-level nesting.
## `bg-model-call` wrapper deferred
The background model call wrapper is referenced by modes as a pattern but not required to ship. Deferred to FUTURE.md as out of scope for the launcher.
## `escalates_to` field in mode frontmatter, not hardcoded in statusline
Which modes display the `[Sonnet->Opus]` arrow is declared per-mode via an `escalates_to` frontmatter field, not hardcoded in `statusline.sh`. Keeps the status line generic; any future mode can declare its own escalation target without touching statusline code.
## statusline reads `CLAUDE_DRIVER` short name from active-mode.env, not session JSON
`statusline.sh` reads `CLAUDE_DRIVER` (short name like "Sonnet") from `CLAUDE_CONFIG_DIR/active-mode.env` rather than using `model.display_name` from the session JSON. Session JSON returns the full name ("Claude Sonnet 4.6") which doesn't fit the concise status format, and using the profile-provided short name also future-proofs against model renames.
## Parse `active-mode.env` with `grep | cut`, never `source`
`statusline.sh` reads `CLAUDE_CONFIG_DIR/active-mode.env` using `grep | cut` rather than `source`. Sourcing arbitrary env files is unsafe (code-injection) and, combined with `set -euo pipefail`, any error in the sourced file would abort the status line. The grep/cut approach is inert and bounded.
## Provider config lives in profile directories via `provider.env`
Provider choice (Anthropic, MiniMax, etc.) was previously an interactive phase in `claude-profile`. It is now embedded per-profile via `provider.env` in the profile directory. Different providers = different profiles, which is already the natural isolation boundary. Eliminates an entire interactive phase, and avoids the dead-code `CLAUDE_PROVIDER` env var.
## Hardcode Anthropic driver→model_id, `provider.env` only for non-Anthropic
Anthropic is the default path and needs zero config. The driver→model_id mapping for Anthropic (e.g., `sonnet``claude-sonnet-4-6`) is hardcoded in `claude-profile`. Only non-Anthropic profiles ship a `provider.env` file. Keeps the common case friction-free.
## `ANTHROPIC_API_KEY` is the env var name for all provider keys
Claude Code reads `ANTHROPIC_API_KEY` for its credential regardless of provider. `provider.env` therefore does not need a configurable key-name field — it always exports `ANTHROPIC_API_KEY=<value>`. Simplifies provider.env to a flat value list.
## Decouple WezTerm theme name from profile name via `wezterm-theme` file
Profile names changed (e.g. `oreillyit``oreillyit-anthropic`) but the WezTerm theme table still keys on the old short names. Each profile dir ships a one-line `wezterm-theme` file that maps the new profile name to the theme key, so WezTerm theme emission can fire immediately after profile selection without renaming themes.