From dcc71d9577f87d298c69064c60cd7759818ba2a1 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 2 Jul 2026 17:02:56 +0200 Subject: [PATCH] feat(trekbrief): add fable tier option to Phase 3.5 loop --- commands/trekbrief.md | 14 ++++++++++++-- templates/trekbrief-template.md | 5 +++-- tests/commands/trekbrief.test.mjs | 18 ++++++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/commands/trekbrief.md b/commands/trekbrief.md index c9f02d8..f16d81f 100644 --- a/commands/trekbrief.md +++ b/commands/trekbrief.md @@ -367,13 +367,14 @@ in the question body so the operator sees why it was picked. ### The loop — 4 tier-coupled AskUserQuestion calls Loop over `[research, plan, execute, review]` in order. For each phase, -issue one `AskUserQuestion` with 3 options: +issue one `AskUserQuestion` with 4 options: | Option | Maps to phase_signals entry | |--------|----------------------------| | **Low effort** | `{phase: , effort: low, model: sonnet}` | | **Standard (default)** | `{phase: , effort: standard}` *(model omitted — composition falls through to profile)* | | **High effort** | `{phase: , effort: high, model: opus}` | +| **Fable (max quality)** | `{phase: , effort: high, model: fable}` | The proposed tier per phase (from the default-derivation heuristic) MUST be labelled `(default)` in the option list so the operator can one-click @@ -384,6 +385,15 @@ The mapping table is canonical: - `low → {effort: low, model: sonnet}` (force sonnet for the low-cost path) - `standard → {effort: standard}` (model omitted; composition rule resolves via profile) - `high → {effort: high, model: opus}` (force opus for the high-confidence path) +- `fable → {effort: high, model: fable}` (force Fable 5 for the max-quality path) + +The fable tier reuses `effort: high` semantics — full swarm, contrarian + +gemini always-on; `EFFORT_LEVELS` is unchanged (Voyage effort is orchestration +shape, not model reasoning effort). Model reasoning effort is inherited from +the session: Fable 5's default effort is `high`, NOT xhigh. To run xhigh, the +operator sets it at session level via `/effort xhigh`, the `effortLevel` +setting, or `CLAUDE_CODE_EFFORT_LEVEL` — switching model resets effort to the +model default, so it does not follow the model. ### Force-stop handling @@ -891,7 +901,7 @@ Never let stats failures block the workflow. ## Profile (v4.1) Accepts `--profile ` where `` is one of `economy`, `balanced`, -`premium`, or a custom profile under `voyage-profiles/`. Default: `premium`. +`premium`, `fable`, or a custom profile under `voyage-profiles/`. Default: `premium`. Resolution order (per `lib/profiles/resolver.mjs`): 1. `--profile` flag (source: `flag`) diff --git a/templates/trekbrief-template.md b/templates/trekbrief-template.md index 242e954..c8ec99e 100644 --- a/templates/trekbrief-template.md +++ b/templates/trekbrief-template.md @@ -17,8 +17,9 @@ source: {interview | manual} # plan polishing a wrong premise after a rejected iteration). framing: {preserve | refine | replace | new-direction} # v5.1 — per-phase effort + model signal (Phase 3.5). -# `effort` ∈ {low, standard, high}. Omit `model:` for `standard` so composition -# falls through to profile resolver. Force-stop alternative is the commented +# `effort` ∈ {low, standard, high}; `model` ∈ {sonnet, opus, fable} (v5.9). +# Omit `model:` for `standard` so composition falls through to profile +# resolver. Force-stop alternative is the commented # `phase_signals_partial: true` below (mutually exclusive with `phase_signals`). phase_signals: - phase: research diff --git a/tests/commands/trekbrief.test.mjs b/tests/commands/trekbrief.test.mjs index 3badca3..83c1462 100644 --- a/tests/commands/trekbrief.test.mjs +++ b/tests/commands/trekbrief.test.mjs @@ -16,6 +16,7 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs'; import { validateBriefContent, PHASE_SIGNAL_PHASES, EFFORT_LEVELS } from '../../lib/validators/brief-validator.mjs'; +import { BASE_ALLOWED_MODELS } from '../../lib/validators/profile-validator.mjs'; import { parseDocument } from '../../lib/util/frontmatter.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); @@ -84,8 +85,8 @@ test('trekbrief — SC1: each of 4 phases has both effort AND model on full-sign assert.ok(EFFORT_LEVELS.includes(r.effort), `phase=${phase}: effort "${r.effort}" not in EFFORT_LEVELS`); if ('model' in r) { - assert.ok(['sonnet', 'opus'].includes(r.model), - `phase=${phase}: model "${r.model}" not in [sonnet, opus]`); + assert.ok(BASE_ALLOWED_MODELS.includes(r.model), + `phase=${phase}: model "${r.model}" not in [${BASE_ALLOWED_MODELS.join(', ')}]`); } } }); @@ -99,6 +100,19 @@ test('trekbrief — SC1: missing phase_signals + brief_version 2.1 triggers BRIE ); }); +// --- v5.9 — fable tier option in the Phase 3.5 loop --- + +test('trekbrief — v5.9 Phase 3.5 canonical mapping contains the fable row and offers 4 options', () => { + const text = read(); + const startIdx = text.indexOf('## Phase 3.5'); + assert.ok(startIdx >= 0, 'Phase 3.5 not found'); + const section = text.slice(startIdx, text.indexOf('## Phase 4', startIdx)); + assert.ok(section.includes('fable → {effort: high, model: fable}'), + 'Phase 3.5 canonical mapping must contain the fable tier row'); + assert.ok(section.includes('with 4 options'), + 'Phase 3.5 loop must offer 4 options (AskUserQuestion maxItems: 4)'); +}); + // --- v5.5 — framing enforcement + TL;DR + memory-alignment prose-pins --- test('trekbrief — v5.5 Phase 2.5 framing declaration heading present', () => {