diff --git a/docs/operations.md b/docs/operations.md index fb0c771..d61c07a 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -43,7 +43,7 @@ Three built-in model profiles plus operator-defined `.yaml`. Each profil ### Custom profiles -Create `lib/profiles/.yaml` to define a new tier. The validator (`lib/validators/profile-validator.mjs`) enforces: every `phase_models[].phase` must be a known phase enum; every `phase_models[].model` must match `^(opus|sonnet)(\b|-).*` or one of the canonical short names. Custom profiles override built-ins of the same name (lookup is alphabetical with `` taking precedence). +Create `voyage-profiles/.yaml` in the repo root (or `~/.claude/voyage-profiles/.yaml`) to define a **new** tier — the name must not be a built-in. The validator (`lib/validators/profile-validator.mjs`) enforces: every `phase_models[].phase` must be a known phase enum; every `phase_models[].model` must match `^(opus|sonnet)(\b|-).*` or one of the canonical short names. `findProfilePath` (`lib/profiles/resolver.mjs`) resolves **built-in first** (`lib/profiles/.yaml` for `economy`/`balanced`/`premium`), then repo-root `voyage-profiles/`, then `~/.claude/voyage-profiles/`. A custom file named after a built-in therefore **cannot** shadow it (custom profiles must use new names); for the same custom name, repo-root takes precedence over home. Drift between plan-frontmatter `profile:` and step-manifest `profile_used:` emits a `MANIFEST_PROFILE_DRIFT` warning from `plan-validator --strict` (Step 20). Plan remains valid; the warning surfaces accidental tier-mismatch. diff --git a/tests/lib/doc-consistency.test.mjs b/tests/lib/doc-consistency.test.mjs index 3951d2f..6ce5ea2 100644 --- a/tests/lib/doc-consistency.test.mjs +++ b/tests/lib/doc-consistency.test.mjs @@ -323,6 +323,27 @@ test('orchestrator docs do not claim ALL sub-agents run on Sonnet (agents are mo } }); +// --- S20 — anti-false-claim: operations.md custom-profile prose must match findProfilePath --- +// +// findProfilePath (lib/profiles/resolver.mjs:57) resolves built-in FIRST, then +// repo-root voyage-profiles/, then ~/.claude/voyage-profiles/. Custom profiles +// live in voyage-profiles/ (NOT lib/profiles/), must use NEW names, and CANNOT +// shadow a built-in. The old prose claimed the opposite (lib/profiles/; +// "override built-ins of the same name"; "alphabetical … precedence") — guard it. +test('operations.md custom-profile prose matches findProfilePath resolution order [S20]', () => { + const ops = read('docs/operations.md'); + for (const bad of ['lib/profiles/', 'alphabetical', 'override built-ins of the same name']) { + assert.ok( + !ops.includes(bad), + `docs/operations.md: false custom-profile claim "${bad}" contradicts findProfilePath — fix the prose, not this test`, + ); + } + assert.ok( + ops.includes('voyage-profiles/'), + 'docs/operations.md must point custom profiles at voyage-profiles/ (not lib/profiles/)', + ); +}); + // --- v4.1 Step 21 — pin --profile + phase_models on the 6 commands --- // // CLAUDE.md / README.md pinning is deferred to Step 22 (post-write of