diff --git a/commands/trekexecute.md b/commands/trekexecute.md index 024c4d4..fcd36d3 100644 --- a/commands/trekexecute.md +++ b/commands/trekexecute.md @@ -1577,7 +1577,7 @@ Never let stats failures block the workflow. ## Profile (v4.1) Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, -or a custom profile under `voyage-profiles/`. Default: `premium`. +`fable`, or a custom profile under `voyage-profiles/`. Default: `premium`. Resolution order (per `lib/profiles/resolver.mjs`): 1. `--profile` flag (source: `flag`) @@ -1609,12 +1609,25 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. Composition is mechanically resolved via -`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs` -invoked in Phase 2.4; the resolved JSON is captured as `phase_signal_result` -and consumed when picking the orchestration model + parallel-wave -strategy. The resolver controls only the orchestrator — sub-agents read -`model:` from their own `agents/*.md` frontmatter (still pinned to `opus`). +gaps. Both fields are mechanically resolved by the single composed CLI, +invoked in Phase 2.4 alongside the sequencing-gate brief-validator call: + +```bash +# v5.9 — composed phase-model resolution (brief > profile > default) for the +# execute phase. ONE call returns {effort, model, source}; captured as +# phase_signal_result. Append --profile {profile} when the operator passed +# --profile. +node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model --phase execute --brief-path "{dir}/brief.md" [--profile {profile}] --json +``` + +`phase_signal_result.effort` is consumed when picking the execution +strategy (gates auto-escalation, parallel-wave choice — see High-effort +behavior below). The resolver does NOT control the orchestrator's own +model — that is fixed at invocation time (command frontmatter omits +`model:`, so it follows the session model) and cannot be switched mid-turn. +`/trekexecute` spawns no sub-agent swarm (Hard Rule 10), so +`phase_signal_result.model` has no spawn site here; it is returned for +cross-command uniformity and stats. For `/trekexecute` specifically: `effort == 'low'` activates `--gates open` + sequential-only execution (no worktree-isolated parallel waves — runs diff --git a/commands/trekplan.md b/commands/trekplan.md index 8f98662..db8c61c 100644 --- a/commands/trekplan.md +++ b/commands/trekplan.md @@ -75,10 +75,11 @@ Parse `$ARGUMENTS` for mode flags. Order of precedence: # older brief that sidesteps framing enforcement. node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json [--min-version {min_brief_version}] "{dir}/brief.md" - # v5.1.1 — resolve per-phase brief-signal for plan phase. Result is - # captured as phase_signal_result and used at Agent-spawn sites below - # to override the orchestrator model when a signal is present. - node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs --brief "{dir}/brief.md" --phase plan --json + # v5.9 — composed phase-model resolution (brief > profile > default) for + # the plan phase. ONE call returns {effort, model, source}; captured as + # phase_signal_result and injected at Agent-spawn sites below. + # Append --profile {profile} when the operator passed --profile. + node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model --phase plan --brief-path "{dir}/brief.md" [--profile {profile}] --json # Research briefs (if any) — drift-warn only, none of these block the run [ -d "{dir}/research" ] && \ @@ -824,7 +825,7 @@ Never let tracking failures block the main workflow. ## Profile (v4.1) -Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, +Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, `fable`, or a custom profile under `voyage-profiles/`. Default: `premium`. Resolution order (per `lib/profiles/resolver.mjs`): @@ -859,13 +860,15 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. Composition is mechanically resolved via -`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs` -invoked in Phase 1; the resolved JSON is captured as `phase_signal_result` -and passed to `Agent` tool calls explicitly. The resolver controls only -the orchestrator and the model parameter at Agent-spawn sites — sub-agents -otherwise read `model:` from their own `agents/*.md` frontmatter (still -pinned to `opus`). +gaps. Both fields are mechanically resolved by the single composed CLI +`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model` +invoked in Phase 1; the resolved JSON `{effort, model, source}` is captured +as `phase_signal_result` and passed to `Agent` tool calls explicitly. The +resolver controls the `model` parameter at Agent-spawn sites only — the +orchestrator's own model is fixed at invocation time (command frontmatter +omits `model:`, so it follows the session model) and cannot be switched +mid-turn. Sub-agents fall back to `model:` in their own `agents/*.md` +frontmatter when no spawn-site injection happens. For `/trekplan` specifically: `effort == 'low'` activates the existing `--quick`-equivalent code-path (skip Phase 5 agent swarm — plan directly @@ -910,10 +913,11 @@ Standard and low effort: do NOT run the additional pass. inadequate, stop and ask the user to run `/trekbrief` again. - **Scope**: Only explore the current working directory and its subdirectories. Never read files outside the repo (no ~/.env, no credentials, no other repos). -- **Cost**: Sub-agents use their pinned `model:` frontmatter (currently `opus`). - When `phase_signals[].model` is set, the orchestrator AND Agent-spawn - sites use the resolved model (`phase_signal_result.model`) for that phase. - Frontmatter is the default; brief signal is the per-phase override. +- **Cost**: Model resolution at Agent-spawn sites is a three-layer fallback: + brief `phase_signals[].model` > `profile.phase_models[]` > + agent frontmatter `model:`. The composed resolver returns the first two + layers as `phase_signal_result.model`; spawn sites inject it, and agent + frontmatter is the fallback when no injection happens. - **Privacy**: Never log, store, or repeat file contents that look like secrets, tokens, or credentials. Never log prompt text. - **No premature execution**: Do not modify any project files until the user diff --git a/commands/trekresearch.md b/commands/trekresearch.md index 2fa5798..2b09864 100644 --- a/commands/trekresearch.md +++ b/commands/trekresearch.md @@ -55,15 +55,18 @@ Supported flags: Create `{dir}/research/` if it does not already exist. When `{dir}/brief.md` exists, ALWAYS run the brief-validator (soft mode) - AND the phase-signal-resolver for this command's phase before continuing. - The resolver's JSON output is captured as `phase_signal_result` and used - at Agent-spawn sites in Phase 4 to inject the brief-resolved model: + AND the composed phase-model resolver for this command's phase before + continuing. The resolver's JSON output `{effort, model, source}` + (brief signal > profile > default) is captured as `phase_signal_result` + and used at Agent-spawn sites in Phase 4 to inject the resolved model: ```bash # When --min-brief-version was passed, append --min-version {min_brief_version} # so an older brief raises BRIEF_VERSION_BELOW_MINIMUM (warn, never block). node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json [--min-version {min_brief_version}] "{dir}/brief.md" - node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs --brief "{dir}/brief.md" --phase research --json + # v5.9 — composed resolver: ONE call returns {effort, model, source}. + # Append --profile {profile} when the operator passed --profile. + node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model --phase research --brief-path "{dir}/brief.md" [--profile {profile}] --json ``` 6. `--gates` — autonomy control. When present, set `gates_mode = true`. The @@ -546,7 +549,7 @@ If `${CLAUDE_PLUGIN_DATA}` is not set or not writable, skip tracking silently. ## Profile (v4.1) -Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, +Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, `fable`, or a custom profile under `voyage-profiles/`. Default: `premium`. Resolution order (per `lib/profiles/resolver.mjs`): @@ -581,13 +584,15 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. Composition is mechanically resolved via -`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs` -invoked in Phase 1; the resolved JSON is captured as `phase_signal_result` -and passed to `Agent` tool calls explicitly. The resolver controls only -the orchestrator and the model parameter at Agent-spawn sites — sub-agents -otherwise read `model:` from their own `agents/*.md` frontmatter (still -pinned to `opus`). +gaps. Both fields are mechanically resolved by the single composed CLI +`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model` +invoked in Phase 1; the resolved JSON `{effort, model, source}` is captured +as `phase_signal_result` and passed to `Agent` tool calls explicitly. The +resolver controls the `model` parameter at Agent-spawn sites only — the +orchestrator's own model is fixed at invocation time (command frontmatter +omits `model:`, so it follows the session model) and cannot be switched +mid-turn. Sub-agents fall back to `model:` in their own `agents/*.md` +frontmatter when no spawn-site injection happens. For `/trekresearch` specifically: `effort == 'low'` activates the existing `--quick`-equivalent code-path (inline research, no agent swarm). @@ -632,10 +637,11 @@ Low effort: inline research only, no agent swarm (existing Triangulate AFTER independent research. - **Graceful degradation:** If MCP tools are unavailable (Tavily, Gemini, MS Learn), proceed with available tools and note limitations in brief metadata. -- **Cost:** Sub-agents use their pinned `model:` frontmatter (currently `opus`). - When `phase_signals[].model` is set, the orchestrator AND Agent-spawn - sites use the resolved model (`phase_signal_result.model`) for that phase. - Frontmatter is the default; brief signal is the per-phase override. +- **Cost:** Model resolution at Agent-spawn sites is a three-layer fallback: + brief `phase_signals[].model` > `profile.phase_models[]` > + agent frontmatter `model:`. The composed resolver returns the first two + layers as `phase_signal_result.model`; spawn sites inject it, and agent + frontmatter is the fallback when no injection happens. - **Privacy:** Never log secrets, tokens, or credentials. - **Honesty:** If the question is trivially answerable, say so. Don't inflate research. - **Scope of codebase:** Only analyze the current working directory for local research. diff --git a/commands/trekreview.md b/commands/trekreview.md index a7f593c..6f47d39 100644 --- a/commands/trekreview.md +++ b/commands/trekreview.md @@ -92,10 +92,12 @@ as the file is parseable: ```bash node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{brief_path}" -# v5.1.1 — resolve the review-phase brief signal. The JSON is captured as +# v5.9 — composed phase-model resolution (brief > profile > default) for the +# review phase. ONE call returns {effort, model, source}; captured as # phase_signal_result and used in Phase 7 at the reviewer-launch site to -# inject the brief-resolved model. -node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs --brief "{brief_path}" --phase review --json +# inject the resolved model. Append --profile {profile} when the operator +# passed --profile. +node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model --phase review --brief-path "{brief_path}" [--profile {profile}] --json ``` Read the JSON output. If `valid: false` AND any error has code @@ -420,7 +422,7 @@ the contract for that handover (see `docs/HANDOVER-CONTRACTS.md`). ## Profile (v4.1) -Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, +Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, `fable`, or a custom profile under `voyage-profiles/`. Default: `premium`. Resolution order (per `lib/profiles/resolver.mjs`): @@ -452,13 +454,15 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. Composition is mechanically resolved via -`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs` -invoked in Phase 2; the resolved JSON is captured as `phase_signal_result` -and passed to `Agent` tool calls explicitly. The resolver controls only -the orchestrator and the model parameter at Agent-spawn sites — sub-agents -otherwise read `model:` from their own `agents/*.md` frontmatter (still -pinned to `opus`). +gaps. Both fields are mechanically resolved by the single composed CLI +`node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model` +invoked in Phase 2; the resolved JSON `{effort, model, source}` is captured +as `phase_signal_result` and passed to `Agent` tool calls explicitly. The +resolver controls the `model` parameter at Agent-spawn sites only — the +orchestrator's own model is fixed at invocation time (command frontmatter +omits `model:`, so it follows the session model) and cannot be switched +mid-turn. Sub-agents fall back to `model:` in their own `agents/*.md` +frontmatter when no spawn-site injection happens. For `/trekreview` specifically: `effort == 'low'` activates the existing `--quick`-equivalent code-path (skip the brief-conformance reviewer; run @@ -515,10 +519,11 @@ Low effort: skip the brief-conformance reviewer entirely (existing `findings:\n - a\n - b`. - **Refuse-with-suggestion above 100 files / 100K tokens.** Never run blind on a giant diff. Use AskUserQuestion to surface the gate. -- **Cost.** Sub-agents use their pinned `model:` frontmatter (currently `opus`). - When `phase_signals[].model` is set, the orchestrator AND Agent-spawn - sites use the resolved model (`phase_signal_result.model`) for that phase. - Frontmatter is the default; brief signal is the per-phase override. +- **Cost.** Model resolution at Agent-spawn sites is a three-layer fallback: + brief `phase_signals[].model` > `profile.phase_models[]` > + agent frontmatter `model:`. The composed resolver returns the first two + layers as `phase_signal_result.model`; spawn sites inject it, and agent + frontmatter is the fallback when no injection happens. - **Privacy.** Never log secrets, tokens, or credentials in review.md. Findings citing files with secret-like content must redact the secret in the `detail` field. diff --git a/tests/lib/doc-consistency.test.mjs b/tests/lib/doc-consistency.test.mjs index cfd00c9..bb769c2 100644 --- a/tests/lib/doc-consistency.test.mjs +++ b/tests/lib/doc-consistency.test.mjs @@ -1292,3 +1292,26 @@ test('S38: forward-guard — no product-facing doc asserts main-context relief u offenders.join('\n'), ); }); + +// ── v5.9 (fable-tier step 6) — composed-resolver wiring pin ──────────────── +// The four pipeline commands must resolve {effort, model} via the composed +// CLI (`resolver.mjs --resolve-phase-model`, brief > profile > default). A +// direct `phase-signal-resolver.mjs --brief` invocation in a command Bash +// block is the brief-only CLI: it silently drops the profile layer (the +// AP2-1 footgun — `--profile ` would never reach sub-agent spawns again). +// If this pin fails, re-wire the command to the composed CLI — do not relax +// the pin. + +for (const cmd of ['trekresearch', 'trekplan', 'trekreview', 'trekexecute']) { + test(`v5.9: commands/${cmd}.md invokes the composed resolver, not the brief-only CLI`, () => { + const text = read(`commands/${cmd}.md`); + assert.ok( + text.includes('--resolve-phase-model'), + `commands/${cmd}.md must invoke resolver.mjs --resolve-phase-model (composed brief > profile > default)`, + ); + assert.ok( + !/phase-signal-resolver\.mjs --brief/.test(text), + `commands/${cmd}.md must not invoke the brief-only phase-signal-resolver CLI — the profile layer would be silently dropped`, + ); + }); +}