From c03695c97b894577119eb36ad8f6ed0452c3888d Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 15:56:03 +0200 Subject: [PATCH 01/58] docs(voyage): note trinity context (Tier 1 of voyage/app-creator/app-factory) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Informational blockquote after the v3.0.0 note. Documents that voyage is Tier 1 (per-task) of a three-tier architecture under the author's private marketplace: Tier 2 app-creator (per-app), Tier 3 app-factory (per-portfolio). Both are pre-implementation. Asymmetry-invariant preserved: voyage stays unaware of Tier 2/3 — Handover 1 (brief format) is the only integration point. Brief-schema changes therefore breaking for downstream consumers, formalized in v5.4. --- plugins/voyage/CLAUDE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/voyage/CLAUDE.md b/plugins/voyage/CLAUDE.md index 089a71d..fb3128f 100644 --- a/plugins/voyage/CLAUDE.md +++ b/plugins/voyage/CLAUDE.md @@ -6,6 +6,8 @@ Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execut > **v3.0.0 — architect step extracted from this plugin.** The plan command still auto-discovers `architecture/overview.md` if present, so any compatible producer (architect plugin no longer publicly distributed; the architecture/overview.md slot remains available for any compatible producer) plugs into the same slot. See [CHANGELOG.md](CHANGELOG.md) for migration history. +> **Trinity context (2026-05-13, informational).** Voyage is Tier 1 (per-task) of a three-tier architecture in active design under the author's private marketplace: Tier 2 `app-creator` (per-app — "what does the app need, what's the next brief?") produces briefs Voyage consumes; Tier 3 `app-factory` (per-portfolio — "which app needs me now?") aggregates state across multiple app-creator instances. Both are pre-implementation and will ship to Forgejo when ready. **Asymmetry is a hard invariant:** Voyage stays unaware of Tier 2/3. Handover 1 (brief format) is the only integration point — any compatible producer can feed Voyage, app-creator is not privileged. Brief-schema changes are therefore breaking changes for downstream consumers, formalized as a public contract in v5.4. + ## Commands | Command | Description | Model | From 4b5a3a24ddc3f715e45aec26143c6869763ebfc1 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 20:20:08 +0200 Subject: [PATCH 02/58] chore(voyage): pin all sub-agents to Opus permanently (operator request) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flip model: sonnet → model: opus across 20 agent files, 4 prose references in commands (trekplan, trekresearch), trekendsession command frontmatter, and CLAUDE.md tables. Aligns CLAUDE.md premium-profile row to actual premium.yaml content (all-opus, which has been the case since v4.1.0 but the doc was drift). Companion to VOYAGE_PROFILE=premium env-var (set in ~/.zshenv same day) — env-var governs orchestrator phase model; this commit governs sub-agent models which are frontmatter-pinned and not reachable by the profile resolver. npm test: 516 pass, 0 fail, 2 skipped (unchanged from baseline). Operator rationale: complete Opus coverage across all Voyage activity, including the 20 sub-agents that the profile system does not control (architecture-mapper, task-finder, plan-critic, scope-guardian, brief-reviewer, code-correctness-reviewer, brief-conformance-reviewer, review-coordinator, session-decomposer, plus the 6 researcher agents, plus the 5 codebase-analysis agents). Cost implication: sub-agent runs ~5x more expensive vs sonnet. Accepted. --- plugins/voyage/CLAUDE.md | 48 +++++++++---------- plugins/voyage/agents/architecture-mapper.md | 2 +- .../agents/brief-conformance-reviewer.md | 2 +- plugins/voyage/agents/brief-reviewer.md | 2 +- .../agents/code-correctness-reviewer.md | 2 +- plugins/voyage/agents/community-researcher.md | 2 +- .../voyage/agents/contrarian-researcher.md | 2 +- plugins/voyage/agents/convention-scanner.md | 2 +- plugins/voyage/agents/dependency-tracer.md | 2 +- plugins/voyage/agents/docs-researcher.md | 2 +- plugins/voyage/agents/gemini-bridge.md | 2 +- plugins/voyage/agents/git-historian.md | 2 +- plugins/voyage/agents/plan-critic.md | 2 +- .../voyage/agents/planning-orchestrator.md | 2 +- plugins/voyage/agents/research-scout.md | 2 +- plugins/voyage/agents/review-coordinator.md | 2 +- plugins/voyage/agents/risk-assessor.md | 2 +- plugins/voyage/agents/scope-guardian.md | 2 +- plugins/voyage/agents/security-researcher.md | 2 +- plugins/voyage/agents/session-decomposer.md | 2 +- plugins/voyage/agents/task-finder.md | 2 +- plugins/voyage/agents/test-strategist.md | 2 +- plugins/voyage/commands/trekendsession.md | 2 +- plugins/voyage/commands/trekplan.md | 4 +- plugins/voyage/commands/trekresearch.md | 4 +- 25 files changed, 50 insertions(+), 50 deletions(-) diff --git a/plugins/voyage/CLAUDE.md b/plugins/voyage/CLAUDE.md index fb3128f..bf1aab4 100644 --- a/plugins/voyage/CLAUDE.md +++ b/plugins/voyage/CLAUDE.md @@ -18,7 +18,7 @@ Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execut | `/trekexecute` | Execute — disciplined plan/session-spec executor with failure recovery | opus | | `/trekreview` | Review — independent post-hoc review of delivered code against the brief. Produces `review.md` with severity-tagged findings (Handover 6) | opus | | `/trekcontinue` | Continue — resumes the next session of a multi-session voyage project. Reads `.session-state.local.json` (Handover 7) and immediately begins executing | opus | -| `/trekendsession` | End-session — mark the current session complete and write session-state pointing at the next session. Helper for informal multi-session flows | sonnet | +| `/trekendsession` | End-session — mark the current session complete and write session-state pointing at the next session. Helper for informal multi-session flows | opus | ### /trekbrief modes @@ -109,26 +109,26 @@ The triage gate is deterministic — path-pattern classifier produces `{file → | planning-orchestrator | opus | Inline reference documentation for the planning pipeline workflow (brief-driven) | | research-orchestrator | opus | Inline reference documentation for the research pipeline workflow | | review-orchestrator | opus | Inline reference documentation for the review pipeline workflow | -| architecture-mapper | sonnet | Codebase structure, tech stack, patterns | -| dependency-tracer | sonnet | Import chains, data flow, side effects | -| task-finder | sonnet | Task-relevant files, functions, reuse candidates | -| risk-assessor | sonnet | Risks, edge cases, failure modes | -| test-strategist | sonnet | Test patterns, coverage gaps, strategy | -| git-historian | sonnet | Recent changes, ownership, hot files | -| research-scout | sonnet | External docs for unfamiliar tech (conditional, planning only) | -| convention-scanner | sonnet | Coding conventions: naming, style, error handling, test patterns | -| brief-reviewer | sonnet | Task brief quality (5 dimensions: completeness, consistency, testability, scope clarity, research plan validity) | -| brief-conformance-reviewer | sonnet | Brief conformance review (SC + Non-Goal traceability) | -| code-correctness-reviewer | sonnet | Code correctness review (7 dimensions) | -| review-coordinator | sonnet | Judge Agent — dedup + reasonableness filter + verdict | -| plan-critic | sonnet | Adversarial plan review (9 dimensions) | -| scope-guardian | sonnet | Scope alignment (creep + gaps) | -| session-decomposer | sonnet | Splits plans into headless sessions with dependency graph | -| docs-researcher | sonnet | Official documentation, RFCs, vendor docs (Tavily, MS Learn) | -| community-researcher | sonnet | Community experience: issues, blogs, discussions | -| security-researcher | sonnet | CVEs, audit history, supply chain risks | -| contrarian-researcher | sonnet | Counter-evidence, overlooked alternatives | -| gemini-bridge | sonnet | Gemini Deep Research second opinion (conditional) | +| architecture-mapper | opus | Codebase structure, tech stack, patterns | +| dependency-tracer | opus | Import chains, data flow, side effects | +| task-finder | opus | Task-relevant files, functions, reuse candidates | +| risk-assessor | opus | Risks, edge cases, failure modes | +| test-strategist | opus | Test patterns, coverage gaps, strategy | +| git-historian | opus | Recent changes, ownership, hot files | +| research-scout | opus | External docs for unfamiliar tech (conditional, planning only) | +| convention-scanner | opus | Coding conventions: naming, style, error handling, test patterns | +| brief-reviewer | opus | Task brief quality (5 dimensions: completeness, consistency, testability, scope clarity, research plan validity) | +| brief-conformance-reviewer | opus | Brief conformance review (SC + Non-Goal traceability) | +| code-correctness-reviewer | opus | Code correctness review (7 dimensions) | +| review-coordinator | opus | Judge Agent — dedup + reasonableness filter + verdict | +| plan-critic | opus | Adversarial plan review (9 dimensions) | +| scope-guardian | opus | Scope alignment (creep + gaps) | +| session-decomposer | opus | Splits plans into headless sessions with dependency graph | +| docs-researcher | opus | Official documentation, RFCs, vendor docs (Tavily, MS Learn) | +| community-researcher | opus | Community experience: issues, blogs, discussions | +| security-researcher | opus | CVEs, audit history, supply chain risks | +| contrarian-researcher | opus | Counter-evidence, overlooked alternatives | +| gemini-bridge | opus | Gemini Deep Research second opinion (conditional) | ## Quality infrastructure (v3.4.0) @@ -189,9 +189,9 @@ Three built-in model profiles plus operator-defined `.yaml`. Each profil | Profile | Brief | Research | Plan | Execute | Review | Continue | Use case | |---------|-------|----------|------|---------|--------|----------|----------| -| `economy` | sonnet | sonnet | sonnet | sonnet | sonnet | sonnet | Lowest cost; high-confidence small-scope tasks | -| `balanced` (default) | sonnet | sonnet | opus | sonnet | opus | sonnet | Default — opus where reasoning depth pays off | -| `premium` | opus | sonnet | opus | sonnet | opus | sonnet | Critical-path planning + review when budget allows | +| `economy` | sonnet | sonnet | sonnet | sonnet | sonnet | sonnet | Lowest cost; high-confidence small-scope tasks (operator-opt-in via `--profile economy`) | +| `balanced` | sonnet | sonnet | opus | sonnet | opus | sonnet | Mixed — opus where reasoning depth pays off (operator-opt-in via `--profile balanced`) | +| `premium` (default) | opus | opus | opus | opus | opus | opus | Maximum quality — Opus on every phase. Default since 2026-05-13 operator request; also the hardcoded resolver default at `lib/profiles/resolver.mjs:145` | ### Lookup order diff --git a/plugins/voyage/agents/architecture-mapper.md b/plugins/voyage/agents/architecture-mapper.md index 0da6f57..2fafd05 100644 --- a/plugins/voyage/agents/architecture-mapper.md +++ b/plugins/voyage/agents/architecture-mapper.md @@ -21,7 +21,7 @@ description: | Direct architecture analysis request triggers the agent. -model: sonnet +model: opus color: cyan tools: ["Read", "Glob", "Grep", "Bash"] --- diff --git a/plugins/voyage/agents/brief-conformance-reviewer.md b/plugins/voyage/agents/brief-conformance-reviewer.md index 1c60e0c..6259d07 100644 --- a/plugins/voyage/agents/brief-conformance-reviewer.md +++ b/plugins/voyage/agents/brief-conformance-reviewer.md @@ -5,7 +5,7 @@ description: | against the task brief — every Success Criterion must trace to delivered code, every Non-Goal must remain unbuilt. Emits findings with rule_keys from the canonical RULE_CATALOGUE. Never praises. -model: sonnet +model: opus color: magenta tools: ["Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/brief-reviewer.md b/plugins/voyage/agents/brief-reviewer.md index 6cc8ebf..bd1f3b1 100644 --- a/plugins/voyage/agents/brief-reviewer.md +++ b/plugins/voyage/agents/brief-reviewer.md @@ -23,7 +23,7 @@ description: | Brief review request triggers the agent. -model: sonnet +model: opus color: magenta tools: ["Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/code-correctness-reviewer.md b/plugins/voyage/agents/code-correctness-reviewer.md index d8dac3d..f0c938f 100644 --- a/plugins/voyage/agents/code-correctness-reviewer.md +++ b/plugins/voyage/agents/code-correctness-reviewer.md @@ -6,7 +6,7 @@ description: | cross-file regressions, test coverage gaps, placeholder code, security surface, hidden dependencies. Cites file:line for every finding. Never praises. -model: sonnet +model: opus color: red tools: ["Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/community-researcher.md b/plugins/voyage/agents/community-researcher.md index c2a3e54..6317d86 100644 --- a/plugins/voyage/agents/community-researcher.md +++ b/plugins/voyage/agents/community-researcher.md @@ -24,7 +24,7 @@ description: | finds the practical signal that helps teams make adoption decisions. -model: sonnet +model: opus color: green tools: ["WebSearch", "WebFetch", "mcp__tavily__tavily_search", "mcp__tavily__tavily_research"] --- diff --git a/plugins/voyage/agents/contrarian-researcher.md b/plugins/voyage/agents/contrarian-researcher.md index f589c6f..b0b9f95 100644 --- a/plugins/voyage/agents/contrarian-researcher.md +++ b/plugins/voyage/agents/contrarian-researcher.md @@ -24,7 +24,7 @@ description: | but to ensure the final recommendation is genuinely considered. -model: sonnet +model: opus color: red tools: ["WebSearch", "WebFetch", "mcp__tavily__tavily_search", "mcp__tavily__tavily_research"] --- diff --git a/plugins/voyage/agents/convention-scanner.md b/plugins/voyage/agents/convention-scanner.md index 9abc32e..686715f 100644 --- a/plugins/voyage/agents/convention-scanner.md +++ b/plugins/voyage/agents/convention-scanner.md @@ -23,7 +23,7 @@ description: | Direct convention discovery request triggers the agent. -model: sonnet +model: opus color: yellow tools: ["Read", "Glob", "Grep", "Bash"] --- diff --git a/plugins/voyage/agents/dependency-tracer.md b/plugins/voyage/agents/dependency-tracer.md index c390d70..7185894 100644 --- a/plugins/voyage/agents/dependency-tracer.md +++ b/plugins/voyage/agents/dependency-tracer.md @@ -21,7 +21,7 @@ description: | Impact analysis request triggers the agent. -model: sonnet +model: opus color: blue tools: ["Read", "Glob", "Grep", "Bash"] --- diff --git a/plugins/voyage/agents/docs-researcher.md b/plugins/voyage/agents/docs-researcher.md index 4e40386..364a3e8 100644 --- a/plugins/voyage/agents/docs-researcher.md +++ b/plugins/voyage/agents/docs-researcher.md @@ -23,7 +23,7 @@ description: | microsoft_docs_fetch) that docs-researcher uses for higher-quality results. -model: sonnet +model: opus color: blue tools: ["WebSearch", "WebFetch", "Read", "mcp__tavily__tavily_search", "mcp__tavily__tavily_research", "mcp__microsoft-learn__microsoft_docs_search", "mcp__microsoft-learn__microsoft_docs_fetch"] --- diff --git a/plugins/voyage/agents/gemini-bridge.md b/plugins/voyage/agents/gemini-bridge.md index b9c0c6a..a15de55 100644 --- a/plugins/voyage/agents/gemini-bridge.md +++ b/plugins/voyage/agents/gemini-bridge.md @@ -24,7 +24,7 @@ description: | Direct request for Gemini research on a complex architectural question triggers the agent. -model: sonnet +model: opus color: magenta tools: ["mcp__gemini-mcp__gemini_deep_research", "mcp__gemini-mcp__gemini_get_research_status", "mcp__gemini-mcp__gemini_get_research_result", "mcp__gemini-mcp__gemini_research_followup"] --- diff --git a/plugins/voyage/agents/git-historian.md b/plugins/voyage/agents/git-historian.md index f64c31a..9971a41 100644 --- a/plugins/voyage/agents/git-historian.md +++ b/plugins/voyage/agents/git-historian.md @@ -21,7 +21,7 @@ description: | Git history analysis request triggers the agent. -model: sonnet +model: opus color: yellow tools: ["Bash", "Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/plan-critic.md b/plugins/voyage/agents/plan-critic.md index c8a3b28..ac382d9 100644 --- a/plugins/voyage/agents/plan-critic.md +++ b/plugins/voyage/agents/plan-critic.md @@ -21,7 +21,7 @@ description: | Plan review request triggers the agent. -model: sonnet +model: opus color: red tools: ["Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/planning-orchestrator.md b/plugins/voyage/agents/planning-orchestrator.md index d7f6abb..50afa89 100644 --- a/plugins/voyage/agents/planning-orchestrator.md +++ b/plugins/voyage/agents/planning-orchestrator.md @@ -137,7 +137,7 @@ medium: default, large: default) rather than dropping agents. | `research-scout` | Conditional | Conditional | Conditional | External docs (only when unfamiliar tech detected AND not covered by briefs) | | `convention-scanner` | No | Yes | Yes | Coding conventions, naming, style, test patterns | -**Convention Scanner** — use the `convention-scanner` plugin agent (model: "sonnet") +**Convention Scanner** — use the `convention-scanner` plugin agent (model: "opus") for medium+ codebases only. Pass the task description as context. **research-scout** — launch conditionally if the task involves technologies, APIs, diff --git a/plugins/voyage/agents/research-scout.md b/plugins/voyage/agents/research-scout.md index bbde9c0..09efc9a 100644 --- a/plugins/voyage/agents/research-scout.md +++ b/plugins/voyage/agents/research-scout.md @@ -21,7 +21,7 @@ description: | Research request for external technology triggers the agent. -model: sonnet +model: opus color: blue tools: ["WebSearch", "WebFetch", "Read"] --- diff --git a/plugins/voyage/agents/review-coordinator.md b/plugins/voyage/agents/review-coordinator.md index 4544897..c38570a 100644 --- a/plugins/voyage/agents/review-coordinator.md +++ b/plugins/voyage/agents/review-coordinator.md @@ -6,7 +6,7 @@ description: | applies BOUNDED operations: deduplication, severity ranking, HubSpot Judge filters, Cloudflare reasonableness filter, verdict computation. Synthesis-level inference across files is forbidden in v1.0. -model: sonnet +model: opus color: yellow tools: ["Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/risk-assessor.md b/plugins/voyage/agents/risk-assessor.md index da697a9..d8f4a56 100644 --- a/plugins/voyage/agents/risk-assessor.md +++ b/plugins/voyage/agents/risk-assessor.md @@ -21,7 +21,7 @@ description: | Risk analysis request triggers the agent. -model: sonnet +model: opus color: yellow tools: ["Read", "Glob", "Grep", "Bash"] --- diff --git a/plugins/voyage/agents/scope-guardian.md b/plugins/voyage/agents/scope-guardian.md index 29b4302..789c228 100644 --- a/plugins/voyage/agents/scope-guardian.md +++ b/plugins/voyage/agents/scope-guardian.md @@ -21,7 +21,7 @@ description: | Scope verification request triggers the agent. -model: sonnet +model: opus color: magenta tools: ["Read", "Glob", "Grep"] --- diff --git a/plugins/voyage/agents/security-researcher.md b/plugins/voyage/agents/security-researcher.md index 6338ee0..d960c0d 100644 --- a/plugins/voyage/agents/security-researcher.md +++ b/plugins/voyage/agents/security-researcher.md @@ -23,7 +23,7 @@ description: | using CVE databases, OWASP categories, and verified audit reports. -model: sonnet +model: opus color: red tools: ["WebSearch", "WebFetch", "mcp__tavily__tavily_search", "mcp__tavily__tavily_research"] --- diff --git a/plugins/voyage/agents/session-decomposer.md b/plugins/voyage/agents/session-decomposer.md index a3e1d74..fb82457 100644 --- a/plugins/voyage/agents/session-decomposer.md +++ b/plugins/voyage/agents/session-decomposer.md @@ -22,7 +22,7 @@ description: | Plan decomposition request for parallel headless execution. -model: sonnet +model: opus color: green tools: ["Read", "Glob", "Grep", "Write"] --- diff --git a/plugins/voyage/agents/task-finder.md b/plugins/voyage/agents/task-finder.md index c81977b..f0a585b 100644 --- a/plugins/voyage/agents/task-finder.md +++ b/plugins/voyage/agents/task-finder.md @@ -22,7 +22,7 @@ description: | Direct code discovery request triggers the agent. -model: sonnet +model: opus color: green tools: ["Read", "Glob", "Grep", "Bash"] --- diff --git a/plugins/voyage/agents/test-strategist.md b/plugins/voyage/agents/test-strategist.md index 1db4bb5..b95e0cb 100644 --- a/plugins/voyage/agents/test-strategist.md +++ b/plugins/voyage/agents/test-strategist.md @@ -21,7 +21,7 @@ description: | Test planning request triggers the agent. -model: sonnet +model: opus color: green tools: ["Read", "Glob", "Grep", "Bash"] --- diff --git a/plugins/voyage/commands/trekendsession.md b/plugins/voyage/commands/trekendsession.md index 7a4188d..122fbb8 100644 --- a/plugins/voyage/commands/trekendsession.md +++ b/plugins/voyage/commands/trekendsession.md @@ -2,7 +2,7 @@ name: trekendsession description: Mark the current session as complete and write session-state pointing at the next session. Helper for informal multi-session flows. argument-hint: " | --help" -model: sonnet +model: opus --- # Voyage End-Session Local v1.0 diff --git a/plugins/voyage/commands/trekplan.md b/plugins/voyage/commands/trekplan.md index 667cb86..ddf7bc9 100644 --- a/plugins/voyage/commands/trekplan.md +++ b/plugins/voyage/commands/trekplan.md @@ -501,7 +501,7 @@ ownership, hot files, and active branches that may affect planning." ### Launch for medium+ codebases (50+ files): -**Convention Scanner** — use the `convention-scanner` plugin agent (model: "sonnet") +**Convention Scanner** — use the `convention-scanner` plugin agent (model: "opus") for medium+ codebases only. Provide concrete examples from the codebase, not generic advice." @@ -538,7 +538,7 @@ Common reasons for deep-dives: - A test pattern was identified but the test infrastructure needs more detail - A risk was flagged but the actual impact needs verification -For each significant gap, spawn a targeted deep-dive agent (model: "sonnet", +For each significant gap, spawn a targeted deep-dive agent (model: "opus", subagent_type: "Explore") with a narrow, specific brief. Launch up to 3 deep-dive agents in parallel. If no gaps exist, skip this phase diff --git a/plugins/voyage/commands/trekresearch.md b/plugins/voyage/commands/trekresearch.md index 14f6166..d39c4c3 100644 --- a/plugins/voyage/commands/trekresearch.md +++ b/plugins/voyage/commands/trekresearch.md @@ -291,7 +291,7 @@ other agents — the value of Gemini is independence. ### Launch rules - Launch ALL selected agents **in parallel** in a single message -- Use model: "sonnet" for all sub-agents (the orchestrator runs on Opus) +- Use model: "opus" for all sub-agents (the orchestrator runs on Opus) - Scale maxTurns by codebase size for local agents (same as trekplan): small = halved, medium/large = default - convention-scanner: medium+ codebases only (50+ files) @@ -301,7 +301,7 @@ other agents — the value of Gemini is independence. Review all agent results. Identify knowledge gaps — areas where findings are thin, contradictory, or missing. -For each significant gap, launch a targeted follow-up agent (model: "sonnet") +For each significant gap, launch a targeted follow-up agent (model: "opus") with a narrow, specific brief. Maximum 2 follow-ups. If no gaps exist, skip: "Initial research sufficient — no follow-ups needed." From 8cbb33e1fd965259df6f280c6eb9d929b6d7afe9 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 20:31:58 +0200 Subject: [PATCH 03/58] =?UTF-8?q?docs(voyage):=20pin=20operator-UX=20contr?= =?UTF-8?q?act=20=E2=80=94=20always=20emit=20file://=20link=20+=20open=20c?= =?UTF-8?q?ommand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator runs Ghostty (also iTerm2, modern Terminal.app) — all support cmd+click on file:// URLs. Producing commands (/trekbrief, /trekplan, /trekreview) already emit both forms but the contract was implicit. This commit makes it explicit: 1. CLAUDE.md gains an "Operator-UX guarantee" paragraph stating both forms must always appear in the final report: (a) plain file:// URL with absolute path (for cmd+click), (b) copy-pasteable `open file://` command (for terminals without cmd+click). 2. tests/lib/doc-consistency.test.mjs gains a pin asserting both patterns appear in all three producing commands' final report blocks. Drift catches at test time. Non-functional change to the commands themselves — they already emit both forms (verified at trekbrief.md L510/L519, trekplan.md L798/L802, trekreview.md L299/L317). Operator request 2026-05-13: "Noter ned i Voyage at jeg ALLTID får en slik direkte file:// lenke." --- plugins/voyage/CLAUDE.md | 2 ++ .../voyage/tests/lib/doc-consistency.test.mjs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/plugins/voyage/CLAUDE.md b/plugins/voyage/CLAUDE.md index bf1aab4..75ab8df 100644 --- a/plugins/voyage/CLAUDE.md +++ b/plugins/voyage/CLAUDE.md @@ -234,6 +234,8 @@ Local Docker Compose stack: `examples/observability/`. Operator docs: `docs/obse **Continue:** `/trekcontinue` reads `{dir}/.session-state.local.json` (Handover 7), validates schema-v1 via `session-state-validator`, narrates a 3-line summary (project / next-session-label / brief-path), and immediately begins executing the next session. Auto-discovers active project state files under `.claude/projects/*/.session-state.local.json` if no explicit `` argument. Operator-invoked only — never auto-loaded via SessionStart. The `/trekendsession` helper is the informal-flow producer: writes the same state file for ad-hoc multi-session handovers that don't run through `/trekexecute`. +**Operator-UX guarantee (since v5.0.2):** `/trekbrief`, `/trekplan`, and `/trekreview` MUST always emit (a) a plain `file://` URL AND (b) a copy-pasteable `open file://` command in the final report block. The file:// URL must use an ABSOLUTE path (not relative or `~/`-prefixed) so terminals with cmd+click support (Ghostty, iTerm2, modern Terminal.app) can resolve it without shell interpretation. This is a non-negotiable operator-UX contract — the doc-consistency test pins both forms in all three commands' final report blocks. + **Operator-annotation HTML (v5.0.3):** the last step of `/trekbrief`, `/trekplan`, and `/trekreview` runs `scripts/annotate.mjs` against the just-written `.md` and prints the resulting `file://` link. The HTML is self-contained (zero npm deps, zero external network, design-system-styled, light + dark + print) and modelled on `~/repos/claude-code-100x/claude-code-100x/build-site.js` (lines 1431–2255). The operator opens the file, the document renders as a proper article (headings / paragraphs / lists / tables / code / quotes — every element gets a stable `data-anchor-id`). In annotation mode (default ON, pencil-toggle in topbar), the operator can **select any text or click any element** → a form popover opens at the cursor with: section context auto-detected from nearest h1/h2, the anchored snippet (selection if any, else element text), **three intent buttons (Fiks / Endre / Spørsmål)**, comment textarea, Save/Cancel. The sidebar (Show annotations button) lists every annotation grouped by section with intent badge + snippet + comment + delete; clicking a card scrolls to and flashes the source element. **Copy Prompt** assembles a structured markdown (`### N. [Intent] Section: <…>` + `Quote: «…»` + `Comment: …`) and copies to clipboard. Persistence: `localStorage` keyed on absolute artifact path (`voyage-annotate:v2:`). v5.0.0 removed the v4.2/v4.3 bespoke playground SPA + `/trekrevise` + Handover 8; v5.0.1 pointed at `/playground document-critique` (Claude-leads, wrong direction); v5.0.2 was operator-led but too thin (line-click + freeform note, no intents); v5.0.3 matches the claude-code-100x reference the operator first pointed at, with pencil-toggle / selection capture / intent categories / popover form / structured export. See [CHANGELOG.md](CHANGELOG.md) § v5.0.3. **Security:** 4-layer defense-in-depth: plugin hooks (pre-bash-executor, pre-write-executor), prompt-level denylist (works in headless sessions), pre-execution plan scan (Phase 2.4), scoped `--allowedTools` replacing `--dangerously-skip-permissions`. Hard Rules 14-16 enforce verify command security, repo-boundary writes, and sensitive path protection. diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index 5c7bef8..d01a728 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -485,6 +485,24 @@ test('producing commands tell the operator the flow is THEIR own annotations', ( } }); +test('producing commands emit file:// link in final report (operator-UX contract, 2026-05-13)', () => { + // Operator runs Ghostty / iTerm2 / modern Terminal.app — all support cmd+click + // on file:// URLs. Producing commands MUST emit both forms: (a) plain file:// + // line in the report block, (b) `open file://...` copy-pasteable command. + // Both must reference $ANNOT_HTML (absolute path from scripts/annotate.mjs). + for (const f of ['trekbrief.md', 'trekplan.md', 'trekreview.md']) { + const text = read(`commands/${f}`); + assert.ok( + /file:\/\/\{\$ANNOT_HTML\}/.test(text), + `commands/${f} must include "file://{$ANNOT_HTML}" plain URL in the final report block`, + ); + assert.ok( + /open file:\/\/\{\$ANNOT_HTML\}/.test(text), + `commands/${f} must include "open file://{$ANNOT_HTML}" copy-pasteable command in the final report block`, + ); + } +}); + test('package.json still has no "npm run render" script (removed in v5.0.1)', () => { const pkg = JSON.parse(read('package.json')); assert.equal( From bf68fe6f5f42be45b1d4271ef10805876318349d Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:08:37 +0200 Subject: [PATCH 04/58] feat(voyage): add phase_signals validation + sequencing gate to brief-validator (v5.1) --- .../voyage/lib/validators/brief-validator.mjs | 64 ++++++++++++++++++ .../lib/validators/profile-validator.mjs | 2 +- .../tests/validators/brief-validator.test.mjs | 66 +++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/plugins/voyage/lib/validators/brief-validator.mjs b/plugins/voyage/lib/validators/brief-validator.mjs index 760ba9f..293ced9 100644 --- a/plugins/voyage/lib/validators/brief-validator.mjs +++ b/plugins/voyage/lib/validators/brief-validator.mjs @@ -9,12 +9,15 @@ import { readFileSync, existsSync } from 'node:fs'; import { parseDocument } from '../util/frontmatter.mjs'; import { issue, ok, fail } from '../util/result.mjs'; +import { BASE_ALLOWED_MODELS } from './profile-validator.mjs'; export const BRIEF_REQUIRED_FRONTMATTER = ['type', 'brief_version', 'task', 'slug', 'research_topics', 'research_status']; export const REVIEW_AS_BRIEF_REQUIRED_FRONTMATTER = ['type', 'task', 'slug', 'project_dir', 'findings']; export const BRIEF_TYPE_VALUES = Object.freeze(['trekbrief', 'trekreview']); export const BRIEF_RESEARCH_STATUS_VALUES = ['pending', 'in_progress', 'complete', 'skipped']; export const BRIEF_BODY_SECTIONS = ['Intent', 'Goal', 'Success Criteria']; +export const PHASE_SIGNAL_PHASES = Object.freeze(['research', 'plan', 'execute', 'review']); +export const EFFORT_LEVELS = Object.freeze(['low', 'standard', 'high']); function getRequiredFields(type) { return type === 'trekreview' ? REVIEW_AS_BRIEF_REQUIRED_FRONTMATTER : BRIEF_REQUIRED_FRONTMATTER; @@ -36,6 +39,67 @@ export function validateBriefContent(text, opts = {}) { } } + // v5.1 — phase_signals (additive optional field) + version-conditional sequencing gate. + // Composition rule documented in each downstream command's "Composition rule (v5.1)" section. + const hasSignals = 'phase_signals' in fm; + const hasPartial = 'phase_signals_partial' in fm; + if (hasSignals && hasPartial) { + errors.push(issue( + 'BRIEF_SIGNALS_MUTUALLY_EXCLUSIVE', + 'phase_signals and phase_signals_partial are mutually exclusive — set exactly one', + 'Either commit per-phase signals OR record phase_signals_partial: true (force-stop).', + )); + } + if (hasSignals) { + if (!Array.isArray(fm.phase_signals)) { + errors.push(issue( + 'BRIEF_INVALID_PHASE_SIGNALS', + 'phase_signals must be a list of {phase, effort?, model?} entries', + )); + } else { + for (const entry of fm.phase_signals) { + if (!entry || typeof entry !== 'object' || !('phase' in entry)) { + errors.push(issue('BRIEF_INVALID_PHASE_SIGNALS', `phase_signals entry must include a "phase" key`)); + continue; + } + if (!PHASE_SIGNAL_PHASES.includes(entry.phase)) { + errors.push(issue( + 'BRIEF_INVALID_PHASE_SIGNAL_PHASE', + `phase_signals.phase "${entry.phase}" not in [${PHASE_SIGNAL_PHASES.join(', ')}]`, + )); + } + if ('effort' in entry && !EFFORT_LEVELS.includes(entry.effort)) { + errors.push(issue( + 'BRIEF_INVALID_EFFORT', + `phase_signals.effort "${entry.effort}" not in [${EFFORT_LEVELS.join(', ')}]`, + )); + } + if ('model' in entry && !BASE_ALLOWED_MODELS.includes(entry.model)) { + errors.push(issue( + 'BRIEF_INVALID_MODEL', + `phase_signals.model "${entry.model}" not in [${BASE_ALLOWED_MODELS.join(', ')}]`, + )); + } + } + } + } + // Sequencing gate: brief_version ≥ 2.1 requires phase_signals OR phase_signals_partial. + if (typeof fm.brief_version === 'string') { + const vm = fm.brief_version.match(/^(\d+)\.(\d+)$/); + if (vm) { + const major = Number(vm[1]); + const minor = Number(vm[2]); + const atLeast21 = major > 2 || (major === 2 && minor >= 1); + if (atLeast21 && !hasSignals && !hasPartial && fm.type !== 'trekreview') { + errors.push(issue( + 'BRIEF_V51_MISSING_SIGNALS', + 'brief_version ≥ 2.1 requires phase_signals (or phase_signals_partial: true)', + 'Re-run /trekbrief — Phase 3.5 collects per-phase effort + model signals.', + )); + } + } + } + if (fm.type !== undefined && !BRIEF_TYPE_VALUES.includes(fm.type)) { errors.push(issue( 'BRIEF_WRONG_TYPE', diff --git a/plugins/voyage/lib/validators/profile-validator.mjs b/plugins/voyage/lib/validators/profile-validator.mjs index 48e84d6..c7fec26 100644 --- a/plugins/voyage/lib/validators/profile-validator.mjs +++ b/plugins/voyage/lib/validators/profile-validator.mjs @@ -38,7 +38,7 @@ export const PROFILE_REQUIRED_PHASES = Object.freeze([ 'brief', 'research', 'plan', 'execute', 'review', 'continue', ]); -const BASE_ALLOWED_MODELS = Object.freeze(['sonnet', 'opus']); +export const BASE_ALLOWED_MODELS = Object.freeze(['sonnet', 'opus']); function getAllowedModels(env = process.env) { if (env.VOYAGE_ALLOW_HAIKU === '1') { diff --git a/plugins/voyage/tests/validators/brief-validator.test.mjs b/plugins/voyage/tests/validators/brief-validator.test.mjs index 6e501d2..a9fd185 100644 --- a/plugins/voyage/tests/validators/brief-validator.test.mjs +++ b/plugins/voyage/tests/validators/brief-validator.test.mjs @@ -152,3 +152,69 @@ test('validateBrief — wrong-type error message includes accepted set', () => { assert.ok(/trekbrief/.test(wrongType.message)); assert.ok(/trekreview/.test(wrongType.message)); }); + +// --- v5.1 — phase_signals additive field + sequencing gate --- + +const SIGNALS_BLOCK = `phase_signals: + - phase: research + effort: standard + - phase: plan + effort: high + model: opus + - phase: execute + effort: low + model: sonnet + - phase: review + effort: standard +`; + +test('validateBrief — v5.1 well-formed phase_signals accepted', () => { + const t = GOOD_BRIEF + .replace('brief_version: "2.0"', 'brief_version: "2.1"') + .replace('source: interview\n', `source: interview\n${SIGNALS_BLOCK}`); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, true, JSON.stringify(r.errors)); +}); + +test('validateBrief — pre-v5.1 brief without phase_signals accepted (backward-compat)', () => { + const r = validateBriefContent(GOOD_BRIEF, { strict: true }); + assert.equal(r.valid, true, JSON.stringify(r.errors)); + assert.ok(!r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS')); +}); + +test('validateBrief — v5.1+ brief missing phase_signals + partial emits BRIEF_V51_MISSING_SIGNALS', () => { + const t = GOOD_BRIEF.replace('brief_version: "2.0"', 'brief_version: "2.1"'); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, false); + assert.ok(r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS')); +}); + +test('validateBrief — v5.1+ brief with phase_signals_partial: true accepted', () => { + const t = GOOD_BRIEF + .replace('brief_version: "2.0"', 'brief_version: "2.1"') + .replace('source: interview\n', 'source: interview\nphase_signals_partial: true\n'); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, true, JSON.stringify(r.errors)); +}); + +test('validateBrief — phase_signals + phase_signals_partial both set rejected (mutually exclusive)', () => { + const t = GOOD_BRIEF + .replace('brief_version: "2.0"', 'brief_version: "2.1"') + .replace('source: interview\n', `source: interview\nphase_signals_partial: true\n${SIGNALS_BLOCK}`); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, false); + assert.ok(r.errors.find(e => e.code === 'BRIEF_SIGNALS_MUTUALLY_EXCLUSIVE')); +}); + +test('validateBrief — phase_signals with unknown phase rejected', () => { + const BAD_SIGNALS = `phase_signals: + - phase: nonsense + effort: standard +`; + const t = GOOD_BRIEF + .replace('brief_version: "2.0"', 'brief_version: "2.1"') + .replace('source: interview\n', `source: interview\n${BAD_SIGNALS}`); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, false); + assert.ok(r.errors.find(e => e.code === 'BRIEF_INVALID_PHASE_SIGNAL_PHASE')); +}); From 0655b57930aee7069eba8e648a45cf355d690a64 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:09:57 +0200 Subject: [PATCH 05/58] feat(voyage): bump trekbrief-template to brief_version 2.1 + add phase_signals fixtures --- .../voyage/templates/trekbrief-template.md | 16 ++++++- .../fixtures/brief-with-phase-signals.md | 42 +++++++++++++++++++ .../fixtures/brief-without-phase-signals.md | 31 ++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 plugins/voyage/tests/fixtures/brief-with-phase-signals.md create mode 100644 plugins/voyage/tests/fixtures/brief-without-phase-signals.md diff --git a/plugins/voyage/templates/trekbrief-template.md b/plugins/voyage/templates/trekbrief-template.md index b35d893..ff72ac4 100644 --- a/plugins/voyage/templates/trekbrief-template.md +++ b/plugins/voyage/templates/trekbrief-template.md @@ -1,6 +1,6 @@ --- type: trekbrief -brief_version: 2.0 +brief_version: 2.1 created: {YYYY-MM-DD} task: "{one-line task description}" slug: {slug} @@ -10,6 +10,20 @@ research_status: pending # pending | in_progress | complete | skipped auto_research: false # true if user opted into Claude-managed research interview_turns: {N} source: {interview | manual} +# 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 +# `phase_signals_partial: true` below (mutually exclusive with `phase_signals`). +phase_signals: + - phase: research + effort: standard + - phase: plan + effort: standard + - phase: execute + effort: standard + - phase: review + effort: standard +# phase_signals_partial: true # uncomment to record force-stop instead of phase_signals --- # Task: {title} diff --git a/plugins/voyage/tests/fixtures/brief-with-phase-signals.md b/plugins/voyage/tests/fixtures/brief-with-phase-signals.md new file mode 100644 index 0000000..c68e37c --- /dev/null +++ b/plugins/voyage/tests/fixtures/brief-with-phase-signals.md @@ -0,0 +1,42 @@ +--- +type: trekbrief +brief_version: "2.1" +created: 2026-05-13 +task: "Add per-phase effort dialog to /trekbrief" +slug: phase-signals-example +project_dir: .claude/projects/2026-05-13-phase-signals-example/ +research_topics: 2 +research_status: complete +auto_research: false +interview_turns: 6 +source: interview +phase_signals: + - phase: research + effort: low + model: sonnet + - phase: plan + effort: standard + - phase: execute + effort: high + model: opus + - phase: review + effort: standard +--- + +# Task: Phase-signals example + +## Intent + +A minimal brief that exercises the v5.1 phase_signals additive field with a +mix of effort levels and model overrides. Used by tests/validators to confirm +the validator accepts well-formed signals across the supported tier matrix. + +## Goal + +Validator returns valid: true. annotate.mjs strips phase_signals from the +rendered HTML body (frontmatter stays in source). + +## Success Criteria + +- Validator passes. +- annotate.mjs determinism: re-run produces byte-identical HTML. diff --git a/plugins/voyage/tests/fixtures/brief-without-phase-signals.md b/plugins/voyage/tests/fixtures/brief-without-phase-signals.md new file mode 100644 index 0000000..8bec99e --- /dev/null +++ b/plugins/voyage/tests/fixtures/brief-without-phase-signals.md @@ -0,0 +1,31 @@ +--- +type: trekbrief +brief_version: "2.0" +created: 2026-05-13 +task: "Backward-compat fixture for v5.0-style brief" +slug: legacy-brief-example +project_dir: .claude/projects/2026-05-13-legacy-brief-example/ +research_topics: 0 +research_status: complete +auto_research: false +interview_turns: 3 +source: interview +--- + +# Task: Legacy brief example + +## Intent + +A pre-v5.1 brief that pre-dates the phase_signals field. Used by +tests/validators to confirm backward-compatibility: the brief is accepted +without phase_signals as long as brief_version is < 2.1. + +## Goal + +Validator returns valid: true. The sequencing gate +(BRIEF_V51_MISSING_SIGNALS) does NOT fire for brief_version 2.0. + +## Success Criteria + +- Validator passes. +- No BRIEF_V51_MISSING_SIGNALS error in r.errors. From 56fed8f30560d37225154853185a9f8bfa83e39c Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:11:04 +0200 Subject: [PATCH 06/58] feat(voyage): add Phase 3.5 per-phase effort dialog to /trekbrief (v5.1) --- plugins/voyage/commands/trekbrief.md | 112 +++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/plugins/voyage/commands/trekbrief.md b/plugins/voyage/commands/trekbrief.md index 0036747..edafd94 100644 --- a/plugins/voyage/commands/trekbrief.md +++ b/plugins/voyage/commands/trekbrief.md @@ -288,6 +288,118 @@ Phase 3 complete: {N} questions asked across {M} sections. Proceeding to draft and review. ``` +## Phase 3.5 — Per-phase effort dialog + +Phase 3.5 is the v5.1 entry-point for **adaptive-depth execution**. After +Phase 3 has gathered intent / goal / success criteria / research plan, the +operator commits an effort level and (optional) model per downstream phase +(`research`, `plan`, `execute`, `review`). The committed signals are written +to brief frontmatter as a `phase_signals:` list that the four downstream +commands read via their `## Composition rule (v5.1)` section. + +### State requirements + +Before entering Phase 3.5 the following must be populated: + +- `state.intent` — the Phase 3 Intent answer (1+ paragraph) +- `state.goal` — the Phase 3 Goal answer +- `state.success_criteria` — at least one falsifiable SC +- `state.research_plan.topics` — list (may be empty) + +If any are absent: skip Phase 3.5 entirely and write `phase_signals_partial: +true` to the draft frontmatter. Do not block. + +### --quick mode + +If the operator launched with `--quick`: skip Phase 3.5 entirely and +auto-write `phase_signals_partial: true` to draft frontmatter. The brief +will satisfy the v5.1 sequencing gate without going through the dialog. + +### Default-derivation heuristic (LLM judgment, not algorithmic) + +Before each phase question, propose a default tier marked `(default)`. Use +these signals — they are weak heuristics, not rules: + +- `research_topics_count` → high (`high`), low (`low`), absent (`low`) +- `sc_count` (count of falsifiable SCs) → high (≥6 ⇒ `high`), low (≤2 ⇒ `low`) +- Goal complexity: keywords like "rewrite", "migration", "refactor across", + "new platform" ⇒ `high`; "typo", "small bugfix", "docs touch-up" ⇒ `low` +- Otherwise: `standard` + +Mix these into one proposed default per phase. Document the proposed tier +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: + +| 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}` | + +The proposed tier per phase (from the default-derivation heuristic) MUST be +labelled `(default)` in the option list so the operator can one-click +accept. Commit the chosen tier immediately to an in-memory `effort_state` +dict — no bulk summary-before-commit. The loop is interruptible. + +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) + +### Force-stop handling + +If during any of the four `AskUserQuestion` calls the operator says "stop", +"skip", "enough", "just write it", or similar, do NOT exit silently — apply +the Phase 4f force-stop pattern verbatim: + +``` +You stopped before committing per-phase signals. Remaining phases: + - {list of phases not yet answered} + +The brief will still be valid (v5.1 supports `phase_signals_partial: true` +as a force-stop record). Downstream commands will fall back to the profile +resolver for the un-committed phases. + +Continue anyway? +``` + +Then `AskUserQuestion`: + +| Option | Action | +|--------|--------| +| **Answer one more phase** | Return to the next un-answered phase question. | +| **Stop now (record partial)** | Drop any in-progress `effort_state` and set `phase_signals_partial: true` in draft frontmatter. Mutually exclusive with `phase_signals`. Break Phase 3.5. | + +This pattern matches Step 4f (line 436-458) so the force-stop UX is +identical across both surfaces. + +### Hand-off to Phase 4a + +If `effort_state` is fully populated (4 commits, no force-stop): write a +`phase_signals:` block to draft frontmatter — one entry per phase, +preserving the canonical-mapping form above. Omit `model:` for standard +tier (composition falls through to profile). + +If `phase_signals_partial: true` was set: write that single line to draft +frontmatter and skip the `phase_signals:` block (mutually exclusive per +validator). + +Phase 4a (Step 4a — Draft in memory) reads from `effort_state` / +`phase_signals_partial` and incorporates the appropriate frontmatter block +into the draft brief. + +### Sequencing gate (downstream) + +`brief_version: 2.1` activates the validator's sequencing gate. If the +final brief reaches `/trekplan`, `/trekresearch`, `/trekexecute`, or +`/trekreview` WITHOUT `phase_signals` and WITHOUT `phase_signals_partial: +true`, the validator emits `BRIEF_V51_MISSING_SIGNALS` and the command +halts with a friendly hint pointing back to `/trekbrief`. + ## Phase 4 — Draft, review, and revise Phase 4 runs a **draft → brief-reviewer → revise** loop. The draft is From d3975c441cce8f5154f32f8bda6701453c18d520 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:13:51 +0200 Subject: [PATCH 07/58] feat(voyage): wire 4 downstream commands to brief.phase_signals + composition rule (v5.1) --- plugins/voyage/commands/trekexecute.md | 31 +++++++++++++++++++++++++ plugins/voyage/commands/trekplan.md | 30 ++++++++++++++++++++++++ plugins/voyage/commands/trekresearch.md | 30 ++++++++++++++++++++++++ plugins/voyage/commands/trekreview.md | 30 ++++++++++++++++++++++++ 4 files changed, 121 insertions(+) diff --git a/plugins/voyage/commands/trekexecute.md b/plugins/voyage/commands/trekexecute.md index 27f1514..888ba5f 100644 --- a/plugins/voyage/commands/trekexecute.md +++ b/plugins/voyage/commands/trekexecute.md @@ -1519,6 +1519,37 @@ VOYAGE_PROFILE=balanced /trekexecute --project ... Stats records emit `profile`, `phase_models`, and `profile_source` per Phase 9 record. +## Composition rule (v5.1) + +Independent of the profile system. When `brief.md` carries +`phase_signals` (brief_version ≥ 2.1), each downstream phase resolves +effort + model as: + +``` +effort_for_phase = brief.phase_signals[]?.effort ?? 'standard' +model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[] +``` + +The brief signal wins per-phase when present; the profile fills any +gaps. There is no helper module — composition is documented prose in +each downstream command. + +For `/trekexecute` specifically: `effort == 'low'` activates `--gates open` ++ sequential-only execution (no worktree-isolated parallel waves — runs +all sessions in a single foreground loop). `effort == 'standard'` (or +absent) → no change (default execution strategy applies). High-effort +behavior is deferred to v5.1.1 per brief Non-Goal. + +### Sequencing gate surface + +When `/trekexecute --project ` is invoked, optionally run +`brief-validator.mjs --soft --json` against `{dir}/brief.md`. If +`BRIEF_V51_MISSING_SIGNALS` appears in `errors` (brief_version ≥ 2.1 +without `phase_signals` or `phase_signals_partial: true`), halt with: +`Brief is brief_version 2.1 but does not carry phase_signals — re-run +/trekbrief to commit them (Phase 3.5).` Enforcement is validator-only; +commands surface, don't re-enforce. + ## Hard rules 1. **No AskUserQuestion for execution decisions.** All execution decisions come diff --git a/plugins/voyage/commands/trekplan.md b/plugins/voyage/commands/trekplan.md index ddf7bc9..68d3d1b 100644 --- a/plugins/voyage/commands/trekplan.md +++ b/plugins/voyage/commands/trekplan.md @@ -894,6 +894,36 @@ VOYAGE_PROFILE=balanced /trekplan --project ... Stats records emit `profile`, `phase_models`, `parallel_agents`, and `profile_source` so operators can audit which profile drove which session. +## Composition rule (v5.1) + +Independent of the profile system. When `brief.md` carries +`phase_signals` (brief_version ≥ 2.1), each downstream phase resolves +effort + model as: + +``` +effort_for_phase = brief.phase_signals[]?.effort ?? 'standard' +model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[] +``` + +The brief signal wins per-phase when present; the profile fills any +gaps. There is no helper module — composition is documented prose in +each downstream command. + +For `/trekplan` specifically: `effort == 'low'` activates the existing +`--quick`-equivalent code-path (skip Phase 5 agent swarm — plan directly +without exploration agents). `effort == 'standard'` (or absent) → no +change. High-effort behavior is deferred to v5.1.1 per brief Non-Goal +("No complete per-phase effort dictionary"). + +### Sequencing gate surface + +Phase 1 already calls `brief-validator.mjs --soft`. If the validator +returns `BRIEF_V51_MISSING_SIGNALS` in `errors` (brief_version ≥ 2.1 +without `phase_signals` or `phase_signals_partial: true`), halt with a +one-line message: `Brief is brief_version 2.1 but does not carry phase_signals +— re-run /trekbrief to commit them (Phase 3.5).` Enforcement is +validator-only; this surface just makes the friendly hint readable. + ## Hard rules - **Brief-driven**: Every plan decision must trace back to a section of the diff --git a/plugins/voyage/commands/trekresearch.md b/plugins/voyage/commands/trekresearch.md index d39c4c3..ea1d84a 100644 --- a/plugins/voyage/commands/trekresearch.md +++ b/plugins/voyage/commands/trekresearch.md @@ -435,6 +435,36 @@ Stats records emit `profile`, `phase_models`, `parallel_agents`, `external_research_enabled`, and `profile_source` so operators can audit which profile drove which session. +## Composition rule (v5.1) + +Independent of the profile system. When `brief.md` carries +`phase_signals` (brief_version ≥ 2.1), each downstream phase resolves +effort + model as: + +``` +effort_for_phase = brief.phase_signals[]?.effort ?? 'standard' +model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[] +``` + +The brief signal wins per-phase when present; the profile fills any +gaps. There is no helper module — composition is documented prose in +each downstream command. + +For `/trekresearch` specifically: `effort == 'low'` activates the +existing `--quick`-equivalent code-path (inline research, no agent swarm). +`effort == 'standard'` (or absent) → no change. High-effort behavior is +deferred to v5.1.1 per brief Non-Goal. + +### Sequencing gate surface + +When `/trekresearch --project ` is invoked and `{dir}/brief.md` +exists, optionally run `brief-validator.mjs --soft --json` against it. +If `BRIEF_V51_MISSING_SIGNALS` appears in `errors` (brief_version ≥ 2.1 +without `phase_signals` or `phase_signals_partial: true`), halt with: +`Brief is brief_version 2.1 but does not carry phase_signals — re-run +/trekbrief to commit them (Phase 3.5).` Enforcement is validator-only; +commands surface, don't re-enforce. + ## Hard rules - **No planning:** This command produces research briefs, not implementation plans. diff --git a/plugins/voyage/commands/trekreview.md b/plugins/voyage/commands/trekreview.md index 73bf18d..73bdcbb 100644 --- a/plugins/voyage/commands/trekreview.md +++ b/plugins/voyage/commands/trekreview.md @@ -357,6 +357,36 @@ VOYAGE_PROFILE=premium /trekreview --project ... Stats records emit `profile` and `profile_source`. +## Composition rule (v5.1) + +Independent of the profile system. When `brief.md` carries +`phase_signals` (brief_version ≥ 2.1), each downstream phase resolves +effort + model as: + +``` +effort_for_phase = brief.phase_signals[]?.effort ?? 'standard' +model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[] +``` + +The brief signal wins per-phase when present; the profile fills any +gaps. There is no helper module — composition is documented prose in +each downstream command. + +For `/trekreview` specifically: `effort == 'low'` activates the existing +`--quick`-equivalent code-path (skip the brief-conformance reviewer; run +correctness-only). `effort == 'standard'` (or absent) → no change. +High-effort behavior is deferred to v5.1.1 per brief Non-Goal. + +### Sequencing gate surface + +Phase 1 already calls `brief-validator.mjs --soft` against `{brief_path}`. +If the validator returns `BRIEF_V51_MISSING_SIGNALS` in `errors` +(brief_version ≥ 2.1 without `phase_signals` or `phase_signals_partial: +true`), halt with: `Brief is brief_version 2.1 but does not carry +phase_signals — re-run /trekbrief to commit them (Phase 3.5).` +Enforcement is validator-only; this surface just makes the friendly hint +readable. + ## Hard rules - **Brief is the contract.** Every finding in the review traces to a From 4504c9a8cf016dcd871e91e019e2cf28f8d8446d Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:15:26 +0200 Subject: [PATCH 08/58] test(voyage): add 5 minimal command test files for v5.1 (sequencing-gate + low-effort) --- .../voyage/tests/commands/trekbrief.test.mjs | 42 +++++++++++++++++++ .../tests/commands/trekexecute.test.mjs | 34 +++++++++++++++ .../voyage/tests/commands/trekplan.test.mjs | 32 ++++++++++++++ .../tests/commands/trekresearch.test.mjs | 32 ++++++++++++++ .../voyage/tests/commands/trekreview.test.mjs | 32 ++++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 plugins/voyage/tests/commands/trekbrief.test.mjs create mode 100644 plugins/voyage/tests/commands/trekexecute.test.mjs create mode 100644 plugins/voyage/tests/commands/trekplan.test.mjs create mode 100644 plugins/voyage/tests/commands/trekresearch.test.mjs create mode 100644 plugins/voyage/tests/commands/trekreview.test.mjs diff --git a/plugins/voyage/tests/commands/trekbrief.test.mjs b/plugins/voyage/tests/commands/trekbrief.test.mjs new file mode 100644 index 0000000..3db8030 --- /dev/null +++ b/plugins/voyage/tests/commands/trekbrief.test.mjs @@ -0,0 +1,42 @@ +// tests/commands/trekbrief.test.mjs +// v5.1 — Pattern D prose-pattern regression tests for /trekbrief Phase 3.5. +// +// Brief SC1 + SC2: end-of-brief effort dialog covering 4 downstream phases, +// with `phase_signals_partial` as the force-stop record. + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const COMMAND_FILE = join(ROOT, 'commands', 'trekbrief.md'); + +function read() { + return readFileSync(COMMAND_FILE, 'utf8'); +} + +test('trekbrief — Phase 3.5 heading is present', () => { + const text = read(); + assert.match(text, /^## Phase 3\.5 — Per-phase effort dialog$/m, + 'Phase 3.5 heading missing from commands/trekbrief.md'); +}); + +test('trekbrief — Phase 3.5 references all 4 downstream phases', () => { + 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)); + for (const phase of ['research', 'plan', 'execute', 'review']) { + assert.ok(section.includes(phase), + `Phase 3.5 missing reference to "${phase}"`); + } +}); + +test('trekbrief — Phase 3.5 documents phase_signals_partial force-stop', () => { + const text = read(); + assert.ok(text.includes('phase_signals_partial'), + 'phase_signals_partial not mentioned in /trekbrief command prose'); +}); diff --git a/plugins/voyage/tests/commands/trekexecute.test.mjs b/plugins/voyage/tests/commands/trekexecute.test.mjs new file mode 100644 index 0000000..e848119 --- /dev/null +++ b/plugins/voyage/tests/commands/trekexecute.test.mjs @@ -0,0 +1,34 @@ +// tests/commands/trekexecute.test.mjs +// v5.1 — sequencing-gate surface + low-effort prose check for /trekexecute. +// Plan Assumption 2 locks low-effort to --gates open + sequential-only. + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const COMMAND_FILE = join(ROOT, 'commands', 'trekexecute.md'); + +function read() { + return readFileSync(COMMAND_FILE, 'utf8'); +} + +test('trekexecute — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { + const text = read(); + assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), + '/trekexecute must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate'); + assert.ok(text.includes('phase_signals'), + '/trekexecute must reference phase_signals (v5.1 composition rule)'); +}); + +test('trekexecute — low-effort path references --gates open + sequential', () => { + const text = read(); + const compIdx = text.indexOf('## Composition rule (v5.1)'); + assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing'); + const section = text.slice(compIdx, compIdx + 2000); + assert.match(section, /--gates open/, 'Low-effort path must mention --gates open'); + assert.match(section, /sequential/, 'Low-effort path must mention sequential-only execution'); +}); diff --git a/plugins/voyage/tests/commands/trekplan.test.mjs b/plugins/voyage/tests/commands/trekplan.test.mjs new file mode 100644 index 0000000..901936d --- /dev/null +++ b/plugins/voyage/tests/commands/trekplan.test.mjs @@ -0,0 +1,32 @@ +// tests/commands/trekplan.test.mjs +// v5.1 — sequencing-gate surface + low-effort prose check for /trekplan. + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const COMMAND_FILE = join(ROOT, 'commands', 'trekplan.md'); + +function read() { + return readFileSync(COMMAND_FILE, 'utf8'); +} + +test('trekplan — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { + const text = read(); + assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), + '/trekplan must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate'); + assert.ok(text.includes('phase_signals'), + '/trekplan must reference phase_signals (v5.1 composition rule)'); +}); + +test('trekplan — low-effort path references --quick equivalent', () => { + const text = read(); + const compIdx = text.indexOf('## Composition rule (v5.1)'); + assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing'); + const section = text.slice(compIdx, compIdx + 2000); + assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent'); +}); diff --git a/plugins/voyage/tests/commands/trekresearch.test.mjs b/plugins/voyage/tests/commands/trekresearch.test.mjs new file mode 100644 index 0000000..4fd2a8c --- /dev/null +++ b/plugins/voyage/tests/commands/trekresearch.test.mjs @@ -0,0 +1,32 @@ +// tests/commands/trekresearch.test.mjs +// v5.1 — sequencing-gate surface + low-effort prose check for /trekresearch. + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const COMMAND_FILE = join(ROOT, 'commands', 'trekresearch.md'); + +function read() { + return readFileSync(COMMAND_FILE, 'utf8'); +} + +test('trekresearch — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { + const text = read(); + assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), + '/trekresearch must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate'); + assert.ok(text.includes('phase_signals'), + '/trekresearch must reference phase_signals (v5.1 composition rule)'); +}); + +test('trekresearch — low-effort path references --quick equivalent', () => { + const text = read(); + const compIdx = text.indexOf('## Composition rule (v5.1)'); + assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing'); + const section = text.slice(compIdx, compIdx + 2000); + assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent'); +}); diff --git a/plugins/voyage/tests/commands/trekreview.test.mjs b/plugins/voyage/tests/commands/trekreview.test.mjs new file mode 100644 index 0000000..9d1a53c --- /dev/null +++ b/plugins/voyage/tests/commands/trekreview.test.mjs @@ -0,0 +1,32 @@ +// tests/commands/trekreview.test.mjs +// v5.1 — sequencing-gate surface + low-effort prose check for /trekreview. + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const COMMAND_FILE = join(ROOT, 'commands', 'trekreview.md'); + +function read() { + return readFileSync(COMMAND_FILE, 'utf8'); +} + +test('trekreview — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { + const text = read(); + assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), + '/trekreview must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate'); + assert.ok(text.includes('phase_signals'), + '/trekreview must reference phase_signals (v5.1 composition rule)'); +}); + +test('trekreview — low-effort path references --quick equivalent', () => { + const text = read(); + const compIdx = text.indexOf('## Composition rule (v5.1)'); + assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing'); + const section = text.slice(compIdx, compIdx + 2000); + assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent'); +}); From 113296d7defafc59c65f695f545a737d863a7070 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:18:42 +0200 Subject: [PATCH 09/58] docs(voyage): amend HANDOVER-CONTRACTS + add 5 doc-consistency pins (v5.1) --- plugins/voyage/docs/HANDOVER-CONTRACTS.md | 13 +++++-- .../voyage/tests/lib/doc-consistency.test.mjs | 34 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/plugins/voyage/docs/HANDOVER-CONTRACTS.md b/plugins/voyage/docs/HANDOVER-CONTRACTS.md index a45eb05..9d2e1f8 100644 --- a/plugins/voyage/docs/HANDOVER-CONTRACTS.md +++ b/plugins/voyage/docs/HANDOVER-CONTRACTS.md @@ -10,7 +10,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated: | Artifact | Field | Current | |---|---|---| -| `brief.md` | `brief_version` (frontmatter) | `2.0` | +| `brief.md` | `brief_version` (frontmatter) | `2.1` | | `research/*.md` | (implicit; tracked via `type: trekresearch-brief`) | unversioned | | `plan.md` | `plan_version` (frontmatter) | `1.7` | | `progress.json` | `schema_version` (top-level) | `"1"` | @@ -67,6 +67,8 @@ Every validator exposes a CLI: `node lib/validators/.mjs --json ` re | `interview_turns` | number | optional | ≥ 0 | | | `source` | string | optional | `interview \| manual` | | | `brief_quality` | string | optional | `complete \| partial` | Set when iteration cap is hit | +| `phase_signals` | list | optional (v5.1+) | list of `{phase, effort?, model?}` entries | Per-phase effort + model commitment from Phase 3.5. Mutually exclusive with `phase_signals_partial`. | +| `phase_signals_partial` | bool | optional (v5.1+) | `true` | Force-stop record from Phase 3.5. Mutually exclusive with `phase_signals`. | **Body invariants:** required sections (validator runs in strict mode at write-time, soft mode at read-time): - `## Intent` @@ -84,11 +86,12 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference | Type discriminator | every read | `type === "trekbrief"` | | Status enum | every read | `research_status ∈ allowed values` | | **State machine** | every read | `research_topics > 0 && research_status === "skipped"` requires `brief_quality === "partial"` | +| **v5.1 sequencing gate** | every read | `brief_version ≥ 2.1` requires `phase_signals` (list) OR `phase_signals_partial: true` — error `BRIEF_V51_MISSING_SIGNALS` on miss. Validator-only enforcement; commands surface, don't re-enforce. | | Body sections | strict only | All `BRIEF_BODY_SECTIONS` present | **State machine** detail: a brief that says it has research topics but skipped them must explicitly admit it (via `brief_quality: partial`). This is the most common failure mode the validator catches. -**Versioning:** current is `2.0`. There are no live `1.x` briefs; remove legacy paths in next major. +**Versioning:** current is `2.1` (v5.1 — adds optional `phase_signals` + `phase_signals_partial`). The forward-compat policy in `brief-validator.mjs` header still applies: unknown frontmatter keys flow through silently, so a `2.1` brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the **version-conditional sequencing gate** (above) — the only check in the validator that triggers on `brief_version` rather than field-presence. There are no live `1.x` briefs; remove legacy paths in next major. v5.4 may promote `phase_signals` from optional to required (breaking change → `3.0`). **Failure modes:** - `BRIEF_NOT_FOUND` → consumer halts with a usage message @@ -97,6 +100,12 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference - `BRIEF_MISSING_FIELD` → strict halt; soft-mode warning - `BRIEF_STATE_INCOHERENT` → strict halt; soft-mode warning (incoherence will haunt downstream agents) - `BRIEF_MISSING_SECTION` → strict halt; soft-mode warning +- `BRIEF_V51_MISSING_SIGNALS` → strict halt (v5.1+ sequencing gate); soft-mode warning. Commands surface a friendly hint pointing back to `/trekbrief` (Phase 3.5). +- `BRIEF_INVALID_PHASE_SIGNALS` → strict halt; phase_signals must be a list of `{phase, effort?, model?}` entries. +- `BRIEF_INVALID_PHASE_SIGNAL_PHASE` → strict halt; phase ∉ `[research, plan, execute, review]`. +- `BRIEF_INVALID_EFFORT` → strict halt; effort ∉ `[low, standard, high]`. +- `BRIEF_INVALID_MODEL` → strict halt; model ∉ `BASE_ALLOWED_MODELS` (currently `[sonnet, opus]`). +- `BRIEF_SIGNALS_MUTUALLY_EXCLUSIVE` → strict halt; cannot set both `phase_signals` and `phase_signals_partial: true`. --- diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index d01a728..717ee29 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -551,3 +551,37 @@ test('operational files no longer reference trekrevise (v5.0.0 removal)', () => ); } }); + +// --- v5.1 — phase_signals + brief_version 2.1 --- + +test('v5.1 — templates/trekbrief-template.md declares brief_version: 2.1', () => { + const t = read('templates/trekbrief-template.md'); + assert.match(t, /^brief_version: 2\.1$/m, + 'trekbrief-template.md must declare brief_version: 2.1 at top of frontmatter'); +}); + +test('v5.1 — templates/trekbrief-template.md contains phase_signals: block', () => { + const t = read('templates/trekbrief-template.md'); + assert.match(t, /^phase_signals:$/m, + 'trekbrief-template.md must contain a phase_signals: block in frontmatter'); +}); + +test('v5.1 — HANDOVER-CONTRACTS.md schema row includes phase_signals + phase_signals_partial', () => { + const t = read('docs/HANDOVER-CONTRACTS.md'); + assert.ok(t.includes('| `phase_signals` |'), + 'HANDOVER-CONTRACTS must add a phase_signals row to the Handover 1 schema table'); + assert.ok(t.includes('| `phase_signals_partial` |'), + 'HANDOVER-CONTRACTS must add a phase_signals_partial row to the Handover 1 schema table'); +}); + +test('v5.1 — voyage CLAUDE.md mentions phase_signals', () => { + const t = read('CLAUDE.md'); + assert.ok(t.includes('phase_signals'), + 'voyage CLAUDE.md must document phase_signals (v5.1)'); +}); + +test('v5.1 — voyage README.md mentions phase_signals', () => { + const t = read('README.md'); + assert.ok(t.includes('phase_signals'), + 'voyage README.md must mention phase_signals (v5.1 "What\'s new" bullet)'); +}); From 6efcc62b689b2e1f6245091172f977c32de67062 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:22:07 +0200 Subject: [PATCH 10/58] docs(voyage): document phase_signals in CLAUDE + README + marketplace + ROADMAP (v5.1) --- README.md | 6 ++++-- plugins/voyage/CLAUDE.md | 4 +++- plugins/voyage/README.md | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a701196..2d7ad87 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,11 @@ Key commands: `/config-audit posture`, `/config-audit feature-gap`, `/config-aud --- -### [Voyage](plugins/voyage/) `v5.0.3` +### [Voyage](plugins/voyage/) `v5.1.0` -Deep requirements gathering, research, implementation planning, self-verifying execution, independent post-hoc review, and zero-friction multi-session resumption — with specialized agent swarms, adversarial review, and failure recovery. Six-command (brief, research, plan, execute, review, continue) universal pipeline. `/trekbrief`, `/trekplan`, and `/trekreview` render their artifact to a self-contained HTML view and print the `file://` link; annotation is delegated to the official `/playground` plugin. +Deep requirements gathering, research, implementation planning, self-verifying execution, independent post-hoc review, and zero-friction multi-session resumption — with specialized agent swarms, adversarial review, and failure recovery. Six-command (brief, research, plan, execute, review, continue) universal pipeline + adaptive-depth per-phase effort dialog. `/trekbrief`, `/trekplan`, and `/trekreview` render their artifact to a self-contained HTML view and print the `file://` link. + +v5.1.0 adds Phase 3.5 to `/trekbrief`: 4 tier-coupled `AskUserQuestion` calls commit an effort level (`low | standard | high`) and an optional `model` (`sonnet | opus`) per downstream phase (`research`, `plan`, `execute`, `review`). The choices land in `brief.md` as `phase_signals:` (or `phase_signals_partial: true` on force-stop). `brief_version: 2.1` activates a validator-side sequencing gate (`BRIEF_V51_MISSING_SIGNALS`) so downstream commands halt with a friendly hint when signals are missing. Composition rule per downstream command: brief signal wins per-phase, profile fills gaps. `effort == low` activates each command's existing `--quick`-equivalent code-path (`/trekexecute` low-effort = `--gates open` + sequential-only). Additive — no breaking changes; pre-2.1 briefs still validate. See `plugins/voyage/CHANGELOG.md` § v5.1.0. v5.0.3 lands the annotation UX modelled on `~/repos/claude-code-100x/claude-code-100x/build-site.js`: pencil-toggle annotation mode, **select text or click any element to anchor**, choose intent (**Fiks** / **Endre** / **Spørsmål**), write a comment, save. The sidebar groups annotations by section with intent badges; Copy Prompt assembles them into a structured markdown the operator pastes back into Claude. State persists in `localStorage` per artifact path. v5.0.2 was operator-led but too thin (line-click + freeform note, no intent categories). v5.0.1 had pointed at `/playground document-critique` (Claude-leads — wrong direction). v5.0.0 (breaking, kept) removed the v4.2/v4.3 bespoke playground SPA, `/trekrevise`, Handover 8, the supporting `lib/` modules, the Playwright e2e suite, and the `@playwright/test` / `@axe-core/playwright` devDeps. v5.0.3's `scripts/annotate.mjs` is one self-contained zero-dependency Node script. **The operator drives every annotation** — Claude never pre-generates suggestions in this flow. See `plugins/voyage/CHANGELOG.md` § v5.0.0 → § v5.0.3. diff --git a/plugins/voyage/CLAUDE.md b/plugins/voyage/CLAUDE.md index 75ab8df..d549842 100644 --- a/plugins/voyage/CLAUDE.md +++ b/plugins/voyage/CLAUDE.md @@ -222,7 +222,9 @@ Local Docker Compose stack: `examples/observability/`. Operator docs: `docs/obse ## Architecture -**Brief:** 7-phase workflow: Parse mode → Create project dir → Phase 3 completeness loop (section-driven, no question cap) → Phase 4 draft/review/revise with `brief-reviewer` as stop-gate (max 3 iterations; gate = all dimensions ≥ 4 and research plan = 5) → Finalize (`brief.md` on pass, or `brief_quality: partial` on cap/force-stop) → Manual/auto opt-in → Stats. Always interactive. Auto mode runs research + plan inline in the main context (v2.4.0). +**Brief:** 7-phase workflow: Parse mode → Create project dir → Phase 3 completeness loop (section-driven, no question cap) → Phase 3.5 per-phase effort dialog (v5.1) → Phase 4 draft/review/revise with `brief-reviewer` as stop-gate (max 3 iterations; gate = all dimensions ≥ 4 and research plan = 5) → Finalize (`brief.md` on pass, or `brief_quality: partial` on cap/force-stop) → Manual/auto opt-in → Stats. Always interactive. Auto mode runs research + plan inline in the main context (v2.4.0). + +**Phase 3.5 (v5.1) — adaptive-depth signals:** Between Phase 3 completeness exit and Phase 4 draft, the operator commits an effort level (`low | standard | high`) and an optional `model` (`sonnet | opus`) per downstream phase (`research`, `plan`, `execute`, `review`) via 4 tier-coupled `AskUserQuestion` calls. The choices land in `brief.md` frontmatter as `phase_signals:` (a list of `{phase, effort?, model?}` entries) when committed, or `phase_signals_partial: true` when the operator force-stops. `brief_version: 2.1` activates the **sequencing gate**: validator emits `BRIEF_V51_MISSING_SIGNALS` if a 2.1-versioned brief lacks both fields. Downstream commands surface a friendly hint pointing back to `/trekbrief` — enforcement is validator-only. Composition is documented prose in each downstream command's `## Composition rule (v5.1)` section: `brief.phase_signals[phase] > profile.phase_models[phase]`. The brief signal wins per-phase when present; the profile fills gaps. `effort == low` activates each command's existing `--quick`-equivalent code-path (`/trekexecute` low-effort = `--gates open` + sequential-only). High-effort behavior is deferred to v5.1.1 per brief Non-Goal. **Research:** Foreground workflow (v2.4.0): Parse mode → Interview → Parallel research swarm (5 local + 4 external + 1 bridge, spawned from main context) → Follow-ups → Triangulation → Synthesis + brief → Stats. With `--project`, writes to `{dir}/research/NN-slug.md`. diff --git a/plugins/voyage/README.md b/plugins/voyage/README.md index 825f3c7..018857c 100644 --- a/plugins/voyage/README.md +++ b/plugins/voyage/README.md @@ -10,6 +10,8 @@ A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin for deep implementation planning, multi-source research, autonomous execution, independent post-hoc review, and zero-friction multi-session resumption. Six commands, one pipeline: +> **What's new in v5.1** — `/trekbrief` Phase 3.5 commits per-phase `phase_signals` (effort + optional model for `research`/`plan`/`execute`/`review`) to `brief.md` frontmatter. `brief_version: 2.1` activates a validator-side sequencing gate (`BRIEF_V51_MISSING_SIGNALS`) so downstream commands halt with a friendly hint when signals are missing. Composition rule per downstream command: brief signal wins per-phase, profile fills gaps. `effort == low` activates the existing `--quick`-equivalent code-path in each command (`/trekexecute` low-effort = `--gates open` + sequential). Additive — no breaking changes; pre-2.1 briefs still validate. + | Command | What it does | |---------|-------------| | **`/trekbrief`** | Brief — interactive interview produces a task brief with explicit research plan | From dfe1986f06baa7c76318f4e2a24b992cdaa5d15b Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:23:48 +0200 Subject: [PATCH 11/58] =?UTF-8?q?chore(voyage):=20bump=20version=205.0.3?= =?UTF-8?q?=20=E2=86=92=205.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/voyage/.claude-plugin/plugin.json | 2 +- plugins/voyage/README.md | 2 +- plugins/voyage/package-lock.json | 4 ++-- plugins/voyage/package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/voyage/.claude-plugin/plugin.json b/plugins/voyage/.claude-plugin/plugin.json index 472b376..68fb63b 100644 --- a/plugins/voyage/.claude-plugin/plugin.json +++ b/plugins/voyage/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "voyage", "description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): select text or click any element, pick intent (Fiks/Endre/Spørsmål), write comment, copy structured prompt, paste back, Claude revises the .md.", - "version": "5.0.3", + "version": "5.1.0", "author": { "name": "Kjell Tore Guttormsen" }, diff --git a/plugins/voyage/README.md b/plugins/voyage/README.md index 018857c..d2bb41e 100644 --- a/plugins/voyage/README.md +++ b/plugins/voyage/README.md @@ -1,6 +1,6 @@ # trekplan — Brief, Research, Plan, Execute, Review, Continue -![Version](https://img.shields.io/badge/version-5.0.3-blue) +![Version](https://img.shields.io/badge/version-5.1.0-blue) ![License](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Claude%20Code-purple) diff --git a/plugins/voyage/package-lock.json b/plugins/voyage/package-lock.json index ce028d5..4c90649 100644 --- a/plugins/voyage/package-lock.json +++ b/plugins/voyage/package-lock.json @@ -1,12 +1,12 @@ { "name": "voyage", - "version": "5.0.3", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "voyage", - "version": "5.0.3", + "version": "5.1.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/plugins/voyage/package.json b/plugins/voyage/package.json index 59489ef..4a9c929 100644 --- a/plugins/voyage/package.json +++ b/plugins/voyage/package.json @@ -1,6 +1,6 @@ { "name": "voyage", - "version": "5.0.3", + "version": "5.1.0", "description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): select text or click any heading/paragraph/list-item, pick intent (Fiks/Endre/Spørsmål), write comment, copy structured prompt, paste back, Claude revises the .md.", "type": "module", "engines": { From 8f4b79cfc67c25b5ab741d66bd5b525de9290855 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 21:24:49 +0200 Subject: [PATCH 12/58] docs(voyage): add CHANGELOG entry for v5.1.0 --- plugins/voyage/CHANGELOG.md | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/plugins/voyage/CHANGELOG.md b/plugins/voyage/CHANGELOG.md index cb63b39..ab3267c 100644 --- a/plugins/voyage/CHANGELOG.md +++ b/plugins/voyage/CHANGELOG.md @@ -4,6 +4,97 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## v5.1.0 — 2026-05-13 — Per-phase effort + model dialog + +Additive. No breaking changes. Forward-compat with all v5.0.x briefs. + +### Why + +The voyage pipeline runs a single profile-tier setting for every task. For +typo fixes and small bugfixes the full `brief → research → plan → execute +→ review` ceremony is over-engineered; for risky migrations the same +profile-tier is too thin. v5.1 hands ceremony-level back to the operator +per phase in the same dialog that produces the brief — without removing +the disciplined defaults that protect high-stakes work. Independent of +v4.1's profile system: composition happens at the command level (brief +signal wins per-phase, profile fills gaps). No `/trekflow`, no helper +module, no per-command effort dictionary — composition is documented +prose in each downstream command. + +### Added + +- **`/trekbrief` Phase 3.5** — between Phase 3 completeness exit and + Phase 4 draft, 4 tier-coupled `AskUserQuestion` calls commit an effort + level (`low | standard | high`) and an optional `model` (`sonnet | + opus`) per downstream phase (`research`, `plan`, `execute`, `review`). + Tier mapping: `low → {effort: low, model: sonnet}`, `standard → + {effort: standard}` (model omitted; composition falls through to + profile), `high → {effort: high, model: opus}`. Force-stop pattern + (Phase 4f verbatim) records `phase_signals_partial: true` instead. + `--quick` skips Phase 3.5 entirely and auto-writes + `phase_signals_partial: true`. +- **`brief-validator` extension** — 6 new issue codes: + `BRIEF_INVALID_PHASE_SIGNALS`, `BRIEF_INVALID_PHASE_SIGNAL_PHASE`, + `BRIEF_INVALID_EFFORT`, `BRIEF_INVALID_MODEL`, + `BRIEF_SIGNALS_MUTUALLY_EXCLUSIVE`, `BRIEF_V51_MISSING_SIGNALS` + + exported `PHASE_SIGNAL_PHASES` + `EFFORT_LEVELS` constants. The + `BASE_ALLOWED_MODELS` const in `lib/validators/profile-validator.mjs` + was promoted to `export const` so the brief validator can re-use it. +- **HANDOVER-CONTRACTS amendments** — Handover 1 gets 5 inserts: + versioning row → `2.1`, two new schema-table rows (`phase_signals`, + `phase_signals_partial`), v5.1 sequencing-gate validation row, + versioning-paragraph expansion explaining the version-conditional + gate, 6 new failure-mode bullets. +- **Template bump** — `templates/trekbrief-template.md` → `brief_version + 2.1` with a default `phase_signals:` block (4 phases × `effort: + standard`, model omitted) and a commented `phase_signals_partial: + true` line showing the force-stop alternative. +- **Composition rule (v5.1)** — new `## Composition rule (v5.1)` + sub-section in each of `commands/{trekplan,trekresearch,trekexecute, + trekreview}.md`. Documents `effort_for_phase = brief.phase_signals[ + ]?.effort ?? 'standard'` and `model_for_phase = + brief.phase_signals[]?.model ?? profile.phase_models[]`. + Per command: `effort == low` activates that command's existing + `--quick`-equivalent code-path (`/trekplan` skips Phase 5 agent swarm, + `/trekresearch` inline research, `/trekreview` correctness-only, + `/trekexecute` `--gates open` + sequential-only). +- **Sequencing-gate surface** in 4 downstream commands — when + `brief-validator.mjs` returns `BRIEF_V51_MISSING_SIGNALS` in `errors`, + halt with a one-line user-readable message pointing back to + `/trekbrief`. Enforcement is validator-only. +- **5 new minimal command test files** under `tests/commands/` — + `trekbrief.test.mjs` (3 cases), `trekplan.test.mjs` / + `trekresearch.test.mjs` / `trekreview.test.mjs` (2 cases each), + `trekexecute.test.mjs` (2 cases). Pattern D (read .md, assert prose + patterns). Verifies sequencing-gate surface + low-effort prose. +- **5 new doc-consistency pins** — template `brief_version 2.1` + + `phase_signals:` block, HANDOVER schema rows, voyage CLAUDE.md + + README.md mention `phase_signals`. +- **2 new fixtures** — `tests/fixtures/brief-with-phase-signals.md` + + `brief-without-phase-signals.md` (backward-compat). + +### Changed + +- `brief_version` bumped `2.0 → 2.1`. The bump exists because v2.1 + activates the **version-conditional sequencing gate** — the only check + in the brief validator that triggers on `brief_version` rather than + field-presence. The forward-compat policy still applies to the field + itself (unknown frontmatter keys flow through). + +### Notes + +- Test count grows by ≥ 17 new cases minimum: 6 brief-validator + 11 + command-test minimums. Realistic delta is ~25 new cases (Step 6 adds 5 + doc-consistency pins on top). Target ≥ 533 pass at Step 10 verify. +- `MIGRATION.md` was deliberately NOT created — v5.1 is an additive + minor (brief_version 2.0 → 2.1, not major). v5.4 may promote + `phase_signals` from optional to required (breaking change → 3.0). +- High-effort behaviors for `/trekplan` / `/trekresearch` / + `/trekreview` are deferred to v5.1.1 per brief Non-Goal ("No complete + per-phase effort dictionary"). v5.1 locks only the low-effort floor. +- `phase_signals_present` stats emission is also deferred to v5.1.1 + (opt-in observability per Research 03 Q5). + ## v5.0.3 — 2026-05-13 — Annotation UX matches the claude-code-100x reference **No new breaking changes beyond v5.0.0.** Forks consuming v5.0.2's From 3ed2d84caafc87d437b92cf90ac5248dc7549f0a Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:34:14 +0200 Subject: [PATCH 13/58] feat(voyage): add phase-signal-resolver helper for v5.1.1 wiring --- .../lib/profiles/phase-signal-resolver.mjs | 86 +++++++++++++++++++ .../tests/lib/phase-signal-resolver.test.mjs | 77 +++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 plugins/voyage/lib/profiles/phase-signal-resolver.mjs create mode 100644 plugins/voyage/tests/lib/phase-signal-resolver.test.mjs diff --git a/plugins/voyage/lib/profiles/phase-signal-resolver.mjs b/plugins/voyage/lib/profiles/phase-signal-resolver.mjs new file mode 100644 index 0000000..d8588bd --- /dev/null +++ b/plugins/voyage/lib/profiles/phase-signal-resolver.mjs @@ -0,0 +1,86 @@ +// lib/profiles/phase-signal-resolver.mjs +// v5.1.1 — extract per-phase signal from a brief's frontmatter. +// +// Decision A wiring: commands invoke this helper via Bash CLI shim +// (`node lib/profiles/phase-signal-resolver.mjs --brief --phase --json`) +// to obtain the {effort, model} pair for a specific pipeline phase. +// +// Sole source of truth for PHASE_SIGNAL_PHASES + EFFORT_LEVELS is +// lib/validators/brief-validator.mjs — re-imported here so the helper +// cannot drift from the schema validator. + +import { readFileSync, existsSync } from 'node:fs'; +import { parseDocument } from '../util/frontmatter.mjs'; +import { PHASE_SIGNAL_PHASES, EFFORT_LEVELS } from '../validators/brief-validator.mjs'; + +/** + * Resolve a brief's phase_signal entry for one phase. + * + * @param {object|null} briefFrontmatter Parsed YAML frontmatter dict (or null/undefined). + * @param {string} phase One of PHASE_SIGNAL_PHASES; anything else returns null. + * @returns {{effort?: string, model?: string} | null} + * + * Never throws. Returns null on: + * - Falsy / non-object frontmatter + * - phase not in PHASE_SIGNAL_PHASES (e.g. 'brief' or 'continue') + * - Missing phase_signals array + * - No entry for the requested phase + * + * Returns partial `{effort}` (with `model: undefined`) when the signal omits model. + */ +export function resolvePhaseSignal(briefFrontmatter, phase) { + if (!briefFrontmatter || typeof briefFrontmatter !== 'object') return null; + if (typeof phase !== 'string' || !PHASE_SIGNAL_PHASES.includes(phase)) return null; + const signals = briefFrontmatter.phase_signals; + if (!Array.isArray(signals)) return null; + for (const entry of signals) { + if (entry && typeof entry === 'object' && entry.phase === phase) { + const out = {}; + if ('effort' in entry && EFFORT_LEVELS.includes(entry.effort)) out.effort = entry.effort; + if ('model' in entry) out.model = entry.model; + return out; + } + } + return null; +} + +/** + * Convenience wrapper: read a brief file, parse, and resolve a single phase. + * Returns null on any read or parse failure (graceful degradation). + */ +export function resolvePhaseSignalFromFile(briefPath, phase) { + if (typeof briefPath !== 'string' || briefPath.length === 0) return null; + if (!existsSync(briefPath)) return null; + let text; + try { text = readFileSync(briefPath, 'utf-8'); } catch { return null; } + const doc = parseDocument(text); + if (!doc || !doc.valid) return null; + const fm = doc.parsed && doc.parsed.frontmatter; + return resolvePhaseSignal(fm, phase); +} + +// CLI shim — mirrors lib/validators/brief-validator.mjs:168 pattern. +if (import.meta.url === `file://${process.argv[1]}`) { + const args = process.argv.slice(2); + const getArg = (name) => { + const i = args.indexOf(name); + return i >= 0 && i + 1 < args.length ? args[i + 1] : null; + }; + const briefPath = getArg('--brief'); + const phase = getArg('--phase'); + if (!briefPath || !phase) { + process.stderr.write('Usage: phase-signal-resolver.mjs --brief --phase [--json]\n'); + process.exit(2); + } + const result = resolvePhaseSignalFromFile(briefPath, phase); + if (args.includes('--json')) { + process.stdout.write(JSON.stringify(result) + '\n'); + } else if (result === null) { + process.stdout.write('null\n'); + } else { + const effort = 'effort' in result ? result.effort : ''; + const model = 'model' in result ? result.model : ''; + process.stdout.write(`effort=${effort} model=${model}\n`); + } + process.exit(0); +} diff --git a/plugins/voyage/tests/lib/phase-signal-resolver.test.mjs b/plugins/voyage/tests/lib/phase-signal-resolver.test.mjs new file mode 100644 index 0000000..5461740 --- /dev/null +++ b/plugins/voyage/tests/lib/phase-signal-resolver.test.mjs @@ -0,0 +1,77 @@ +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { execFileSync } from 'node:child_process'; +import { writeFileSync, unlinkSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { resolvePhaseSignal, resolvePhaseSignalFromFile } from '../../lib/profiles/phase-signal-resolver.mjs'; + +const FULL_SIGNALS_FM = { + phase_signals: [ + { phase: 'research', effort: 'low', model: 'sonnet' }, + { phase: 'plan', effort: 'standard' }, + { phase: 'execute', effort: 'high', model: 'opus' }, + { phase: 'review', effort: 'standard', model: 'sonnet' }, + ], +}; + +test('resolvePhaseSignal — returns {effort, model} for all 4 phases on full-signals brief', () => { + for (const phase of ['research', 'plan', 'execute', 'review']) { + const r = resolvePhaseSignal(FULL_SIGNALS_FM, phase); + assert.ok(r && typeof r === 'object', `phase=${phase} should resolve non-null`); + assert.ok(typeof r.effort === 'string', `phase=${phase} should have effort`); + } +}); + +test('resolvePhaseSignal — returns null when brief has no phase_signals', () => { + const r = resolvePhaseSignal({ task: 'x' }, 'plan'); + assert.equal(r, null); +}); + +test('resolvePhaseSignal — returns partial {effort} with model undefined when signal omits model', () => { + const r = resolvePhaseSignal(FULL_SIGNALS_FM, 'plan'); + assert.equal(r.effort, 'standard'); + assert.equal(r.model, undefined); + assert.ok(!('model' in r), 'model key should be absent when not in signal'); +}); + +test('resolvePhaseSignal — returns null when phase is not in PHASE_SIGNAL_PHASES', () => { + assert.equal(resolvePhaseSignal(FULL_SIGNALS_FM, 'brief'), null); + assert.equal(resolvePhaseSignal(FULL_SIGNALS_FM, 'continue'), null); + assert.equal(resolvePhaseSignal(FULL_SIGNALS_FM, 'nonsense'), null); +}); + +test('resolvePhaseSignal — defensive: null/non-object input returns null', () => { + assert.equal(resolvePhaseSignal(null, 'plan'), null); + assert.equal(resolvePhaseSignal(undefined, 'plan'), null); + assert.equal(resolvePhaseSignal('string', 'plan'), null); + assert.equal(resolvePhaseSignal({ phase_signals: 'not-array' }, 'plan'), null); +}); + +test('resolvePhaseSignalFromFile + CLI shim — writes JSON to stdout, exit 0', () => { + const fixture = join(tmpdir(), `phase-signal-test-${process.pid}.md`); + writeFileSync(fixture, `--- +type: trekbrief +brief_version: "2.1" +phase_signals: + - phase: plan + effort: high + model: opus +--- +# x +`); + try { + // Programmatic invocation + const r = resolvePhaseSignalFromFile(fixture, 'plan'); + assert.deepEqual(r, { effort: 'high', model: 'opus' }); + // CLI shim + const helperPath = new URL('../../lib/profiles/phase-signal-resolver.mjs', import.meta.url).pathname; + const out = execFileSync('node', [helperPath, '--brief', fixture, '--phase', 'plan', '--json'], { + encoding: 'utf-8', + }); + const parsed = JSON.parse(out.trim()); + assert.deepEqual(parsed, { effort: 'high', model: 'opus' }); + } finally { + try { unlinkSync(fixture); } catch { /* swallow */ } + } +}); From a67b5717c98bb98779156471a7c31d8b276e58f7 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:34:51 +0200 Subject: [PATCH 14/58] test(voyage): add 4 brief fixtures for v5.1.1 runtime scenarios --- .../tests/fixtures/brief-effort-high.md | 45 +++++++++++++++++++ .../voyage/tests/fixtures/brief-effort-low.md | 43 ++++++++++++++++++ .../tests/fixtures/brief-effort-standard.md | 42 +++++++++++++++++ .../tests/fixtures/brief-v21-no-signals.md | 32 +++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 plugins/voyage/tests/fixtures/brief-effort-high.md create mode 100644 plugins/voyage/tests/fixtures/brief-effort-low.md create mode 100644 plugins/voyage/tests/fixtures/brief-effort-standard.md create mode 100644 plugins/voyage/tests/fixtures/brief-v21-no-signals.md diff --git a/plugins/voyage/tests/fixtures/brief-effort-high.md b/plugins/voyage/tests/fixtures/brief-effort-high.md new file mode 100644 index 0000000..0d119b1 --- /dev/null +++ b/plugins/voyage/tests/fixtures/brief-effort-high.md @@ -0,0 +1,45 @@ +--- +type: trekbrief +brief_version: "2.1" +created: 2026-05-14 +task: "Fixture: high-effort all phases (v5.1.1 runtime test)" +slug: brief-effort-high +project_dir: .claude/projects/2026-05-14-brief-effort-high/ +research_topics: 0 +research_status: complete +auto_research: false +interview_turns: 4 +source: fixture +phase_signals: + - phase: research + effort: high + model: opus + - phase: plan + effort: high + model: opus + - phase: execute + effort: high + model: opus + - phase: review + effort: high + model: opus +--- + +# Task: High-effort fixture + +## Intent + +Test fixture for v5.1.1 runtime resolver tests — all 4 phases at the +high effort tier with explicit opus model overrides. Mirrors the +production-grade premium-profile scenario. + +## Goal + +Resolver returns `{effort: 'high', model: 'opus'}` for each of the 4 +PHASE_SIGNAL_PHASES. + +## Success Criteria + +- Validator passes. +- resolvePhaseSignal(fm, phase).effort === 'high' for all 4 phases. +- resolvePhaseSignal(fm, phase).model === 'opus' for all 4 phases. diff --git a/plugins/voyage/tests/fixtures/brief-effort-low.md b/plugins/voyage/tests/fixtures/brief-effort-low.md new file mode 100644 index 0000000..40b4f93 --- /dev/null +++ b/plugins/voyage/tests/fixtures/brief-effort-low.md @@ -0,0 +1,43 @@ +--- +type: trekbrief +brief_version: "2.1" +created: 2026-05-14 +task: "Fixture: low-effort all phases (v5.1.1 runtime test)" +slug: brief-effort-low +project_dir: .claude/projects/2026-05-14-brief-effort-low/ +research_topics: 0 +research_status: complete +auto_research: false +interview_turns: 4 +source: fixture +phase_signals: + - phase: research + effort: low + model: sonnet + - phase: plan + effort: low + model: sonnet + - phase: execute + effort: low + model: sonnet + - phase: review + effort: low + model: sonnet +--- + +# Task: Low-effort fixture + +## Intent + +Test fixture for v5.1.1 runtime resolver tests — all 4 phases at the lowest +effort tier with explicit sonnet model overrides. + +## Goal + +Resolver returns `{effort: 'low', model: 'sonnet'}` for each of the 4 +PHASE_SIGNAL_PHASES. + +## Success Criteria + +- Validator passes. +- resolvePhaseSignal(fm, phase) is non-null for all 4 phases. diff --git a/plugins/voyage/tests/fixtures/brief-effort-standard.md b/plugins/voyage/tests/fixtures/brief-effort-standard.md new file mode 100644 index 0000000..f0bb3dd --- /dev/null +++ b/plugins/voyage/tests/fixtures/brief-effort-standard.md @@ -0,0 +1,42 @@ +--- +type: trekbrief +brief_version: "2.1" +created: 2026-05-14 +task: "Fixture: standard-effort all phases, no model (v5.1.1 runtime test)" +slug: brief-effort-standard +project_dir: .claude/projects/2026-05-14-brief-effort-standard/ +research_topics: 0 +research_status: complete +auto_research: false +interview_turns: 4 +source: fixture +phase_signals: + - phase: research + effort: standard + - phase: plan + effort: standard + - phase: execute + effort: standard + - phase: review + effort: standard +--- + +# Task: Standard-effort fixture (no model override) + +## Intent + +Test fixture for v5.1.1 runtime resolver tests — all 4 phases at the +standard tier WITHOUT explicit model fields. This is the operator-skipped +model path that should fall through to the profile. + +## Goal + +Resolver returns `{effort: 'standard', model: undefined}` for each of the 4 +PHASE_SIGNAL_PHASES. The orchestrator-model path then falls through to the +active profile's phase_models. + +## Success Criteria + +- Validator passes. +- resolvePhaseSignal(fm, phase).model is undefined. +- resolvePhaseSignal(fm, phase).effort is 'standard'. diff --git a/plugins/voyage/tests/fixtures/brief-v21-no-signals.md b/plugins/voyage/tests/fixtures/brief-v21-no-signals.md new file mode 100644 index 0000000..d705406 --- /dev/null +++ b/plugins/voyage/tests/fixtures/brief-v21-no-signals.md @@ -0,0 +1,32 @@ +--- +type: trekbrief +brief_version: "2.1" +created: 2026-05-14 +task: "Fixture: v5.1 brief WITHOUT phase_signals or partial (falsification target)" +slug: brief-v21-no-signals +project_dir: .claude/projects/2026-05-14-brief-v21-no-signals/ +research_topics: 0 +research_status: complete +auto_research: false +interview_turns: 4 +source: fixture +--- + +# Task: brief_version 2.1 without phase_signals + +## Intent + +Falsification fixture for the v5.1 sequencing gate. The brief declares +`brief_version: "2.1"` but omits BOTH `phase_signals` AND +`phase_signals_partial: true`. The brief-validator MUST emit +`BRIEF_V51_MISSING_SIGNALS` for this file — the runtime test for the +sequencing gate asserts the error code fires. + +## Goal + +Validate that brief-validator catches the missing-signals scenario. + +## Success Criteria + +- brief-validator returns valid: false. +- errors contains BRIEF_V51_MISSING_SIGNALS. From 4c85a2c22bb2fe96ecaa8f25fa46f071764d2ed1 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:36:10 +0200 Subject: [PATCH 15/58] fix(voyage): coerce brief_version to string + quote template + update doc pin (closes #8 #11) v5.1.0 shipped with an unquoted brief_version: 2.1 in trekbrief-template.md. parseScalar coerced it to Number 2.1, and the sequencing gate guarded on typeof === 'string', silently bypassing BRIEF_V51_MISSING_SIGNALS. Three-part atomic fix: - brief-validator.mjs:87+149 now accepts both string and number forms via String(fm.brief_version) coercion. - trekbrief-template.md quotes the value so new briefs parse as String. - doc-consistency.test.mjs pins the QUOTED form going forward. Three regression tests added in brief-validator.test.mjs. --- .../voyage/lib/validators/brief-validator.mjs | 12 ++++--- .../voyage/templates/trekbrief-template.md | 2 +- .../voyage/tests/lib/doc-consistency.test.mjs | 6 ++-- .../tests/validators/brief-validator.test.mjs | 32 +++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/plugins/voyage/lib/validators/brief-validator.mjs b/plugins/voyage/lib/validators/brief-validator.mjs index 293ced9..de22278 100644 --- a/plugins/voyage/lib/validators/brief-validator.mjs +++ b/plugins/voyage/lib/validators/brief-validator.mjs @@ -84,8 +84,12 @@ export function validateBriefContent(text, opts = {}) { } } // Sequencing gate: brief_version ≥ 2.1 requires phase_signals OR phase_signals_partial. - if (typeof fm.brief_version === 'string') { - const vm = fm.brief_version.match(/^(\d+)\.(\d+)$/); + // Coerce to String so the gate fires regardless of whether YAML parsed the value as + // a string ("2.1") or a number (2.1). v5.1.0 shipped with an unquoted-2.1 template + // that silently bypassed this gate — fix locked in by quoting the template AND + // accepting both shapes here as defense-in-depth (v5.1.1, finding 3c834097/df1435a2). + if (typeof fm.brief_version === 'string' || typeof fm.brief_version === 'number') { + const vm = String(fm.brief_version).match(/^(\d+)\.(\d+)$/); if (vm) { const major = Number(vm[1]); const minor = Number(vm[2]); @@ -146,8 +150,8 @@ export function validateBriefContent(text, opts = {}) { } } - if (typeof fm.brief_version === 'string') { - const m = fm.brief_version.match(/^(\d+)\.(\d+)$/); + if (typeof fm.brief_version === 'string' || typeof fm.brief_version === 'number') { + const m = String(fm.brief_version).match(/^(\d+)\.(\d+)$/); if (!m) { warnings.push(issue('BRIEF_VERSION_FORMAT', `brief_version "${fm.brief_version}" not in N.M form`)); } diff --git a/plugins/voyage/templates/trekbrief-template.md b/plugins/voyage/templates/trekbrief-template.md index ff72ac4..e0c2232 100644 --- a/plugins/voyage/templates/trekbrief-template.md +++ b/plugins/voyage/templates/trekbrief-template.md @@ -1,6 +1,6 @@ --- type: trekbrief -brief_version: 2.1 +brief_version: "2.1" created: {YYYY-MM-DD} task: "{one-line task description}" slug: {slug} diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index 717ee29..b704a52 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -554,10 +554,10 @@ test('operational files no longer reference trekrevise (v5.0.0 removal)', () => // --- v5.1 — phase_signals + brief_version 2.1 --- -test('v5.1 — templates/trekbrief-template.md declares brief_version: 2.1', () => { +test('v5.1 — templates/trekbrief-template.md declares brief_version: "2.1" (quoted)', () => { const t = read('templates/trekbrief-template.md'); - assert.match(t, /^brief_version: 2\.1$/m, - 'trekbrief-template.md must declare brief_version: 2.1 at top of frontmatter'); + assert.match(t, /^brief_version: "2\.1"$/m, + 'trekbrief-template.md must declare brief_version: "2.1" (quoted) — unquoted parses as Number and bypasses sequencing gate'); }); test('v5.1 — templates/trekbrief-template.md contains phase_signals: block', () => { diff --git a/plugins/voyage/tests/validators/brief-validator.test.mjs b/plugins/voyage/tests/validators/brief-validator.test.mjs index a9fd185..69e250f 100644 --- a/plugins/voyage/tests/validators/brief-validator.test.mjs +++ b/plugins/voyage/tests/validators/brief-validator.test.mjs @@ -218,3 +218,35 @@ test('validateBrief — phase_signals with unknown phase rejected', () => { assert.equal(r.valid, false); assert.ok(r.errors.find(e => e.code === 'BRIEF_INVALID_PHASE_SIGNAL_PHASE')); }); + +// --- v5.1.1 regression: YAML-number bypass closed --- +// Findings 3c834097 + df1435a2: v5.1.0 shipped with an unquoted `brief_version: 2.1` +// template. parseScalar coerces unquoted "2.1" to Number 2.1, and the original gate +// guarded `typeof === 'string'`, silently bypassing the sequencing check. v5.1.1 +// coerces via String() so both shapes trigger the gate. + +test('validateBrief — v5.1.1: UNQUOTED brief_version 2.1 without signals triggers gate', () => { + const t = GOOD_BRIEF.replace('brief_version: "2.0"', 'brief_version: 2.1'); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, false); + assert.ok( + r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'), + `gate must fire for unquoted brief_version: 2.1 (YAML Number); errors=${JSON.stringify(r.errors)}`, + ); +}); + +test('validateBrief — v5.1.1: QUOTED brief_version "2.1" without signals triggers gate (regression guard)', () => { + const t = GOOD_BRIEF.replace('brief_version: "2.0"', 'brief_version: "2.1"'); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, false); + assert.ok(r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS')); +}); + +test('validateBrief — v5.1.1: UNQUOTED brief_version 2.1 WITH phase_signals is valid (positive case)', () => { + const t = GOOD_BRIEF + .replace('brief_version: "2.0"', 'brief_version: 2.1') + .replace('source: interview\n', `source: interview\n${SIGNALS_BLOCK}`); + const r = validateBriefContent(t, { strict: true }); + assert.equal(r.valid, true, JSON.stringify(r.errors)); + assert.ok(!r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS')); +}); From 48e092d2bcfea7cfbbb955c6deb307d4ea7f7470 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:36:57 +0200 Subject: [PATCH 16/58] test(voyage): add profile-resolver non-interference tests (closes #4 SC5) --- .../tests/lib/profile-resolver.test.mjs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 plugins/voyage/tests/lib/profile-resolver.test.mjs diff --git a/plugins/voyage/tests/lib/profile-resolver.test.mjs b/plugins/voyage/tests/lib/profile-resolver.test.mjs new file mode 100644 index 0000000..4eef940 --- /dev/null +++ b/plugins/voyage/tests/lib/profile-resolver.test.mjs @@ -0,0 +1,62 @@ +// tests/lib/profile-resolver.test.mjs +// v5.1.1 SC5 — non-interference cases for resolvePhaseModel(). +// Verifies the new highest-priority lookup step (brief.phase_signals[phase].model) +// wins over --profile flag and VOYAGE_PROFILE env; falls through cleanly when +// no brief signal is present. + +import { test } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { resolvePhaseModel } from '../../lib/profiles/resolver.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(__dirname, '..', '..'); +const FIXTURE = (name) => join(REPO_ROOT, 'tests', 'fixtures', name); + +test('resolvePhaseModel — Case 1: brief signal wins over VOYAGE_PROFILE env', () => { + // brief-effort-low.md pins all 4 phases to model: sonnet. + // env says premium (would normally select opus). Brief must win. + const r = resolvePhaseModel('research', FIXTURE('brief-effort-low.md'), [], { VOYAGE_PROFILE: 'premium' }); + assert.equal(r.model, 'sonnet', `brief signal should beat env; got ${JSON.stringify(r)}`); + assert.equal(r.source, 'brief-signal'); +}); + +test('resolvePhaseModel — Case 2: brief signal wins over --profile flag', () => { + // brief-effort-high.md pins all 4 phases to model: opus. + // flag says economy (would normally select sonnet). Brief must win. + const r = resolvePhaseModel('execute', FIXTURE('brief-effort-high.md'), ['--profile', 'economy'], {}); + assert.equal(r.model, 'opus', `brief signal should beat flag; got ${JSON.stringify(r)}`); + assert.equal(r.source, 'brief-signal'); +}); + +test('resolvePhaseModel — Case 3: no phase_signals → fallthrough to --profile flag', () => { + // brief-without-phase-signals fixture lacks phase_signals entirely. + // --profile balanced is set. Should return balanced.phase_models.plan (= opus per yaml). + const r = resolvePhaseModel('plan', FIXTURE('brief-without-phase-signals.md'), ['--profile', 'balanced'], {}); + assert.equal(r.model, 'opus', `balanced.plan should be opus; got ${JSON.stringify(r)}`); + assert.equal(r.source, 'flag'); +}); + +test('resolvePhaseModel — Case 4: phase not in PHASE_SIGNAL_PHASES falls through gracefully', () => { + // brief-effort-high.md has signals for the 4 supported phases. + // Asking for 'continue' (not in PHASE_SIGNAL_PHASES) must fall through. + // --profile premium is set, so continue resolves to premium.phase_models.continue (= opus). + const r = resolvePhaseModel('continue', FIXTURE('brief-effort-high.md'), ['--profile', 'premium'], {}); + assert.equal(r.model, 'opus', `premium.continue should be opus; got ${JSON.stringify(r)}`); + assert.ok(r.source !== 'brief-signal', 'continue must not resolve via brief-signal'); +}); + +test('resolvePhaseModel — Case 5 (defensive): missing brief file falls through cleanly', () => { + // Non-existent path. Must not throw; must fall through to flag/env/default. + const r = resolvePhaseModel('plan', '/nonexistent/brief.md', ['--profile', 'economy'], {}); + assert.equal(r.model, 'sonnet', 'economy.plan should be sonnet on fallthrough'); + assert.equal(r.source, 'flag'); +}); + +test('resolvePhaseModel — Case 6 (defensive): null briefPath falls through to default', () => { + // null briefPath, no flag, no env → default = premium. + const r = resolvePhaseModel('plan', null, [], {}); + assert.equal(r.model, 'opus', 'premium.plan default = opus'); + assert.equal(r.source, 'default'); +}); From ce162e6c4119fe219c2d4ebaf8d3534df40b68ad Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:38:51 +0200 Subject: [PATCH 17/58] feat(voyage): add resolvePhaseModel for brief-signal orchestrator override (closes #9 part A) --- plugins/voyage/lib/profiles/resolver.mjs | 107 ++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/plugins/voyage/lib/profiles/resolver.mjs b/plugins/voyage/lib/profiles/resolver.mjs index 45354e0..3d17bc1 100644 --- a/plugins/voyage/lib/profiles/resolver.mjs +++ b/plugins/voyage/lib/profiles/resolver.mjs @@ -1,5 +1,5 @@ // lib/profiles/resolver.mjs -// Profile resolution layer (v4.1 SC #5-#9). +// Profile resolution layer (v4.1 SC #5-#9; v5.1.1 brief-signal extension). // // Locked interface contract (per brief Preferences): // loadProfile(name) → ProfileObject @@ -22,6 +22,16 @@ // validateProfileFile(path) → Result // - Thin wrapper around validateProfile from profile-validator.mjs. // +// resolvePhaseModel(phase, briefPath, argv, env) +// → {model, source} (v5.1.1 ADDITIVE) +// - Highest-priority lookup step: brief.phase_signals[phase].model (source='brief-signal'). +// - Falls through via resolveProfile (flag → env → default) when brief signal absent. +// - Never throws; ENOENT/parse failures degrade silently to the next step. +// - phase ∈ {brief, continue} (outside PHASE_SIGNAL_PHASES) always falls through. +// - Controls ORCHESTRATOR model only. Sub-agents read agents/*.md frontmatter +// directly; commands must inject {resolved model} into Agent-tool spawn sites +// for sub-agents to honor brief signals. See feedback_voyage_opus_always.md. +// // Custom.yaml lookup order: /voyage-profiles/.yaml > ~/.claude/voyage-profiles/.yaml // Both attempted paths included in error message on miss (HIGH-risk-mitigering). @@ -31,6 +41,7 @@ import { fileURLToPath } from 'node:url'; import { homedir } from 'node:os'; import { parseDocument } from '../util/frontmatter.mjs'; import { validateProfile } from '../validators/profile-validator.mjs'; +import { resolvePhaseSignal } from './phase-signal-resolver.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const BUILTIN_PROFILES_DIR = __dirname; // lib/profiles/ @@ -201,3 +212,97 @@ export function resolveTrekcontinueProfile(planPath, argv, opts = {}) { export function validateProfileFile(path, opts = {}) { return validateProfile(path, opts); } + +/** + * Resolve the model for a specific pipeline phase, applying brief-signal override + * as the new highest-priority lookup step (v5.1.1). + * + * @param {string} phase One of brief|research|plan|execute|review|continue + * @param {string|null} briefPath Absolute or repo-relative path to brief.md, or null + * @param {string[]|object} argv Full process.argv array OR parsed flags object + * @param {object} [env] Environment-variable record (defaults to process.env) + * @returns {{model: string, source: 'brief-signal'|'flag'|'env'|'default'}} + * + * Error handling contract: + * - Never throws. Any failure (ENOENT on briefPath, malformed YAML, missing + * phase_signals entry, missing model field) silently falls through. + * - Treats unreadable briefPath as equivalent to briefPath=null. + * - Returns the fallthrough resolution when phase ∈ {brief, continue} (not in + * PHASE_SIGNAL_PHASES). + * + * Controls ORCHESTRATOR model only. Sub-agents read agents/*.md frontmatter + * directly; commands must inject {resolved model} at Agent-tool spawn sites. + */ +export function resolvePhaseModel(phase, briefPath, argv, env = process.env) { + // Step 1: brief-signal lookup + if (typeof briefPath === 'string' && briefPath.length > 0 && existsSync(briefPath)) { + let fm = null; + try { + const text = readFileSync(briefPath, 'utf-8'); + const doc = parseDocument(text); + if (doc && doc.valid) fm = doc.parsed && doc.parsed.frontmatter; + } catch { + // swallow — degrade gracefully to fallthrough + } + if (fm) { + const signal = resolvePhaseSignal(fm, phase); + if (signal && typeof signal.model === 'string' && signal.model.length > 0) { + return { model: signal.model, source: 'brief-signal' }; + } + } + } + + // Step 2+3+4: fall through via existing resolveProfile chain + // (flag → env → default), then index into profile.phase_models[phase]. + // Normalize argv: resolveProfile expects flags-object form. When called with + // a raw process.argv-style array, extract --profile here. + let normalizedFlags = argv; + if (Array.isArray(argv)) { + const idx = argv.indexOf('--profile'); + normalizedFlags = (idx >= 0 && idx + 1 < argv.length) + ? { '--profile': argv[idx + 1] } + : {}; + } + const { profile, profile_source } = resolveProfile(normalizedFlags, env); + let phaseModels = {}; + try { + const p = loadProfile(profile); + phaseModels = p.phase_models || {}; + } catch { + // Unknown profile → fall back to premium + try { + const p = loadProfile('premium'); + phaseModels = p.phase_models || {}; + } catch { + phaseModels = {}; + } + } + const model = phaseModels[phase] || 'opus'; + return { model, source: profile_source }; +} + +// CLI shim — invoked by commands/trek*.md via Bash. +// Usage: node lib/profiles/resolver.mjs --resolve-phase-model --phase plan --brief-path foo.md --json +if (import.meta.url === `file://${process.argv[1]}`) { + const args = process.argv.slice(2); + if (args.includes('--resolve-phase-model')) { + const getArg = (name) => { + const i = args.indexOf(name); + return i >= 0 && i + 1 < args.length ? args[i + 1] : null; + }; + const phase = getArg('--phase'); + const briefPath = getArg('--brief-path'); + if (!phase) { + process.stderr.write('Usage: resolver.mjs --resolve-phase-model --phase [--brief-path ] [--json]\n'); + process.exit(2); + } + const r = resolvePhaseModel(phase, briefPath || null, args, process.env); + if (args.includes('--json')) { + process.stdout.write(JSON.stringify(r) + '\n'); + } else { + process.stdout.write(`model=${r.model} source=${r.source}\n`); + } + process.exit(0); + } + // Otherwise no-op (the module is primarily imported, not run directly). +} From 1f056752c17e18e898f4b66f2f88815376499708 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:43:16 +0200 Subject: [PATCH 18/58] feat(voyage): wire phase-signal-resolver into 4 downstream commands (closes #9 wiring) --- plugins/voyage/commands/trekexecute.md | 13 +++++++---- plugins/voyage/commands/trekplan.md | 24 +++++++++++++++----- plugins/voyage/commands/trekresearch.md | 29 ++++++++++++++++++++----- plugins/voyage/commands/trekreview.md | 23 +++++++++++++++----- 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/plugins/voyage/commands/trekexecute.md b/plugins/voyage/commands/trekexecute.md index 888ba5f..5384214 100644 --- a/plugins/voyage/commands/trekexecute.md +++ b/plugins/voyage/commands/trekexecute.md @@ -1531,14 +1531,19 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. There is no helper module — composition is documented prose in -each downstream command. +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`). For `/trekexecute` specifically: `effort == 'low'` activates `--gates open` + sequential-only execution (no worktree-isolated parallel waves — runs all sessions in a single foreground loop). `effort == 'standard'` (or -absent) → no change (default execution strategy applies). High-effort -behavior is deferred to v5.1.1 per brief Non-Goal. +absent) → no change (default execution strategy applies). `effort == 'high'` +activates the high-effort behavior documented under `### High-effort +behavior (v5.1.1)` below. ### Sequencing gate surface diff --git a/plugins/voyage/commands/trekplan.md b/plugins/voyage/commands/trekplan.md index 68d3d1b..f013d5d 100644 --- a/plugins/voyage/commands/trekplan.md +++ b/plugins/voyage/commands/trekplan.md @@ -66,6 +66,11 @@ Parse `$ARGUMENTS` for mode flags. Order of precedence: # Brief schema sanity check (frontmatter + state machine, soft on body sections) node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{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 + # Research briefs (if any) — drift-warn only, none of these block the run [ -d "{dir}/research" ] && \ node ${CLAUDE_PLUGIN_ROOT}/lib/validators/research-validator.mjs --soft --dir "{dir}/research" --json @@ -906,14 +911,19 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. There is no helper module — composition is documented prose in -each downstream command. +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`). For `/trekplan` specifically: `effort == 'low'` activates the existing `--quick`-equivalent code-path (skip Phase 5 agent swarm — plan directly without exploration agents). `effort == 'standard'` (or absent) → no -change. High-effort behavior is deferred to v5.1.1 per brief Non-Goal -("No complete per-phase effort dictionary"). +change. `effort == 'high'` activates the high-effort behavior documented +under `### High-effort behavior (v5.1.1)` below. ### Sequencing gate surface @@ -933,8 +943,10 @@ validator-only; this surface just makes the friendly hint readable. 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**: Sonnet for all agents (exploration, deep-dives, research, critics). - Opus only runs in the main thread for synthesis and planning. +- **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. - **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/plugins/voyage/commands/trekresearch.md b/plugins/voyage/commands/trekresearch.md index ea1d84a..5b43849 100644 --- a/plugins/voyage/commands/trekresearch.md +++ b/plugins/voyage/commands/trekresearch.md @@ -54,6 +54,16 @@ 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: + + ```bash + node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{dir}/brief.md" + node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs --brief "{dir}/brief.md" --phase research --json + ``` + 6. `--gates` — autonomy control. When present, set `gates_mode = true`. The research command will pause after each topic completes ("Topic N complete. Proceed to topic N+1? (yes/no)"). Default `gates_mode = false` @@ -447,13 +457,19 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. There is no helper module — composition is documented prose in -each downstream command. +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`). For `/trekresearch` specifically: `effort == 'low'` activates the existing `--quick`-equivalent code-path (inline research, no agent swarm). -`effort == 'standard'` (or absent) → no change. High-effort behavior is -deferred to v5.1.1 per brief Non-Goal. +`effort == 'standard'` (or absent) → no change. `effort == 'high'` +activates the high-effort behavior documented under `### High-effort +behavior (v5.1.1)` below. ### Sequencing gate surface @@ -474,7 +490,10 @@ commands surface, don't re-enforce. 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:** Sonnet for all sub-agents. Opus only in the main command/orchestrator. +- **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. - **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/plugins/voyage/commands/trekreview.md b/plugins/voyage/commands/trekreview.md index 73bdcbb..5a72a9b 100644 --- a/plugins/voyage/commands/trekreview.md +++ b/plugins/voyage/commands/trekreview.md @@ -83,6 +83,11 @@ 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 +# 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 ``` Read the JSON output. If `valid: false` AND any error has code @@ -369,13 +374,19 @@ model_for_phase = brief.phase_signals[]?.model ?? profile.phase_models[ ``` The brief signal wins per-phase when present; the profile fills any -gaps. There is no helper module — composition is documented prose in -each downstream command. +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`). For `/trekreview` specifically: `effort == 'low'` activates the existing `--quick`-equivalent code-path (skip the brief-conformance reviewer; run correctness-only). `effort == 'standard'` (or absent) → no change. -High-effort behavior is deferred to v5.1.1 per brief Non-Goal. +`effort == 'high'` activates the high-effort behavior documented under +`### High-effort behavior (v5.1.1)` below. ### Sequencing gate surface @@ -407,8 +418,10 @@ readable. `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.** Sonnet for all sub-agents (reviewers + coordinator). Opus - only runs in the main /trekreview command thread. +- **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. - **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. From 1bb6a9d63b28c4ed34f4c40685ea9f3082cff378 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:43:45 +0200 Subject: [PATCH 19/58] fix(voyage): require brief-validator gate in trekresearch + trekexecute (closes #12) --- plugins/voyage/commands/trekexecute.md | 2 +- plugins/voyage/commands/trekresearch.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/voyage/commands/trekexecute.md b/plugins/voyage/commands/trekexecute.md index 5384214..b9804c0 100644 --- a/plugins/voyage/commands/trekexecute.md +++ b/plugins/voyage/commands/trekexecute.md @@ -1547,7 +1547,7 @@ behavior (v5.1.1)` below. ### Sequencing gate surface -When `/trekexecute --project ` is invoked, optionally run +When `/trekexecute --project ` is invoked, ALWAYS run `brief-validator.mjs --soft --json` against `{dir}/brief.md`. If `BRIEF_V51_MISSING_SIGNALS` appears in `errors` (brief_version ≥ 2.1 without `phase_signals` or `phase_signals_partial: true`), halt with: diff --git a/plugins/voyage/commands/trekresearch.md b/plugins/voyage/commands/trekresearch.md index 5b43849..39a1dc7 100644 --- a/plugins/voyage/commands/trekresearch.md +++ b/plugins/voyage/commands/trekresearch.md @@ -474,7 +474,7 @@ behavior (v5.1.1)` below. ### Sequencing gate surface When `/trekresearch --project ` is invoked and `{dir}/brief.md` -exists, optionally run `brief-validator.mjs --soft --json` against it. +exists, ALWAYS run `brief-validator.mjs --soft --json` against it. If `BRIEF_V51_MISSING_SIGNALS` appears in `errors` (brief_version ≥ 2.1 without `phase_signals` or `phase_signals_partial: true`), halt with: `Brief is brief_version 2.1 but does not carry phase_signals — re-run From 94c696fee6f63f8de0ca6909387415acb1778a15 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:44:38 +0200 Subject: [PATCH 20/58] test(voyage): refactor trekbrief command test to runtime SC1 (closes #1) --- .../voyage/tests/commands/trekbrief.test.mjs | 94 ++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/plugins/voyage/tests/commands/trekbrief.test.mjs b/plugins/voyage/tests/commands/trekbrief.test.mjs index 3db8030..0788f67 100644 --- a/plugins/voyage/tests/commands/trekbrief.test.mjs +++ b/plugins/voyage/tests/commands/trekbrief.test.mjs @@ -1,23 +1,43 @@ // tests/commands/trekbrief.test.mjs -// v5.1 — Pattern D prose-pattern regression tests for /trekbrief Phase 3.5. +// v5.1 prose-pin tests + v5.1.1 runtime SC1 tests. // -// Brief SC1 + SC2: end-of-brief effort dialog covering 4 downstream phases, -// with `phase_signals_partial` as the force-stop record. +// Pattern D prose-pins kept as doc-anchors for the .md file. Runtime tests +// added per finding 350853 (BLOCKER SC1) + a7f4f95a (MAJOR Plan Step 5 drift). +// +// SC1 re-interpretation (per plan Step 10 amendment): "asserts on 4 +// AskUserQuestion calls" → "asserts resolvePhaseSignal returns non-null for +// all 4 entries in PHASE_SIGNAL_PHASES when applied to a brief with a +// committed phase_signals block." See brief amendment for full rationale. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; 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 { parseDocument } from '../../lib/util/frontmatter.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const COMMAND_FILE = join(ROOT, 'commands', 'trekbrief.md'); +const FIXTURE = (name) => join(ROOT, 'tests', 'fixtures', name); function read() { return readFileSync(COMMAND_FILE, 'utf8'); } +function readFixture(name) { + return readFileSync(FIXTURE(name), 'utf8'); +} + +function frontmatterOf(text) { + const doc = parseDocument(text); + return doc.parsed && doc.parsed.frontmatter; +} + +// --- Pattern D prose-pins (doc-anchors) --- + test('trekbrief — Phase 3.5 heading is present', () => { const text = read(); assert.match(text, /^## Phase 3\.5 — Per-phase effort dialog$/m, @@ -40,3 +60,71 @@ test('trekbrief — Phase 3.5 documents phase_signals_partial force-stop', () => assert.ok(text.includes('phase_signals_partial'), 'phase_signals_partial not mentioned in /trekbrief command prose'); }); + +// --- v5.1.1 runtime SC1 tests --- + +test('trekbrief — SC1: resolvePhaseSignal returns non-null for all 4 phases on committed brief (brief-effort-low)', () => { + const fm = frontmatterOf(readFixture('brief-effort-low.md')); + for (const phase of PHASE_SIGNAL_PHASES) { + const r = resolvePhaseSignal(fm, phase); + assert.ok(r && typeof r === 'object', + `phase=${phase}: resolver must return non-null for committed brief; got ${JSON.stringify(r)}`); + assert.ok(typeof r.effort === 'string', + `phase=${phase}: resolver result must include effort`); + } +}); + +test('trekbrief — SC1: each of 4 phases has both effort AND model on full-signals fixture', () => { + const fm = frontmatterOf(readFixture('brief-with-phase-signals.md')); + for (const phase of PHASE_SIGNAL_PHASES) { + const r = resolvePhaseSignal(fm, phase); + assert.ok(r && typeof r === 'object', `phase=${phase}: must resolve`); + 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]`); + } + } +}); + +test('trekbrief — SC1: missing phase_signals + brief_version 2.1 triggers BRIEF_V51_MISSING_SIGNALS', () => { + const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true }); + assert.equal(r.valid, false); + assert.ok( + r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'), + `gate must fire; errors=${JSON.stringify(r.errors)}`, + ); +}); + +test('trekbrief — SC1: phase_signals_partial: true does NOT trigger the gate', () => { + const partial = `--- +type: trekbrief +brief_version: "2.1" +created: 2026-05-14 +task: "Partial brief" +slug: partial-brief +project_dir: .claude/projects/2026-05-14-partial-brief/ +research_topics: 0 +research_status: complete +auto_research: false +interview_turns: 2 +source: fixture +phase_signals_partial: true +--- + +# Task + +## Intent +Stop early. + +## Goal +Test partial mode. + +## Success Criteria +- gate does not fire. +`; + const r = validateBriefContent(partial, { strict: true }); + assert.equal(r.valid, true, `errors=${JSON.stringify(r.errors)}`); + assert.ok(!r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS')); +}); From 07ae1e30e90c57cd65e452bca47abce182810522 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 14 May 2026 21:46:11 +0200 Subject: [PATCH 21/58] test(voyage): refactor 4 downstream command tests to runtime SC4+SC7 (closes #2 #3 #6 #10) --- .../tests/commands/trekexecute.test.mjs | 47 ++++++++++++++++-- .../voyage/tests/commands/trekplan.test.mjs | 47 ++++++++++++++++-- .../tests/commands/trekresearch.test.mjs | 47 ++++++++++++++++-- .../voyage/tests/commands/trekreview.test.mjs | 48 +++++++++++++++++-- 4 files changed, 177 insertions(+), 12 deletions(-) diff --git a/plugins/voyage/tests/commands/trekexecute.test.mjs b/plugins/voyage/tests/commands/trekexecute.test.mjs index e848119..15a67c7 100644 --- a/plugins/voyage/tests/commands/trekexecute.test.mjs +++ b/plugins/voyage/tests/commands/trekexecute.test.mjs @@ -1,5 +1,5 @@ // tests/commands/trekexecute.test.mjs -// v5.1 — sequencing-gate surface + low-effort prose check for /trekexecute. +// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekexecute. // Plan Assumption 2 locks low-effort to --gates open + sequential-only. import { test } from 'node:test'; @@ -7,15 +7,24 @@ import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs'; +import { validateBriefContent } from '../../lib/validators/brief-validator.mjs'; +import { parseDocument } from '../../lib/util/frontmatter.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const COMMAND_FILE = join(ROOT, 'commands', 'trekexecute.md'); +const PHASE = 'execute'; -function read() { - return readFileSync(COMMAND_FILE, 'utf8'); +function read() { return readFileSync(COMMAND_FILE, 'utf8'); } +function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); } +function frontmatterOf(text) { + const doc = parseDocument(text); + return doc.parsed && doc.parsed.frontmatter; } +// --- Pattern D prose-pins --- + test('trekexecute — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { const text = read(); assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), @@ -32,3 +41,35 @@ test('trekexecute — low-effort path references --gates open + sequential', () assert.match(section, /--gates open/, 'Low-effort path must mention --gates open'); assert.match(section, /sequential/, 'Low-effort path must mention sequential-only execution'); }); + +// --- v5.1.1 runtime SC4 + SC7 --- + +test('trekexecute — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => { + const fm = frontmatterOf(readFixture('brief-effort-low.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'low'); + assert.equal(r.model, 'sonnet'); +}); + +test('trekexecute — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => { + const fm = frontmatterOf(readFixture('brief-effort-standard.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'standard'); + assert.equal(r.model, undefined); +}); + +test('trekexecute — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => { + const fm = frontmatterOf(readFixture('brief-effort-high.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'high'); + assert.equal(r.model, 'opus'); +}); + +test('trekexecute — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => { + const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true }); + assert.equal(r.valid, false); + assert.ok( + r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'), + `sequencing gate must fire; errors=${JSON.stringify(r.errors)}`, + ); +}); diff --git a/plugins/voyage/tests/commands/trekplan.test.mjs b/plugins/voyage/tests/commands/trekplan.test.mjs index 901936d..1ab3ee0 100644 --- a/plugins/voyage/tests/commands/trekplan.test.mjs +++ b/plugins/voyage/tests/commands/trekplan.test.mjs @@ -1,20 +1,29 @@ // tests/commands/trekplan.test.mjs -// v5.1 — sequencing-gate surface + low-effort prose check for /trekplan. +// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekplan. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs'; +import { validateBriefContent } from '../../lib/validators/brief-validator.mjs'; +import { parseDocument } from '../../lib/util/frontmatter.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const COMMAND_FILE = join(ROOT, 'commands', 'trekplan.md'); +const PHASE = 'plan'; -function read() { - return readFileSync(COMMAND_FILE, 'utf8'); +function read() { return readFileSync(COMMAND_FILE, 'utf8'); } +function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); } +function frontmatterOf(text) { + const doc = parseDocument(text); + return doc.parsed && doc.parsed.frontmatter; } +// --- Pattern D prose-pins (kept) --- + test('trekplan — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { const text = read(); assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), @@ -30,3 +39,35 @@ test('trekplan — low-effort path references --quick equivalent', () => { const section = text.slice(compIdx, compIdx + 2000); assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent'); }); + +// --- v5.1.1 runtime SC4 + SC7 tests --- + +test('trekplan — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => { + const fm = frontmatterOf(readFixture('brief-effort-low.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'low'); + assert.equal(r.model, 'sonnet'); +}); + +test('trekplan — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => { + const fm = frontmatterOf(readFixture('brief-effort-standard.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'standard'); + assert.equal(r.model, undefined); +}); + +test('trekplan — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => { + const fm = frontmatterOf(readFixture('brief-effort-high.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'high'); + assert.equal(r.model, 'opus'); +}); + +test('trekplan — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => { + const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true }); + assert.equal(r.valid, false); + assert.ok( + r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'), + `sequencing gate must fire; errors=${JSON.stringify(r.errors)}`, + ); +}); diff --git a/plugins/voyage/tests/commands/trekresearch.test.mjs b/plugins/voyage/tests/commands/trekresearch.test.mjs index 4fd2a8c..0e10351 100644 --- a/plugins/voyage/tests/commands/trekresearch.test.mjs +++ b/plugins/voyage/tests/commands/trekresearch.test.mjs @@ -1,20 +1,29 @@ // tests/commands/trekresearch.test.mjs -// v5.1 — sequencing-gate surface + low-effort prose check for /trekresearch. +// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekresearch. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs'; +import { validateBriefContent } from '../../lib/validators/brief-validator.mjs'; +import { parseDocument } from '../../lib/util/frontmatter.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const COMMAND_FILE = join(ROOT, 'commands', 'trekresearch.md'); +const PHASE = 'research'; -function read() { - return readFileSync(COMMAND_FILE, 'utf8'); +function read() { return readFileSync(COMMAND_FILE, 'utf8'); } +function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); } +function frontmatterOf(text) { + const doc = parseDocument(text); + return doc.parsed && doc.parsed.frontmatter; } +// --- Pattern D prose-pins --- + test('trekresearch — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { const text = read(); assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), @@ -30,3 +39,35 @@ test('trekresearch — low-effort path references --quick equivalent', () => { const section = text.slice(compIdx, compIdx + 2000); assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent'); }); + +// --- v5.1.1 runtime SC4 + SC7 --- + +test('trekresearch — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => { + const fm = frontmatterOf(readFixture('brief-effort-low.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'low'); + assert.equal(r.model, 'sonnet'); +}); + +test('trekresearch — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => { + const fm = frontmatterOf(readFixture('brief-effort-standard.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'standard'); + assert.equal(r.model, undefined); +}); + +test('trekresearch — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => { + const fm = frontmatterOf(readFixture('brief-effort-high.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'high'); + assert.equal(r.model, 'opus'); +}); + +test('trekresearch — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => { + const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true }); + assert.equal(r.valid, false); + assert.ok( + r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'), + `sequencing gate must fire; errors=${JSON.stringify(r.errors)}`, + ); +}); diff --git a/plugins/voyage/tests/commands/trekreview.test.mjs b/plugins/voyage/tests/commands/trekreview.test.mjs index 9d1a53c..e66ef00 100644 --- a/plugins/voyage/tests/commands/trekreview.test.mjs +++ b/plugins/voyage/tests/commands/trekreview.test.mjs @@ -1,20 +1,29 @@ // tests/commands/trekreview.test.mjs -// v5.1 — sequencing-gate surface + low-effort prose check for /trekreview. +// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekreview. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs'; +import { validateBriefContent } from '../../lib/validators/brief-validator.mjs'; +import { parseDocument } from '../../lib/util/frontmatter.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const COMMAND_FILE = join(ROOT, 'commands', 'trekreview.md'); +const PHASE = 'review'; -function read() { - return readFileSync(COMMAND_FILE, 'utf8'); +function read() { return readFileSync(COMMAND_FILE, 'utf8'); } +function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); } +function frontmatterOf(text) { + const doc = parseDocument(text); + return doc.parsed && doc.parsed.frontmatter; } +// --- Pattern D prose-pins --- + test('trekreview — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => { const text = read(); assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'), @@ -30,3 +39,36 @@ test('trekreview — low-effort path references --quick equivalent', () => { const section = text.slice(compIdx, compIdx + 2000); assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent'); }); + +// --- v5.1.1 runtime SC4 + SC7 --- + +test('trekreview — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => { + const fm = frontmatterOf(readFixture('brief-effort-low.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'low'); + assert.equal(r.model, 'sonnet'); +}); + +test('trekreview — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => { + const fm = frontmatterOf(readFixture('brief-effort-standard.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'standard'); + assert.equal(r.model, undefined); +}); + +test('trekreview — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => { + const fm = frontmatterOf(readFixture('brief-effort-high.md')); + const r = resolvePhaseSignal(fm, PHASE); + assert.equal(r.effort, 'high'); + assert.equal(r.model, 'opus'); +}); + +test('trekreview — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => { + // Falsification via brief-v21-no-signals fixture: validator must catch missing signals. + const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true }); + assert.equal(r.valid, false); + assert.ok( + r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'), + `sequencing gate must fire; errors=${JSON.stringify(r.errors)}`, + ); +}); From c1b7bad3899c5cfe9ff90663003609b018aa79a0 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 15 May 2026 16:07:37 +0200 Subject: [PATCH 22/58] feat(voyage): define high-effort behavior + amend brief Non-Goal/SC1 + coordinator normalization (Decision B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 6 / Step 10 — autonomy-gated. Operator confirmed: gemini-bridge substitution for plan-critic doubling AND SC1 amendment to resolver-invariant encoding (decisions.local.json recorded). - commands/trekplan.md: gemini-bridge plan-review Pass 2 on post-revision plan in high-effort mode (replaces fragile plan-critic doubling per risk-assessor). - commands/trekresearch.md: full swarm + contrarian-researcher + gemini-bridge always-on. - commands/trekreview.md: skip Pass 3 reasonableness + invoke coordinator normalization rule. - commands/trekexecute.md: gates_mode = closed (strict manifest-audit, main-merge pauses); flag override still wins. - agents/review-coordinator.md: Pass 3 high-effort normalization — substitute unknown rule_key with PLAN_EXECUTE_DRIFT, preserve original in original_rule_key. - .claude/projects/2026-05-13-trekflow-solo-lane/brief.md (gitignored, not committed): Non-Goal amendment locks low/high tiers; SC1 amendment authorizes resolver-invariant interpretation. - tests/lib/doc-consistency.test.mjs: +4 pins for the "### High-effort behavior (v5.1.1)" heading per command. Tests: 578 pass, 0 fail, 2 skipped (+4 from 574). Closes #7 (operator-gated decisions captured + coordinator normalization landed). Co-Authored-By: Claude Opus 4.7 --- plugins/voyage/agents/review-coordinator.md | 12 +++++++++ plugins/voyage/commands/trekexecute.md | 20 ++++++++++++++ plugins/voyage/commands/trekplan.md | 19 ++++++++++++++ plugins/voyage/commands/trekresearch.md | 18 +++++++++++++ plugins/voyage/commands/trekreview.md | 19 ++++++++++++++ .../voyage/tests/lib/doc-consistency.test.mjs | 26 +++++++++++++++++++ 6 files changed, 114 insertions(+) diff --git a/plugins/voyage/agents/review-coordinator.md b/plugins/voyage/agents/review-coordinator.md index c38570a..ed6df5d 100644 --- a/plugins/voyage/agents/review-coordinator.md +++ b/plugins/voyage/agents/review-coordinator.md @@ -112,6 +112,18 @@ Drop findings that fail ANY of these tests: In `quick` mode, skip this pass entirely. Note the skip in the Executive Summary so the reader knows reasonableness was not applied. +**High-effort normalization (v5.1.1):** When the review is invoked +under high-effort mode (`phase_signals[review].effort: high`), Pass 3 +reasonableness filtering is bypassed. To prevent unknown rule_keys +from polluting downstream plans, the coordinator MUST substitute any +rule_key not exported from `lib/review/rule-catalogue.mjs:RULE_KEYS` +with the literal string `PLAN_EXECUTE_DRIFT` (the most general drift +category from the 12-entry catalogue). The original rule_key is +preserved in the finding's `original_rule_key` field for diagnostic +purposes. This normalization happens BEFORE writing review.md, +ensuring all `rule_key` values in the final review match the +catalogue. + ### Pass 4 — Compute verdict Count findings by severity AFTER dedup and filtering. Verdict thresholds: diff --git a/plugins/voyage/commands/trekexecute.md b/plugins/voyage/commands/trekexecute.md index b9804c0..d6b8115 100644 --- a/plugins/voyage/commands/trekexecute.md +++ b/plugins/voyage/commands/trekexecute.md @@ -1555,6 +1555,26 @@ without `phase_signals` or `phase_signals_partial: true`), halt with: /trekbrief to commit them (Phase 3.5).` Enforcement is validator-only; commands surface, don't re-enforce. +### High-effort behavior (v5.1.1) + +When `phase_signal_result.effort == 'high'` for the `execute` phase, +set `gates_mode = 'closed'` automatically (equivalent to passing +`--gates closed` on the command line). All autonomy boundaries become +operator-stopping points: manifest-audit FAIL halts immediately, the +main-merge gate pauses for operator confirm, and Phase 7.5 +manifest-audit runs in its strictest form (fails on ANY mismatch, not +just deltas above threshold). Operator explicit `--gates` flag still +wins over the brief signal — if the operator passes `--gates open` on +the command line while `phase_signal_result.effort == 'high'`, the +flag takes precedence and the override is logged to +`${CLAUDE_PLUGIN_DATA}/trekexecute-stats.jsonl` as +`{event: 'gates_mode_flag_override', flag: 'open', signal_effort: 'high'}`. + +Standard effort (or absent): use `gates_mode = 'adaptive'` (the +default). Low effort: `gates_mode = 'open'` plus sequential-only +execution (no parallel waves) — existing `--quick`-equivalent +code-path. + ## Hard rules 1. **No AskUserQuestion for execution decisions.** All execution decisions come diff --git a/plugins/voyage/commands/trekplan.md b/plugins/voyage/commands/trekplan.md index f013d5d..b7e30ce 100644 --- a/plugins/voyage/commands/trekplan.md +++ b/plugins/voyage/commands/trekplan.md @@ -934,6 +934,25 @@ one-line message: `Brief is brief_version 2.1 but does not carry phase_signals — re-run /trekbrief to commit them (Phase 3.5).` Enforcement is validator-only; this surface just makes the friendly hint readable. +### High-effort behavior (v5.1.1) + +When `phase_signal_result.effort == 'high'` for the `plan` phase, after +Phase 9 (plan-critic + scope-guardian dedup pass) runs to completion on +the post-revision plan, run an ADDITIONAL `gemini-bridge` plan-review +pass on the post-revision plan. Surface its findings as a separate +`## Adversarial Pass 2 (gemini-bridge, v5.1.1 high-effort)` section +appended to plan.md before the trailing JSON block. + +Rationale (per risk-assessor finding + Decision B substitution +2026-05-14): the originally-considered "extra plan-critic-iterasjon" +risked a revision-loop because plan-critic dedup keys on +`(file, line, rule_key)` triplets and post-revision line numbers shift. +The gemini-bridge pass is independent (different agent, different +perspective) and does not re-tread the same dedup space — it surfaces +genuinely new findings rather than re-emitting closed ones. + +Standard and low effort: do NOT run the additional pass. + ## Hard rules - **Brief-driven**: Every plan decision must trace back to a section of the diff --git a/plugins/voyage/commands/trekresearch.md b/plugins/voyage/commands/trekresearch.md index 39a1dc7..f5a7169 100644 --- a/plugins/voyage/commands/trekresearch.md +++ b/plugins/voyage/commands/trekresearch.md @@ -481,6 +481,24 @@ without `phase_signals` or `phase_signals_partial: true`), halt with: /trekbrief to commit them (Phase 3.5).` Enforcement is validator-only; commands surface, don't re-enforce. +### High-effort behavior (v5.1.1) + +When `phase_signal_result.effort == 'high'` for the `research` phase, +run the FULL swarm regardless of normal triggering rules: 5 local +agents + 4 external agents + 1 bridge agent, AND force +`contrarian-researcher` AND `gemini-bridge` to always-on. Normally +`contrarian-researcher` triggers conditionally when a leading +recommendation is emerging from initial agents; in high-effort mode it +runs unconditionally so the final brief always carries an adversarial +counter-evidence pass. Similarly, `gemini-bridge` normally activates on +significant architectural questions or when triangulation value is +high; in high-effort mode it runs unconditionally to provide an +independent second opinion. + +Standard effort (or absent): use the existing conditional triggers. +Low effort: inline research only, no agent swarm (existing +`--quick`-equivalent code-path). + ## Hard rules - **No planning:** This command produces research briefs, not implementation plans. diff --git a/plugins/voyage/commands/trekreview.md b/plugins/voyage/commands/trekreview.md index 5a72a9b..4fd24b3 100644 --- a/plugins/voyage/commands/trekreview.md +++ b/plugins/voyage/commands/trekreview.md @@ -398,6 +398,25 @@ phase_signals — re-run /trekbrief to commit them (Phase 3.5).` Enforcement is validator-only; this surface just makes the friendly hint readable. +### High-effort behavior (v5.1.1) + +When `phase_signal_result.effort == 'high'` for the `review` phase, +skip Pass 3 (Cloudflare reasonableness filter) in +`agents/review-coordinator.md`. Passes 1, 2, and 4 still run. +Rationale: high-effort review trusts the operator to weigh borderline +findings rather than have the coordinator drop them. To prevent +unknown `rule_key` values from polluting downstream remediation plans +(Handover 6), the coordinator applies its v5.1.1 high-effort +normalization rule — substituting unknown `rule_key` values with the +literal string `PLAN_EXECUTE_DRIFT` (the most general drift category +in the 12-entry catalogue) and preserving the original in +`original_rule_key`. See `agents/review-coordinator.md` § Pass 3 +"High-effort normalization (v5.1.1)" for the full normalization spec. + +Standard effort (or absent): run all 4 passes as usual. +Low effort: skip the brief-conformance reviewer entirely (existing +`--quick`-equivalent code-path). + ## Hard rules - **Brief is the contract.** Every finding in the review traces to a diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index b704a52..bc96ab4 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -585,3 +585,29 @@ test('v5.1 — voyage README.md mentions phase_signals', () => { assert.ok(t.includes('phase_signals'), 'voyage README.md must mention phase_signals (v5.1 "What\'s new" bullet)'); }); + +// --- v5.1.1 — High-effort behavior sub-section per command (Step 10) --- + +test('v5.1.1 — commands/trekplan.md contains ### High-effort behavior (v5.1.1) sub-section', () => { + const t = read('commands/trekplan.md'); + assert.match(t, /^### High-effort behavior \(v5\.1\.1\)$/m, + 'trekplan.md must contain ### High-effort behavior (v5.1.1) sub-section under Composition rule (Decision B + gemini-bridge)'); +}); + +test('v5.1.1 — commands/trekresearch.md contains ### High-effort behavior (v5.1.1) sub-section', () => { + const t = read('commands/trekresearch.md'); + assert.match(t, /^### High-effort behavior \(v5\.1\.1\)$/m, + 'trekresearch.md must contain ### High-effort behavior (v5.1.1) sub-section under Composition rule (contrarian-researcher + gemini-bridge always-on)'); +}); + +test('v5.1.1 — commands/trekreview.md contains ### High-effort behavior (v5.1.1) sub-section', () => { + const t = read('commands/trekreview.md'); + assert.match(t, /^### High-effort behavior \(v5\.1\.1\)$/m, + 'trekreview.md must contain ### High-effort behavior (v5.1.1) sub-section under Composition rule (skip Pass 3 + coordinator normalization)'); +}); + +test('v5.1.1 — commands/trekexecute.md contains ### High-effort behavior (v5.1.1) sub-section', () => { + const t = read('commands/trekexecute.md'); + assert.match(t, /^### High-effort behavior \(v5\.1\.1\)$/m, + 'trekexecute.md must contain ### High-effort behavior (v5.1.1) sub-section under Composition rule (gates_mode = closed)'); +}); From 9affdca23e71e56a6b0080be26d7a48dc4d41ce6 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 15 May 2026 16:11:55 +0200 Subject: [PATCH 23/58] =?UTF-8?q?chore(voyage):=20bump=20version=205.1.0?= =?UTF-8?q?=20=E2=86=92=205.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++- plugins/voyage/.claude-plugin/plugin.json | 2 +- plugins/voyage/CHANGELOG.md | 43 +++++++++++++++++++++++ plugins/voyage/README.md | 11 ++++-- plugins/voyage/package-lock.json | 4 +-- plugins/voyage/package.json | 2 +- 6 files changed, 59 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2d7ad87..73e11b3 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,12 @@ Key commands: `/config-audit posture`, `/config-audit feature-gap`, `/config-aud --- -### [Voyage](plugins/voyage/) `v5.1.0` +### [Voyage](plugins/voyage/) `v5.1.1` Deep requirements gathering, research, implementation planning, self-verifying execution, independent post-hoc review, and zero-friction multi-session resumption — with specialized agent swarms, adversarial review, and failure recovery. Six-command (brief, research, plan, execute, review, continue) universal pipeline + adaptive-depth per-phase effort dialog. `/trekbrief`, `/trekplan`, and `/trekreview` render their artifact to a self-contained HTML view and print the `file://` link. +v5.1.1 is a 13-step remediation patch closing 11 of 12 findings from the v5.1.0 review (the SC8 dogfood gate is operator-manual, scheduled for after-execute). Load-bearing bug fixes: YAML-number bypass in `brief-validator` so the gate fires for both quoted and unquoted `brief_version` (#8 + #11). Wiring: a new `lib/profiles/phase-signal-resolver.mjs` helper is invoked from `/trekplan`/`/trekresearch`/`/trekreview`/`/trekexecute` Phase 1, the resolved JSON is captured as `phase_signal_result`, and the `brief-validator --soft` gate is required uniformly across all 4 downstream commands (#9 + #12). Test refactor: runtime SC1 walk for trekbrief + per-tier resolver-output + missing-signals falsification per downstream command + dedicated profile-resolver non-interference test (#1 #2 #3 #4 #6 #7 #10). Documentation: Decision B high-effort behavior locked per command (gemini-bridge pass for `/trekplan`, full swarm + always-on `contrarian-researcher` for `/trekresearch`, skip Pass 3 + coordinator normalization for `/trekreview`, `gates_mode: closed` for `/trekexecute`) + brief Non-Goal/SC1 amendments + REMEMBER dogfood scaffolding. v5.1.1 is additive — no breaking changes against v5.1.0. See `plugins/voyage/CHANGELOG.md` § v5.1.1. + v5.1.0 adds Phase 3.5 to `/trekbrief`: 4 tier-coupled `AskUserQuestion` calls commit an effort level (`low | standard | high`) and an optional `model` (`sonnet | opus`) per downstream phase (`research`, `plan`, `execute`, `review`). The choices land in `brief.md` as `phase_signals:` (or `phase_signals_partial: true` on force-stop). `brief_version: 2.1` activates a validator-side sequencing gate (`BRIEF_V51_MISSING_SIGNALS`) so downstream commands halt with a friendly hint when signals are missing. Composition rule per downstream command: brief signal wins per-phase, profile fills gaps. `effort == low` activates each command's existing `--quick`-equivalent code-path (`/trekexecute` low-effort = `--gates open` + sequential-only). Additive — no breaking changes; pre-2.1 briefs still validate. See `plugins/voyage/CHANGELOG.md` § v5.1.0. v5.0.3 lands the annotation UX modelled on `~/repos/claude-code-100x/claude-code-100x/build-site.js`: pencil-toggle annotation mode, **select text or click any element to anchor**, choose intent (**Fiks** / **Endre** / **Spørsmål**), write a comment, save. The sidebar groups annotations by section with intent badges; Copy Prompt assembles them into a structured markdown the operator pastes back into Claude. State persists in `localStorage` per artifact path. v5.0.2 was operator-led but too thin (line-click + freeform note, no intent categories). v5.0.1 had pointed at `/playground document-critique` (Claude-leads — wrong direction). v5.0.0 (breaking, kept) removed the v4.2/v4.3 bespoke playground SPA, `/trekrevise`, Handover 8, the supporting `lib/` modules, the Playwright e2e suite, and the `@playwright/test` / `@axe-core/playwright` devDeps. v5.0.3's `scripts/annotate.mjs` is one self-contained zero-dependency Node script. **The operator drives every annotation** — Claude never pre-generates suggestions in this flow. See `plugins/voyage/CHANGELOG.md` § v5.0.0 → § v5.0.3. diff --git a/plugins/voyage/.claude-plugin/plugin.json b/plugins/voyage/.claude-plugin/plugin.json index 68fb63b..957ce0e 100644 --- a/plugins/voyage/.claude-plugin/plugin.json +++ b/plugins/voyage/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "voyage", "description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): select text or click any element, pick intent (Fiks/Endre/Spørsmål), write comment, copy structured prompt, paste back, Claude revises the .md.", - "version": "5.1.0", + "version": "5.1.1", "author": { "name": "Kjell Tore Guttormsen" }, diff --git a/plugins/voyage/CHANGELOG.md b/plugins/voyage/CHANGELOG.md index ab3267c..54cd59a 100644 --- a/plugins/voyage/CHANGELOG.md +++ b/plugins/voyage/CHANGELOG.md @@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## v5.1.1 — 2026-05-14 — Remediation patch (11/12 review findings closed) + +Additive. No breaking changes against v5.1.0. Closes 11 of 12 BLOCKER/MAJOR/MINOR findings from the v5.1.0 review (sesjon 5, review.md SHA range `8cbb33e..8f4b79c`). SC8 dogfood gate (#5) is scheduled for sesjon 8 as a fresh-CC-session operator action — its closure cannot happen inside the v5.1.1 execute session. + +### Bug fixes (load-bearing) + +- **#8 YAML-number bypass closed.** `lib/validators/brief-validator.mjs` now coerces `brief_version` to string before comparison; both `brief_version: 2.1` (parsed as Number) and `brief_version: "2.1"` (parsed as String) trigger the v5.1+ sequencing gate. Previously the unquoted form silently bypassed the gate. Three regression tests added in `tests/validators/brief-validator.test.mjs`. +- **#11 Doc-consistency pin lock-in.** `tests/lib/doc-consistency.test.mjs` pins `templates/trekbrief-template.md` to emit `brief_version: "2.1"` (quoted) so future template edits can't reintroduce the bypass. + +### Wiring (closes #9 + #12) + +- **`lib/profiles/phase-signal-resolver.mjs` helper** (new). Reads `brief.phase_signals` (or `phase_signals_partial`) and the active profile's `phase_models`, returns the resolved `{effort, model, source}` per phase. Composition rule: brief signal wins per-phase, profile fills gaps. 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`). +- **4 downstream commands wired.** `/trekplan`, `/trekresearch`, `/trekreview`, `/trekexecute` each invoke the resolver in their Phase 1 (or equivalent first-phase block), capture the result as `phase_signal_result`, and pass it to `Agent` tool calls explicitly. Per-command low-effort code-paths reuse existing `--quick`-equivalent logic. +- **`resolvePhaseModel` orchestrator-override added** (#9 part A). Profile-tier model picks are overridden when `brief.phase_signals[].model` is set. Non-interference verified by new test `tests/lib/profile-resolver.test.mjs` (Step 7 TDD pair, Red→Green). +- **`brief-validator --soft` gate required uniformly** in `/trekresearch` + `/trekexecute` (previously only `/trekplan` + `/trekreview`). `BRIEF_V51_MISSING_SIGNALS` halts all 4 commands with a clear hint pointing back to `/trekbrief`. + +### Test refactor (closes #1 #2 #3 #4 #6 #7 #10) + +- **Runtime SC1 walk for `tests/commands/trekbrief.test.mjs`** (#1). Replaces prose-grep with `resolvePhaseSignal` invariant: returns non-null for all 4 entries in `PHASE_SIGNAL_PHASES` when applied to a fixture brief with a committed `phase_signals` block; returns gate-fire when applied to a v2.1 brief missing the block. +- **Per-tier resolver-output + missing-signals falsification** (#2 #3 #6 #10). `tests/commands/trekplan.test.mjs`, `trekresearch.test.mjs`, `trekreview.test.mjs`, `trekexecute.test.mjs` each refactored from doc-pin pattern to runtime assertion against 4 new fixture briefs (`tests/fixtures/briefs/{low,standard,high,partial}.md`). +- **Dedicated SC5 profile-resolver non-interference test** (#7). `tests/lib/profile-resolver.test.mjs` asserts that brief-emitted signals are not overridden by `VOYAGE_PROFILE`, and that `--profile` does not silently override brief-emitted answers. +- **#4 SC5 invariant** locked at resolver level rather than test-strategy level (no scope expansion — see `source_findings` audit trail in plan.md frontmatter). +- **Total test delta:** +12 tests (539 baseline + 12 + 4 doc-pins from Step 10 = 555 expected; actual 578 because the existing tests grew underneath us during the v5.1.0→v5.1.1 cycle). 0 fail, 2 skipped. + +### Documentation (closes #5 scheduling + Decision B high-effort) + +- **#5 SC8 dogfood scheduling.** `REMEMBER.md` (gitignored) gains a `## v5.1.1 sesjon 8 — DOGFOOD GATE OBSERVATIONS` reserved placeholder with concrete procedure: fresh CC-session → real v5.1-eligible task → Phase 3.5 observation → 5 specific questions to answer. The actual observation is a sesjon 8 manual operator action; sesjon 7 produces scaffolding only. +- **Decision B high-effort behavior locked per command** (operator-confirmed, decisions.local.json): + - `/trekplan` high: gemini-bridge plan-review Pass 2 on the post-revision plan (replaces fragile plan-critic doubling per risk-assessor finding — line numbers shift after revisions, breaking dedup triplets). + - `/trekresearch` high: full swarm + `contrarian-researcher` AND `gemini-bridge` always-on regardless of normal triggering rules. + - `/trekreview` high: skip Pass 3 reasonableness filter; coordinator applies `PLAN_EXECUTE_DRIFT` normalization for unknown rule_keys (preserves original in `original_rule_key`). + - `/trekexecute` high: `gates_mode = 'closed'` automatically; explicit `--gates` flag still wins. +- **Brief Non-Goal amendment** (`.claude/projects/2026-05-13-trekflow-solo-lane/brief.md`, gitignored): low/high effort tiers now locked in v5.1.1; standard remains fallthrough default. Continue + brief phases remain effort-less. +- **Brief SC1 amendment** (same file, gitignored): SC1 "4 AskUserQuestion calls" interpreted as the resolver-invariant `resolvePhaseSignal` returns non-null for all 4 entries in `PHASE_SIGNAL_PHASES`. The literal AskUserQuestion mocking interpretation would require a state-machine harness module (significant scope expansion); the resolver-invariant interpretation is authorized as equivalent. **Operator-confirmed via Pre-Step-10 autonomy gate.** + +### Known scheduling + +SC8 (#5) dogfood-gate closure happens in sesjon 8 (fresh CC-session, manual `/trekbrief` walk-through). v5.1.1 push to Forgejo gated on sesjon 9 `/trekreview` returning `ALLOW`. If sesjon 9 returns `BLOCK`, loop back to sesjon 6 (new remediation plan). + +### Source findings audit trail + +Plan-frontmatter `source_findings:` lists the 12 review-finding IDs each step closes. Audit trail traces `review.md` → `/trekplan --brief review.md` (Handover 6) → `plan.md` → `/trekexecute` → individual commits. + ## v5.1.0 — 2026-05-13 — Per-phase effort + model dialog Additive. No breaking changes. Forward-compat with all v5.0.x briefs. diff --git a/plugins/voyage/README.md b/plugins/voyage/README.md index d2bb41e..30eea85 100644 --- a/plugins/voyage/README.md +++ b/plugins/voyage/README.md @@ -1,6 +1,6 @@ # trekplan — Brief, Research, Plan, Execute, Review, Continue -![Version](https://img.shields.io/badge/version-5.1.0-blue) +![Version](https://img.shields.io/badge/version-5.1.1-blue) ![License](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Claude%20Code-purple) @@ -10,7 +10,14 @@ A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin for deep implementation planning, multi-source research, autonomous execution, independent post-hoc review, and zero-friction multi-session resumption. Six commands, one pipeline: -> **What's new in v5.1** — `/trekbrief` Phase 3.5 commits per-phase `phase_signals` (effort + optional model for `research`/`plan`/`execute`/`review`) to `brief.md` frontmatter. `brief_version: 2.1` activates a validator-side sequencing gate (`BRIEF_V51_MISSING_SIGNALS`) so downstream commands halt with a friendly hint when signals are missing. Composition rule per downstream command: brief signal wins per-phase, profile fills gaps. `effort == low` activates the existing `--quick`-equivalent code-path in each command (`/trekexecute` low-effort = `--gates open` + sequential). Additive — no breaking changes; pre-2.1 briefs still validate. +> **What's new in v5.1.1** — Remediation patch closing 11 of 12 findings from the v5.1.0 review (SC8 dogfood gate scheduled for sesjon 8). Lukker: +> - **Bug fixes (load-bearing):** YAML-number bypass in `brief-validator` (#8) + doc-consistency pin lock-in (#11) so the gate fires for both quoted and unquoted `brief_version`. +> - **Wiring:** `phase-signal-resolver` helper wired into all 4 downstream commands (#9) with TDD pair `resolvePhaseModel` + profile-resolver non-interference test (#4 SC5); `brief-validator` gate required uniformly in `/trekresearch` + `/trekexecute` (#12). +> - **Test refactor:** runtime SC1 walk for trekbrief (#1) + per-tier resolver-output + missing-signals falsification for `/trekplan`/`/trekresearch`/`/trekreview`/`/trekexecute` (#2 #3 #6 #10) + dedicated SC5 test (#7). +> - **Documentation:** Dogfood-gate scheduling in REMEMBER (#5, sesjon 8 manual) + Decision B high-effort behavior per command + brief Non-Goal/SC1 amendments + coordinator high-effort normalization. +> v5.1.1 is additive — no breaking changes against v5.1.0. +> +> **What v5.1 introduced** — `/trekbrief` Phase 3.5 commits per-phase `phase_signals` (effort + optional model for `research`/`plan`/`execute`/`review`) to `brief.md` frontmatter. `brief_version: 2.1` activates a validator-side sequencing gate (`BRIEF_V51_MISSING_SIGNALS`) so downstream commands halt with a friendly hint when signals are missing. Composition rule per downstream command: brief signal wins per-phase, profile fills gaps. `effort == low` activates the existing `--quick`-equivalent code-path in each command (`/trekexecute` low-effort = `--gates open` + sequential). Additive — no breaking changes; pre-2.1 briefs still validate. | Command | What it does | |---------|-------------| diff --git a/plugins/voyage/package-lock.json b/plugins/voyage/package-lock.json index 4c90649..7d55cd3 100644 --- a/plugins/voyage/package-lock.json +++ b/plugins/voyage/package-lock.json @@ -1,12 +1,12 @@ { "name": "voyage", - "version": "5.1.0", + "version": "5.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "voyage", - "version": "5.1.0", + "version": "5.1.1", "license": "MIT", "engines": { "node": ">=18" diff --git a/plugins/voyage/package.json b/plugins/voyage/package.json index 4a9c929..faab9fc 100644 --- a/plugins/voyage/package.json +++ b/plugins/voyage/package.json @@ -1,6 +1,6 @@ { "name": "voyage", - "version": "5.1.0", + "version": "5.1.1", "description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): select text or click any heading/paragraph/list-item, pick intent (Fiks/Endre/Spørsmål), write comment, copy structured prompt, paste back, Claude revises the .md.", "type": "module", "engines": { From d8882f5220b4146cc7b65f7e73da82779d9a800a Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sat, 16 May 2026 20:58:51 +0200 Subject: [PATCH 24/58] =?UTF-8?q?feat(ms-ai-architect):=20v1.15.0=20?= =?UTF-8?q?=E2=80=94=20playground=20v3=20project-view=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Erstatter v2 project-surface (screen-tabs + category-tabs + per-command paste-cards) med v3 renderProjectView (sidebar med 17 artifacts + main-area + import-modal overlay). renderActive() ruter project-surface til renderProjectSurfaceV3() som wrapper renderProjectView + topbar + app-shell. V2-surface helt fjernet: - renderProjectSurface (152 linjer) - renderCommandSubCard (87 linjer) - rehydratePasteImports (15 linjer) - ACTIONS['project-screen'], currentProjectScreen - 5 v2-CSS-klasser: .project-tabs, .project-tab*, .sub-zone, .paste-import-row, .project-header__*, .command-cards Zombie-handlers beholdt for test-back-compat: currentProjectTab, ACTIONS['project-tab'], ACTIONS['parse'], handlePasteImport, window.__handlePasteImport. Unreachable fra v3 DOM men nødvendige for test-playground-v3.sh + test-playground-parsers.sh. 2 fingerprint-gap lukket: - requirements.headers: utvidet med "EU AI Act — Krav" pattern - license.headers: utvidet med "Lisens-kapabilitetsmatrise" pattern - KNOWN_GAP_FIXTURES = {} i test-playground-fingerprints.sh migrateDataVersion utvidet med parserFor (3. arg): - Demo-state med kun raw_markdown auto-parses til project.artifacts[cid] - defaultParserFor(cmdId) resolverer PARSERS[archetypeFor(cmdId)] - 3 bootstrap-callsites oppdatert (cold-load, import, load-demo) Ship-QA bugfixes funnet via browser-dogfood: - components-tier4-project-view.css lagt til i -kjeden (var ikke loaded -> modal-overlay og two-column layout virket ikke) - renderImportModal setter data-open="true" (DS-kontrakt for display: flex) Bundler også sesjon 2-4 deliverables som ikke ble committed tidligere: - shared/playground-design-system v0.6.0 (Tier 4 project-view CSS + 6 tokens) - ms-ai-architect/playground/vendor/ re-sync til DS v0.6.0 - tests/test-playground-fingerprints.sh (sesjon 4 NY - 32 PASS) - tests/test-playground-projectview.sh (sesjon 4 NY - 30 PASS) - tests/test-playground-actions.sh (sesjon 4 NY - 19 PASS) - tests/test-playground-migrations.sh utvidet (7 -> 16 PASS) - tests/run-e2e.sh wirer alle 6 playground-suiter Stats: - bash tests/run-e2e.sh --playground: 386 PASS, 0 FAIL, 2 WARN (pre-eks) - bash tests/run-e2e.sh (full): All E2E suites passed - bash tests/validate-plugin.sh: 219 PASS Screenshots regenerert til playground/screenshots/v1.15.0/ (24 PNG-er, 12 surfaces x 2 tema). Nye v3-surfaces: project-overview, project-artifact-*, project-import-modal (viewport-only), project-search. Docs oppdatert (3 nivåer): README.md (badge + version history), CHANGELOG.md, CLAUDE.md (playground-seksjon + valideringstabell), rot-README.md + rot-CLAUDE.md (marketplace-landingen + plugin-index). .gitignore: ny pattern *.local.html + *.local.json for sesjon-state-filer. Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 21 +- README.md | 8 +- .../.claude-plugin/plugin.json | 2 +- plugins/ms-ai-architect/.gitignore | 2 + plugins/ms-ai-architect/CHANGELOG.md | 61 + plugins/ms-ai-architect/CLAUDE.md | 25 +- plugins/ms-ai-architect/README.md | 3 +- .../ms-ai-architect-playground.html | 1326 +++++++++++++---- .../v1.15.0/01-onboarding-empty-dark.png | Bin 0 -> 734866 bytes .../v1.15.0/01-onboarding-empty-light.png | Bin 0 -> 251047 bytes .../v1.15.0/02-project-overview-dark.png | Bin 0 -> 634826 bytes .../v1.15.0/02-project-overview-light.png | Bin 0 -> 631086 bytes .../03-project-artifact-classify-dark.png | Bin 0 -> 899955 bytes .../03-project-artifact-classify-light.png | Bin 0 -> 893075 bytes .../04-project-artifact-security-dark.png | Bin 0 -> 982840 bytes .../04-project-artifact-security-light.png | Bin 0 -> 976542 bytes .../v1.15.0/05-project-artifact-ros-dark.png | Bin 0 -> 883370 bytes .../v1.15.0/05-project-artifact-ros-light.png | Bin 0 -> 877439 bytes .../v1.15.0/06-project-artifact-cost-dark.png | Bin 0 -> 761708 bytes .../06-project-artifact-cost-light.png | Bin 0 -> 756280 bytes .../07-project-artifact-summary-dark.png | Bin 0 -> 835449 bytes .../07-project-artifact-summary-light.png | Bin 0 -> 828184 bytes .../v1.15.0/08-project-import-modal-dark.png | Bin 0 -> 271539 bytes .../v1.15.0/08-project-import-modal-light.png | Bin 0 -> 267928 bytes .../v1.15.0/09-project-search-dark.png | Bin 0 -> 608708 bytes .../v1.15.0/09-project-search-light.png | Bin 0 -> 602636 bytes .../screenshots/v1.15.0/10-home-dark.png | Bin 0 -> 221526 bytes .../screenshots/v1.15.0/10-home-light.png | Bin 0 -> 219143 bytes .../screenshots/v1.15.0/11-catalog-dark.png | Bin 0 -> 425099 bytes .../screenshots/v1.15.0/11-catalog-light.png | Bin 0 -> 421652 bytes .../v1.15.0/12-onboarding-prefilled-dark.png | Bin 0 -> 253195 bytes .../v1.15.0/12-onboarding-prefilled-light.png | Bin 0 -> 251047 bytes .../playground-design-system/CHANGELOG.md | 60 + .../playground-design-system/MANIFEST.json | 13 +- .../vendor/playground-design-system/base.css | 1 + .../components-tier4-project-view.css | 666 +++++++++ .../playground-design-system/tokens.css | 11 + plugins/ms-ai-architect/tests/run-e2e.sh | 4 + .../ms-ai-architect/tests/screenshot/run.mjs | 129 +- .../tests/test-playground-actions.sh | 248 +++ .../tests/test-playground-fingerprints.sh | 218 +++ .../tests/test-playground-migrations.sh | 274 +++- .../tests/test-playground-projectview.sh | 323 ++++ .../tests/test-playground-v3.sh | 19 +- shared/playground-design-system/CHANGELOG.md | 48 + .../components-tier4-project-view.css | 665 +++++++++ shared/playground-design-system/tokens.css | 8 + 47 files changed, 3724 insertions(+), 411 deletions(-) create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/01-onboarding-empty-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/01-onboarding-empty-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/02-project-overview-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/02-project-overview-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/03-project-artifact-classify-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/03-project-artifact-classify-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/04-project-artifact-security-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/04-project-artifact-security-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/05-project-artifact-ros-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/05-project-artifact-ros-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/06-project-artifact-cost-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/06-project-artifact-cost-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/07-project-artifact-summary-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/07-project-artifact-summary-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/08-project-import-modal-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/08-project-import-modal-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/09-project-search-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/09-project-search-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/10-home-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/10-home-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/11-catalog-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/11-catalog-light.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/12-onboarding-prefilled-dark.png create mode 100644 plugins/ms-ai-architect/playground/screenshots/v1.15.0/12-onboarding-prefilled-light.png create mode 100644 plugins/ms-ai-architect/playground/vendor/playground-design-system/components-tier4-project-view.css create mode 100755 plugins/ms-ai-architect/tests/test-playground-actions.sh create mode 100755 plugins/ms-ai-architect/tests/test-playground-fingerprints.sh create mode 100755 plugins/ms-ai-architect/tests/test-playground-projectview.sh create mode 100644 shared/playground-design-system/components-tier4-project-view.css diff --git a/CLAUDE.md b/CLAUDE.md index 97ba05b..7542e0b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,12 +11,12 @@ plugins/ graceful-handoff/ v2.1.0 — Auto-trigger handoff via Stop hook (skill + JSON pipeline + 4-step model-aware context resolution) linkedin-thought-leadership/ v1.2.0 — LinkedIn content pipeline + analytics llm-security/ v6.0.0 — Security scanning, auditing, threat modeling - ms-ai-architect/ v1.13.1 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command + ms-ai-architect/ v1.15.0 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command + v3 project-view (sidebar med 17 artifacts + main + import-modal overlay, v2-surface fjernet i v1.15.0) okr/ v1.0.0 — OKR guidance for Norwegian public sector voyage/ v5.0.3 — Brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline (six-command universal pipeline + multi-session resumption + --gates autonomy chain). /trekbrief, /trekplan, and /trekreview each end by running scripts/annotate.mjs against the just-written .md and printing the file:// link to a self-contained operator-annotation HTML modelled on claude-code-100x/build-site.js: pencil-toggle annotation mode, select text or click any element, choose intent (Fiks/Endre/Spørsmål), comment, sidebar groups by section with delete + Copy Prompt, localStorage persistence per artifact path. v5.0.0 removed the v4.2/v4.3 bespoke playground + /trekrevise + Handover 8; v5.0.1 pointed at /playground document-critique (wrong direction); v5.0.2 was operator-led but too thin; v5.0.3 matches the reference the operator pointed at from day one. shared/ - playground-design-system/ v0.1 — Aksel/Digdir-aligned CSS design system + JSON schemas + self-hosted Inter/JetBrains Mono/Source Serif 4 fonts (Tier 1+2+3 wave 1+wave 2 = 20 Tier 3 components total). Consumed by ms-ai-architect, okr, llm-security, voyage, config-audit + playground-design-system/ v0.6.0 — Aksel/Digdir-aligned CSS design system + JSON schemas + self-hosted Inter/JetBrains Mono/Source Serif 4 fonts. Tier 1 base + Tier 2 + Tier 3 wave 1+2 (20 components) + Tier 4 project-view-arketype (v0.6.0 — sidebar + main + import-modal overlay). Consumed by ms-ai-architect, okr, llm-security, voyage, config-audit. playground-examples/ — Reference scenarios (ROS-Lier, OKR-Bærum, security-Direktorat) + showcase landing + 12 isolated Tier 3 wave 2 component demos under components/ ``` @@ -53,3 +53,20 @@ Disse trackes IKKE i git. Oppdater ved sesjonsslutt. 3. Les REMEMBER.md og TODO.md for sesjonsstatus 4. Jobb innenfor scope 5. Oppdater REMEMBER.md ved avslutning + +## Communication patterns + +### Linking to local files + +When pointing to local files in responses, always use markdown link syntax with a descriptive name: + +- Use `[Human-friendly name](file:///absolute/path)` — never bare `file:///...` URLs or autolinks ``. +- Always use absolute paths. Never `~/` or relative paths. +- For multiple files, render as a bullet list of named markdown links. + +Why: bare `file://` URLs only render the first as clickable across multiple lines. Named markdown links make each entry independently clickable and look cleaner. + +Example: + +- [Brief](file:///Users/ktg/.../brief.html) +- [Research summary](file:///Users/ktg/.../research/summary.md) diff --git a/README.md b/README.md index 73e11b3..02f5eb2 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ Key command: `/graceful-handoff [topic-slug] [--no-commit] [--no-push] [--dry-ru --- -### [MS AI Architect — Azure AI and Microsoft Foundry](plugins/ms-ai-architect/) `v1.14.0` `🇳🇴 Norwegian` +### [MS AI Architect — Azure AI and Microsoft Foundry](plugins/ms-ai-architect/) `v1.15.0` `🇳🇴 Norwegian` Microsoft AI solution architecture guidance for Norwegian public sector and enterprise. @@ -187,11 +187,11 @@ Key commands: `/architect`, `/architect:ros`, `/architect:security`, `/architect 12 specialized agents · 25 commands · 5 skills (387 reference docs) · 2 hooks · manual sitemap-driven KB refresh -**One-click demo (v1.14.0, 2026-05-08):** "Last inn demo-data"-knappen på onboarding bootstrapper en ferdig "Acme Kommune" med demo-prosjektet "Acme: Kunde-chatbot" og alle 17 rapport-typer pre-importert som `raw_markdown` (konsistente navn på tvers av alle fixtures). Visualisering rehydreres automatisk på project-surface mount. 24 retina-screenshots committed under `playground/screenshots/v1.14.0/` (12 surfaces × 2 tema), så forkere ser pluginen uten å kjøre noe. Standalone Playwright-runner under `tests/screenshot/` (egen `package.json`). +**One-click demo (v1.15.0, 2026-05-16):** "Last inn demo-data"-knappen på onboarding bootstrapper en ferdig "Acme Kommune" med demo-prosjektet "Acme: Kunde-chatbot" og alle 17 rapport-typer pre-importert. v2→v3 migrasjon auto-parser `raw_markdown` til `project.artifacts[cid]` så project-view viser aggregert verdict (BLOKKERT), key stats (17/17 artifacts), top-risks-liste, og navigerbart artifact-sidebar i én navigasjon. 24 retina-screenshots committed under `playground/screenshots/v1.15.0/` (12 surfaces × 2 tema), så forkere ser pluginen uten å kjøre noe. Standalone Playwright-runner under `tests/screenshot/` (egen `package.json`). -**Playground (v3, v1.14.0 — root-cause refaktor, 2026-05-08):** Multi-surface decision-builder + report viewer. The single-file HTML app lives at `playground/ms-ai-architect-playground.html` (~3870+ lines). v1.14.0 leverer DS-konvensjon-adopsjon på 14 renderere over 6 sesjoner: B-DS-1/2/3 fikset i shared/ DS v0.4.0 (kanban-card word-break, expansion title-block, matrix-bubble cursor); 3 risk-renderere til DS-summary-grid + ros-layout; 6 compliance/govern-renderere bytter `.report-meta`-wrapper mot DS-konvensjon; renderMigrate + renderPoc til expansion-list per fase; 5b-fixes i renderCost/renderCompare/renderUtredning. Lokal ` + + +
+${bodyHtml} +
+ + +`; +} + +function fail(code, msg) { + process.stderr.write(`render-report: ${msg}\n`); + process.exit(code); +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + if (!args.commandId) fail(1, 'missing (e.g. scan, audit, posture, deep-scan)'); + if (!args.in) fail(1, 'missing --in '); + + const parser = PARSERS[args.commandId]; + if (!parser) fail(1, `unknown commandId "${args.commandId}". Valid: ${Object.keys(PARSERS).join(', ')}`); + + const rendererName = commandToRendererName(args.commandId); + const renderer = RENDERERS[rendererName]; + if (typeof renderer !== 'function') fail(1, `no renderer found for "${args.commandId}" (looked up ${rendererName})`); + + let md; + try { + md = args.in === '-' ? readStdinSync() : readFileSync(resolve(args.in), 'utf8'); + } catch (e) { + fail(1, `cannot read input: ${e.message}`); + } + if (!md || !md.trim()) fail(1, 'input markdown is empty'); + + let data; + try { + data = parser(md); + } catch (e) { + fail(1, `parser threw: ${e.message}`); + } + if (!data || typeof data !== 'object') fail(1, 'parser returned non-object'); + + const slot = { innerHTML: '' }; + try { + renderer(data, slot); + } catch (e) { + fail(2, `renderer threw: ${e.message}`); + } + if (!slot.innerHTML) fail(2, 'renderer produced empty HTML'); + + const dsCss = loadDsCss(); + const html = wrapHtml(args.commandId, slot.innerHTML, dsCss); + + if (args.out === '-') { + process.stdout.write(html); + return; + } + + let outPath; + if (args.out) { + outPath = isAbsolute(args.out) ? args.out : resolve(args.out); + } else { + const reportsDir = resolve('reports'); + try { + mkdirSync(reportsDir, { recursive: true }); + } catch (e) { + fail(3, `cannot create reports dir: ${e.message}`); + } + outPath = join(reportsDir, `${args.commandId}-${timestamp()}.html`); + } + + try { + mkdirSync(dirname(outPath), { recursive: true }); + writeFileSync(outPath, html, 'utf8'); + } catch (e) { + fail(3, `cannot write ${outPath}: ${e.message}`); + } + + process.stdout.write(`file://${outPath}\n`); +} + +main(); From 3b034d9266444ed2255f9b14fb0e8b02525bd312 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Mon, 18 May 2026 13:12:21 +0200 Subject: [PATCH 52/58] =?UTF-8?q?feat(llm-security):=20v7.7.0=20=E2=80=94?= =?UTF-8?q?=20HTML-rapport=20for=20alle=2018=20skill-kommandoer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hver /security som produserer rapport printer nå en klikkbar file://-lenke til en self-contained HTML-versjon. Levert over fem sesjoner; sesjon 5 wirer de 14 resterende skill-filene + slipper v7.7.0 (versjonsbump + docs). Sesjon-historikk: - Sesjon 1 (0dc7ff4) — playground katalog list-view + builder-pane med copy-knapp på alle 18 rapporter - Sesjon 2 (86d6ecd) — playground prosjekt-surface opprydding (stub-screen + topbar-splitt) - Sesjon 3 (fa5fb48) — extract 18 inline parsers + 18 inline renderers fra playground til canonical ESM-modul scripts/lib/report-renderers.mjs (playground beholder bit-identisk inline-kopi siden ESM import ikke fungerer fra file://) - Sesjon 4 (db80854) — ny zero-dep CLI scripts/render-report.mjs (stdin/file/stdout-modus, kebab→camel commandId-routing, ~140 KB self-contained HTML med 6 inlined DS-stylesheets + lokal .report-table, absolutte file://-paths for Ghostty cmd-click). 4 skills wired: scan, audit, posture, deep-scan. - Sesjon 5 (denne) — 14 resterende skills wired: plugin-audit, mcp-audit, mcp-inspect, ide-scan, supply-check, dashboard, pre-deploy, diff, watch, registry, clean, harden, threat-model, red-team. Hver skill-fil har nå en HTML Report-step som instruerer Claude å skrive markdown verbatim, kjøre CLI, og appende klikkbar file://-lenke til respons. Release-arbeid: - Versjonsbump v7.6.1 → v7.7.0 i 6 plugin-filer + 2 rot-filer (package.json, .claude-plugin/plugin.json, README badge, CLAUDE.md header + state-seksjon, docs/version-history.md, plugin Recent versions- tabell, rot README plugin-entry, rot CLAUDE.md plugin-katalog) - CHANGELOG [7.7.0] med full historikk fra sesjon 1-5 - docs/version-history.md v7.7.0-seksjon Verifisert: - 18/18 commandIds i CLI gir > 138 KB self-contained HTML - 1819/1820 tester grønne (pre-compact-scan-perf-flake fyrte under last, passerer i isolasjon på 1582 ms — pre-eksisterende, defer til v7.7.x) - 18/18 skill-filer har HTML Report-step - Ingen kildefil-treff på 7.6.1 utenfor historiske changelog/version- history/README releases-tabell Ingen scanner- eller hook-atferdsendringer — purely additive surface. Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 2 +- README.md | 3 +- .../llm-security/.claude-plugin/plugin.json | 2 +- plugins/llm-security/CHANGELOG.md | 65 +++++++++++++++++++ plugins/llm-security/CLAUDE.md | 8 ++- plugins/llm-security/README.md | 3 +- plugins/llm-security/commands/clean.md | 20 ++++++ plugins/llm-security/commands/dashboard.md | 20 ++++++ plugins/llm-security/commands/diff.md | 20 ++++++ plugins/llm-security/commands/harden.md | 20 ++++++ plugins/llm-security/commands/ide-scan.md | 20 ++++++ plugins/llm-security/commands/mcp-audit.md | 20 ++++++ plugins/llm-security/commands/mcp-inspect.md | 20 ++++++ plugins/llm-security/commands/plugin-audit.md | 20 ++++++ plugins/llm-security/commands/pre-deploy.md | 20 ++++++ plugins/llm-security/commands/red-team.md | 20 ++++++ plugins/llm-security/commands/registry.md | 20 ++++++ plugins/llm-security/commands/supply-check.md | 20 ++++++ plugins/llm-security/commands/threat-model.md | 20 ++++++ plugins/llm-security/commands/watch.md | 20 ++++++ plugins/llm-security/docs/version-history.md | 16 +++++ plugins/llm-security/package.json | 2 +- 22 files changed, 373 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7542e0b..82a6d8d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ plugins/ config-audit/ v3.1.0 — Configuration intelligence (health, opportunities, auto-fix, whats-active) graceful-handoff/ v2.1.0 — Auto-trigger handoff via Stop hook (skill + JSON pipeline + 4-step model-aware context resolution) linkedin-thought-leadership/ v1.2.0 — LinkedIn content pipeline + analytics - llm-security/ v6.0.0 — Security scanning, auditing, threat modeling + llm-security/ v7.7.0 — Security scanning, auditing, threat modeling + HTML-rapport for alle 18 skill-kommandoer (render-report CLI + canonical ESM-modul som speiles bit-identisk i playground) ms-ai-architect/ v1.15.0 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command + v3 project-view (sidebar med 17 artifacts + main + import-modal overlay, v2-surface fjernet i v1.15.0) okr/ v1.0.0 — OKR guidance for Norwegian public sector voyage/ v5.0.3 — Brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline (six-command universal pipeline + multi-session resumption + --gates autonomy chain). /trekbrief, /trekplan, and /trekreview each end by running scripts/annotate.mjs against the just-written .md and printing the file:// link to a self-contained operator-annotation HTML modelled on claude-code-100x/build-site.js: pencil-toggle annotation mode, select text or click any element, choose intent (Fiks/Endre/Spørsmål), comment, sidebar groups by section with delete + Copy Prompt, localStorage persistence per artifact path. v5.0.0 removed the v4.2/v4.3 bespoke playground + /trekrevise + Handover 8; v5.0.1 pointed at /playground document-critique (wrong direction); v5.0.2 was operator-led but too thin; v5.0.3 matches the reference the operator pointed at from day one. diff --git a/README.md b/README.md index 40431d1..8ae6ef9 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Then open Claude Code and type `/plugin` to browse and install plugins from the ## Plugins -### [LLM Security](plugins/llm-security/) `v7.6.1` +### [LLM Security](plugins/llm-security/) `v7.7.0` Security scanning, auditing, and threat modeling for agentic AI projects. @@ -36,6 +36,7 @@ Built on OWASP LLM Top 10 (2025), OWASP Agentic AI Top 10, and the AI Agent Trap - **Deterministic scanning** — 23 Node.js scanners (10 orchestrated + 13 standalone) for byte-level analysis: Shannon entropy, Unicode codepoints, typosquatting detection, taint flow, DNS resolution, git forensics, AI-BOM, attack simulation, IDE extension prescan (VS Code + JetBrains — URL fetch from Marketplace / OpenVSX / direct VSIX / JetBrains Marketplace, hardened ZIP extractor for zip-slip / symlinks / bombs, plus OS sandbox via `sandbox-exec` / `bwrap` so the kernel enforces FS confinement), MCP cumulative-drift baseline reset (E14 — sticky baseline catches slow-burn rug-pulls). Bash-normalize T1-T6 for obfuscation-resistant denylists - **Advisory analysis** — 20 commands that scan, audit, and model threats with structured reports, letter grades, and actionable remediation - **Enterprise governance** — Compliance mapping (EU AI Act, NIST AI RMF, ISO 42001), SARIF 2.1.0 output, structured audit trail, policy-as-code, standalone CLI +- **v7.7.0 HTML-rapport for alle 18 skill-kommandoer (2026-05-18)** — Hver `/security ` som produserer rapport printer nå en klikkbar `file://`-lenke til en self-contained HTML-versjon. Levert over fem sesjoner: (1) playground katalog list-view + builder-pane med copy-knapp; (2) playground prosjekt-surface opprydding (stub-screen + topbar-splitt); (3) 18 inline parserne + rendererne flyttet til canonical ESM-modul `scripts/lib/report-renderers.mjs` (playground beholder bit-identisk inline-kopi siden ESM `import` ikke fungerer fra `file://`); (4) ny zero-dep CLI `scripts/render-report.mjs` — stdin/file/stdout-modus, kebab→camel commandId-routing, inliner 6 DS-stylesheets, ~140 KB self-contained HTML med system-font-fallback, absolutte `file://`-paths for Ghostty cmd-click; (5) alle 18 skills wired (4 i sesjon 4 + 14 i sesjon 5). Ingen scanner- eller hook-atferdsendringer — purely additive - **v7.6.1 playground visuell-patch (2026-05-06)** — Seks bugs fanget av maintainer ved manuell verifisering i nettleser etter v7.6.0-release. Alle skyldtes mismatch mellom DS-klasser og hvordan playground-rendrere brukte dem (eller manglende DS-implementasjoner av klasser playground-rendrere antok eksisterte): `renderFindingsBlock` brukte `.findings` outer-class (DS' 2-kolonners list+detail-grid) → erstattet med `
` + korrekt `findings__list`-mønster; `.report-table` manglet helt i DS men brukes i 7+ rendrere → lokal CSS-implementasjon; `renderPreDeploy` traffic-lights brukte fast 28×28 px `.sm-card__grade` for "PASS"/"PASS-WITH-NOTES"/"FAIL" → bredde-tilpasset status-pill; threat-model matrix-bobler ikke klikkbare → `